468 lines
17 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}