Skip to content

Commit 01559b2

Browse files
authored
Merge pull request #70 from weibaohui/reg_map_error
refactor(doc): 移除全局变量以避免并发问题并改进代码结构
2 parents ed8d7d9 + 0072128 commit 01559b2

File tree

1 file changed

+114
-93
lines changed

1 file changed

+114
-93
lines changed

kom/doc/doc.go

Lines changed: 114 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package doc
22

33
import (
4-
"encoding/json"
5-
"strings"
4+
"encoding/json"
5+
"strings"
66

7-
"github.com/duke-git/lancet/v2/slice"
8-
openapi_v2 "github.com/google/gnostic-models/openapiv2"
9-
"github.com/weibaohui/kom/utils"
10-
"k8s.io/klog/v2"
7+
"github.com/duke-git/lancet/v2/slice"
8+
openapi_v2 "github.com/google/gnostic-models/openapiv2"
9+
"github.com/weibaohui/kom/utils"
10+
"k8s.io/klog/v2"
1111
)
1212

1313
// 移除全局变量 trees
@@ -96,16 +96,21 @@ type Definitions struct {
9696
AdditionalProperties []map[string]interface{} `json:"additional_properties"`
9797
}
9898

99-
// definitionsMap 存储所有定义,以便处理引用
100-
var definitionsMap map[string]SchemaDefinition
99+
// 注意:移除全局 definitionsMap,避免并发写入导致崩溃
101100

102101
var blackList = []string{
103-
"#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
104-
"#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSON",
105-
"#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
102+
"#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
103+
"#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSON",
104+
"#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
106105
}
107106

108107
// parseOpenAPISchema 解析 OpenAPI Schema JSON 字符串并返回根 TreeNode
108+
// 参数:
109+
// - schemaJSON: OpenAPI 单个 definition 的 JSON 字符串
110+
// - defs: definitions 映射,用于处理 $ref 引用,采用局部传参避免并发问题
111+
// 返回:
112+
// - TreeNode: 构建的树根节点
113+
// - error: 解析错误
109114
// Example:
110115
//
111116
// JSON样例
@@ -116,17 +121,16 @@ var blackList = []string{
116121
// },
117122
// "vendor_extension": [ {},{}]
118123
// }
119-
func parseOpenAPISchema(schemaJSON string) (TreeNode, error) {
120-
var def SchemaDefinition
121-
err := json.Unmarshal([]byte(schemaJSON), &def)
122-
if err != nil {
123-
return TreeNode{}, err
124-
}
125-
// klog.V(2).Infof("add def cache %s", def.Name)
126-
definitionsMap[def.Name] = def
127-
// klog.V(2).Infof("add def length %d", len(definitionsMap))
128-
129-
return buildTree(def, ""), nil
124+
func parseOpenAPISchema(schemaJSON string, defs map[string]SchemaDefinition) (TreeNode, error) {
125+
var def SchemaDefinition
126+
err := json.Unmarshal([]byte(schemaJSON), &def)
127+
if err != nil {
128+
return TreeNode{}, err
129+
}
130+
// 将解析后的定义写入局部映射,避免全局并发写入
131+
defs[def.Name] = def
132+
// 构建树
133+
return buildTree(def, "", defs), nil
130134
}
131135
func parseID(id string) (group, version, kind string) {
132136
parts := strings.Split(id, ".")
@@ -150,9 +154,15 @@ func parseID(id string) (group, version, kind string) {
150154
}
151155

152156
// buildTree 根据 SchemaDefinition 构建 TreeNode
153-
func buildTree(def SchemaDefinition, parentId string) TreeNode {
154-
// todo 应该使用GVK作为
155-
klog.V(8).Infof("buildTree %s", def.Name)
157+
// 参数:
158+
// - def: 单个 SchemaDefinition
159+
// - parentId: 父节点ID,用于构建 FullId
160+
// - defs: definitions 映射,用于处理 $ref 引用
161+
// 返回:
162+
// - TreeNode: 构建的树节点
163+
func buildTree(def SchemaDefinition, parentId string, defs map[string]SchemaDefinition) TreeNode {
164+
// 打印中文日志,便于定位构建过程
165+
klog.V(8).Infof("构建树节点: %s", def.Name)
156166

157167
labelParts := strings.Split(def.Name, ".")
158168
label := labelParts[len(labelParts)-1]
@@ -163,16 +173,16 @@ func buildTree(def SchemaDefinition, parentId string) TreeNode {
163173
}
164174
var children []*TreeNode
165175

166-
for _, prop := range def.Value.Properties.AdditionalProperties {
167-
children = append(children, buildPropertyNode(prop, def.Name))
168-
}
176+
for _, prop := range def.Value.Properties.AdditionalProperties {
177+
children = append(children, buildPropertyNode(prop, def.Name, defs))
178+
}
169179

170180
group, version, kind := parseID(def.Name)
171181
return TreeNode{
172182
ID: def.Name,
173183
FullId: parentId + "." + def.Name,
174184
Label: label,
175-
Value: utils.RandNLengthString(20),
185+
Value: utils.RandNLengthString(20),
176186
Description: def.Value.Description,
177187
Type: nodeType,
178188
Children: children,
@@ -184,13 +194,19 @@ func buildTree(def SchemaDefinition, parentId string) TreeNode {
184194
}
185195

186196
// buildPropertyNode 根据 Property 构建 TreeNode
187-
func buildPropertyNode(prop Property, parentId string) *TreeNode {
188-
label := prop.Name
189-
nodeID := prop.Name
190-
fullID := parentId + "." + prop.Name
191-
description := prop.Value.Description
192-
nodeType := ""
193-
ref := ""
197+
// 参数:
198+
// - prop: 属性定义
199+
// - parentId: 父节点ID
200+
// - defs: definitions 映射,用于解析 $ref 引用
201+
// 返回:
202+
// - *TreeNode: 构建的属性节点
203+
func buildPropertyNode(prop Property, parentId string, defs map[string]SchemaDefinition) *TreeNode {
204+
label := prop.Name
205+
nodeID := prop.Name
206+
fullID := parentId + "." + prop.Name
207+
description := prop.Value.Description
208+
nodeType := ""
209+
ref := ""
194210

195211
if prop.Value.Type != nil && len(prop.Value.Type.Value) > 0 {
196212
nodeType = prop.Value.Type.Value[0]
@@ -202,34 +218,34 @@ func buildPropertyNode(prop Property, parentId string) *TreeNode {
202218
var children []*TreeNode
203219

204220
// 如果有引用,查找定义并递归构建子节点
205-
if ref != "" && !slice.Contain(blackList, ref) {
206-
// 假设 ref 的格式为 "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
207-
refParts := strings.Split(ref, "/")
208-
refName := refParts[len(refParts)-1]
209-
// 构建完整的引用路径
210-
// fullRef := strings.Join(refParts[1:], ".")
211-
212-
// 这个可能会导致 循环引用溢出
213-
if def, exists := definitionsMap[refName]; exists {
214-
if !slice.Contain(blackList, refName) {
215-
childNode := buildTree(def, fullID)
216-
children = append(children, &childNode)
217-
}
218-
} else {
219-
// 如果引用的定义不存在,可以记录为一个叶子节点或处理为需要进一步扩展
220-
children = append(children, &TreeNode{
221-
ID: refName,
222-
FullId: fullID + "." + refName,
223-
Label: refName,
224-
Value: refName,
225-
Description: "Referenced definition not found",
226-
})
227-
}
228-
}
229-
230-
for _, pp := range prop.Value.Properties.AdditionalProperties {
231-
children = append(children, buildPropertyNode(pp, fullID))
232-
}
221+
if ref != "" && !slice.Contain(blackList, ref) {
222+
// 假设 ref 的格式为 "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
223+
refParts := strings.Split(ref, "/")
224+
refName := refParts[len(refParts)-1]
225+
// 构建完整的引用路径
226+
// fullRef := strings.Join(refParts[1:], ".")
227+
228+
// 这个可能会导致 循环引用溢出
229+
if def, exists := defs[refName]; exists {
230+
if !slice.Contain(blackList, refName) {
231+
childNode := buildTree(def, fullID, defs)
232+
children = append(children, &childNode)
233+
}
234+
} else {
235+
// 如果引用的定义不存在,可以记录为一个叶子节点或处理为需要进一步扩展
236+
children = append(children, &TreeNode{
237+
ID: refName,
238+
FullId: fullID + "." + refName,
239+
Label: refName,
240+
Value: refName,
241+
Description: "未找到引用的 definition",
242+
})
243+
}
244+
}
245+
246+
for _, pp := range prop.Value.Properties.AdditionalProperties {
247+
children = append(children, buildPropertyNode(pp, fullID, defs))
248+
}
233249

234250
return &TreeNode{
235251
ID: nodeID,
@@ -264,44 +280,49 @@ func printTree(node *TreeNode, level int) {
264280
}
265281
}
266282

267-
// InitTrees 解析 OpenAPI Schema 并构建 Docs 结构体,避免全局变量,减少内存占用
283+
// InitTrees 解析 OpenAPI Schema 并构建 Docs 结构体,避免全局变量,减少并发风险
284+
// 参数:
285+
// - schema: OpenAPI v2 文档对象
286+
// 返回:
287+
// - *Docs: 构建好的文档树
268288
func InitTrees(schema *openapi_v2.Document) *Docs {
269-
definitionsMap = make(map[string]SchemaDefinition)
289+
// 使用局部 definitionsMap,避免并发写入导致崩溃
290+
definitionsMap := make(map[string]SchemaDefinition)
270291

271292
// 将 OpenAPI Schema 转换为 JSON 字符串
272-
schemaBytes, err := json.Marshal(schema)
273-
if err != nil {
274-
klog.V(2).Infof("Error marshaling OpenAPI schema to JSON: %v\n", err)
275-
return nil
276-
}
293+
schemaBytes, err := json.Marshal(schema)
294+
if err != nil {
295+
klog.V(2).Infof("序列化 OpenAPI Schema 为 JSON 失败: %v", err)
296+
return nil
297+
}
277298

278299
root := &RootDefinitions{}
279-
err = json.Unmarshal(schemaBytes, root)
280-
if err != nil {
281-
klog.V(2).Infof("Error unmarshaling OpenAPI schema: %v\n", err)
282-
return nil
283-
}
300+
err = json.Unmarshal(schemaBytes, root)
301+
if err != nil {
302+
klog.V(2).Infof("反序列化 OpenAPI Schema 失败: %v", err)
303+
return nil
304+
}
284305
definitionList := root.Definitions.AdditionalProperties
285306

286307
var trees []TreeNode
287308
// 进行第一遍处理,此时Ref并没有读取,只是记录了引用
288-
for _, definition := range definitionList {
289-
str := utils.ToJSON(definition)
290-
// 解析 Schema 并构建树形结构
291-
treeRoot, err := parseOpenAPISchema(str)
292-
if err != nil {
293-
klog.V(2).Infof("Error parsing OpenAPI schema: %v\n", err)
294-
continue
295-
}
296-
trees = append(trees, treeRoot)
297-
}
309+
for _, definition := range definitionList {
310+
str := utils.ToJSON(definition)
311+
// 解析 Schema 并构建树形结构(中文日志)
312+
treeRoot, err := parseOpenAPISchema(str, definitionsMap)
313+
if err != nil {
314+
klog.V(2).Infof("解析 OpenAPI Schema 失败: %v", err)
315+
continue
316+
}
317+
trees = append(trees, treeRoot)
318+
}
298319

299320
docs := &Docs{Trees: trees}
300321
// 进行遍历处理,将child中ref对应的类型提取出来
301322
// 此时应该所有的类型都已经存在了
302-
for i := range docs.Trees {
303-
docs.loadChild(&docs.Trees[i])
304-
}
323+
for i := range docs.Trees {
324+
docs.loadChild(&docs.Trees[i])
325+
}
305326
for i := range docs.Trees {
306327
docs.loadArrayItems(&docs.Trees[i])
307328
}
@@ -314,10 +335,10 @@ func InitTrees(schema *openapi_v2.Document) *Docs {
314335
}
315336
// 将所有节点的ID,改为唯一的
316337

317-
for i := range docs.Trees {
318-
docs.uniqueID(&docs.Trees[i])
319-
}
320-
return docs
338+
for i := range docs.Trees {
339+
docs.uniqueID(&docs.Trees[i])
340+
}
341+
return docs
321342
}
322343

323344
// loadArrayItems 递归处理数组类型引用,作为 Docs 的方法

0 commit comments

Comments
 (0)