// 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} } docConfig.Host = wrapper.String(docConfig.Host).ReplaceChar(map[string]string{ consts.SchemeHTTP + "://": "", consts.SchemeHTTPS + "://": "", }).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() // 默认标签 itemPath.TagList = wrapper.TernaryOperator.Array(len(itemPath.TagList) == 0, wrapper.ArrayType([]string{"未分组"}), wrapper.ArrayType(itemPath.TagList)).ToStringSlice() 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, 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.RequestLocationBody && !strings.Contains(realParamName, ".") { realParamName = "jsonBody" parentPath = 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: map[string]string{}, } if len(parentPath) > 0 { generateParam.Schema[consts.SwaggerRefKey] = 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(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.RequestLocationBody, 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: map[string]string{ "$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) { if isGlobalMapType(paramConfig.Type) { // 只要最终类型存在mao, 就一定会用到 global_map setGlobalMapDefinition(swaggerInfo) } uri = strings.TrimLeft(uri, "/") parentPath = strings.TrimLeft(parentPath, "/") if nil == swaggerInfo.Definitions { swaggerInfo.Definitions = map[string]*define.SwaggerDefinition{} } parentPathArr := strings.Split(parentPath, ".") checkPath := parentPath if len(parentPathArr) >= 2 { checkPath = strings.Join([]string{parentPathArr[0], parentPathArr[1]}, ".") } subPathArr := strings.Split(subPath, ".") if _, exist := swaggerInfo.Definitions[checkPath]; !exist && len(parentPath) > 0 { initAnyDefinition(swaggerInfo, checkPath) } if len(subPathArr) == 1 { if paramConfig.In != strings.ToLower(consts.RequestLocationBody) { // 长度为1, 还不在 body, 无需生成结构体 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), } return } if len(parentPath) == 0 { parentPath = uri + ".input" } if len(subPathArr) == 2 { if _, exist := swaggerInfo.Definitions[parentPath]; !exist && subPathArr[0] != "[]" { initAnyDefinition(swaggerInfo, parentPath) } if subPathArr[1] == "[]" { swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]] = &define.SwaggerDefinitionProperty{ Description: paramConfig.Description, Type: "array", Items: map[string]string{ "type": util.GetSwaggerType(paramConfig.Type), }, } } 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{map[string]string{ "$ref": 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() if paramConfig.Required { swaggerInfo.Definitions[storageSubPath].Required = append(swaggerInfo.Definitions[storageSubPath].Required, subPathArr[1]) } 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: "array", Items: map[string]string{ "$ref": getRefValue(nextParentPath), }, } 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{ "$ref": getRefValue(parentPath + "." + subPathArr[0]), }} } else if itemSwaggerDefinition.Type == "array" { itemSwaggerDefinition.Description = "数组描述" itemSwaggerDefinition.Items = map[string]string{ "$ref": getRefValue(parentPath + "." + subPathArr[0] + ".item"), } } swaggerInfo.Definitions[parentPath].Properties[subPathArr[0]] = itemSwaggerDefinition } // fmt.Println(parentPath + "." + subPathArr[0]) generateParameterDefinitions(swaggerInfo, uri, parentPath+"."+subPathArr[0], strings.Join(subPathArr[1:], "."), paramConfig) } 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) { if nil == swaggerInfo.Definitions { swaggerInfo.Definitions = map[string]*define.SwaggerDefinition{} } if _, exist := swaggerInfo.Definitions["global_map"]; exist { return } swaggerInfo.Definitions["global_map"] = &define.SwaggerDefinition{ Type: consts.SwaggerDataTypeObject, Required: nil, 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, consts.DataTypeMapStrUint, consts.DataTypeMapStrInt, consts.DataTypeMapStrSlice, consts.DataTypeMapStrFloat, consts.DataTypeMapStrBool, consts.DataTypeMapStrAny, }).Has(dataType) >= 0 } // initAnyDefinition 初始化一个definition // // Author : go_developer@163.com<白茶清欢> // // Date : 16:21 2024/4/25 func initAnyDefinition(swaggerInfo *define.Swagger, definitionName string) { 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, 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, "/") }