// Package api_doc ... // // Description : api_doc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2024-07-22 15:55 package api_doc import ( "errors" "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/wrapper" "net/http" "reflect" "strings" ) // NewOpenapiDoc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:56 2024/7/22 func NewOpenapiDoc(info *define.Info, servers []*define.ServerItem) *Generate { if nil == info { info = &define.Info{ Description: "openapi接口文档", Title: "openapi接口文档", TermsOfService: "", Contact: nil, License: nil, Version: "", } } if nil == info.License { info.License = &define.License{ Name: consts.LicenseApache20, Url: consts.LicenseUrlTable[consts.LicenseApache20], } } if nil == info.Contact { info.Contact = &define.Contact{ Name: "研发人员(developer)", Url: "", Email: "", } } if nil == servers { servers = []*define.ServerItem{} } return &Generate{ docData: &define.OpenapiDoc{ Openapi: consts.SwaggerDocVersion3, Info: info, Servers: servers, Components: &define.Components{Schemas: map[string]*define.Schema{}}, Tags: make([]*define.TagItem, 0), Paths: make(map[string]*define.PathConfig), }, } } // Generate 文档生成实例 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:57 2024/7/22 type Generate struct { docData *define.OpenapiDoc } // SetLicense 设置文档协议 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:56 2024/8/14 func (g *Generate) SetLicense(name string, url string) { g.docData.Info.License.Name = name g.docData.Info.License.Url = url } // AddTag 新增tag // // Author : go_developer@163.com<白茶清欢> // // Date : 14:23 2024/8/14 func (g *Generate) AddTag(tagName string, tagDesc string) { isHasTag := false for _, item := range g.docData.Tags { if item.Name == tagName { if len(tagDesc) > 0 { item.Description = tagDesc } isHasTag = true break } } if !isHasTag { g.docData.Tags = append(g.docData.Tags, &define.TagItem{ Name: tagName, Description: tagDesc, }) } } // AddServer 添加server // // Author : go_developer@163.com<白茶清欢> // // Date : 14:45 2024/8/14 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, }) } } // AddApi 新增Api // // Author : go_developer@163.com<白茶清欢> // // Date : 15:04 2024/8/14 // // baseCfg : 接口基础配置, 示例数据 // // &define.UriBaseConfig{ // Uri: "/foo/bar", // Method: http.MethodPost, // ContentType: ["application/json"], // TagList: []string{"测试标签"}, // Summary: "这是一份示例基础配置", // Description: "这是一份示例基础配置", // } // // paramList : 参数列表 // // resultList : 返回值列表 func (g *Generate) AddApi(baseCfg *define.UriBaseConfig, paramList []*define.ParamConfig, resultList []*define.ResultConfig) error { return nil } // AddApiFromInAndOut 通过请求参数的 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:22 2025/2/9 func (g *Generate) AddApiFromInAndOut(baseCfg *define.UriBaseConfig, paramType reflect.Type, resultType reflect.Type) error { if nil == baseCfg { return errors.New("baseCfg is nil") } if baseCfg.Method == "" { return errors.New("baseCfg.Method is empty") } if baseCfg.Uri == "" { return errors.New("baseCfg.Uri is empty") } baseCfg.Method = strings.ToUpper(baseCfg.Method) paramMethod := []string{ http.MethodGet, http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace, } if wrapper.ArrayType(paramMethod).Has(baseCfg.Method) >= 0 { // Get类请求, TODO : get类解析 return nil } // post类解析 defaultPkgPath := wrapper.String(strings.ReplaceAll(strings.TrimLeft(baseCfg.Uri, "/"), "/", "_")).SnakeCaseToCamel() paramPkgPath := defaultPkgPath + baseCfg.Method + "Param" resultPkgPath := defaultPkgPath + baseCfg.Method + "Result" g.AddComponentsSchema(paramPkgPath, paramType) g.AddComponentsSchema(resultPkgPath, resultType) if _, exist := g.docData.Paths[baseCfg.Uri]; !exist { g.docData.Paths[baseCfg.Uri] = &define.PathConfig{} } cfg := &define.PathItemOperationConfig{ Tags: baseCfg.TagList, Summary: baseCfg.Summary, Description: baseCfg.Description, ExternalDocs: nil, OperationID: defaultPkgPath + baseCfg.Method, Parameters: nil, RequestBody: &define.RequestBody{ Required: true, Description: "", Content: map[string]*define.Media{}, Ref: "", }, Responses: map[string]*define.Response{ fmt.Sprintf("%v", http.StatusOK): &define.Response{ Content: map[string]*define.Media{}, }, }, Callbacks: nil, Deprecated: false, Security: nil, Servers: nil, } for _, itemType := range baseCfg.ContentType { cfg.RequestBody.Content[itemType] = &define.Media{ Schema: &define.Schema{ Ref: g.getSchemaRes(paramPkgPath), }, Example: "", Examples: nil, Encoding: nil, } } for _, itemOutputType := range baseCfg.OutputContentType { cfg.Responses[fmt.Sprintf("%v", http.StatusOK)].Content[itemOutputType] = &define.Media{ Schema: &define.Schema{ Ref: g.getSchemaRes(resultPkgPath), }, Example: "", Examples: nil, Encoding: nil, } } switch baseCfg.Method { case http.MethodGet: g.docData.Paths[baseCfg.Uri].Get = cfg case http.MethodHead: g.docData.Paths[baseCfg.Uri].Head = cfg case http.MethodPost: g.docData.Paths[baseCfg.Uri].Post = cfg case http.MethodPut: g.docData.Paths[baseCfg.Uri].Put = cfg case http.MethodPatch: g.docData.Paths[baseCfg.Uri].Patch = cfg case http.MethodDelete: g.docData.Paths[baseCfg.Uri].Delete = cfg case http.MethodConnect: g.docData.Paths[baseCfg.Uri].Connect = cfg case http.MethodOptions: g.docData.Paths[baseCfg.Uri].Options = cfg case http.MethodTrace: g.docData.Paths[baseCfg.Uri].Trace = cfg } return nil } // AddComponentsSchema 添加schema // // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(schemaName string, inputType reflect.Type) string { if _, exist := g.docData.Components.Schemas[schemaName]; !exist { g.docData.Components.Schemas[schemaName] = &define.Schema{ Nullable: false, Discriminator: nil, ReadOnly: false, WriteOnly: false, Xml: nil, ExternalDocs: nil, Example: "", Deprecated: false, Properties: make(map[string]*define.Property), Required: make([]string, 0), Enum: make([]any, 0), Type: "", Ref: "", } } 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 := g.parseSliceItem(inputType) g.docData.Components.Schemas[schemaName].Properties[inputType.Name()] = &define.Property{ Type: consts.SwaggerDataTypeArray, Format: inputType.String(), Items: &define.PropertyXOf{Ref: g.getSchemaRes(sliceItemType)}, } return schemaName } // 结构体 if inputType.Kind() == reflect.Struct { for i := 0; i < inputType.NumField(); i++ { if inputType.Field(i).Type.Kind() == reflect.Ptr || 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 { g.AddComponentsSchema(inputType.PkgPath(), inputType.Field(i).Type) } else { g.docData.Components.Schemas[schemaName].Properties[inputType.Field(i).Type.String()] = &define.Property{ Type: "string", Format: inputType.Field(i).Type.String(), } } } } return schemaName } // parseSliceItem 解析数组每一项 // // Author : go_developer@163.com<白茶清欢> // // Date : 21:33 2025/2/8 func (g *Generate) parseSliceItem(inputType reflect.Type) string { if inputType.Kind() != reflect.Slice && inputType.Kind() != reflect.Array { // 不是数组 return "" } sliceValue := reflect.MakeSlice(inputType, 1, 1) sliceItemType := sliceValue.Index(0).Type() g.AddComponentsSchema(sliceItemType.PkgPath(), sliceItemType) if len(sliceItemType.PkgPath()) == 0 { return sliceItemType.String() } return sliceItemType.PkgPath() + "." + sliceItemType.String() } // getSchemaRes 获取引用的类型 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:25 2025/2/9 func (g *Generate) getSchemaRes(schemaName string) string { if "" == schemaName { return "" } return "#/components/schemas/" + schemaName }