diff --git a/geerate_option.go b/geerate_option.go deleted file mode 100644 index c4f5114..0000000 --- a/geerate_option.go +++ /dev/null @@ -1,95 +0,0 @@ -// Package api_doc ... -// -// Description : api_doc ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 2025-08-23 09:30 -package api_doc - -import ( - "git.zhangdeman.cn/zhangdeman/api-doc/define" - "git.zhangdeman.cn/zhangdeman/api-doc/enums" -) - -// SetGenerateOption 设置文档生成选项 -type SetGenerateOption func(opt *define.OpenapiDoc) - -// generateOption 生成文档的一些配置选项 -type generateOption struct { - license enums.License // 文档的license - description string // 文档的描述 - title string // 文档的标题 -} - -// WithDocLicense 设置文档协议名称 + 协议链接 -func WithDocLicense(l enums.License) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - if l == "" { - return - } - opt.Info.License.Name = l - opt.Info.License.Url = enums.LicenseUrlTable[l] - } -} - -// WithDocDescription 设置文档描述 -func WithDocDescription(desc string) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - if desc == "" { - return - } - opt.Info.Description = desc - } -} - -// WithDocTitle 设置文档标题 -func WithDocTitle(title string) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - if len(title) == 0 { - return - } - opt.Info.Title = title - } -} - -// WithDocVersion 设置文档版本 -func WithDocVersion(version string) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - opt.Info.Version = version - } -} - -// WithDocContactName 设置文档联系人名称 -func WithDocContactName(name string) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - if name == "" { - return - } - opt.Info.Contact.Name = name - } -} - -// WithDocContactEmail 设置文档联系人邮箱 -func WithDocContactEmail(email string) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - opt.Info.Contact.Email = email - } -} - -// WithDocContactHomePage 设置文档联系人主页 -func WithDocContactHomePage(url string) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - opt.Info.Contact.Url = url - } -} - -// WithDocServers 设置文档服务器列表 -func WithDocServers(serverList []*define.ServerItem) SetGenerateOption { - return func(opt *define.OpenapiDoc) { - if len(serverList) == 0 { - return - } - opt.Servers = serverList - } -} diff --git a/generate.go b/generate.go deleted file mode 100644 index 38cb01d..0000000 --- a/generate.go +++ /dev/null @@ -1,831 +0,0 @@ -// Package api_doc ... -// -// Description : api_doc ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 2024-07-22 15:55 -package api_doc - -import ( - "errors" - "fmt" - "net/http" - "reflect" - "strings" - - "git.zhangdeman.cn/zhangdeman/api-doc/define" - "git.zhangdeman.cn/zhangdeman/api-doc/enums" - "git.zhangdeman.cn/zhangdeman/consts" - "git.zhangdeman.cn/zhangdeman/wrapper/op_array" - "git.zhangdeman.cn/zhangdeman/wrapper/op_string" -) - -// NewOpenapiDoc ... -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:56 2024/7/22 -func NewOpenapiDoc(ofs ...SetGenerateOption) *Generate { - // 初始默认值 - docCfg := &define.OpenapiDoc{ - Openapi: consts.SwaggerDocVersion3, - Info: &define.Info{ - Description: "openapi接口文档", - Title: "openapi接口文档", - TermsOfService: "", - Contact: &define.Contact{ - Name: "研发人员(developer)", - Url: "", - Email: "", - }, - License: &define.License{ - Name: enums.LicenseApache20, - Url: enums.LicenseUrlTable[consts.LicenseApache20], - }, - Version: "0.0.1", - }, - Servers: []*define.ServerItem{}, - Components: &define.Components{Schemas: map[string]*define.Schema{}}, - Tags: make([]*define.TagItem, 0), - Paths: make(map[string]*define.PathConfig), - } - - for _, option := range ofs { - option(docCfg) - } - return &Generate{ - readMethodList: []string{ - http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, - }, - docData: docCfg, - } -} - -// Generate 文档生成实例 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:57 2024/7/22 -type Generate struct { - docData *define.OpenapiDoc - readMethodList []string -} - -func (g *Generate) Doc() *define.OpenapiDoc { - return g.docData -} - -// AddServer 添加server -func (g *Generate) AddServer(serverDomain string, serverDesc string, serverVariable map[string]*define.ServerItemVariable) { - if nil == serverVariable { - serverVariable = make(map[string]*define.ServerItemVariable) - } - serverDomain = strings.TrimRight(serverDomain, "/") - isHasServer := false - for _, item := range g.docData.Servers { - if item.Url != serverDomain { - continue - } - isHasServer = true - if len(serverDesc) > 0 { - item.Description = serverDesc - } - for varName, varValue := range serverVariable { - item.Variables[varName] = varValue - } - break - } - if !isHasServer { - g.docData.Servers = append(g.docData.Servers, &define.ServerItem{ - Url: serverDomain, - Description: serverDesc, - Variables: serverVariable, - }) - } -} - -// AddApiFromInAndOut 通过请求参数的 -func (g *Generate) AddApiFromInAndOut(uriPrefix string, paramType reflect.Type, resultType reflect.Type) error { - if paramType.Kind() == reflect.Ptr { - paramType = paramType.Elem() - } - if resultType.Kind() == reflect.Ptr { - resultType = resultType.Elem() - } - baseCfg, err := g.parseBaseUriConfig(uriPrefix, paramType) - if nil != err { - return err - } - if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { - g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} - } - // 接口文档初始化 - cfg := g.getApiDocBaseCfg(baseCfg, paramType) - - if op_array.ArrayType[string](g.readMethodList).Has(baseCfg.Method) >= 0 { - cfg.RequestBody = nil // get类请求没有request body - // 参数解析 - g.ParseReadConfigParam(baseCfg, cfg, paramType) - } else { - // post类解析 - paramSchemaName := g.AddComponentsSchema("", paramType.PkgPath(), paramType) - for _, itemType := range baseCfg.ContentType { - cfg.RequestBody.Content[itemType] = &define.Media{ - Schema: &define.Schema{ - Ref: g.getSchemaRef(paramSchemaName), - }, - Example: nil, - Examples: nil, - Encoding: nil, - } - } - } - // 无论什么请求, 对于result解析逻辑一致 - resultSchemaName := g.AddComponentsSchema("", resultType.PkgPath(), resultType) - for _, itemOutputType := range baseCfg.OutputContentType { - cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ - Schema: &define.Schema{ - Ref: g.getSchemaRef(resultSchemaName), - }, - Example: nil, - Examples: nil, - Encoding: nil, - } - } - g.setApiDoc(baseCfg, cfg) - return nil -} - -// setApiDoc 设置文档配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:13 2025/2/14 -func (g *Generate) setApiDoc(baseCfg *define.UriBaseConfig, apiDocCfg *define.PathItemOperationConfig) { - switch baseCfg.Method { - case http.MethodGet: - g.docData.Paths[baseCfg.Uri].Get = apiDocCfg - case http.MethodHead: - g.docData.Paths[baseCfg.Uri].Head = apiDocCfg - case http.MethodPost: - g.docData.Paths[baseCfg.Uri].Post = apiDocCfg - case http.MethodPut: - g.docData.Paths[baseCfg.Uri].Put = apiDocCfg - case http.MethodPatch: - g.docData.Paths[baseCfg.Uri].Patch = apiDocCfg - case http.MethodDelete: - g.docData.Paths[baseCfg.Uri].Delete = apiDocCfg - case http.MethodConnect: - g.docData.Paths[baseCfg.Uri].Connect = apiDocCfg - case http.MethodOptions: - g.docData.Paths[baseCfg.Uri].Options = apiDocCfg - case http.MethodTrace: - g.docData.Paths[baseCfg.Uri].Trace = apiDocCfg - default: - panic("unknown method: " + baseCfg.Method) - } -} - -// getApiDocBaseCfg 获取接口文档的基础配置 -func (g *Generate) getApiDocBaseCfg(baseCfg *define.UriBaseConfig, paramType reflect.Type) *define.PathItemOperationConfig { - cfg := &define.PathItemOperationConfig{ - Tags: baseCfg.TagList, - Summary: baseCfg.Summary, - Description: baseCfg.Description, - ExternalDocs: nil, - OperationID: baseCfg.Summary + "(" + baseCfg.Method + "-" + strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "-") + ")", - Parameters: make([]*define.PathConfigParameter, 0), - RequestBody: &define.RequestBody{ - Required: true, - Description: "", - Content: map[string]*define.Media{}, - Ref: "", - }, - Responses: map[string]*define.Response{ - fmt.Sprintf("%v", http.StatusOK): { - Content: map[string]*define.Media{}, - }, - }, - Callbacks: nil, - Deprecated: baseCfg.Deprecated, - Security: nil, - Servers: nil, - } - // 解析绑定在url中的参数 - if paramList := define.UriParamRegexp.FindAllString(baseCfg.Uri, -1); len(paramList) > 0 { - for _, param := range paramList { - param = strings.TrimPrefix(param, "{") - param = strings.TrimSuffix(param, "}") - cfg.Parameters = append(cfg.Parameters, &define.PathConfigParameter{ - Name: param, - In: enums.DocParamLocationPath.String(), - Description: param, - Required: true, - Deprecated: false, - Schema: &define.Schema{ - Type: enums.SwaggerDataTypeString.String(), - Format: consts.DataTypeString.String(), - }, - AllowEmptyValue: false, - }) - } - } - return cfg -} - -// ParseReadConfigParam 解析get类请求参数 -func (g *Generate) ParseReadConfigParam(requestCfg *define.UriBaseConfig, baseReqCfg *define.PathItemOperationConfig, inputType reflect.Type) { - if inputType.Kind() == reflect.Ptr { - inputType = inputType.Elem() - } - if inputType.Kind() != reflect.Struct { - panic(requestCfg.Uri + " : request param not struct") - } - if nil == baseReqCfg.Parameters { - baseReqCfg.Parameters = make([]*define.PathConfigParameter, 0) - } - for i := 0; i < inputType.NumField(); i++ { - if inputType.Field(i).Anonymous { - // 匿名字段, 直接对齐到当前的父级 - g.ParseReadConfigParam(requestCfg, baseReqCfg, inputType.Field(i).Type) - continue - } - propertyName := ParseStructFieldTag.GetParamName(inputType.Field(i)) - if propertyName == "-" { - continue - } - if inputType.Field(i).Type.Kind() == reflect.Struct && (inputType.Field(i).Type.String() == "Meta" || strings.HasSuffix(inputType.Field(i).Type.String(), ".Meta")) && inputType.Field(i).Type.NumField() == 0 { - // 空Meta字段认为是用来描述元信息的, 忽略 - continue - } - convertBaseType, isBaseType := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()) - realInputTypeFormat := inputType.Field(i).Type.String() - fieldType := inputType.Field(i).Type - if isBaseType { - // 当做默认基础类型, 默认不会出现 *map *[] - itemParam := &define.PathConfigParameter{ - Name: propertyName, - In: consts.SwaggerParameterInQuery, - Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), - Required: ValidateRule.IsRequired(inputType.Field(i)), - Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), - Schema: &define.Schema{ - Type: convertBaseType, - Format: realInputTypeFormat, - }, - AllowEmptyValue: false, - Style: "", - Explode: false, - AllowReserved: false, - } - g.setStructFieldProperty(itemParam.Schema, inputType.Field(i)) - baseReqCfg.Parameters = append(baseReqCfg.Parameters, itemParam) - continue - } - if inputType.Field(i).Type.Kind() == reflect.Interface { - itemParam := &define.PathConfigParameter{ - Name: ParseStructFieldTag.GetParamName(inputType.Field(i)), - In: consts.SwaggerParameterInQuery, - Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), - Required: ValidateRule.IsRequired(inputType.Field(i)), - Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), - Schema: &define.Schema{ - OneOf: g.anyTypeConfig(inputType.Field(i)).OneOf, - Format: realInputTypeFormat, - }, - } - g.setStructFieldProperty(itemParam.Schema, inputType.Field(i)) - baseReqCfg.Parameters = append(baseReqCfg.Parameters, itemParam) - continue - } - if inputType.Field(i).Type.Kind() == reflect.Ptr { - // 处理指针 - if inputType.Field(i).Type.Elem().Kind() == reflect.Struct { - // 结构体指针 - schemaNameNext := g.AddComponentsSchema("", propertyName, inputType.Field(i).Type.Elem()) - baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ - Name: propertyName, - In: consts.SwaggerParameterInQuery, - Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), - Required: ValidateRule.IsRequired(inputType.Field(i)), - Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), - Schema: &define.Schema{ - // Format: realInputTypeFormat, - Ref: g.getSchemaRef(schemaNameNext), - }, AllowEmptyValue: false, - Style: "", - Explode: false, - AllowReserved: false, - }) - } else { - - } - continue - } - if fieldType.Kind() == reflect.Struct || - fieldType.Kind() == reflect.Map || - fieldType.Kind() == reflect.Array || - fieldType.Kind() == reflect.Slice { - baseReqCfg.Parameters = append(baseReqCfg.Parameters, &define.PathConfigParameter{ - Name: ParseStructFieldTag.GetParamName(inputType.Field(i)), - In: consts.SwaggerParameterInQuery, - Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), - Required: ValidateRule.IsRequired(inputType.Field(i)), - Deprecated: ParseStructFieldTag.Deprecated(inputType.Field(i)), - Schema: &define.Schema{ - Type: convertBaseType, - Items: nil, - Ref: "", - Format: realInputTypeFormat, - Enum: ValidateRule.Enum(inputType.Field(i)), - XEnumDescription: ParseStructFieldTag.EnumDescription(inputType.Field(i)), - Example: ParseStructFieldTag.GetExampleValue(inputType.Field(i)), - }, - AllowEmptyValue: false, - Style: "", - Explode: false, - AllowReserved: false, - Ref: "", - }) - } - } - switch requestCfg.Method { - case http.MethodGet: - g.docData.Paths[requestCfg.Uri].Get = baseReqCfg - case http.MethodHead: - g.docData.Paths[requestCfg.Uri].Head = baseReqCfg - case http.MethodConnect: - g.docData.Paths[requestCfg.Uri].Connect = baseReqCfg - case http.MethodOptions: - g.docData.Paths[requestCfg.Uri].Options = baseReqCfg - case http.MethodTrace: - g.docData.Paths[requestCfg.Uri].Trace = baseReqCfg - } -} - -// AddComponentsSchema 添加schema -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:25 2025/2/8 -func (g *Generate) AddComponentsSchema(rootSchemaName string, pkgPath string, inputType reflect.Type) string { - if inputType.Kind() == reflect.Struct && (inputType.String() == "Meta" || strings.HasSuffix(inputType.String(), ".Meta")) && inputType.NumField() == 0 { - // 空Meta字段认为是用来描述元信息的, 忽略 - return "-" - } - inputNameArr := strings.Split(inputType.Name(), ".") - inputName := inputNameArr[len(inputNameArr)-1] - schemaName := strings.ReplaceAll(pkgPath+"."+inputName, "/", ".") - if schemaName == "-" { - // 忽略的属性 - return schemaName - } - if _, exist := g.docData.Components.Schemas[schemaName]; exist { - // 已存在, 无需重复生成 - return schemaName - } - if _, exist := g.docData.Components.Schemas[schemaName]; !exist { - s := &define.Schema{ - Nullable: false, - Deprecated: false, - Properties: make(map[string]*define.Property), - Required: make([]string, 0), - Enum: make([]any, 0), - Type: consts.SwaggerDataTypeObject, // TODO : 区分数组 - Ref: g.getSchemaRef(schemaName), - } - if len(rootSchemaName) == 0 || inputType.Kind() == reflect.Struct { - s.Ref = "" - } - g.docData.Components.Schemas[schemaName] = s - } - if inputType.Kind() == reflect.Map { - // map, 直接添加公共 - g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeObject - return schemaName - } - // 数组 - if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { - sliceItemType, itemIsBaseType := g.parseSliceItem(schemaName, inputType) - propertyXOf := &define.PropertyXOf{} - if itemIsBaseType { - propertyXOf.Type, _ = g.realBaseType2SwaggerType(sliceItemType) - propertyXOf.Format = sliceItemType - propertyXOf.Ref = "" - } else { - propertyXOf.Type = "" - propertyXOf.Format = "" - propertyXOf.Ref = g.getSchemaRef(sliceItemType) - } - if len(rootSchemaName) == 0 { - g.docData.Components.Schemas[schemaName].Type = consts.SwaggerDataTypeArray - g.docData.Components.Schemas[schemaName].Items = propertyXOf - } else { - g.docData.Components.Schemas[rootSchemaName].Properties[schemaName] = &define.Property{ - Type: consts.SwaggerDataTypeArray, - Format: inputType.String(), - Items: propertyXOf, - } - } - return schemaName - } - // 结构体 - if inputType.Kind() == reflect.Struct { - for i := 0; i < inputType.NumField(); i++ { - propertyName := ParseStructFieldTag.GetParamName(inputType.Field(i)) - if propertyName == "-" { - continue - } - - if inputType.Field(i).Anonymous { - // 处理匿名字段 - g.handleAnonymousField(schemaName, inputType.Field(i)) - continue - } - if inputType.Kind() == reflect.Interface { - // 处理interface{}类型参数 - g.docData.Components.Schemas[schemaName].Properties[propertyName] = g.anyTypeConfig(inputType.Field(i)) - continue - } - if inputType.Field(i).Type.Kind() == reflect.Ptr { - // 处理指针 - if inputType.Field(i).Type.Elem().Kind() == reflect.Struct { - // 结构体指针 - schemaNameNext := g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type.Elem()) - g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ - // Description: ParseStructFieldTag.GetParamDesc(inputType.Field(i)), - Ref: g.getSchemaRef(schemaNameNext), - } - } else { - // 当做默认基础类型, 默认不会出现 *map *[] - convertBaseType, _ := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()) - g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ - Type: convertBaseType, - Format: inputType.Field(i).Type.String(), - } - } - // 设置参数各种属性 - g.setStructFieldProperty(g.docData.Components.Schemas[schemaName], inputType.Field(i)) - continue - } - if inputType.Field(i).Type.Kind() == reflect.Struct || - inputType.Field(i).Type.Kind() == reflect.Map || - inputType.Field(i).Type.Kind() == reflect.Array || - inputType.Field(i).Type.Kind() == reflect.Slice { - if inputType.Field(i).Type.Kind() == reflect.Struct || - inputType.Field(i).Type.Kind() == reflect.Map { - g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ - Type: consts.SwaggerDataTypeObject, - Format: inputType.Field(i).Type.String(), - Properties: map[string]*define.Property{}, - } - } else if inputType.Field(i).Type.Kind() == reflect.Array || - inputType.Field(i).Type.Kind() == reflect.Slice { - sliceItemType, itemIsBaseType := g.parseSliceItem(schemaName, inputType.Field(i).Type) - propertyXOf := &define.PropertyXOf{} - if itemIsBaseType { - propertyXOf.Type, _ = g.realBaseType2SwaggerType(sliceItemType) - propertyXOf.Format = sliceItemType - propertyXOf.Ref = "" - } else { - propertyXOf.Type = "" - propertyXOf.Format = "" - propertyXOf.Ref = g.getSchemaRef(sliceItemType) - } - g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ - Type: consts.SwaggerDataTypeArray, - Format: inputType.Field(i).Type.String(), - Items: propertyXOf, - } - } else { - g.AddComponentsSchema(schemaName, propertyName, inputType.Field(i).Type) - } - } else { - if inputType.Field(i).Type.Kind() == reflect.Interface { - g.docData.Components.Schemas[schemaName].Properties[propertyName] = g.anyTypeConfig(inputType.Field(i)) - g.docData.Components.Schemas[schemaName].Properties[propertyName].Example = ParseStructFieldTag.GetExampleValue(inputType.Field(i)) - } else { - convertBaseType, _ := g.realBaseType2SwaggerType(inputType.Field(i).Type.String()) - g.docData.Components.Schemas[schemaName].Properties[propertyName] = &define.Property{ - Type: convertBaseType, - Format: inputType.Field(i).Type.String(), - } - } - } - // 设置参数各种属性 - g.setStructFieldProperty(g.docData.Components.Schemas[schemaName], inputType.Field(i)) - } - return schemaName - } - // 指针 - if inputType.Kind() == reflect.Ptr { - if inputType.Elem().Kind() == reflect.Struct { - // 非基础数据类型 - return g.AddComponentsSchema(schemaName, inputType.Elem().String(), inputType.Elem()) - } else { - convertType, _ := g.realBaseType2SwaggerType(inputType.String()) - g.docData.Components.Schemas[schemaName].Properties[schemaName] = &define.Property{ - Type: convertType, - Format: inputType.String(), - Properties: map[string]*define.Property{}, - } - } - } - return schemaName -} - -// handleAnonymousField 处理匿名字段 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 18:43 2025/2/17 -func (g *Generate) handleAnonymousField(schemaName string, field reflect.StructField) { - if !field.Anonymous { - // 不是匿名字段 - return - } - handleType := field.Type - if handleType.Kind() == reflect.Ptr { - handleType = handleType.Elem() - } - for i := 0; i < handleType.NumField(); i++ { - itemField := handleType.Field(i) - if itemField.Anonymous { - // 递归处理多层嵌套匿名字段 - g.handleAnonymousField(schemaName, itemField) - continue - } else { - baseConvertType, isBaseType := g.realBaseType2SwaggerType(itemField.Type.String()) - if !isBaseType { - paramName := ParseStructFieldTag.GetParamName(itemField) - if itemField.Type.Kind() == reflect.Interface { - g.docData.Components.Schemas[schemaName].Properties[paramName] = g.anyTypeConfig(itemField) - } - g.AddComponentsSchema(schemaName, handleType.Field(i).Type.PkgPath(), handleType.Field(i).Type) - continue - } else { - paramName := ParseStructFieldTag.GetParamName(itemField) - g.docData.Components.Schemas[schemaName].Properties[paramName] = &define.Property{ - Type: baseConvertType, - Format: itemField.Type.String(), - Example: ParseStructFieldTag.GetExampleValue(itemField), - Enum: ValidateRule.Enum(itemField), - XEnumDescription: ParseStructFieldTag.EnumDescription(itemField), - Default: ParseStructFieldTag.GetDefaultValue(handleType.Field(i)), - Description: ParseStructFieldTag.GetParamDesc(handleType.Field(i)), - } - if ValidateRule.IsRequired(itemField) { - g.docData.Components.Schemas[schemaName].Required = append(g.docData.Components.Schemas[schemaName].Required, paramName) - } - } - } - } -} - -// parseSliceItem 解析数组每一项 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 21:33 2025/2/8 -func (g *Generate) parseSliceItem(rootSchemaName string, inputType reflect.Type) (string, bool) { - if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array { - // 不是数组 - return "", false - } - sliceValue := reflect.MakeSlice(inputType, 1, 1) - sliceItemType := sliceValue.Index(0).Type() - realSliceItemType := sliceItemType.String() - if sliceItemType.Kind() == reflect.Ptr { - sliceItemType = sliceItemType.Elem() - } - _, isBaseType := g.realBaseType2SwaggerType(sliceItemType.String()) - if isBaseType { - return realSliceItemType, true - } - g.AddComponentsSchema(rootSchemaName, sliceItemType.PkgPath(), sliceItemType) - if len(sliceItemType.PkgPath()) == 0 { - return realSliceItemType, false - } - return sliceItemType.PkgPath() + "." + sliceItemType.Name(), false -} - -// getSchemaRef 获取引用的类型 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:25 2025/2/9 -func (g *Generate) getSchemaRef(schemaName string) string { - if "" == schemaName { - return "" - } - schemaName = strings.ReplaceAll(schemaName, "*", "") // 去除指针类型 * - convertType, isBaseType := g.realBaseType2SwaggerType(schemaName) - if isBaseType { - return convertType - } - return "#/components/schemas/" + strings.ReplaceAll(convertType, "/", ".") -} - -// realType2SwaggerType golang 真实数据类型转换为golang数据类型 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 20:25 2025/2/11 -func (g *Generate) realBaseType2SwaggerType(realType string) (string, bool) { - switch realType { - case "bool", "*bool": - return consts.SwaggerDataTypeBoolean, true - case "string", "*string": - return consts.SwaggerDataTypeString, true - case "byte", "*byte": - return consts.SwaggerDataTypeByte, true - case "float32", "*float32", "float64", "*float64": - return consts.SwaggerDataTypeDouble, true - case "int", "*int", "uint", "*uint", "int64", "*int64", "uint64", "*uint64": - return consts.SwaggerDataTypeInteger, true - case "int8", "*int8", "uint8", "*uint8", "int16", "*int16", "uint16", "*uint16", "int32", "*int32", "uint32", "*uint32": - return consts.SwaggerDataTypeInteger, true - default: - if strings.HasPrefix(realType, "[]") { - return consts.SwaggerDataTypeArray, true - } - return realType, false - } -} - -// setStructFieldProperty 添加struct_field各种属性 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 16:13 2025/2/13 -func (g *Generate) setStructFieldProperty(schema *define.Schema, structField reflect.StructField) { - paramName := ParseStructFieldTag.GetParamName(structField) - if paramName == "" || paramName == "-" { - return - } - isRequired := ValidateRule.IsRequired(structField) - enum := ValidateRule.Enum(structField) - xEnumDescription := ParseStructFieldTag.EnumDescription(structField) - example := ParseStructFieldTag.GetExampleValue(structField) - description := ParseStructFieldTag.GetParamDesc(structField) - maxVal := ValidateRule.Maximum(structField) - minVal := ValidateRule.Minimum(structField) - if nil != schema { - if isRequired { - schema.Required = append(schema.Required, paramName) - } - if nil == schema.Properties[paramName] { - if schema.Type == consts.SwaggerDataTypeString { - schema.MinLength = minVal - schema.MaxLength = maxVal - } else { - schema.Minimum = minVal - schema.Maximum = maxVal - } - schema.Enum = enum - schema.XEnumDescription = xEnumDescription - schema.Example = example - schema.Description = description - return - } - if schema.Properties[paramName].Type == consts.SwaggerDataTypeString { - schema.Properties[paramName].MinLength = minVal - schema.Properties[paramName].MaxLength = maxVal - } else { - schema.Properties[paramName].Minimum = minVal - schema.Properties[paramName].Maximum = maxVal - } - schema.Properties[paramName].Enum = enum - schema.Properties[paramName].XEnumDescription = xEnumDescription - schema.Properties[paramName].Example = example - schema.Properties[paramName].Description = description - } -} - -// parseBaseUriConfig 通过Meta字段解析Uri基础配置信息 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 15:13 2025/2/14 -func (g *Generate) parseBaseUriConfig(uriPrefix string, paramType reflect.Type) (*define.UriBaseConfig, error) { - // 解析meta信息 - metaField, metaFieldExist := paramType.FieldByName("Meta") - if !metaFieldExist { - return nil, errors.New("Meta field not found") - } - res := &define.UriBaseConfig{ - Uri: "", - Method: "", - ContentType: nil, - OutputContentType: nil, - TagList: nil, - Summary: "", - Description: "", - ParamList: nil, - ResultList: nil, - Deprecated: false, - } - res.Uri = metaField.Tag.Get(define.TagPath) - if len(uriPrefix) > 0 { - res.Uri = strings.TrimRight(uriPrefix, "/") + "/" + strings.TrimLeft(res.Uri, "/") - } - // 保证接口路由以 /开头 - res.Uri = "/" + strings.TrimLeft(res.Uri, "/") - - res.Method = strings.ToUpper(metaField.Tag.Get(define.TagMethod)) - res.Description = metaField.Tag.Get(define.TagDesc) - res.TagList = strings.Split(metaField.Tag.Get(define.TagUriTag), ",") - // 解析第一个返回值, 要求必须是结构体或者是map - outputStrictModel := metaField.Tag.Get(define.TagOutputStrict) - res.OutputStrict = outputStrictModel == "1" || outputStrictModel == "true" - deprecated := metaField.Tag.Get(define.TagDeprecated) - res.Deprecated = deprecated == "1" || deprecated == "true" - requestContentType := strings.TrimSpace(metaField.Tag.Get(define.TagContentType)) - if len(requestContentType) == 0 { - if op_array.ArrayType[string](g.readMethodList).Has(res.Method) >= 0 { - // get类请求 - requestContentType = consts.MimeTypeXWWWFormUrlencoded - } else { - requestContentType = consts.MimeTypeJson - } - } - res.ContentType = strings.Split(requestContentType, ",") - responseContentType := strings.TrimSpace(metaField.Tag.Get(define.TagOutputContentType)) - if len(responseContentType) == 0 { - // 未设置响应类型默认JSON数据 - responseContentType = consts.MimeTypeJson - } - res.OutputContentType = strings.Split(responseContentType, ",") - res.Summary = ParseStructFieldTag.Summary(metaField) - if len(res.Summary) == 0 { - res.Summary = op_string.SnakeCaseToCamel(strings.ReplaceAll(strings.TrimLeft(res.Uri, "/"), "/", "_")) - } - if res.Method == "" { - return nil, errors.New("baseCfg.Method is empty") - } - if res.Uri == "" { - return nil, errors.New("baseCfg.Uri is empty") - } - if nil == g.docData.Tags { - g.docData.Tags = make([]*define.TagItem, 0) - } - // 增加tag - for _, itemTag := range res.TagList { - exist := false - for _, t := range g.docData.Tags { - if itemTag == t.Name { - exist = true - break - } - } - if !exist { - g.docData.Tags = append(g.docData.Tags, &define.TagItem{ - Name: itemTag, - Description: itemTag, - }) - } - } - return res, nil -} - -// anyTypeConfig 任意类型数据配置 -// -// Author : go_developer@163.com<白茶清欢> -// -// Date : 14:33 2025/2/19 -func (g *Generate) anyTypeConfig(structField reflect.StructField) *define.Property { - return &define.Property{ - Description: ParseStructFieldTag.GetParamDesc(structField), - OneOf: []*define.PropertyXOf{ - { - Type: consts.SwaggerDataTypeObject, - Format: "map[string]any", - }, - /* { - Type: consts.SwaggerDataTypeArray, - Format: "[]any", - Items: &define.PropertyXOf{ - Items: nil, - }, - },*/ - { - Type: consts.SwaggerDataTypeInteger, - Format: "int/uint", - }, - { - Type: consts.SwaggerDataTypeNumber, - Format: "int/uint/float", - }, - { - Type: consts.SwaggerDataTypeString, - Format: "string", - }, - { - Type: consts.SwaggerDataTypeBoolean, - Format: "bool", - }, - }, - } - -} diff --git a/swagger_ui.go b/swagger_ui.go index ac73b80..afd1498 100644 --- a/swagger_ui.go +++ b/swagger_ui.go @@ -8,12 +8,10 @@ package api_doc import ( - "fmt" "net/http" "path/filepath" "strings" - "git.zhangdeman.cn/zhangdeman/api-doc/define" "git.zhangdeman.cn/zhangdeman/api-doc/enums" "git.zhangdeman.cn/zhangdeman/api-doc/theme" "git.zhangdeman.cn/zhangdeman/consts" @@ -25,96 +23,18 @@ import ( ) // NewSwaggerUI ... -func NewSwaggerUI(info *define.Info, servers []*define.ServerItem, uiTheme enums.SwaggerUITheme) *SwaggerUI { - if nil == info { - info = &define.Info{ - Description: "", - Title: "", - TermsOfService: "", - Contact: &define.Contact{ - Name: "", - Url: "", - Email: "", - }, - License: nil, - Version: "", - } - } - if nil == info.Contact { - info.Contact = &define.Contact{ - Name: "", - Url: "", - Email: "", - } - } - if nil == info.License { - info.License = &define.License{ - Name: "", - Url: "", - } - } +func NewSwaggerUI(docTitle string, docBaseUri string, uiTheme enums.SwaggerUITheme) *SwaggerUI { return &SwaggerUI{ - docInstance: NewOpenapiDoc( - WithDocDescription(info.Description), - WithDocTitle(info.Title), - WithDocContactEmail(info.Contact.Email), - WithDocContactName(info.Contact.Name), - WithDocLicense(info.License.Name), - WithDocServers(servers), - ), - uiTheme: uiTheme, + baseUri: docBaseUri, + docTitle: docTitle, + uiTheme: uiTheme, } } type SwaggerUI struct { - docInstance *Generate // 文档实例 - uiTheme enums.SwaggerUITheme // 文档主题, swaggerUI / knife4go, 默认 knife4go - router *gin.Engine - baseUri string -} - -// DocInstance 文档实例 -func (su *SwaggerUI) DocInstance() *Generate { - return su.docInstance -} - -// RegisterHandler ... -func (su *SwaggerUI) RegisterHandler(router *gin.Engine, baseUri string) { - su.router = router - baseUri = strings.TrimRight(baseUri, "/") - if len(baseUri) == 0 { - baseUri = "/docs/swagger" - } - su.baseUri = baseUri - router.GET(baseUri+"/*any", func(ctx *gin.Context) { - if ctx.Request.RequestURI == baseUri+"/doc.json" { - // 默认swagger, 通过此接口读取文档数据 - ctx.JSON(http.StatusOK, su.docInstance.Doc()) - ctx.Abort() - } - if ctx.Request.RequestURI == "/doc/swagger/openapi.json" { - // knife4go 文档通过此接口读取文档列表 - ctx.JSON(http.StatusOK, []map[string]any{ - { - "name": "服务文档", - "url": "doc.json", - "swaggerVersion": enums.SwaggerDocVersion3.String(), - }, - }) - ctx.Abort() - } - }, su.Handler()) - router.GET("/swagger-resources", func(ctx *gin.Context) { // lucky UI获取分组信息 - ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 允许访问所有域 - ctx.JSON(http.StatusOK, []map[string]any{ - { - "name": "服务文档", - "url": baseUri + "/doc.json", - "swaggerVersion": enums.SwaggerDocVersion3.String(), - }, - }) - // ctx.JSON(http.StatusOK, swaggerInstance.docInstance.Data()) - }) + baseUri string + docTitle string + uiTheme enums.SwaggerUITheme // 文档主题, swaggerUI / knife4go, 默认 knife4go } // Handler 访问文档的接口处理 @@ -178,7 +98,7 @@ func (su *SwaggerUI) HandleRedocFreeUI() func(ctx *gin.Context) { return func(ctx *gin.Context) { // TODO : 这部分数据支持外部传参替换 replaceTable := map[string]string{ - "{{DOC_TITLE}}": fmt.Sprintf("【%v】%v", su.docInstance.Doc().Info.Version, su.docInstance.Doc().Info.Title), + "{{DOC_TITLE}}": su.docTitle, "{{CSS_FAMILY}}": "https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700", "{{DOC_PATH}}": "doc.json", "{{REDOC_STANDALONE_JS}}": "https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js",