// Package swagger ... // // Description : swagger ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2024-04-19 18:16 package swagger import ( "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/gateway/api-doc/util" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/wrapper" "net/http" "strings" ) // Generate 生成文档 // // Author : zhangdeman001@ke.com<张德满> // // Date : 18:17 2024/4/19 func Generate(docConfig *define.SwaggerInput) (*define.Swagger, error) { formatDocConfig(docConfig) swaggerInfo := &define.Swagger{ Schemes: docConfig.Schemes, Swagger: consts.SwaggerDocVersion2, Host: docConfig.Host, BasePath: docConfig.BasePath, Info: docConfig.Info, Paths: map[string]map[string]*define.SwaggerPathConfig{}, Definitions: map[string]*define.SwaggerDefinition{}, } generatePathConfig(swaggerInfo, docConfig) return swaggerInfo, nil } // formatDocConfig 格式化填充dock配置 // // Author : go_developer@163.com<白茶清欢> // // Date : 10:54 2024/4/22 func formatDocConfig(docConfig *define.SwaggerInput) { if len(docConfig.Schemes) == 0 { docConfig.Schemes = []string{ consts.SchemeHTTP.String(), } } docConfig.Host = wrapper.String(docConfig.Host).ReplaceChar(map[string]string{ consts.SchemeHTTP.String() + "://": "", consts.SchemeHTTPS.String() + "://": "", }).Value() for _, itemPath := range docConfig.PathConfigList { // 默认请求类型 application/json itemPath.ContentType = strings.TrimSpace(itemPath.ContentType) itemPath.ContentType = wrapper.TernaryOperator.String(len(itemPath.ContentType) == 0, consts.MimeTypeJson, wrapper.String(itemPath.ContentType)).Value() // 默认post请求 itemPath.Method = strings.TrimSpace(itemPath.Method) itemPath.Method = wrapper.TernaryOperator.String(len(itemPath.ContentType) == 0, wrapper.String(strings.ToLower(http.MethodPost)), wrapper.String(strings.ToLower(itemPath.Method))).Value() // 默认summary itemPath.Summary = strings.TrimSpace(itemPath.Summary) itemPath.Summary = wrapper.TernaryOperator.String(len(itemPath.Summary) == 0, wrapper.String("接口 : "+itemPath.Uri), wrapper.String(itemPath.Summary)).Value() // 默认标签 if len(itemPath.TagList) == 0 { itemPath.TagList = []string{"未分组"} } for _, itemParam := range itemPath.ParameterList { // 填充默认参数位置 itemParam.In = strings.TrimSpace(itemParam.In) itemParam.In = wrapper.TernaryOperator.String(len(itemParam.In) == 0, wrapper.String(util.GetParameterDefaultLocation(itemPath.Method)), wrapper.String(itemParam.In)).Value() // 参数类型没填, 按照字符串处理 itemParam.Type = strings.TrimSpace(itemParam.Type) itemParam.Type = wrapper.TernaryOperator.String(len(itemParam.Type) == 0, wrapper.String(itemParam.Type), wrapper.String(itemParam.Type)).Value() // 参数描述 itemParam.Description = strings.TrimSpace(itemParam.Description) itemParam.Description = wrapper.TernaryOperator.String(len(itemParam.Description) == 0, wrapper.String(itemParam.Type+" : "+itemParam.Name), wrapper.String(itemParam.Description)).Value() } for _, itemResponseConfig := range itemPath.ResponseList { for _, itemResponse := range itemResponseConfig.List { // 默认返回数据类型 itemResponse.Type = strings.TrimSpace(itemResponse.Type) itemResponse.Type = wrapper.TernaryOperator.String(len(itemResponse.Type) == 0, wrapper.String(consts.DataTypeString), wrapper.String(itemResponse.Type)).Value() // 填充默认描述 itemResponse.Description = strings.TrimSpace(itemResponse.Description) itemResponse.Description = wrapper.TernaryOperator.String(len(itemResponse.Description) == 0, wrapper.String(itemResponse.Type+" : "+itemResponse.Field), wrapper.String(itemResponse.Description)).Value() } } } } // generatePathConfig 生成接口配置 // // Author : go_developer@163.com<白茶清欢> // // Date : 12:00 2024/4/22 func generatePathConfig(swaggerInfo *define.Swagger, docConfig *define.SwaggerInput) { for _, itemPath := range docConfig.PathConfigList { itemPath.Uri = getUri(itemPath.Uri) if _, exist := swaggerInfo.Paths[itemPath.Uri]; !exist { swaggerInfo.Paths[itemPath.Uri] = map[string]*define.SwaggerPathConfig{} } // 处理请求类型的配置 swaggerInfo.Paths[itemPath.Uri][itemPath.Method] = &define.SwaggerPathConfig{ Description: itemPath.Description, Consumes: []string{itemPath.ContentType}, Tags: itemPath.TagList, Summary: itemPath.Summary, Parameters: make([]*define.SwaggerPathConfigParameter, 0), Responses: make(map[string]*define.SwaggerPathConfigResponse), } // 生成参数配置 generatePathParameterConfig(swaggerInfo, itemPath) // 生成相应配置 generatePathResponseConfig(swaggerInfo, itemPath) } } // generatePathParameterConfig 生成接口的参数配置 // // Author : go_developer@163.com<白茶清欢> // // Date : 12:09 2024/4/22 func generatePathParameterConfig(swaggerInfo *define.Swagger, pathConfig *define.SwaggerPathInput) { swaggerInfo.Paths[pathConfig.Uri][pathConfig.Method].Parameters = make([]*define.SwaggerPathConfigParameter, 0) hasDealTable := map[string]bool{} for _, itemParamInput := range pathConfig.ParameterList { if len(itemParamInput.Name) == 0 { // 未指定参数名, 忽略 continue } // name 可能是 x.x.x 递归数组, 或者 x.x.[].x namePath := strings.Split(itemParamInput.Name, ".") realParamName := namePath[0] parentPath := "" if strings.ToUpper(itemParamInput.In) == consts.RequestDataLocationBody.String() && !strings.Contains(realParamName, ".") { realParamName = "jsonBody" parentPath = strings.ReplaceAll(pathConfig.Uri, ".", "-") + ".jsonBody" } generateParameterDefinitions(swaggerInfo, pathConfig.Uri, parentPath, itemParamInput.Name, itemParamInput) if _, exist := hasDealTable[realParamName]; !exist { hasDealTable[realParamName] = true generateParam := &define.SwaggerPathConfigParameter{ Type: wrapper.TernaryOperator.String(realParamName == "jsonBody", "", wrapper.String(itemParamInput.Type)).Value(), Description: wrapper.TernaryOperator.String(realParamName == "jsonBody", "参数结构", wrapper.String(itemParamInput.Description)).Value(), Name: realParamName, In: itemParamInput.In, Required: itemParamInput.Required, Schema: &define.SwaggerPathConfigParameterSchema{ Ref: "", }, } if len(parentPath) > 0 { generateParam.Schema.Ref = getRefValue(pathConfig.Uri + ".jsonBody") generateParam.Type = "" } swaggerInfo.Paths[pathConfig.Uri][pathConfig.Method].Parameters = append(swaggerInfo.Paths[pathConfig.Uri][pathConfig.Method].Parameters, generateParam) } } } // generatePathResponseConfig 生成响应配置 // // Author : go_developer@163.com<白茶清欢> // // Date : 16:43 2024/4/25 func generatePathResponseConfig(swaggerInfo *define.Swagger, pathConfig *define.SwaggerPathInput) { hasDealResponseTable := map[string]bool{} for _, itemResponseConfig := range pathConfig.ResponseList { for _, itemResponseInput := range itemResponseConfig.List { if len(itemResponseInput.Field) == 0 { // 未指定参数名, 忽略 continue } // name 可能是 x.x.x 递归数组, 或者 x.x.[].x outputDefine := strings.TrimLeft(strings.ReplaceAll(pathConfig.Uri, ".", "-"), "/") + "." + itemResponseConfig.Code + ".output" generateParameterDefinitions(swaggerInfo, pathConfig.Uri, outputDefine, itemResponseInput.Field, &define.SwaggerParameterInput{ Type: itemResponseInput.Type, Description: itemResponseInput.Description, Name: itemResponseInput.Field, In: consts.RequestDataLocationBody.String(), Required: false, EnumList: nil, }) namePath := strings.Split(itemResponseInput.Field, ".") if _, exist := hasDealResponseTable[namePath[0]]; !exist { hasDealResponseTable[namePath[0]] = true swaggerInfo.Paths[pathConfig.Uri][pathConfig.Method].Responses[itemResponseConfig.Code] = &define.SwaggerPathConfigResponse{ Description: "返回数据", Schema: &define.SwaggerPathConfigResponseSchema{ Ref: getRefValue(outputDefine), }, } } } } } // generateParameterDefinitions 生成请求参数的描述 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:06 2024/4/22 func generateParameterDefinitions(swaggerInfo *define.Swagger, uri string, parentPath string, subPath string, paramConfig *define.SwaggerParameterInput) { setGlobalMapDefinition(swaggerInfo, paramConfig.Type) uri = strings.ReplaceAll(strings.TrimLeft(uri, "/"), ".", "-") parentPath = strings.TrimLeft(parentPath, "/") checkPath := getCheckPath(parentPath) subPathArr := strings.Split(subPath, ".") initAnyDefinition(swaggerInfo, checkPath) if len(subPathArr) == 1 { handleOneLevelSubPath(swaggerInfo, uri, parentPath, subPath, paramConfig) return } if len(parentPath) == 0 { parentPath = uri + ".input" } if len(subPathArr) == 2 { if subPathArr[0] != "[]" { initAnyDefinition(swaggerInfo, parentPath) } if subPathArr[1] == "[]" { swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]] = &define.SwaggerDefinitionProperty{ Description: paramConfig.Description, Type: consts.SwaggerDataTypeArray, Items: &define.SwaggerDefinitionPropertyItem{ Type: util.GetSwaggerType(paramConfig.Type), Ref: "", Enum: nil, }, } } else { if subPathArr[0] == "[]" { generateParameterDefinitions(swaggerInfo, uri, parentPath+".item", subPathArr[1], paramConfig) return } else { swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]] = &define.SwaggerDefinitionProperty{ Description: "参数描述", Type: consts.SwaggerDataTypeObject, AllOf: []map[string]string{ { consts.SwaggerRefKey: getRefValue(parentPath + "." + subPathArr[0]), }, }, } } storageSubPath := parentPath + "." + subPathArr[0] if subPathArr[1] == "[]" { storageSubPath += ".item" } initAnyDefinition(swaggerInfo, storageSubPath) swaggerInfo.Definitions[storageSubPath].Type = wrapper.TernaryOperator.String(strings.HasSuffix(parentPath, "[]"), "array", consts.SwaggerDataTypeObject).Value() // 设置字段必传 addRequiredField(swaggerInfo, storageSubPath, subPathArr[1], paramConfig.Required) swaggerInfo.Definitions[storageSubPath].Properties[subPathArr[1]] = &define.SwaggerDefinitionProperty{ Description: paramConfig.Description, Type: util.GetSwaggerType(paramConfig.Type), } } return } if _, exist := swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]]; !exist { if subPathArr[1] == "[]" { nextParentPath := parentPath + "." + subPathArr[0] + ".item" swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]] = &define.SwaggerDefinitionProperty{ Description: "参数描述", Type: consts.SwaggerDataTypeArray, Items: &define.SwaggerDefinitionPropertyItem{ Type: "", Ref: getRefValue(nextParentPath), Enum: nil, }, } generateParameterDefinitions(swaggerInfo, uri, nextParentPath, strings.Join(subPathArr[2:], "."), paramConfig) return } itemSwaggerDefinition := &define.SwaggerDefinitionProperty{ Description: "对象描述", Type: wrapper.TernaryOperator.String(subPathArr[1] == "[]", "array", consts.SwaggerDataTypeObject).Value(), Items: nil, AllOf: nil, } if itemSwaggerDefinition.Type == consts.SwaggerDataTypeObject { itemSwaggerDefinition.AllOf = []map[string]string{map[string]string{ consts.SwaggerRefKey: getRefValue(parentPath + "." + subPathArr[0]), }} } else if itemSwaggerDefinition.Type == consts.SwaggerDataTypeArray { itemSwaggerDefinition.Description = "数组描述" itemSwaggerDefinition.Items = &define.SwaggerDefinitionPropertyItem{ Type: "", Ref: getRefValue(parentPath + "." + subPathArr[0] + ".item"), Enum: nil, } } swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]] = itemSwaggerDefinition } generateParameterDefinitions(swaggerInfo, uri, parentPath+"."+subPathArr[0], strings.Join(subPathArr[1:], "."), paramConfig) } // handleOneLevelSubPath 处理subPath一层的情况 // // Author : go_developer@163.com<白茶清欢> // // Date : 16:50 2024/4/25 func handleOneLevelSubPath(swaggerInfo *define.Swagger, uri string, parentPath string, subPath string, paramConfig *define.SwaggerParameterInput) { if paramConfig.In != strings.ToLower(consts.RequestDataLocationBody.String()) && !isGenerateOutput(parentPath) { // 长度为1, 还不在 body, 无需生成结构体 return } if strings.HasSuffix(paramConfig.Type, "[]") { swaggerInfo.Definitions[parentPath].Properties[subPath] = &define.SwaggerDefinitionProperty{ Description: paramConfig.Description, Type: consts.SwaggerDataTypeArray, Items: &define.SwaggerDefinitionPropertyItem{ Type: util.GetSwaggerType(strings.TrimSuffix(paramConfig.Type, "[]")), Ref: "", Enum: nil, }, } return } if isGlobalMapType(paramConfig.Type) { swaggerInfo.Definitions[parentPath].Properties[subPath] = &define.SwaggerDefinitionProperty{ Description: paramConfig.Description, Type: consts.SwaggerDataTypeObject, AllOf: []map[string]string{ { consts.SwaggerRefKey: getRefValue(consts.SwaggerBaseObjectDefinitionName), }, }, } return } initAnyDefinition(swaggerInfo, parentPath) if paramConfig.Required { swaggerInfo.Definitions[parentPath].Required = append(swaggerInfo.Definitions[parentPath].Required, subPath) } swaggerInfo.Definitions[parentPath].Properties[subPath] = &define.SwaggerDefinitionProperty{ Description: paramConfig.Description, Type: util.GetSwaggerType(paramConfig.Type), } } func getUri(uri string) string { return "/" + strings.TrimLeft(uri, "/") } // setGlobalMapDefinition ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:38 2024/4/25 func setGlobalMapDefinition(swaggerInfo *define.Swagger, dataType string) { if !isGlobalMapType(dataType) { return } // 只要最终类型存在mao, 就一定会用到 consts.SwaggerBaseObjectDefinitionName if nil == swaggerInfo.Definitions { swaggerInfo.Definitions = map[string]*define.SwaggerDefinition{} } if _, exist := swaggerInfo.Definitions[consts.SwaggerBaseObjectDefinitionName]; exist { return } swaggerInfo.Definitions[consts.SwaggerBaseObjectDefinitionName] = &define.SwaggerDefinition{ Type: consts.SwaggerDataTypeObject, Required: make([]string, 0), Properties: make(map[string]*define.SwaggerDefinitionProperty), } } // isGlobalMapType ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:46 2024/4/25 func isGlobalMapType(dataType string) bool { return wrapper.ArrayType([]string{ consts.DataTypeMapAnyAny.String(), consts.DataTypeMapStrUint.String(), consts.DataTypeMapStrInt.String(), consts.DataTypeMapStrSlice.String(), consts.DataTypeMapStrFloat.String(), consts.DataTypeMapStrBool.String(), consts.DataTypeMapStrAny.String(), }).Has(dataType) >= 0 } // initAnyDefinition 初始化一个definition // // Author : go_developer@163.com<白茶清欢> // // Date : 16:21 2024/4/25 func initAnyDefinition(swaggerInfo *define.Swagger, definitionName string) { if len(definitionName) == 0 { return } if nil == swaggerInfo.Definitions { swaggerInfo.Definitions = map[string]*define.SwaggerDefinition{} } if _, exist := swaggerInfo.Definitions[definitionName]; exist { return } swaggerInfo.Definitions[definitionName] = &define.SwaggerDefinition{ Type: consts.SwaggerDataTypeObject, Format: consts.DataTypeMapStrAny.String(), Required: make([]string, 0), Properties: make(map[string]*define.SwaggerDefinitionProperty), } } // getRefValue 获取refValue // // Author : go_developer@163.com<白茶清欢> // // Date : 16:31 2024/4/25 func getRefValue(path string) string { return consts.SwaggerRefValPrefix + strings.TrimLeft(path, "/") } // addRequiredField 添加必传字段 // // Author : go_developer@163.com<白茶清欢> // // Date : 16:57 2024/4/25 func addRequiredField(swaggerInfo *define.Swagger, definitionName string, field string, isRequired bool) { if !isRequired { return } swaggerInfo.Definitions[definitionName].Required = append(swaggerInfo.Definitions[definitionName].Required, definitionName) } // getCheckPath 获取检查的Definition // // Author : go_developer@163.com<白茶清欢> // // Date : 17:01 2024/4/25 func getCheckPath(parentPath string) string { parentPathArr := strings.Split(parentPath, ".") if isGenerateOutput(parentPath) { return strings.Join([]string{parentPathArr[0], parentPathArr[1], parentPathArr[2]}, ".") } else { if len(parentPathArr) >= 2 { return strings.Join([]string{parentPathArr[0], parentPathArr[1]}, ".") } } return parentPath } // isGenerateOutput ... // // Author : go_developer@163.com<白茶清欢> // // Date : 17:52 2024/4/25 func isGenerateOutput(parentPath string) bool { parentPathArr := strings.Split(parentPath, ".") if len(parentPathArr) >= 3 && parentPathArr[2] == "output" { return true } return false }