feat: 完成基础文档生成

This commit is contained in:
2026-01-06 18:05:07 +08:00
parent c1d5dd89d4
commit 0dd0e8f22f
2 changed files with 139 additions and 9 deletions

View File

@@ -8,9 +8,14 @@
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"
)
@@ -91,12 +96,12 @@ func NewGenerate() *Generate {
Security: []openapi3.SecurityRequirement{},
Servers: []*openapi3.Server{},
Tags: []*openapi3.Tag{},
ExternalDocs: &openapi3.ExternalDocs{
/*ExternalDocs: &openapi3.ExternalDocs{
Extensions: map[string]any{},
Origin: nil,
Description: "",
URL: "",
},
},*/
},
}
}
@@ -107,7 +112,7 @@ type Generate struct {
}
// AddApiDoc 添加接口文档
func (g *Generate) AddApiDoc(request any, response any) error {
func (g *Generate) AddApiDoc(apiMeta define.UriConfig, request any, response any) error {
var (
err error
requestType reflect.Type
@@ -129,13 +134,130 @@ func (g *Generate) AddApiDoc(request any, response any) error {
responseType = responseType.Elem()
}
}
requestTypeStr := requestType.String()
if _, exist := g.doc.Components.Schemas[requestTypeStr]; !exist {
g.doc.Components.Schemas[requestTypeStr] = GenerateOpenAPISchema(requestType)
schemaData := GenerateOpenAPISchema(requestType)
apiOperate, isRead := g.initApiConfig(apiMeta)
requestTypeStr := strings.ReplaceAll(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: false,
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,
}
}
responseTypeStr := requestType.String()
// 初始化接口配置
if _, exist := g.doc.Components.Schemas[requestTypeStr]; !exist {
g.doc.Components.Schemas[requestTypeStr] = schemaData
}
responseTypeStr := strings.ReplaceAll(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
}

View File

@@ -10,8 +10,11 @@ package openapi
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"git.zhangdeman.cn/zhangdeman/api-doc/define"
)
func TestGenerate_AddApiDoc(t *testing.T) {
@@ -20,7 +23,7 @@ func TestGenerate_AddApiDoc(t *testing.T) {
Name string `json:"name" description:"分类名称"`
}
type Product struct {
ID int64 `json:"id" description:"产品ID" example:"1001" required:"true"`
ID int64 `json:"id,omitempty" description:"产品ID" example:"1001" required:"true" binding:"required"`
Name string `json:"name" description:"产品名称" example:"iPhone 13" minLength:"2" maxLength:"100" required:"true"`
Price float64 `json:"price" description:"价格" example:"6999.99" min:"0"`
Stock int `json:"stock" description:"库存" example:"100" min:"0"`
@@ -31,7 +34,12 @@ func TestGenerate_AddApiDoc(t *testing.T) {
Category *Category `json:"category,omitempty" description:"分类"`
}
instance := NewGenerate()
instance.AddApiDoc(Category{}, Product{})
instance.AddApiDoc(define.UriConfig{
Path: "/a/b/c",
RequestMethod: http.MethodGet,
TagList: []string{"test"},
Desc: "测试接口",
}, Category{}, Product{})
// 输出 JSON
data, _ := json.MarshalIndent(instance.doc, "", " ")
fmt.Println(string(data))