// Package openapi ... // // Description : openapi ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2026-01-05 17:20 package openapi import ( "fmt" "net/http" "reflect" "strings" "git.zhangdeman.cn/zhangdeman/api-doc/define" "git.zhangdeman.cn/zhangdeman/consts" "git.zhangdeman.cn/zhangdeman/wrapper/op_array" "github.com/getkin/kin-openapi/openapi3" ) // NewGenerate 生成文档实例 func NewGenerate() *Generate { return &Generate{ doc: &openapi3.T{ Extensions: map[string]any{}, OpenAPI: "3.1.0", Components: &openapi3.Components{ Extensions: map[string]any{}, Origin: &openapi3.Origin{ Key: &openapi3.Location{ Line: 0, Column: 0, }, Fields: make(map[string]openapi3.Location), }, Schemas: map[string]*openapi3.SchemaRef{}, Parameters: map[string]*openapi3.ParameterRef{}, Headers: map[string]*openapi3.HeaderRef{}, RequestBodies: map[string]*openapi3.RequestBodyRef{}, Responses: map[string]*openapi3.ResponseRef{}, SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{}, Examples: map[string]*openapi3.ExampleRef{}, Links: map[string]*openapi3.LinkRef{}, Callbacks: map[string]*openapi3.CallbackRef{}, }, Info: &openapi3.Info{ Extensions: map[string]any{}, Origin: &openapi3.Origin{ Key: &openapi3.Location{ Line: 0, Column: 0, }, Fields: make(map[string]openapi3.Location), }, Title: "服务 API接口 文档", Description: "服务 API接口 文档", TermsOfService: "", Contact: &openapi3.Contact{ Extensions: map[string]any{}, Origin: &openapi3.Origin{ Key: &openapi3.Location{ Line: 0, Column: 0, }, Fields: make(map[string]openapi3.Location), }, Name: "developer", URL: "", Email: "developer@test.com", }, License: &openapi3.License{ Extensions: map[string]any{}, Origin: &openapi3.Origin{ Key: &openapi3.Location{ Line: 0, Column: 0, }, Fields: make(map[string]openapi3.Location), }, Name: consts.LicenseApache20, URL: consts.LicenseUrlTable[consts.LicenseApache20], }, Version: "0.0.1", }, Paths: &openapi3.Paths{ Extensions: map[string]any{}, Origin: &openapi3.Origin{ Key: &openapi3.Location{ Line: 0, Column: 0, }, Fields: make(map[string]openapi3.Location), }, }, Security: []openapi3.SecurityRequirement{}, Servers: []*openapi3.Server{ { Extensions: nil, Origin: nil, URL: "http://127.0.0.1:8080/v1", Description: "开发环境", Variables: nil, }, }, Tags: []*openapi3.Tag{}, /*ExternalDocs: &openapi3.ExternalDocs{ Extensions: map[string]any{}, Origin: nil, Description: "", URL: "", },*/ }, } } // Generate 生成 OpenApi 标准规范的文档 type Generate struct { doc *openapi3.T } // AddApiDoc 添加接口文档 func (g *Generate) AddApiDoc(apiMeta define.UriConfig, request any, response any) error { var ( err error requestType reflect.Type responseType reflect.Type ok bool ) // 初始化请求数据与响应数据类型 if requestType, ok = request.(reflect.Type); !ok { requestType = reflect.TypeOf(request) if requestType.Kind() == reflect.Ptr { requestType = requestType.Elem() } } if responseType, ok = response.(reflect.Type); !ok { responseType = reflect.TypeOf(response) if responseType.Kind() == reflect.Ptr { responseType = responseType.Elem() } } schemaData := GenerateOpenAPISchema(requestType) apiOperate, isRead := g.initApiConfig(apiMeta) requestTypeStr := requestType.String() if isRead { for paramName, paramConfig := range schemaData.Value.Properties { apiOperate.Parameters = append(apiOperate.Parameters, &openapi3.ParameterRef{ Extensions: nil, Origin: nil, Ref: "", Value: &openapi3.Parameter{ Extensions: nil, Origin: nil, Name: paramName, In: strings.ToLower(consts.RequestDataLocationQuery.String()), Description: paramConfig.Value.Description, Style: "", Explode: nil, AllowEmptyValue: paramConfig.Value.AllowEmptyValue, AllowReserved: false, Deprecated: false, Required: op_array.ArrayType(paramConfig.Value.Required).Has(paramName) >= 0, Schema: paramConfig, Example: nil, Examples: nil, Content: nil, }, }) } } else { apiOperate.RequestBody = &openapi3.RequestBodyRef{ Extensions: nil, Origin: nil, Ref: "", Value: &openapi3.RequestBody{ Extensions: nil, Origin: nil, Description: "", Required: false, Content: map[string]*openapi3.MediaType{ consts.MimeTypeJson: { Extensions: nil, Origin: nil, Schema: schemaData, Example: nil, Examples: nil, Encoding: nil, }, }, }, } } // 初始化接口配置 if _, exist := g.doc.Components.Schemas[requestTypeStr]; !exist { g.doc.Components.Schemas[requestTypeStr] = schemaData } responseTypeStr := responseType.String() if _, exist := g.doc.Components.Schemas[responseTypeStr]; !exist { g.doc.Components.Schemas[responseTypeStr] = GenerateOpenAPISchema(responseType) } desc := "请求成功" apiOperate.Responses.Set(fmt.Sprintf("%v", http.StatusOK), &openapi3.ResponseRef{ Extensions: nil, Origin: nil, Ref: "", Value: &openapi3.Response{ Extensions: nil, Origin: nil, Description: &desc, Headers: nil, Content: map[string]*openapi3.MediaType{ consts.MimeTypeJson: { Extensions: nil, Origin: nil, Schema: g.doc.Components.Schemas[responseTypeStr], Example: nil, Examples: nil, Encoding: nil, }, }, Links: nil, }, }) return err } // initApiConfig 初始化接口配置, 并返回 operation 以及 是否读请求 func (g *Generate) initApiConfig(apiMeta define.UriConfig) (*openapi3.Operation, bool) { if nil == g.doc.Paths.Value(apiMeta.Path) { g.doc.Paths.Set(apiMeta.Path, &openapi3.PathItem{ Extensions: nil, Origin: nil, Ref: "", Summary: apiMeta.Desc, Description: apiMeta.Desc, Connect: nil, Delete: nil, Get: nil, Head: nil, Options: nil, Patch: nil, Post: nil, Put: nil, Trace: nil, Servers: nil, Parameters: nil, }) } newOperate := openapi3.NewOperation() newOperate.Parameters = make(openapi3.Parameters, 0) newOperate.Responses = openapi3.NewResponses() newOperate.Summary = apiMeta.Desc newOperate.Description = apiMeta.Desc newOperate.Tags = apiMeta.TagList newOperate.OperationID = fmt.Sprintf("%v_%v", apiMeta.RequestMethod, apiMeta.Path) isRead := false switch apiMeta.RequestMethod { case http.MethodGet: g.doc.Paths.Value(apiMeta.Path).Get = newOperate isRead = true case http.MethodHead: g.doc.Paths.Value(apiMeta.Path).Head = newOperate isRead = true case http.MethodConnect: g.doc.Paths.Value(apiMeta.Path).Connect = newOperate isRead = true case http.MethodOptions: g.doc.Paths.Value(apiMeta.Path).Options = newOperate isRead = true case http.MethodTrace: g.doc.Paths.Value(apiMeta.Path).Trace = newOperate isRead = true // 读请求 case http.MethodPost: g.doc.Paths.Value(apiMeta.Path).Post = newOperate case http.MethodPut: g.doc.Paths.Value(apiMeta.Path).Put = newOperate case http.MethodPatch: g.doc.Paths.Value(apiMeta.Path).Patch = newOperate case http.MethodDelete: g.doc.Paths.Value(apiMeta.Path).Delete = newOperate // 写请求 } return newOperate, isRead }