Compare commits
11 Commits
a21dceb01b
...
76e633f4b1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
76e633f4b1 | ||
![]() |
2c9300c1be | ||
![]() |
4fc5fc0bde | ||
![]() |
daf9775177 | ||
![]() |
517ff9a42e | ||
![]() |
80a696bc8c | ||
![]() |
06cc86c13e | ||
![]() |
a4787d7455 | ||
![]() |
8f66ac9272 | ||
![]() |
862371e153 | ||
![]() |
f5343a24b7 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,3 +16,5 @@ Thumbs.db
|
|||||||
|
|
||||||
#
|
#
|
||||||
.swagger-codegen
|
.swagger-codegen
|
||||||
|
swagtest
|
||||||
|
*.exe
|
19
README.md
19
README.md
@ -3,7 +3,7 @@
|
|||||||
### 1. 编译goctl-swagger插件
|
### 1. 编译goctl-swagger插件
|
||||||
|
|
||||||
```
|
```
|
||||||
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/goctl-swagger@latest
|
GOPROXY=https://goproxy.cn/,direct go install github.com/devttl/goctl-swagger@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 配置环境
|
### 2. 配置环境
|
||||||
@ -86,10 +86,21 @@ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/goctl-swagger
|
|||||||
goctl api plugin -plugin goctl-swagger="swagger -filename user.json" -api user.api -dir .
|
goctl api plugin -plugin goctl-swagger="swagger -filename user.json" -api user.api -dir .
|
||||||
```
|
```
|
||||||
|
|
||||||
* 指定Host,basePath [api-host-and-base-path](https://swagger.io/docs/specification/2-0/api-host-and-base-path/)
|
* -pack 开启外层响应包装并指定外层响应结构名称
|
||||||
|
|
||||||
|
```bash
|
||||||
|
goctl api plugin -plugin goctl-swagger='swagger -filename user.json -pack Response' -api user.api -dir .
|
||||||
|
```
|
||||||
|
|
||||||
|
* 使用 -response指定相应结构
|
||||||
|
```bash
|
||||||
|
goctl api plugin -plugin goctl-swagger='swagger -filename user.json -pack Response -response "[{\"name\":\"trace_id\",\"type\":\"string\",\"description\":\"链路追踪id\"},{\"name\":\"code\",\"type\":\"integer\",\"description\":\"状态码\"},{\"name\":\"msg\",\"type\":\"string\",\"description\":\"消息\"},{\"name\":\"data\",\"type\":\"object\",\"description\":\"数据\",\"is_data\":true}]";' -api user.api -dir .
|
||||||
|
```
|
||||||
|
|
||||||
|
* 指定Host,basePath,schemes [api-host-and-base-path](https://swagger.io/docs/specification/2-0/api-host-and-base-path/)
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
goctl api plugin -plugin goctl-swagger="swagger -filename user.json -host 127.0.0.2 -basepath /api" -api user.api -dir .
|
goctl api plugin -plugin goctl-swagger="swagger -filename user.json -host 127.0.0.2 -basepath /api -schemes https,wss" -api user.api -dir .
|
||||||
```
|
```
|
||||||
|
|
||||||
* swagger ui 查看生成的文档
|
* swagger ui 查看生成的文档
|
||||||
@ -107,4 +118,4 @@ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/goctl-swagger
|
|||||||
-l "$l" \
|
-l "$l" \
|
||||||
-o "/go-work/clients/$l"
|
-o "/go-work/clients/$l"
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/devttl/goctl-swagger/generate"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
||||||
"github.com/zeromicro/goctl-swagger/generate"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Generator(ctx *cli.Context) error {
|
func Generator(ctx *cli.Context) error {
|
||||||
@ -19,5 +19,8 @@ func Generator(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
basepath := ctx.String("basepath")
|
basepath := ctx.String("basepath")
|
||||||
host := ctx.String("host")
|
host := ctx.String("host")
|
||||||
return generate.Do(fileName, host, basepath, p)
|
schemes := ctx.String("schemes")
|
||||||
|
pack := ctx.String("pack")
|
||||||
|
response := ctx.String("response")
|
||||||
|
return generate.Do(fileName, host, basepath, schemes, pack, response, p)
|
||||||
}
|
}
|
||||||
|
124
example/user.api
124
example/user.api
@ -1,81 +1,81 @@
|
|||||||
info(
|
info(
|
||||||
title: "type title here"
|
title: "type title here"
|
||||||
desc: "type desc here"
|
desc: "type desc here"
|
||||||
author: "type author here"
|
author: "type author here"
|
||||||
email: "type email here"
|
email: "type email here"
|
||||||
version: "type version here"
|
version: "type version here"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
||||||
//注册请求结构
|
//注册请求结构
|
||||||
RegisterReq {
|
RegisterReq {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Mobile string `json:"mobile"`
|
Mobile string `json:"mobile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginReq {
|
LoginReq {
|
||||||
Username string `json:"username"` //测试
|
Username string `json:"username"` //测试
|
||||||
Password string `json:"password"`//测试2
|
Password string `json:"password"` //测试2
|
||||||
}
|
AppId string `header:"appId"` //APPID-TEST
|
||||||
UserInfoReq {
|
}
|
||||||
Id string `path:"id"`
|
UserInfoReq {
|
||||||
}
|
Id string `path:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
UserInfoReply {
|
UserInfoReply {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Age int `json:"age"`
|
Age int `json:"age"`
|
||||||
Birthday string `json:"birthday"`
|
Birthday string `json:"birthday"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Tag []string `json:"tag"`
|
Tag []string `json:"tag"`
|
||||||
Tags [][]string `json:"tags"`
|
Tags [][]string `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
UserSearchReq {
|
UserSearchReq {
|
||||||
KeyWord string `form:"keyWord"` // 关键词
|
KeyWord string `form:"keyWord"` // 关键词
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorResponse {
|
ErrorResponse {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server(
|
@server(
|
||||||
prefix: /api
|
prefix: /api
|
||||||
)
|
)
|
||||||
|
|
||||||
service user-api {
|
service user-api {
|
||||||
@doc(
|
@doc(
|
||||||
summary: 注册
|
summary: 注册
|
||||||
)
|
)
|
||||||
@handler register
|
@handler register
|
||||||
post /user/register (RegisterReq)
|
post /user/register (RegisterReq)
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: 登录
|
summary: 登录
|
||||||
)
|
)
|
||||||
@handler login
|
@handler login
|
||||||
post /user/login (LoginReq)
|
post /user/login (LoginReq)
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: 获取用户信息
|
summary: 获取用户信息
|
||||||
)
|
)
|
||||||
@handler getUserInfo
|
@handler getUserInfo
|
||||||
/*
|
/*
|
||||||
@respdoc-400 (
|
@respdoc-400 (
|
||||||
100101: out of authority
|
100101: out of authority
|
||||||
100102: user not exist
|
100102: user not exist
|
||||||
) // Error code list
|
) // Error code list
|
||||||
*/
|
*/
|
||||||
/* @respdoc-500 (ErrorResponse) // Server Error */
|
/* @respdoc-500 (ErrorResponse) // Server Error */
|
||||||
get /user/:id (UserInfoReq) returns (UserInfoReply)
|
get /user/:id (UserInfoReq) returns (UserInfoReply)
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: 用户搜索
|
summary: 用户搜索
|
||||||
)
|
)
|
||||||
@handler searchUser
|
@handler searchUser
|
||||||
get /user/search (UserSearchReq) returns (UserInfoReply)
|
get /user/search (UserSearchReq) returns (UserInfoReply)
|
||||||
}
|
}
|
@ -239,21 +239,22 @@ type swaggerSchemaObject struct {
|
|||||||
|
|
||||||
ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"`
|
ExternalDocs *swaggerExternalDocumentationObject `json:"externalDocs,omitempty"`
|
||||||
|
|
||||||
ReadOnly bool `json:"readOnly,omitempty"`
|
ReadOnly bool `json:"readOnly,omitempty"`
|
||||||
MultipleOf float64 `json:"multipleOf,omitempty"`
|
MultipleOf float64 `json:"multipleOf,omitempty"`
|
||||||
Maximum float64 `json:"maximum,omitempty"`
|
Maximum float64 `json:"maximum,omitempty"`
|
||||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
||||||
Minimum float64 `json:"minimum,omitempty"`
|
Minimum float64 `json:"minimum,omitempty"`
|
||||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
||||||
MaxLength uint64 `json:"maxLength,omitempty"`
|
MaxLength uint64 `json:"maxLength,omitempty"`
|
||||||
MinLength uint64 `json:"minLength,omitempty"`
|
MinLength uint64 `json:"minLength,omitempty"`
|
||||||
Pattern string `json:"pattern,omitempty"`
|
Pattern string `json:"pattern,omitempty"`
|
||||||
MaxItems uint64 `json:"maxItems,omitempty"`
|
MaxItems uint64 `json:"maxItems,omitempty"`
|
||||||
MinItems uint64 `json:"minItems,omitempty"`
|
MinItems uint64 `json:"minItems,omitempty"`
|
||||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||||
MaxProperties uint64 `json:"maxProperties,omitempty"`
|
MaxProperties uint64 `json:"maxProperties,omitempty"`
|
||||||
MinProperties uint64 `json:"minProperties,omitempty"`
|
MinProperties uint64 `json:"minProperties,omitempty"`
|
||||||
Required []string `json:"required,omitempty"`
|
Required []string `json:"required,omitempty"`
|
||||||
|
AllOf []swaggerSchemaObject `json:"allOf,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://swagger.io/specification/#definitionsObject
|
// http://swagger.io/specification/#definitionsObject
|
||||||
@ -269,3 +270,11 @@ type enumMap map[string]*descriptor.Enum
|
|||||||
|
|
||||||
// Internal type to store used references.
|
// Internal type to store used references.
|
||||||
type refMap map[string]struct{}
|
type refMap map[string]struct{}
|
||||||
|
|
||||||
|
// responseField 响应字段
|
||||||
|
type responseField struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsData bool `json:"is_data"`
|
||||||
|
}
|
||||||
|
@ -4,13 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Do(filename string, host string, basePath string, in *plugin.Plugin) error {
|
func Do(filename, host, basePath, schemes, pack, response string, in *plugin.Plugin) error {
|
||||||
swagger, err := applyGenerate(in, host, basePath)
|
swagger, err := applyGenerate(in, host, basePath, schemes, pack, response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ func Do(filename string, host string, basePath string, in *plugin.Plugin) error
|
|||||||
|
|
||||||
output := in.Dir + "/" + filename
|
output := in.Dir + "/" + filename
|
||||||
|
|
||||||
err = ioutil.WriteFile(output, formatted.Bytes(), 0666)
|
err = os.WriteFile(output, formatted.Bytes(), 0o666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@ package generate
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -15,7 +17,10 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var strColon = []byte(":")
|
var (
|
||||||
|
strColon = []byte(":")
|
||||||
|
defaultResponse = parseDefaultResponse()
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
validateKey = "validate"
|
validateKey = "validate"
|
||||||
@ -29,6 +34,9 @@ const (
|
|||||||
optionSeparator = "|"
|
optionSeparator = "|"
|
||||||
equalToken = "="
|
equalToken = "="
|
||||||
atRespDoc = "@respdoc-"
|
atRespDoc = "@respdoc-"
|
||||||
|
|
||||||
|
// DefaultResponseJson default response pack json structure.
|
||||||
|
DefaultResponseJson = `[{"name":"trace_id","type":"string","description":"链路追踪id","example":"a1b2c3d4e5f6g7h8"},{"name":"code","type":"integer","description":"状态码","example":0},{"name":"msg","type":"string","description":"消息","example":"ok"},{"name":"data","type":"object","description":"数据","is_data":true}]`
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseRangeOption(option string) (float64, float64, bool) {
|
func parseRangeOption(option string) (float64, float64, bool) {
|
||||||
@ -54,7 +62,7 @@ func parseRangeOption(option string) (float64, float64, bool) {
|
|||||||
return min, max, true
|
return min, max, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyGenerate(p *plugin.Plugin, host string, basePath string) (*swaggerObject, error) {
|
func applyGenerate(p *plugin.Plugin, host, basePath, schemes, pack, response string) (*swaggerObject, error) {
|
||||||
title, _ := strconv.Unquote(p.Api.Info.Properties["title"])
|
title, _ := strconv.Unquote(p.Api.Info.Properties["title"])
|
||||||
version, _ := strconv.Unquote(p.Api.Info.Properties["version"])
|
version, _ := strconv.Unquote(p.Api.Info.Properties["version"])
|
||||||
desc, _ := strconv.Unquote(p.Api.Info.Properties["desc"])
|
desc, _ := strconv.Unquote(p.Api.Info.Properties["desc"])
|
||||||
@ -80,6 +88,19 @@ func applyGenerate(p *plugin.Plugin, host string, basePath string) (*swaggerObje
|
|||||||
s.BasePath = basePath
|
s.BasePath = basePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(schemes) > 0 {
|
||||||
|
supportedSchemes := []string{"http", "https", "ws", "wss"}
|
||||||
|
ss := strings.Split(schemes, ",")
|
||||||
|
for i := range ss {
|
||||||
|
scheme := ss[i]
|
||||||
|
scheme = strings.TrimSpace(scheme)
|
||||||
|
if !contains(supportedSchemes, scheme) {
|
||||||
|
log.Fatalf("unsupport scheme: [%s], only support [http, https, ws, wss]", scheme)
|
||||||
|
}
|
||||||
|
ss[i] = scheme
|
||||||
|
}
|
||||||
|
s.Schemes = ss
|
||||||
|
}
|
||||||
s.SecurityDefinitions = swaggerSecurityDefinitionsObject{}
|
s.SecurityDefinitions = swaggerSecurityDefinitionsObject{}
|
||||||
newSecDefValue := swaggerSecuritySchemeObject{}
|
newSecDefValue := swaggerSecuritySchemeObject{}
|
||||||
newSecDefValue.Name = "Authorization"
|
newSecDefValue.Name = "Authorization"
|
||||||
@ -90,8 +111,22 @@ func applyGenerate(p *plugin.Plugin, host string, basePath string) (*swaggerObje
|
|||||||
|
|
||||||
// s.Security = append(s.Security, swaggerSecurityRequirementObject{"apiKey": []string{}})
|
// s.Security = append(s.Security, swaggerSecurityRequirementObject{"apiKey": []string{}})
|
||||||
|
|
||||||
|
dataKey := "data"
|
||||||
|
if pack != "" {
|
||||||
|
resp := defaultResponse
|
||||||
|
if response != "" {
|
||||||
|
r, dk, err := parseResponse(response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp = r
|
||||||
|
dataKey = dk
|
||||||
|
}
|
||||||
|
s.Definitions[pack] = resp
|
||||||
|
}
|
||||||
|
|
||||||
requestResponseRefs := refMap{}
|
requestResponseRefs := refMap{}
|
||||||
renderServiceRoutes(p.Api.Service, p.Api.Service.Groups, s.Paths, requestResponseRefs)
|
renderServiceRoutes(p.Api.Service, p.Api.Service.Groups, s.Paths, requestResponseRefs, pack, dataKey)
|
||||||
m := messageMap{}
|
m := messageMap{}
|
||||||
|
|
||||||
renderReplyAsDefinition(s.Definitions, m, p.Api.Types, requestResponseRefs)
|
renderReplyAsDefinition(s.Definitions, m, p.Api.Types, requestResponseRefs)
|
||||||
@ -99,7 +134,7 @@ func applyGenerate(p *plugin.Plugin, host string, basePath string) (*swaggerObje
|
|||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swaggerPathsObject, requestResponseRefs refMap) {
|
func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swaggerPathsObject, requestResponseRefs refMap, pack, dataKey string) {
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
for _, route := range group.Routes {
|
for _, route := range group.Routes {
|
||||||
path := group.GetAnnotation("prefix") + route.Path
|
path := group.GetAnnotation("prefix") + route.Path
|
||||||
@ -148,73 +183,13 @@ func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swagge
|
|||||||
}
|
}
|
||||||
if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
|
if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
|
||||||
for _, member := range defineStruct.Members {
|
for _, member := range defineStruct.Members {
|
||||||
if member.Name == "" {
|
if hasHeaderParameters(member) {
|
||||||
memberDefineStruct, _ := member.Type.(spec.DefineStruct)
|
parameters = parseHeader(member, parameters)
|
||||||
for _, m := range memberDefineStruct.Members {
|
|
||||||
if strings.Contains(m.Tag, "header") {
|
|
||||||
tempKind := swaggerMapTypes[strings.Replace(m.Type.Name(), "[]", "", -1)]
|
|
||||||
ftype, format, ok := primitiveSchema(tempKind, m.Type.Name())
|
|
||||||
if !ok {
|
|
||||||
ftype = tempKind.String()
|
|
||||||
format = "UNKNOWN"
|
|
||||||
}
|
|
||||||
sp := swaggerParameterObject{In: "header", Type: ftype, Format: format}
|
|
||||||
|
|
||||||
for _, tag := range m.Tags() {
|
|
||||||
sp.Name = tag.Name
|
|
||||||
if len(tag.Options) == 0 {
|
|
||||||
sp.Required = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
required := true
|
|
||||||
for _, option := range tag.Options {
|
|
||||||
if strings.HasPrefix(option, optionsOption) {
|
|
||||||
segs := strings.SplitN(option, equalToken, 2)
|
|
||||||
if len(segs) == 2 {
|
|
||||||
sp.Enum = strings.Split(segs[1], optionSeparator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(option, rangeOption) {
|
|
||||||
segs := strings.SplitN(option, equalToken, 2)
|
|
||||||
if len(segs) == 2 {
|
|
||||||
min, max, ok := parseRangeOption(segs[1])
|
|
||||||
if ok {
|
|
||||||
sp.Schema.Minimum = min
|
|
||||||
sp.Schema.Maximum = max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(option, defaultOption) {
|
|
||||||
segs := strings.Split(option, equalToken)
|
|
||||||
if len(segs) == 2 {
|
|
||||||
sp.Default = segs[1]
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) {
|
|
||||||
required = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(option, exampleOption) {
|
|
||||||
segs := strings.Split(option, equalToken)
|
|
||||||
if len(segs) == 2 {
|
|
||||||
sp.Example = segs[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sp.Required = required
|
|
||||||
}
|
|
||||||
sp.Description = strings.TrimLeft(m.Comment, "//")
|
|
||||||
parameters = append(parameters, sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.ToUpper(route.Method) == http.MethodGet {
|
if strings.ToUpper(route.Method) == http.MethodGet {
|
||||||
for _, member := range defineStruct.Members {
|
for _, member := range defineStruct.Members {
|
||||||
if strings.Contains(member.Tag, "path") {
|
if hasPathParameters(member) || hasHeaderParameters(member) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if embedStruct, isEmbed := member.Type.(spec.DefineStruct); isEmbed {
|
if embedStruct, isEmbed := member.Type.(spec.DefineStruct); isEmbed {
|
||||||
@ -283,6 +258,17 @@ func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swagge
|
|||||||
if value := group.GetAnnotation("swtags"); len(value) > 0 {
|
if value := group.GetAnnotation("swtags"); len(value) > 0 {
|
||||||
tags = value
|
tags = value
|
||||||
}
|
}
|
||||||
|
schema := swaggerSchemaObject{
|
||||||
|
schemaCore: respSchema,
|
||||||
|
}
|
||||||
|
if pack != "" {
|
||||||
|
schema = swaggerSchemaObject{
|
||||||
|
AllOf: []swaggerSchemaObject{
|
||||||
|
{schemaCore: schemaCore{Ref: "#/definitions/" + strings.TrimPrefix(pack, "/")}},
|
||||||
|
{schemaCore: schemaCore{Type: "object"}, Properties: &swaggerSchemaObjectProperties{{Key: dataKey, Value: respSchema}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
operationObject := &swaggerOperationObject{
|
operationObject := &swaggerOperationObject{
|
||||||
Tags: []string{tags},
|
Tags: []string{tags},
|
||||||
@ -290,9 +276,7 @@ func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swagge
|
|||||||
Responses: swaggerResponsesObject{
|
Responses: swaggerResponsesObject{
|
||||||
"200": swaggerResponseObject{
|
"200": swaggerResponseObject{
|
||||||
Description: desc,
|
Description: desc,
|
||||||
Schema: swaggerSchemaObject{
|
Schema: schema,
|
||||||
schemaCore: respSchema,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -469,7 +453,7 @@ func renderReplyAsDefinition(d swaggerDefinitionsObject, m messageMap, p []spec.
|
|||||||
schema.Title = defineStruct.Name()
|
schema.Title = defineStruct.Name()
|
||||||
|
|
||||||
for _, member := range defineStruct.Members {
|
for _, member := range defineStruct.Members {
|
||||||
if hasPathParameters(member) {
|
if hasPathParameters(member) || hasHeaderParameters(member) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
kv := keyVal{Value: schemaOfField(member)}
|
kv := keyVal{Value: schemaOfField(member)}
|
||||||
@ -480,8 +464,7 @@ func renderReplyAsDefinition(d swaggerDefinitionsObject, m messageMap, p []spec.
|
|||||||
if kv.Key == "" {
|
if kv.Key == "" {
|
||||||
memberStruct, _ := member.Type.(spec.DefineStruct)
|
memberStruct, _ := member.Type.(spec.DefineStruct)
|
||||||
for _, m := range memberStruct.Members {
|
for _, m := range memberStruct.Members {
|
||||||
|
if hasHeaderParameters(m) || hasPathParameters(m) {
|
||||||
if strings.Contains(m.Tag, "header") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,6 +529,15 @@ func hasPathParameters(member spec.Member) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasHeaderParameters(member spec.Member) bool {
|
||||||
|
for _, tag := range member.Tags() {
|
||||||
|
if tag.Key == "header" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func schemaOfField(member spec.Member) swaggerSchemaObject {
|
func schemaOfField(member spec.Member) swaggerSchemaObject {
|
||||||
ret := swaggerSchemaObject{}
|
ret := swaggerSchemaObject{}
|
||||||
|
|
||||||
@ -558,7 +550,7 @@ func schemaOfField(member spec.Member) swaggerSchemaObject {
|
|||||||
comment = strings.Replace(comment, "//", "", -1)
|
comment = strings.Replace(comment, "//", "", -1)
|
||||||
|
|
||||||
switch ft := kind; ft {
|
switch ft := kind; ft {
|
||||||
case reflect.Invalid: //[]Struct 也有可能是 Struct
|
case reflect.Invalid: // []Struct 也有可能是 Struct
|
||||||
// []Struct
|
// []Struct
|
||||||
// map[ArrayType:map[Star:map[StringExpr:UserSearchReq] StringExpr:*UserSearchReq] StringExpr:[]*UserSearchReq]
|
// map[ArrayType:map[Star:map[StringExpr:UserSearchReq] StringExpr:*UserSearchReq] StringExpr:[]*UserSearchReq]
|
||||||
refTypeName := strings.Replace(member.Type.Name(), "[", "", 1)
|
refTypeName := strings.Replace(member.Type.Name(), "[", "", 1)
|
||||||
@ -737,3 +729,113 @@ func contains(s []string, str string) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseHeader(m spec.Member, parameters []swaggerParameterObject) []swaggerParameterObject {
|
||||||
|
|
||||||
|
tempKind := swaggerMapTypes[strings.Replace(m.Type.Name(), "[]", "", -1)]
|
||||||
|
ftype, format, ok := primitiveSchema(tempKind, m.Type.Name())
|
||||||
|
if !ok {
|
||||||
|
ftype = tempKind.String()
|
||||||
|
format = "UNKNOWN"
|
||||||
|
}
|
||||||
|
sp := swaggerParameterObject{In: "header", Type: ftype, Format: format}
|
||||||
|
|
||||||
|
for _, tag := range m.Tags() {
|
||||||
|
sp.Name = tag.Name
|
||||||
|
if len(tag.Options) == 0 {
|
||||||
|
sp.Required = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
required := true
|
||||||
|
for _, option := range tag.Options {
|
||||||
|
if strings.HasPrefix(option, optionsOption) {
|
||||||
|
segs := strings.SplitN(option, equalToken, 2)
|
||||||
|
if len(segs) == 2 {
|
||||||
|
sp.Enum = strings.Split(segs[1], optionSeparator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(option, rangeOption) {
|
||||||
|
segs := strings.SplitN(option, equalToken, 2)
|
||||||
|
if len(segs) == 2 {
|
||||||
|
min, max, ok := parseRangeOption(segs[1])
|
||||||
|
if ok {
|
||||||
|
sp.Schema.Minimum = min
|
||||||
|
sp.Schema.Maximum = max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(option, defaultOption) {
|
||||||
|
segs := strings.Split(option, equalToken)
|
||||||
|
if len(segs) == 2 {
|
||||||
|
sp.Default = segs[1]
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) {
|
||||||
|
required = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(option, exampleOption) {
|
||||||
|
segs := strings.Split(option, equalToken)
|
||||||
|
if len(segs) == 2 {
|
||||||
|
sp.Example = segs[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sp.Required = required
|
||||||
|
}
|
||||||
|
sp.Description = strings.TrimLeft(m.Comment, "//")
|
||||||
|
if m.Name == "" {
|
||||||
|
memberDefineStruct, ok := m.Type.(spec.DefineStruct)
|
||||||
|
if !ok {
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
for _, cm := range memberDefineStruct.Members {
|
||||||
|
if hasHeaderParameters(cm) {
|
||||||
|
parameters = parseHeader(cm, parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(parameters, sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应参数
|
||||||
|
func parseResponse(resp string) (swaggerSchemaObject, string, error) {
|
||||||
|
var fields []responseField
|
||||||
|
err := json.Unmarshal([]byte(resp), &fields)
|
||||||
|
if err != nil {
|
||||||
|
return swaggerSchemaObject{}, "", err
|
||||||
|
}
|
||||||
|
hasData := false
|
||||||
|
dataKey := ""
|
||||||
|
for _, field := range fields {
|
||||||
|
if field.Name == "" || field.Type == "" {
|
||||||
|
return swaggerSchemaObject{}, "", errors.New("响应字段参数错误")
|
||||||
|
}
|
||||||
|
if field.IsData {
|
||||||
|
hasData = true
|
||||||
|
dataKey = field.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasData {
|
||||||
|
return swaggerSchemaObject{}, "", errors.New("请指定包装的数据字段")
|
||||||
|
}
|
||||||
|
properties := new(swaggerSchemaObjectProperties)
|
||||||
|
response := swaggerSchemaObject{schemaCore: schemaCore{Type: "object"}}
|
||||||
|
for _, field := range fields {
|
||||||
|
*properties = append(*properties,
|
||||||
|
keyVal{
|
||||||
|
Key: field.Name,
|
||||||
|
Value: swaggerSchemaObject{schemaCore: schemaCore{Type: field.Type}, Description: field.Description},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
response.Properties = properties
|
||||||
|
return response, dataKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认返回值
|
||||||
|
func parseDefaultResponse() swaggerSchemaObject {
|
||||||
|
response, _, _ := parseResponse(DefaultResponseJson)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
69
go.mod
69
go.mod
@ -1,11 +1,70 @@
|
|||||||
module github.com/zeromicro/goctl-swagger
|
module github.com/devttl/goctl-swagger
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||||
github.com/urfave/cli/v2 v2.11.0
|
github.com/urfave/cli/v2 v2.11.0
|
||||||
github.com/zeromicro/go-zero v1.5.4
|
github.com/zeromicro/go-zero v1.6.5
|
||||||
github.com/zeromicro/go-zero/tools/goctl v1.5.4
|
github.com/zeromicro/go-zero/tools/goctl v1.6.5
|
||||||
golang.org/x/oauth2 v0.7.0
|
golang.org/x/oauth2 v0.17.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||||
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.3.0 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang/glog v1.2.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/spf13/cobra v1.8.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
|
github.com/zeromicro/antlr v0.0.1 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
|
google.golang.org/grpc v1.63.2 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
15
main.go
15
main.go
@ -5,8 +5,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/devttl/goctl-swagger/action"
|
||||||
|
"github.com/devttl/goctl-swagger/generate"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/zeromicro/goctl-swagger/action"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -29,6 +30,18 @@ var (
|
|||||||
Name: "filename",
|
Name: "filename",
|
||||||
Usage: "swagger save file name",
|
Usage: "swagger save file name",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "schemes",
|
||||||
|
Usage: "swagger support schemes: http, https, ws, wss",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "pack", // 开启外层响应包装并指定外层响应结构名称
|
||||||
|
Usage: "use outer packaging response and specify the name, example: Response",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "response", // 指定外层响应结构
|
||||||
|
Usage: "outer packaging response structure, example: " + fmt.Sprintf("%q", generate.DefaultResponseJson),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user