1. 新增生成ts工具make:ts

2. 生成js工具增加http_from、info_tags选项
3. 修复生成js的部分字段类型缺失的问题
4. 修复生成swagger时的required在部分场景不正确问题
This commit is contained in:
Zodial
2023-12-14 15:35:24 +08:00
parent e9016a99be
commit d495e50411
5 changed files with 392 additions and 14 deletions

View File

@@ -119,7 +119,7 @@ service Controller {
message TResponse {}
````
# 生成 js+注释 文件
# 生成 js 接口结构文件
````shell
user@macOs toolset % toolset make:js -h
Usage:
@@ -132,9 +132,34 @@ Option:
-out js文件输出路径
-tag 只生成指定tag的请求
-debug 是否显示明细
-root 获取项目跟路径, 默认当前目录
-root 获取项目跟路径, 默认当前目录
-http_from 指定import的http函数位置
-info_tags 指定注释中的tag显示于接口说明
-h 显示帮助信息
Has:
Description:
根据swagger生成js请求文件
````
# 生成 ts 接口结构文件
比js更完整每个参数带有注释同时生成枚举对象
````shell
user@macOs toolset % toolset make:ts -h
Usage:
make:js
-in = @root/web/swagger.json
-out = @root/resources/src/api/swagger_gen.ts
Arguments:
Option:
-in swagger.json路径, 可本地可远程
-out ts文件输出路径
-tag 只生成指定tag的请求
-debug 是否显示明细
-root 获取项目跟路径, 默认当前目录
-http_from 指定import的http函数位置
-info_tags 指定注释中的tag显示于接口说明
-h 显示帮助信息
Has:
Description:
根据swagger生成ts请求文件
````

View File

@@ -39,6 +39,15 @@ func (j *Js) Configure() command.Configure {
Name: "tag",
Description: "只生成指定tag的请求",
},
{
Name: "http_from",
Description: "指定import的http函数位置",
Default: "@/utils/request",
},
{
Name: "info_tags",
Description: "指定注释中的tag显示于接口说明",
},
},
},
}
@@ -89,8 +98,8 @@ func (j *Js) Execute(input command.Input) {
fixSwaggerType(&swagger)
tag := input.GetOption("tag")
str := `import http from '@/utils/request'
`
infoTags := input.GetOptions("info_tags")
str := fmt.Sprintf("import http from '%s'\n", input.GetOption("http_from"))
for _, url := range sortPathMap(swagger.Paths) {
paths := swagger.Paths[url]
re, _ := regexp.Compile("\\$\\[.+\\]")
@@ -113,6 +122,14 @@ func (j *Js) Execute(input command.Input) {
for _, method := range methods {
if method.cm {
isResponse = false
//Tags说明
var tagInfo string
for _, s := range infoTags {
info := getTagInfo(method.e.Description, s)
if info != "" {
tagInfo += fmt.Sprintf("\n * @%s %s", s, info)
}
}
var paramNames []string
paramStr := genJsRequest(method.e.Parameters, swagger)
var dataStr string
@@ -122,7 +139,7 @@ func (j *Js) Execute(input command.Input) {
}
if len(urlParams) > 0 {
for _, urlParam := range urlParams {
paramStr += "\n * @param " + urlParam.Name + " " + urlParam.Type
paramStr += "\n * @param {string|number} " + urlParam.Name
paramNames = append(paramNames, urlParam.Name)
}
}
@@ -135,15 +152,16 @@ func (j *Js) Execute(input command.Input) {
}
str += fmt.Sprintf(`
/**
* %v%v
* @returns {Promise<{code:Number,data:%v,message:string}>}
* %v%v%v
* @returns {Promise<{code:number,data:%v,message:string}>}
* @callback
*/
export async function %v%v(%v) {
return await http.%v(%v%v)
}
`,
method.e.Description,
method.e.Summary,
tagInfo,
paramStr,
response,
parser.StringToHump(strings.Trim(strings.ReplaceAll(funcName, "/", "_"), "_")),
@@ -189,6 +207,10 @@ func genJsRequest(p openapi.Parameters, swagger openapi.Spec) string {
} else if parameter.Items != nil {
t = getObjectStrFromRef(parameter.Items.Ref, swagger) + t
}
case "", "object":
if parameter.Schema != nil {
t = getObjectStrFromRef(parameter.Schema.Ref, swagger)
}
}
if i != 0 {
str += ","
@@ -302,6 +324,9 @@ func getObjectStrFromRef(ref string, swagger openapi.Spec) string {
def := strings.Replace(ref, "#/definitions/", "", 1)
var params []string
if _, ok := swagger.Definitions[def]; ok {
if isEnum(swagger.Definitions[def]) {
return "number"
}
for key, schema := range swagger.Definitions[def].Properties {
if !isResponse && !parser.InArrString(key, swagger.Definitions[def].Required) {
key = key + "?"
@@ -325,14 +350,14 @@ func getJsType(schema *openapi.Schema, swagger openapi.Spec, ref string) string
if ref == schema.Items.Ref {
t = "{}" + t
} else {
t = getObjectStrFromRef(schema.Items.Ref, swagger) + t
t = getJsType(schema.Items, swagger, schema.Items.Ref) + t
}
}
case "object":
if ref == schema.Items.Ref {
case "object", "":
if ref == schema.Ref {
t = "{}"
} else {
t = getObjectStrFromRef(schema.Items.Ref, swagger)
t = getObjectStrFromRef(schema.Ref, swagger)
}
}
return t

View File

@@ -571,9 +571,10 @@ func findMessage(message string, nowDirProtoc []parser.ProtocFileParser, allProt
}
func filterRequired(doc string) (string, bool) {
re := regexp.MustCompile("@(tag|Tag|TAG)\\(\\\"([a-zA-Z]+)\"[,\\s\\\"]+([a-zA-Z]+)\"\\)")
re := regexp.MustCompile("(?i)[//\\s]+@(tag)\\(\\\"binding\"([,\\s\\\"]+[^\\)]+)\\)")
arr := re.FindStringSubmatch(doc)
if len(arr) == 4 && strings.ToLower(arr[2]) == "binding" && strings.ToLower(arr[3]) == "required" {
r := regexp.MustCompile("(?i)[,\\s\\\"]required[,\\s\\\"]")
if len(arr) == 3 && r.MatchString(arr[2]) {
doc = strings.Trim(re.ReplaceAllString(doc, ""), "\r\n")
return doc, true
}

318
console/commands/ts.go Normal file
View File

@@ -0,0 +1,318 @@
package commands
import (
"encoding/json"
"fmt"
"github.com/ctfang/command"
"github.com/go-home-admin/toolset/console/commands/openapi"
"github.com/go-home-admin/toolset/parser"
"io"
"net/http"
"os"
"regexp"
"strings"
)
// Ts @Bean
type Ts struct {
enums map[string]string
params map[string]string
objects map[string]string
swagger openapi.Spec
}
func (t *Ts) Init() {
t.enums = make(map[string]string)
t.params = make(map[string]string)
t.objects = make(map[string]string)
}
func (t *Ts) Configure() command.Configure {
return command.Configure{
Name: "make:ts",
Description: "根据swagger生成ts结构及接口请求方法",
Input: command.Argument{
Option: []command.ArgParam{
{
Name: "in",
Description: "swagger.json路径, 可本地可远程",
Default: "@root/web/swagger.json",
},
{
Name: "out",
Description: "ts文件输出路径",
Default: "@root/resources/src/api/swagger_gen.ts",
},
{
Name: "tag",
Description: "只生成指定tag的请求",
},
{
Name: "http_from",
Description: "指定import的http函数位置",
Default: "@/utils/request",
},
{
Name: "info_tags",
Description: "指定注释中的tag显示于接口说明",
},
},
},
}
}
func (t *Ts) Execute(input command.Input) {
root := getRootPath()
in := input.GetOption("in")
in = strings.Replace(in, "@root", root, 1)
inSwaggerStr := ""
if strings.Index(in, "http") == 0 {
// 远程获取文件
req, _ := http.NewRequest("GET", in, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
//得到返回结果
body, _ := io.ReadAll(res.Body)
inSwaggerStr = string(body)
} else {
body, _ := os.ReadFile(in)
inSwaggerStr = string(body)
}
out := input.GetOption("out")
out = strings.Replace(out, "@root", root, 1)
t.swagger = openapi.Spec{
Swagger: "2.0",
Info: openapi.Info{
Title: "2",
Description: "2",
Version: "2",
},
Host: "api.swagger.com",
Schemes: []string{"https"},
BasePath: "/",
Produces: []string{"application/json"},
Paths: make(map[string]*openapi.Path),
Definitions: map[string]*openapi.Schema{
"google.protobuf.Any": {
Type: "object",
},
},
Parameters: nil,
Extensions: nil,
GlobalOptions: nil,
}
_ = json.Unmarshal([]byte(inSwaggerStr), &t.swagger)
fixSwaggerType(&t.swagger)
tag := input.GetOption("tag")
infoTags := input.GetOptions("info_tags")
str := fmt.Sprintf("import http from '%s'\n", input.GetOption("http_from"))
for _, url := range sortPathMap(t.swagger.Paths) {
paths := t.swagger.Paths[url]
re, _ := regexp.Compile("\\$\\[.+\\]")
url = re.ReplaceAllString(url, "")
url, funcName, params := analysisUrl(url)
urlQuery := make([]*openapi.Parameter, 0)
for _, p := range params {
urlQuery = append(urlQuery, &openapi.Parameter{
Name: p,
Required: true,
Type: "string",
})
}
methods := make([]makeJsCache, 0)
methods = append(methods, makeJsCache{e: paths.Get, cm: canMakeJs(paths.Get, tag), method: "get"})
methods = append(methods, makeJsCache{e: paths.Put, cm: canMakeJs(paths.Put, tag), method: "put"})
methods = append(methods, makeJsCache{e: paths.Post, cm: canMakeJs(paths.Post, tag), method: "post"})
methods = append(methods, makeJsCache{e: paths.Patch, cm: canMakeJs(paths.Patch, tag), method: "patch"})
methods = append(methods, makeJsCache{e: paths.Delete, cm: canMakeJs(paths.Delete, tag), method: "delete"})
for _, method := range methods {
if !method.cm {
continue
}
//Tags说明
var tagInfo string
for _, s := range infoTags {
info := getTagInfo(method.e.Description, s)
if info != "" {
tagInfo += fmt.Sprintf("\n * @%s %s", s, info)
}
}
//func名
fName := parser.StringToHump(strings.Trim(strings.ReplaceAll(funcName, "/", "_"), "_"))
//请求参数
var paramStr, hasParams string
if len(method.e.Parameters) > 0 {
typeName := fName + parser.StringToHump(method.method) + "Params"
paramStr = "params: " + typeName
hasParams = "\n params,"
t.genTsParams(typeName, method.e.Parameters)
}
//URL参数
for _, urlParam := range urlQuery {
paramStr += fmt.Sprintf(", %s: %s", urlParam.Name, "string|number")
}
paramStr = strings.Trim(paramStr, ", ")
//响应结构
var response string
if _, ok := method.e.Responses["200"]; ok {
if method.e.Responses["200"].Schema != nil {
response = t.genType(method.e.Responses["200"].Schema.Ref)
}
}
str += fmt.Sprintf(`
/**
* %v%v
*/
export const %v%v = (%v) => {
return http.%v(
%v,%v
) as Promise<{code:number,data:%v,message:string}>;
}
`,
strings.Trim(method.e.Summary, " "),
tagInfo,
fName,
parser.StringToHump(method.method),
paramStr,
method.method,
url,
hasParams,
response,
)
}
}
//插入枚举
str += "\n"
for _, e := range t.enums {
str += e
}
//插入请求参数
str += "\n"
for _, e := range t.params {
str += e
}
//插入结构
str += "\n"
for _, e := range t.objects {
str += e
}
_ = os.WriteFile(out, []byte(str), 0766)
}
func (t *Ts) genTsParams(typeName string, params []*openapi.Parameter) {
str := "export type " + typeName + " = {\n"
for _, parameter := range params {
ty := t.getTsTypeFromParameter(parameter)
if !parameter.Required {
parameter.Name = parameter.Name + "?"
}
if parameter.Description != "" {
str += fmt.Sprintf(" // %s\n", t.clearEmpty(parameter.Description))
}
str += fmt.Sprintf(" %v: %v;\n", parameter.Name, ty)
}
str += "}\n"
t.params[typeName] = str
}
func (t *Ts) genType(ref string) string {
def := strings.Replace(ref, "#/definitions/", "", 1)
key := strings.ReplaceAll(def, ".", "_")
if _, ok := t.objects[key]; ok {
return key
}
if _, ok := t.enums[key]; ok {
return key
}
if _, ok := t.swagger.Definitions[def]; ok {
if isEnum(t.swagger.Definitions[def]) {
t.genEnums(key, t.swagger.Definitions[def])
return key
}
if len(t.swagger.Definitions[def].Properties) == 0 {
return "{}"
}
str := fmt.Sprintf("export type %s = {\n", key)
for k, schema := range t.swagger.Definitions[def].Properties {
if schema.Description != "" {
str += fmt.Sprintf(" // %s\n", t.clearEmpty(schema.Description))
}
str += fmt.Sprintf(" %s: %s;\n", k, t.getTsTypeFromSchema(schema, ref))
}
str += "}\n"
t.objects[key] = str
}
return key
}
func (t *Ts) getTsTypeFromParameter(param *openapi.Parameter) string {
if param.Schema != nil {
return t.getTsTypeFromSchema(param.Schema, param.Format)
}
return t.getTsTypeFromSchema(&openapi.Schema{
Description: param.Description,
Ref: param.Ref,
Type: param.Type,
Format: param.Format,
Enum: param.Enum,
Items: param.Items,
}, "")
}
func (t *Ts) getTsTypeFromSchema(schema *openapi.Schema, ref string) string {
ty := schema.Type
switch schema.Type {
case "integer", "Number":
ty = "number"
case "array":
if schema.Items != nil {
ty = t.getTsTypeFromSchema(schema.Items, ref)
}
ty += "[]"
case "", "object":
if ref == schema.Ref {
//结构引用自己,防止死循环
ty = strings.ReplaceAll(strings.Replace(ref, "#/definitions/", "", 1), ".", "_")
} else if schema.Ref != "" {
ty = t.genType(schema.Ref)
}
}
return ty
}
func (t *Ts) genEnums(enumName string, schema *openapi.Schema) {
str := fmt.Sprintf("export enum %s {\n", enumName)
for k, item := range schema.Properties {
desc := t.clearEmpty(strings.TrimLeft(item.Description, "enum|"))
if desc != "" {
str += fmt.Sprintf(" // %s\n", desc)
}
str += fmt.Sprintf(" %s = %s,\n", item.Type, k)
}
str += "}\n"
t.enums[enumName] = str
}
func (t *Ts) clearEmpty(str string) string {
return strings.Trim(strings.ReplaceAll(str, "\n", ""), " ")
}
func isEnum(schema *openapi.Schema) bool {
if first, ok := schema.Properties["0"]; ok && strings.Index(first.Description, "enum") == 0 {
return true
}
return false
}
func getTagInfo(doc, tag string) string {
re := regexp.MustCompile("@(tag|Tag|TAG)\\(\\\"([^\"]+)\"[,\\s\\\"]+([^\"]+)\"\\)")
arr := re.FindAllStringSubmatch(doc, -1)
for _, item := range arr {
if strings.ToLower(item[2]) == strings.ToLower(tag) {
return strings.ToLower(item[3])
}
}
return ""
}

View File

@@ -15,6 +15,7 @@ var _OrmCommandSingle *OrmCommand
var _ProtocCommandSingle *ProtocCommand
var _RouteCommandSingle *RouteCommand
var _SwaggerCommandSingle *SwaggerCommand
var _TsSingle *Ts
func GetAllProvider() []interface{} {
return []interface{}{
@@ -28,6 +29,7 @@ func GetAllProvider() []interface{} {
NewProtocCommand(),
NewRouteCommand(),
NewSwaggerCommand(),
NewTs(),
}
}
@@ -101,3 +103,10 @@ func NewSwaggerCommand() *SwaggerCommand {
}
return _SwaggerCommandSingle
}
func NewTs() *Ts {
if _TsSingle == nil {
_TsSingle = &Ts{}
providers.AfterProvider(_TsSingle, "")
}
return _TsSingle
}