// Package api_doc ... // // Description : api_doc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2024-07-22 15:55 package api_doc import ( "fmt" "git.zhangdeman.cn/gateway/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "reflect" "strings" ) // NewOpenapiDoc ... // // Author : go_developer@163.com<白茶清欢> // // Date : 15:56 2024/7/22 func NewOpenapiDoc() *Generate { return &Generate{ docData: &define.OpenapiDoc{ Openapi: consts.SwaggerDocVersion3, Info: &define.Info{ Description: "openapi接口文档", Title: "openapi接口文档", TermsOfService: "", Contact: &define.Contact{ Name: "", Url: "", Email: "", }, License: &define.License{ Name: "", Url: "", }, Version: "", }, Servers: make([]*define.ServerItem, 0), 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, }) } } /** type PathItemOperationConfig struct { Tags []string `json:"tags"` // 用于控制API文档的标签列表,标签可以用于在逻辑上分组对资源的操作或作为其它用途的先决条件。 Summary string `json:"summary"` // 对此操作行为的简短描述。 Description string `json:"description"` // 对此操作行为的详细解释。CommonMark syntax可以被用来呈现富文本格式. ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` // 附加的外部文档。 OperationID string `json:"operationId"` // 用于标识此操作的唯一字符串,这个id在此API内包含的所有操作中必须是唯一的。相关的工具和库可能会使用此operationId来唯一的标识一个操作,因此推荐在命名时符合一般的编程命名习惯 Parameters []*PathConfigParameter `json:"parameters,omitempty"` // 定义可用于此操作的参数列表,如果一个同名的参数已经存在于 Path Item,那么这里的定义会覆盖它但是不能移除上面的定义。这个列表不允许包含重复的参数,参数的唯一性由 name 和 location 的组合来确定。这个列表可以使用 Reference 对象 来连接定义于 OpenAPI 对象 components/parameters 的参数。 RequestBody *RequestBody `json:"requestBody,omitempty"` // 可用于此操作的请求体。requestBody 只能被用于HTTP 1.1 规范 RFC7231 中明确定义了包含请求体的请求方法,在其他没有明确定义的请求方法中,requestBody的消费者应该应该忽略requestBody。 Responses map[string]*Response `json:"responses"` // 描述一个操作可能发生的响应的响应码与响应包含的响应体的对象。default字段可以用来标记一个响应适用于其他未被规范明确定义的HTTP响应码的默认响应。一个Responses 对象必须至少包含一个响应码,而且是成功的响应。 Callbacks map[string]any `json:"callbacks,omitempty"` // 一组相对于父操作的可能出现的回调映射,映射中的每一个键都唯一的映射一个 Callback 对象 Deprecated bool `json:"deprecated"` // 声明此操作已经被废弃,使用者应该尽量避免使用此操作,默认的值是 false。 Security any `json:"security,omitempty"` // 声明那种安全机制可用于此操作。这个列表可以包含多种可用于此操作的安全需求对象,但是在认证一个请求时应该仅使用其中一种。这里的定义会覆盖任何在顶层 security 中的安全声明,因此可以声明一个空数组来变相的移除顶层的安全声明。 Servers []*ServerItem `json:"servers,omitempty"` // 一个可用于此操作的额外的 server 数组,这里的定义会覆盖 Path Item 对象 或 顶层的定义。 } */ // 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 } // AddComponentsSchema 添加schema // // Author : go_developer@163.com<白茶清欢> // // Date : 15:25 2025/2/8 func (g *Generate) AddComponentsSchema(pkgPath string, inputType reflect.Type) { if nil == g.docData { g.docData = &define.OpenapiDoc{} } if nil == g.docData.Components { g.docData.Components = &define.Components{ Schemas: map[string]*define.Schema{}, } } if nil == g.docData.Components.Schemas { g.docData.Components.Schemas = make(map[string]*define.Schema) } if len(pkgPath) == 0 { pkgPath = inputType.PkgPath() } schemaPrefix := "" if "" != pkgPath { schemaPrefix = pkgPath + "." } if inputType.Kind() == reflect.Ptr { inputType = inputType.Elem() } if inputType.Kind() == reflect.Map { // map, 直接添加公共 if _, exist := g.docData.Components.Schemas[schemaPrefix+inputType.Name()]; !exist { g.docData.Components.Schemas[schemaPrefix+inputType.Name()] = &define.Schema{ Type: consts.SwaggerDataTypeObject, } } return } // 数组 if inputType.Kind() == reflect.Slice || inputType.Kind() == reflect.Array { g.parseSliceItem(inputType) return } // 结构体 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) } fmt.Println(inputType.Field(i).Name) } } } // 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() }