Compare commits
27 Commits
7606e7d48d
...
def62b0aa9
| Author | SHA1 | Date | |
|---|---|---|---|
| def62b0aa9 | |||
| 3ea5af0709 | |||
| 5d86daadeb | |||
| a0090ad524 | |||
| d1b5e16d35 | |||
| d45d6e7890 | |||
| b6821e1497 | |||
| 088b813045 | |||
| 708e327ddf | |||
| 8ed6735d48 | |||
| 0dd0e8f22f | |||
| c1d5dd89d4 | |||
| 2f4e6851f3 | |||
| b03fc5acf9 | |||
| 9f2059837c | |||
| 2cad79cf3d | |||
| e40b2959d7 | |||
| b3b090a020 | |||
| 5ff7dac227 | |||
| f4447d50d6 | |||
| 1157973b65 | |||
| 8ce50fd235 | |||
| 20b6c48640 | |||
| 2996fc4732 | |||
| d74f82d060 | |||
| e031f56393 | |||
| b9c5ed591a |
@@ -36,3 +36,8 @@ const (
|
||||
TagNameOmitempty = "omitempty"
|
||||
TagNameEnumDescription = "enum-desc" // 枚举值描述: enum1:enum1-desc||enum2:enum2-desc
|
||||
)
|
||||
|
||||
type EnumValue struct {
|
||||
Value string `json:"value" dc:"枚举值"`
|
||||
Description string `json:"description" dc:"枚举值描述"`
|
||||
}
|
||||
|
||||
12
go.mod
12
go.mod
@@ -5,7 +5,7 @@ go 1.24.0
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260105014807-92a7dc3bb97f
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260106015608-42e268aea545
|
||||
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20251013044511-86c1a4a3a9dd
|
||||
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20251225094759-09c64ba2541c
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
@@ -24,6 +24,7 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/getkin/kin-openapi v0.133.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
@@ -41,14 +42,22 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/mozillazg/go-pinyin v0.21.0 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.58.0 // indirect
|
||||
github.com/sbabiv/xml2map v1.2.1 // indirect
|
||||
@@ -59,6 +68,7 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/woodsbury/decimal128 v1.4.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@@ -1,5 +1,7 @@
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260105014807-92a7dc3bb97f h1:2p5yxwk472XxY93UTky2Xl2doTU1uqR7vcKara/Dt1E=
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260105014807-92a7dc3bb97f/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260106015608-42e268aea545 h1:A6UeeMcSqAlHUmA2coWIuiCS/W9ySylhZMa/0HbFDcQ=
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20260106015608-42e268aea545/go.mod h1:5p8CEKGBxi7qPtTXDI3HDmqKAfIm5i/aBWdrbkbdNjc=
|
||||
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20251013024601-da007da2fb42 h1:VjYrb4adud7FHeiYS9XA0B/tOaJjfRejzQAlwimrrDc=
|
||||
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20251013024601-da007da2fb42/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI=
|
||||
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20251013044511-86c1a4a3a9dd h1:kTZOpR8iHx27sUufMWVYhDZx9Q4h80j7RWlaR8GIBiU=
|
||||
@@ -26,6 +28,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
@@ -41,6 +45,7 @@ github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3Zlm
|
||||
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
|
||||
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
|
||||
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
@@ -82,6 +87,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
@@ -94,15 +101,29 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mozillazg/go-pinyin v0.21.0 h1:Wo8/NT45z7P3er/9YSLHA3/kjZzbLz5hR7i+jGeIGao=
|
||||
github.com/mozillazg/go-pinyin v0.21.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
@@ -111,6 +132,7 @@ github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4U
|
||||
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/sbabiv/xml2map v1.2.1 h1:1lT7t0hhUvXZCkdxqtq4n8/ZCnwLWGq4rDuDv5XOoFE=
|
||||
github.com/sbabiv/xml2map v1.2.1/go.mod h1:2TPoAfcaM7+Sd4iriPvzyntb2mx7GY+kkQpB/GQa/eo=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
@@ -148,6 +170,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
|
||||
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
|
||||
312
openapi/generate.go
Normal file
312
openapi/generate.go
Normal file
@@ -0,0 +1,312 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
var (
|
||||
DocManager = &Generate{
|
||||
docTable: make(map[string]*openapi3.T),
|
||||
}
|
||||
)
|
||||
|
||||
// NewOpenApiDoc 生成文档实例
|
||||
func NewOpenApiDoc(optionFunc ...OptionFunc) *openapi3.T {
|
||||
t := &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",
|
||||
Description: "开发环境",
|
||||
Variables: nil,
|
||||
},
|
||||
},
|
||||
Tags: []*openapi3.Tag{},
|
||||
/*ExternalDocs: &openapi3.ExternalDocs{
|
||||
Extensions: map[string]any{},
|
||||
Origin: nil,
|
||||
Description: "",
|
||||
URL: "",
|
||||
},*/
|
||||
}
|
||||
for _, item := range optionFunc {
|
||||
item(t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Generate 生成 OpenApi 标准规范的文档
|
||||
type Generate struct {
|
||||
docTable map[string]*openapi3.T
|
||||
}
|
||||
|
||||
// DocData 获取一个文档数据
|
||||
func (g *Generate) DocData(docFlag string) *openapi3.T {
|
||||
return g.docTable[docFlag]
|
||||
}
|
||||
|
||||
func (g *Generate) NewOpenApiDoc(docFlag string, docOption ...OptionFunc) *openapi3.T {
|
||||
t := NewOpenApiDoc()
|
||||
for _, item := range docOption {
|
||||
item(t)
|
||||
}
|
||||
g.docTable[docFlag] = t
|
||||
return t
|
||||
}
|
||||
|
||||
// AddApiDoc 添加接口文档
|
||||
func (g *Generate) AddApiDoc(docFlag string, 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(docFlag, 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.docTable[docFlag].Components.Schemas[requestTypeStr]; !exist {
|
||||
g.docTable[docFlag].Components.Schemas[requestTypeStr] = schemaData
|
||||
}
|
||||
responseTypeStr := responseType.String()
|
||||
if _, exist := g.docTable[docFlag].Components.Schemas[responseTypeStr]; !exist {
|
||||
g.docTable[docFlag].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.docTable[docFlag].Components.Schemas[responseTypeStr],
|
||||
Example: nil,
|
||||
Examples: nil,
|
||||
Encoding: nil,
|
||||
},
|
||||
},
|
||||
Links: nil,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// initApiConfig 初始化接口配置, 并返回 operation 以及 是否读请求
|
||||
func (g *Generate) initApiConfig(docFlag string, apiMeta define.UriConfig) (*openapi3.Operation, bool) {
|
||||
if nil == g.docTable[docFlag].Paths.Value(apiMeta.Path) {
|
||||
g.docTable[docFlag].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.docTable[docFlag].Paths.Value(apiMeta.Path).Get = newOperate
|
||||
isRead = true
|
||||
case http.MethodHead:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Head = newOperate
|
||||
isRead = true
|
||||
case http.MethodConnect:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Connect = newOperate
|
||||
isRead = true
|
||||
case http.MethodOptions:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Options = newOperate
|
||||
isRead = true
|
||||
case http.MethodTrace:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Trace = newOperate
|
||||
isRead = true
|
||||
// 读请求
|
||||
case http.MethodPost:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Post = newOperate
|
||||
case http.MethodPut:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Put = newOperate
|
||||
case http.MethodPatch:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Patch = newOperate
|
||||
case http.MethodDelete:
|
||||
g.docTable[docFlag].Paths.Value(apiMeta.Path).Delete = newOperate
|
||||
// 写请求
|
||||
}
|
||||
return newOperate, isRead
|
||||
}
|
||||
54
openapi/generate_test.go
Normal file
54
openapi/generate_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Package openapi ...
|
||||
//
|
||||
// Description : openapi ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2026-01-06 11:48
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.zhangdeman.cn/zhangdeman/api-doc/define"
|
||||
)
|
||||
|
||||
func TestGenerate_AddApiDoc(t *testing.T) {
|
||||
type Category struct {
|
||||
ID int64 `json:"id" description:"分类ID" eg:"123" binding:"required,min=10,max=100"`
|
||||
Sex string `json:"sex" dc:"性别" binding:"required,oneof=man woman" enum-desc:"man:男||woman:女"`
|
||||
Name string `json:"name" description:"分类名称" eg:"baichaqinghuan" binding:"required,min=8"`
|
||||
}
|
||||
type Product struct {
|
||||
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"`
|
||||
Tags []string `json:"tags" description:"标签"`
|
||||
Attributes map[string]string `json:"attributes" description:"属性"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty" description:"更新时间"`
|
||||
Category *Category `json:"category,omitempty" description:"分类"`
|
||||
}
|
||||
docFlag := "demo"
|
||||
DocManager.NewOpenApiDoc(docFlag)
|
||||
DocManager.AddApiDoc(docFlag, define.UriConfig{
|
||||
Path: "/a/b/c",
|
||||
RequestMethod: http.MethodGet,
|
||||
TagList: []string{"test"},
|
||||
Desc: "测试接口",
|
||||
}, Category{}, Product{})
|
||||
DocManager.AddApiDoc(docFlag, define.UriConfig{
|
||||
Path: "/a/b/c",
|
||||
RequestMethod: http.MethodPost,
|
||||
TagList: []string{"test"},
|
||||
Desc: "测试接口",
|
||||
}, Category{}, Product{})
|
||||
// 输出 JSON
|
||||
data, _ := json.MarshalIndent(DocManager.docTable[docFlag], "", " ")
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
33
openapi/option.go
Normal file
33
openapi/option.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Package openapi ...
|
||||
//
|
||||
// Description : openapi ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2026-01-06 22:48
|
||||
package openapi
|
||||
|
||||
import "github.com/getkin/kin-openapi/openapi3"
|
||||
|
||||
// OptionFunc 设置文档选项
|
||||
type OptionFunc func(t *openapi3.T)
|
||||
|
||||
// WithServers 设置文档服务器
|
||||
func WithServers(serverList openapi3.Servers) OptionFunc {
|
||||
return func(t *openapi3.T) {
|
||||
if len(serverList) == 0 {
|
||||
return
|
||||
}
|
||||
t.Servers = serverList
|
||||
}
|
||||
}
|
||||
|
||||
// WithInfo 文档基础信息
|
||||
func WithInfo(info *openapi3.Info) OptionFunc {
|
||||
return func(t *openapi3.T) {
|
||||
if nil == info {
|
||||
return
|
||||
}
|
||||
t.Info = info
|
||||
}
|
||||
}
|
||||
242
openapi/schema.go
Normal file
242
openapi/schema.go
Normal file
@@ -0,0 +1,242 @@
|
||||
// Package openapi ...
|
||||
//
|
||||
// Description : openapi 扩展版本,添加更多标签支持和类型映射
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2026-01-05 17:27
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"git.zhangdeman.cn/zhangdeman/api-doc/util"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
// StructFieldInfo 结构体字段信息
|
||||
type StructFieldInfo struct {
|
||||
IsString bool `json:"is_string" dc:"是否字符串"`
|
||||
Name string `json:"name" dc:"结构体字段名"`
|
||||
JSONName string `json:"json_name" dc:"json tag"`
|
||||
Type reflect.Type `json:"type" dc:"字段类型"`
|
||||
Description string `json:"description" dc:"参数描述"`
|
||||
Example any `json:"example" dc:"示例值"`
|
||||
Default any `json:"default" dc:"默认值"`
|
||||
Required bool `json:"required" dc:"是否必传"`
|
||||
Min *float64 `json:"min" dc:"最小值"`
|
||||
Max *float64 `json:"max" dc:"最大值"`
|
||||
MinLength *float64 `json:"min_length" dc:"最小长度"`
|
||||
MaxLength *float64 `json:"max_length" dc:"最大长度"`
|
||||
Pattern string `json:"pattern" dc:"模式"`
|
||||
Format string `json:"format" dc:"格式"`
|
||||
Enum []any `json:"enum" dc:"枚举值列表"`
|
||||
EnumDesc map[string]string `json:"enum_desc" dc:"枚举值详细描述"`
|
||||
OmitEmpty bool `json:"omit_empty" dc:"是否可控"`
|
||||
}
|
||||
|
||||
// ParseStructField 解析结构体字段信息
|
||||
func ParseStructField(field reflect.StructField) *StructFieldInfo {
|
||||
if !field.IsExported() {
|
||||
return nil
|
||||
}
|
||||
// 解析验证规则
|
||||
validateRule := util.ParseValidateRule(field.Type, util.ParseStructFieldTag.GetValidateRule(field))
|
||||
info := &StructFieldInfo{
|
||||
IsString: validateRule.IsString,
|
||||
Name: field.Name,
|
||||
JSONName: "",
|
||||
Type: field.Type,
|
||||
Description: util.ParseStructFieldTag.GetParamDesc(field), // 解析参数描述
|
||||
Example: util.ParseStructFieldTag.GetExampleValue(field), // 解析示例值
|
||||
Default: util.ParseStructFieldTag.GetDefaultValue(field), // 解析默认值
|
||||
Required: validateRule.Required,
|
||||
Min: nil,
|
||||
Max: nil,
|
||||
MinLength: nil,
|
||||
MaxLength: nil,
|
||||
Pattern: validateRule.Regexp,
|
||||
Format: field.Type.String(),
|
||||
Enum: validateRule.Oneof, // 解析枚举值
|
||||
EnumDesc: util.ParseStructFieldTag.EnumDescription(field), // 解析枚举值描述
|
||||
OmitEmpty: false,
|
||||
}
|
||||
if info.IsString {
|
||||
info.MinLength = validateRule.Min
|
||||
info.MaxLength = validateRule.Max
|
||||
} else {
|
||||
info.Min = validateRule.Min
|
||||
info.Max = validateRule.Max
|
||||
}
|
||||
|
||||
// 解析 JSON tag
|
||||
info.JSONName, info.OmitEmpty = util.ParseStructFieldTag.GetParamName(field)
|
||||
return info
|
||||
}
|
||||
|
||||
// GenerateOpenAPISchema 生成完整的 OpenAPI Schema
|
||||
func GenerateOpenAPISchema(s any) *openapi3.SchemaRef {
|
||||
var (
|
||||
ok bool
|
||||
tType reflect.Type
|
||||
)
|
||||
|
||||
if tType, ok = s.(reflect.Type); !ok {
|
||||
tType = reflect.TypeOf(s)
|
||||
if tType.Kind() == reflect.Ptr {
|
||||
tType = tType.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
schema := generateSchemaRecursive(tType, make(map[string]bool))
|
||||
return schema
|
||||
}
|
||||
|
||||
// 生成 schema
|
||||
func generateSchemaRecursive(t reflect.Type, seen map[string]bool) *openapi3.SchemaRef {
|
||||
// 检查循环引用
|
||||
typeName := t.PkgPath() + "." + t.Name()
|
||||
if seen[typeName] && t.Kind() == reflect.Struct {
|
||||
return &openapi3.SchemaRef{
|
||||
Value: openapi3.NewObjectSchema(),
|
||||
}
|
||||
}
|
||||
seen[typeName] = true
|
||||
defer delete(seen, typeName)
|
||||
|
||||
// 处理不同类型
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
// 特殊处理 time.Time
|
||||
if t == reflect.TypeOf(time.Time{}) {
|
||||
schema := openapi3.NewDateTimeSchema()
|
||||
return &openapi3.SchemaRef{Value: schema}
|
||||
}
|
||||
|
||||
schema := openapi3.NewObjectSchema()
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
info := ParseStructField(field)
|
||||
if info == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldSchema := generateSchemaFromType(info.Type, seen)
|
||||
if fieldSchema == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 应用字段信息到 Schema
|
||||
applyFieldInfoToSchema(fieldSchema.Value, info)
|
||||
|
||||
if info.Required && !info.OmitEmpty {
|
||||
schema.Required = append(schema.Required, info.JSONName)
|
||||
}
|
||||
|
||||
schema.Properties[info.JSONName] = fieldSchema
|
||||
}
|
||||
|
||||
return &openapi3.SchemaRef{Value: schema}
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
elemType := t.Elem()
|
||||
elemSchema := generateSchemaRecursive(elemType, seen)
|
||||
if elemSchema == nil {
|
||||
return nil
|
||||
}
|
||||
return &openapi3.SchemaRef{
|
||||
Value: openapi3.NewArraySchema().WithItems(elemSchema.Value),
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if t.Key().Kind() != reflect.String {
|
||||
// OpenAPI 只支持字符串键
|
||||
return &openapi3.SchemaRef{
|
||||
Value: openapi3.NewObjectSchema(),
|
||||
}
|
||||
}
|
||||
valueType := t.Elem()
|
||||
valueSchema := generateSchemaRecursive(valueType, seen)
|
||||
if valueSchema == nil {
|
||||
return nil
|
||||
}
|
||||
return &openapi3.SchemaRef{
|
||||
Value: openapi3.NewObjectSchema().
|
||||
WithAdditionalProperties(valueSchema.Value),
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
return generateSchemaRecursive(t.Elem(), seen)
|
||||
|
||||
case reflect.Interface:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewSchema()}
|
||||
|
||||
default:
|
||||
return generatePrimitiveSchema(t)
|
||||
}
|
||||
}
|
||||
|
||||
func generateSchemaFromType(t reflect.Type, seen map[string]bool) *openapi3.SchemaRef {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return generateSchemaRecursive(t, seen)
|
||||
}
|
||||
|
||||
func applyFieldInfoToSchema(schema *openapi3.Schema, info *StructFieldInfo) {
|
||||
if info.Description != "" {
|
||||
schema.Description = info.Description
|
||||
}
|
||||
if info.Example != nil {
|
||||
schema.Example = info.Example
|
||||
}
|
||||
schema.Min = info.Min
|
||||
|
||||
if info.Max != nil {
|
||||
schema.Max = info.Max
|
||||
}
|
||||
if info.MinLength != nil {
|
||||
schema.MinLength = uint64(*info.MinLength)
|
||||
}
|
||||
if info.MaxLength != nil {
|
||||
schema.MaxLength = openapi3.Ptr(uint64(*info.MaxLength))
|
||||
}
|
||||
if info.Pattern != "" {
|
||||
schema.Pattern = info.Pattern
|
||||
}
|
||||
if info.Format != "" {
|
||||
schema.Format = info.Format
|
||||
}
|
||||
if len(info.Enum) > 0 {
|
||||
schema.Enum = info.Enum
|
||||
if nil == schema.Extensions {
|
||||
schema.Extensions = map[string]any{}
|
||||
}
|
||||
schema.Extensions["x-enum-descriptions"] = info.EnumDesc
|
||||
}
|
||||
}
|
||||
|
||||
func generatePrimitiveSchema(t reflect.Type) *openapi3.SchemaRef {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewStringSchema()}
|
||||
case reflect.Bool:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewBoolSchema()}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewInt32Schema()}
|
||||
case reflect.Int64:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewInt64Schema()}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewInt64Schema().WithMin(0)}
|
||||
case reflect.Uint64:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewInt64Schema().WithMin(0)}
|
||||
case reflect.Float32:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewFloat64Schema()}
|
||||
case reflect.Float64:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewFloat64Schema()}
|
||||
default:
|
||||
return &openapi3.SchemaRef{Value: openapi3.NewSchema()}
|
||||
}
|
||||
}
|
||||
58
openapi/schema_test.go
Normal file
58
openapi/schema_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Package openapi ...
|
||||
//
|
||||
// Description : openapi ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2026-01-06 10:50
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
func TestParseStructField(t *testing.T) {
|
||||
type Category struct {
|
||||
ID int64 `json:"id" description:"分类ID"`
|
||||
Name string `json:"name" description:"分类名称"`
|
||||
}
|
||||
type Product struct {
|
||||
ID int64 `json:"id" description:"产品ID" example:"1001" required:"true"`
|
||||
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"`
|
||||
Tags []string `json:"tags" description:"标签"`
|
||||
Attributes map[string]string `json:"attributes" description:"属性"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty" description:"更新时间"`
|
||||
Category *Category `json:"category,omitempty" description:"分类"`
|
||||
}
|
||||
|
||||
// 生成 Product 的 Schema
|
||||
productSchema := GenerateOpenAPISchema(Product{})
|
||||
|
||||
// 创建完整的 OpenAPI 文档
|
||||
doc := &openapi3.T{
|
||||
OpenAPI: "3.0.0",
|
||||
Info: &openapi3.Info{
|
||||
Title: "产品服务 API",
|
||||
Description: "产品管理相关接口",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Components: &openapi3.Components{
|
||||
Schemas: map[string]*openapi3.SchemaRef{
|
||||
"Product": productSchema,
|
||||
"Category": GenerateOpenAPISchema(Category{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 输出 JSON
|
||||
data, _ := json.MarshalIndent(doc, "", " ")
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// Package api_doc ...
|
||||
// Package util ...
|
||||
//
|
||||
// Description : api_doc ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2025-02-12 22:15
|
||||
package api_doc
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.zhangdeman.cn/zhangdeman/api-doc/define"
|
||||
utilPkg "git.zhangdeman.cn/zhangdeman/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,11 +24,7 @@ type parseStructFieldTag struct {
|
||||
}
|
||||
|
||||
// GetParamName 获取参数名称
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:58 2025/2/11
|
||||
func (psf parseStructFieldTag) GetParamName(structField reflect.StructField) string {
|
||||
func (psf parseStructFieldTag) GetParamName(structField reflect.StructField) (string, bool) {
|
||||
paramNameTagList := []string{
|
||||
define.TagJson, define.TagForm,
|
||||
define.TagXml, define.TagYaml,
|
||||
@@ -35,21 +32,33 @@ func (psf parseStructFieldTag) GetParamName(structField reflect.StructField) str
|
||||
}
|
||||
for _, tag := range paramNameTagList {
|
||||
tagVal := structField.Tag.Get(tag)
|
||||
tagVal = strings.TrimSuffix(strings.TrimPrefix(tagVal, define.TagNameOmitempty+","), ","+define.TagNameOmitempty)
|
||||
tagVal = strings.Trim(tagVal, ",")
|
||||
if tagVal != "" && tagVal != define.TagNameOmitempty {
|
||||
return tagVal
|
||||
if tagVal == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(tagVal, ",")
|
||||
jsonName := ""
|
||||
omit := false
|
||||
if len(parts) > 0 {
|
||||
if parts[0] == "-" {
|
||||
return "", false // 跳过该字段
|
||||
}
|
||||
if parts[0] != "" {
|
||||
jsonName = parts[0]
|
||||
}
|
||||
}
|
||||
for _, part := range parts[1:] {
|
||||
switch part {
|
||||
case define.TagNameOmitempty:
|
||||
omit = true
|
||||
}
|
||||
}
|
||||
return jsonName, omit
|
||||
}
|
||||
// 未设置相关字段, 则字段名即为参数名
|
||||
return structField.Name
|
||||
return structField.Name, false
|
||||
}
|
||||
|
||||
// GetParamDesc ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:01 2025/2/11
|
||||
func (psf parseStructFieldTag) GetParamDesc(structField reflect.StructField) string {
|
||||
descTagList := []string{define.TagDc, define.TagDesc, define.TagDescription}
|
||||
for _, tag := range descTagList {
|
||||
@@ -59,14 +68,11 @@ func (psf parseStructFieldTag) GetParamDesc(structField reflect.StructField) str
|
||||
}
|
||||
}
|
||||
// 没有显示的设置参数描述, 则使用参数名作为参数描述
|
||||
return psf.GetParamName(structField)
|
||||
paramName, _ := psf.GetParamName(structField)
|
||||
return paramName
|
||||
}
|
||||
|
||||
// GetDefaultValue 获取默认值
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:05 2025/2/11
|
||||
func (psf parseStructFieldTag) GetDefaultValue(structField reflect.StructField) any {
|
||||
defaultTagList := []string{define.TagD, define.TagDefault}
|
||||
fieldType := structField.Type.Kind().String()
|
||||
@@ -104,10 +110,6 @@ func (psf parseStructFieldTag) GetDefaultValue(structField reflect.StructField)
|
||||
}
|
||||
|
||||
// GetValidateRule 获取验证规则
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:30 2025/2/13
|
||||
func (psf parseStructFieldTag) GetValidateRule(structField reflect.StructField) string {
|
||||
defaultTagList := []string{define.TagValidate, define.TagBinding}
|
||||
for _, tag := range defaultTagList {
|
||||
@@ -119,10 +121,6 @@ func (psf parseStructFieldTag) GetValidateRule(structField reflect.StructField)
|
||||
}
|
||||
|
||||
// Deprecated 是否弃用
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 21:12 2025/2/13
|
||||
func (psf parseStructFieldTag) Deprecated(structField reflect.StructField) bool {
|
||||
defaultTagList := []string{define.TagDeprecated}
|
||||
for _, tag := range defaultTagList {
|
||||
@@ -134,10 +132,6 @@ func (psf parseStructFieldTag) Deprecated(structField reflect.StructField) bool
|
||||
}
|
||||
|
||||
// Summary 摘要信息
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 15:15 2025/2/14
|
||||
func (psf parseStructFieldTag) Summary(structField reflect.StructField) string {
|
||||
defaultTagList := []string{define.TagSummary}
|
||||
for _, tag := range defaultTagList {
|
||||
@@ -145,21 +139,14 @@ func (psf parseStructFieldTag) Summary(structField reflect.StructField) string {
|
||||
return tagVal
|
||||
}
|
||||
}
|
||||
paramName := psf.GetParamName(structField)
|
||||
if paramName == "-" {
|
||||
return ""
|
||||
}
|
||||
paramName, _ := psf.GetParamName(structField)
|
||||
return paramName
|
||||
}
|
||||
|
||||
// EnumDescription .枚举值详细描述
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 22:40 2025/2/18
|
||||
func (psf parseStructFieldTag) EnumDescription(structField reflect.StructField) map[string]string {
|
||||
defaultTagList := []string{define.TagNameEnumDescription}
|
||||
res := map[string]string{}
|
||||
res := make(map[string]string)
|
||||
for _, tag := range defaultTagList {
|
||||
if tagVal, exist := structField.Tag.Lookup(tag); exist && len(tagVal) > 0 {
|
||||
tagVal = strings.ReplaceAll(tagVal, "###", "`")
|
||||
@@ -174,48 +161,43 @@ func (psf parseStructFieldTag) EnumDescription(structField reflect.StructField)
|
||||
return res
|
||||
}
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GetExampleValue 示例值
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 14:42 2025/2/20
|
||||
func (psf parseStructFieldTag) GetExampleValue(structField reflect.StructField) any {
|
||||
descTagList := []string{define.TagEg, define.TagExample}
|
||||
fieldType := structField.Type.Kind().String()
|
||||
for _, tag := range descTagList {
|
||||
val := strings.TrimSpace(structField.Tag.Get(tag))
|
||||
if val == "" {
|
||||
exampleTagList := []string{define.TagEg, define.TagExample}
|
||||
structFieldType := structField.Type
|
||||
if structFieldType.Kind() == reflect.Ptr {
|
||||
structFieldType = structFieldType.Elem()
|
||||
}
|
||||
fieldTypeKind := structFieldType.Kind()
|
||||
for _, tag := range exampleTagList {
|
||||
example := strings.TrimSpace(structField.Tag.Get(tag))
|
||||
if example == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(fieldType, "int") {
|
||||
i, _ := strconv.Atoi(val)
|
||||
return i
|
||||
}
|
||||
if strings.HasPrefix(fieldType, "uint") {
|
||||
uintVal, _ := strconv.ParseUint(val, 10, 64)
|
||||
return uintVal
|
||||
}
|
||||
if strings.HasPrefix(fieldType, "float") {
|
||||
floatVal, _ := strconv.ParseFloat(val, 64)
|
||||
return floatVal
|
||||
}
|
||||
if strings.HasPrefix(fieldType, "string") {
|
||||
return val
|
||||
}
|
||||
if strings.HasPrefix(fieldType, "bool") {
|
||||
if val == "true" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
switch fieldTypeKind {
|
||||
case reflect.String:
|
||||
return example
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var res int64
|
||||
if err := utilPkg.ConvertAssign(&res, example); err == nil {
|
||||
return res
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
var res float64
|
||||
if err := utilPkg.ConvertAssign(&res, example); err == nil {
|
||||
return res
|
||||
}
|
||||
case reflect.Bool:
|
||||
var res bool
|
||||
if err := utilPkg.ConvertAssign(&res, example); err == nil {
|
||||
return res
|
||||
}
|
||||
default:
|
||||
return example
|
||||
}
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
130
util/validate_v10_parse.go
Normal file
130
util/validate_v10_parse.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// Package util ...
|
||||
//
|
||||
// Description : util ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
// Date : 2026-01-05 18:48
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"git.zhangdeman.cn/zhangdeman/consts"
|
||||
utilPkg "git.zhangdeman.cn/zhangdeman/util"
|
||||
"git.zhangdeman.cn/zhangdeman/wrapper/op_string"
|
||||
)
|
||||
|
||||
// ParseValidateRule 解析验证规则
|
||||
func ParseValidateRule(dataType reflect.Type, ruleStr string) ValidateRule {
|
||||
if dataType.Kind() == reflect.Ptr {
|
||||
dataType = dataType.Elem()
|
||||
}
|
||||
dataKind := dataType.Kind()
|
||||
rule := ValidateRule{
|
||||
IsString: dataKind == reflect.String,
|
||||
Omitempty: false,
|
||||
Required: false,
|
||||
Lte: nil,
|
||||
Gte: nil,
|
||||
Lt: nil,
|
||||
Gt: nil,
|
||||
Len: nil,
|
||||
Max: nil,
|
||||
Min: nil,
|
||||
Eq: nil,
|
||||
Ne: nil,
|
||||
Oneof: nil,
|
||||
}
|
||||
ruleList := strings.Split(ruleStr, ",")
|
||||
for _, itemRule := range ruleList {
|
||||
itemRule = strings.ToLower(strings.TrimSpace(itemRule))
|
||||
if len(itemRule) == 0 {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(itemRule, "=") {
|
||||
// 一定是无需要值的验证规则
|
||||
switch itemRule {
|
||||
case consts.ValidatorRuleCommonRequired.String(): // 必传
|
||||
rule.Required = true
|
||||
case consts.ValidatorRuleCommonOmitempty.String(): // 为空则不校验
|
||||
rule.Omitempty = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
itemRuleArr := strings.Split(itemRule, "=")
|
||||
ruleType := itemRuleArr[0]
|
||||
ruleValue := strings.Join(itemRuleArr[1:], "=")
|
||||
if len(ruleValue) == 0 {
|
||||
// 未配置值的校验规则
|
||||
continue
|
||||
}
|
||||
switch ruleType {
|
||||
case consts.ValidatorRuleLte.String(), consts.ValidatorRuleGte.String(), consts.ValidatorRuleGt.String(), consts.ValidatorRuleLt.String(), // 数字取值范围
|
||||
consts.ValidatorRuleCommonLen.String(), consts.ValidatorRuleCommonMin.String(), consts.ValidatorRuleCommonMax.String(): // 长度取值范围
|
||||
var val float64
|
||||
if err := utilPkg.ConvertAssign(&val, ruleValue); nil == err {
|
||||
switch ruleType {
|
||||
case consts.ValidatorRuleLte.String():
|
||||
rule.Lte = &val
|
||||
case consts.ValidatorRuleGte.String():
|
||||
rule.Gte = &val
|
||||
case consts.ValidatorRuleGt.String():
|
||||
rule.Gt = &val
|
||||
case consts.ValidatorRuleLt.String():
|
||||
rule.Lt = &val
|
||||
case consts.ValidatorRuleCommonLen.String():
|
||||
rule.Len = &val
|
||||
|
||||
case consts.ValidatorRuleCommonMin.String():
|
||||
rule.Min = &val
|
||||
|
||||
case consts.ValidatorRuleCommonMax.String():
|
||||
rule.Max = &val
|
||||
}
|
||||
}
|
||||
case consts.ValidatorRuleEq.String():
|
||||
rule.Eq = ruleValue
|
||||
case consts.ValidatorRuleNe.String():
|
||||
rule.Ne = ruleValue
|
||||
case consts.ValidatorRuleRegexp.String():
|
||||
rule.Regexp = ruleValue
|
||||
case consts.ValidatorRuleCommonOneOf.String():
|
||||
rule.Oneof = make([]any, 0)
|
||||
switch dataKind {
|
||||
case reflect.String:
|
||||
valList := strings.Split(ruleValue, " ")
|
||||
for _, item := range valList {
|
||||
rule.Oneof = append(rule.Oneof, item)
|
||||
}
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint,
|
||||
reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int,
|
||||
reflect.Float32, reflect.Float64:
|
||||
valList := op_string.ToBaseTypeSlice[float64](ruleValue, " ").Value
|
||||
for _, item := range valList {
|
||||
rule.Oneof = append(rule.Oneof, item)
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
type ValidateRule struct {
|
||||
IsString bool `json:"is_string" dc:"是否字符串"`
|
||||
Omitempty bool `json:"omitempty" dc:"为空则不校验"`
|
||||
Required bool `json:"required" dc:"必传校验"`
|
||||
Lte *float64 `json:"lte" dc:"数字类型小于等于"`
|
||||
Gte *float64 `json:"gte" dc:"数字类型大于等于"`
|
||||
Lt *float64 `json:"lt" dc:"数字类型小于"`
|
||||
Gt *float64 `json:"gt" dc:"数字类型大于"`
|
||||
Len *float64 `json:"len" dc:"长度等于"`
|
||||
Max *float64 `json:"max" dc:"长度小于等于"`
|
||||
Min *float64 `json:"min" dc:"长度大于等于"`
|
||||
Eq any `json:"eq" dc:"等于"`
|
||||
Ne any `json:"ne" dc:"不等于"`
|
||||
Oneof []any `json:"oneof" dc:"枚举值列表"`
|
||||
Regexp string `json:"regexp" dc:"正则表达式"`
|
||||
}
|
||||
Reference in New Issue
Block a user