264 lines
7.1 KiB
Go
264 lines
7.1 KiB
Go
// 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{},
|
|
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: requestTypeStr,
|
|
Value: nil,
|
|
}
|
|
}
|
|
|
|
// 初始化接口配置
|
|
if _, exist := g.doc.Components.Schemas[requestTypeStr]; !exist {
|
|
g.doc.Components.Schemas[requestTypeStr] = schemaData
|
|
}
|
|
responseTypeStr := requestType.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: "",
|
|
Description: "",
|
|
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()
|
|
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
|
|
}
|