From c3be4df2e20823453b337f8ae0de50d02db0833e Mon Sep 17 00:00:00 2001 From: zodial Date: Wed, 16 Oct 2024 11:04:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E3=80=81Swagger=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=EF=BC=9A=201=E3=80=81post=E7=9A=84payload?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=94=B9=E4=B8=BAapplication/json=202?= =?UTF-8?q?=E3=80=81=E6=94=AF=E6=8C=81=E5=A4=9A=E8=AF=AD=E8=A8=80=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=89=A7=E8=A1=8C=E5=8F=82=E6=95=B0-lang=3D?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E6=A0=87=E8=AF=86=EF=BC=8C=E4=BB=A5=E2=80=9C?= =?UTF-8?q?//@lang=3D=E8=AF=AD=E8=A8=80=20=E8=AF=B4=E6=98=8E=E2=80=9D?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E6=8C=87=E5=AE=9A=E8=AF=AD=E8=A8=80=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=203=E3=80=81=E4=BC=98=E5=8C=96Description=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=EF=BC=8Ctag=E6=8D=A2=E8=A1=8C=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=EF=BC=8C=E5=BC=95=E7=94=A8=E5=AF=B9=E8=B1=A1=E6=97=B6=E9=87=87?= =?UTF-8?q?=E7=94=A8=E6=9C=AC=E5=9C=B0=E8=AF=B4=E6=98=8E=204=E3=80=81?= =?UTF-8?q?=E6=94=AF=E6=8C=81example=E5=AE=9A=E4=B9=89=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E2=80=9C//@example=3D=E2=80=9D=E6=88=96=E2=80=9C//@example()?= =?UTF-8?q?=E2=80=9D=E5=A3=B0=E6=98=8E=EF=BC=8C=E5=89=8D=E8=80=85=E4=B8=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A9=BA=E6=A0=BC=205=E3=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0path=E7=9A=84=E5=8F=82=E6=95=B0=E5=8F=8A=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=EF=BC=8C=E4=BA=8E=E8=AF=B7=E6=B1=82=E5=A3=B0=E6=98=8E?= =?UTF-8?q?=E4=B8=8A=E4=B8=80=E8=A1=8C=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=8C=E4=BE=8B=E5=A6=82=EF=BC=9A`option=20(http.Get)=20=3D?= =?UTF-8?q?=20"/user/:id";`=EF=BC=8C=E4=B8=8A=E4=B8=80=E8=A1=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=EF=BC=9A`//=20@query=3Did=20@lang=3D=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=A0=87=E8=AF=86=20@format=3Dstring=20@example=3DABC?= =?UTF-8?q?=20=E8=AF=B4=E6=98=8E=E6=96=87=E6=9C=AC`=EF=BC=8C=E5=85=B6?= =?UTF-8?q?=E4=B8=ADquery=E6=98=AF=E5=BF=85=E9=A1=BB=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=EF=BC=8Cformat=E9=BB=98=E8=AE=A4=E4=B8=BAint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 二、生成pgorm时,取消[]byte生成关于list的func --- console/commands/js_request.go | 21 +- console/commands/openapi/interface.go | 56 ++++-- console/commands/pgorm/pgsql.go | 3 + console/commands/swagger.go | 275 ++++++++++++++++++++++---- console/commands/ts.go | 49 +++-- parser/protoc.go | 53 +++-- 6 files changed, 370 insertions(+), 87 deletions(-) diff --git a/console/commands/js_request.go b/console/commands/js_request.go index 2798186..c88a703 100644 --- a/console/commands/js_request.go +++ b/console/commands/js_request.go @@ -131,7 +131,12 @@ func (j *Js) Execute(input command.Input) { } } var paramNames []string - paramStr := genJsRequest(method.e.Parameters, swagger) + var paramStr string + if method.method == "get" { + paramStr = genJsRequest(method.e.Parameters, swagger) + } else if method.e.RequestBody != nil { + paramStr = "\n * @param {" + getObjectStrFromRef(method.e.RequestBody.Content.Json.Schema.Ref, swagger) + "} data" + } var dataStr string if paramStr != "" { paramNames = append(paramNames, "data") @@ -341,6 +346,14 @@ func getObjectStrFromRef(ref string, swagger openapi.Spec) string { func getJsType(schema *openapi.Schema, swagger openapi.Spec, ref string) string { t := schema.Type + _ref := schema.Ref + if len(schema.AllOf) > 0 { + for _, s := range schema.AllOf { + if s.Ref != "" { + _ref = s.Ref + } + } + } switch schema.Type { case "integer", "Number": t = "number" @@ -350,10 +363,10 @@ func getJsType(schema *openapi.Schema, swagger openapi.Spec, ref string) string } t += "[]" case "object", "": - if ref == schema.Ref { + if ref == _ref { t = "{}" - } else if schema.Ref != "" { - t = getObjectStrFromRef(schema.Ref, swagger) + } else if _ref != "" { + t = getObjectStrFromRef(_ref, swagger) } } return t diff --git a/console/commands/openapi/interface.go b/console/commands/openapi/interface.go index 6ea91ec..2b7a8f4 100644 --- a/console/commands/openapi/interface.go +++ b/console/commands/openapi/interface.go @@ -46,16 +46,17 @@ type Path struct { } type Parameter struct { - Name string `json:"name,omitempty"` - Description string `json:"description"` - Enum []string `json:"enum,omitempty"` - Format string `json:"format,omitempty"` - In string `json:"in,omitempty"` - Items *Schema `json:"items,omitempty"` - Ref string `json:"$ref,omitempty"` - Required bool `json:"required"` - Schema *Schema `json:"schema,omitempty"` - Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description"` + Enum []string `json:"enum,omitempty"` + Format string `json:"format,omitempty"` + In string `json:"in,omitempty"` + Items *Schema `json:"items,omitempty"` + Ref string `json:"$ref,omitempty"` + Required bool `json:"required"` + Schema *Schema `json:"schema,omitempty"` + Type string `json:"type,omitempty"` + Example interface{} `json:"example,omitempty"` } type Parameters []*Parameter @@ -65,9 +66,28 @@ type Response struct { Schema *Schema `json:"schema,omitempty"` } +type RequestBody struct { + Description string `yaml:"summary" json:"description"` + Required bool `yaml:"summary" json:"required"` + Content RequestBodyContent `yaml:"summary" json:"content"` +} + +type RequestBodyContent struct { + Json *RequestBodyContentType `yaml:"application/json" json:"application/json,omitempty"` + XML *RequestBodyContentType `yaml:"application/xml" json:"application/xml,omitempty"` + Form *RequestBodyContentType `yaml:"application/x-www-form-urlencoded" json:"application/x-www-form-urlencoded,omitempty"` + Text *RequestBodyContentType `yaml:"text/plain" json:"text/plain,omitempty"` +} + +type RequestBodyContentType struct { + Schema Schema `yaml:"schema" json:"schema"` + Examples interface{} `yaml:"examples" json:"examples,omitempty"` +} + type Endpoint struct { Summary string `yaml:"summary" json:"summary"` Description string `yaml:"description" json:"description"` + RequestBody *RequestBody `yaml:"requestBody" json:"requestBody,omitempty"` Parameters Parameters `yaml:"parameters" json:"parameters"` Tags []string `yaml:"tags" json:"tags,omitempty"` Responses map[string]*Response `yaml:"responses" json:"responses"` @@ -81,7 +101,7 @@ type Model struct { } type Schema struct { - Description string `json:"description"` + Description string `json:"description,omitempty"` Ref string `json:"$ref,omitempty"` Type string `json:"type,omitempty"` @@ -95,9 +115,13 @@ type Schema struct { // is an array Items *Schema `json:"items,omitempty"` - Pattern string `json:"pattern,omitempty"` - MaxLength int `json:"maxLength,omitempty"` - MinLength int `json:"minLength,omitempty"` - Maximum int `json:"maximum,omitempty"` - Minimum int `json:"minimum,omitempty"` + Pattern string `json:"pattern,omitempty"` + MaxLength int `json:"maxLength,omitempty"` + MinLength int `json:"minLength,omitempty"` + Maximum int `json:"maximum,omitempty"` + Minimum int `json:"minimum,omitempty"` + Example interface{} `json:"example,omitempty"` + + // only for the different Doc when it's Ref object + AllOf []*Schema `json:"allOf,omitempty"` } diff --git a/console/commands/pgorm/pgsql.go b/console/commands/pgorm/pgsql.go index 21ebbd6..fceabb7 100644 --- a/console/commands/pgorm/pgsql.go +++ b/console/commands/pgorm/pgsql.go @@ -87,6 +87,9 @@ func genListFunc(table string, columns []tableColumn) string { TableName := parser.StringToHump(table) str := "\ntype " + TableName + "List []*" + TableName for _, column := range columns { + if column.GoType == "[]byte" { + continue + } ColumnName := parser.StringToHump(column.ColumnName) // 索引,或者枚举字段 if strInStr(column.ColumnName, []string{"id", "code"}) || strInStr(column.Comment, []string{"@index"}) { diff --git a/console/commands/swagger.go b/console/commands/swagger.go index 5b05afa..6675be9 100644 --- a/console/commands/swagger.go +++ b/console/commands/swagger.go @@ -16,7 +16,7 @@ import ( // SwaggerCommand @Bean type SwaggerCommand struct{} -var isQuery bool +var language string func (SwaggerCommand) Configure() command.Configure { return command.Configure{ @@ -40,9 +40,9 @@ func (SwaggerCommand) Configure() command.Configure { Default: "@root/web/swagger_gen.json", }, { - Name: "query", - Description: "是否强制请求参数为query类型", - Default: "false", + Name: "lang", + Description: "指定文档语言,如说明的下一行是‘// @lang:en name’则‘name’代替原说明", + Default: "", }, }, }, @@ -54,10 +54,7 @@ func (SwaggerCommand) Execute(input command.Input) { source := input.GetOption("source") out := input.GetOption("out") path := input.GetOption("path") - - if input.GetOption("query") == "true" || input.GetOption("query") == "1" { - isQuery = true - } + language = input.GetOption("lang") swagger := openapi.Spec{ Swagger: "2.0", @@ -123,8 +120,10 @@ func (SwaggerCommand) Execute(input command.Input) { } for _, schema := range swagger.Definitions { for _, parameter := range schema.Properties { - like[parameter.Ref] = true - if parameter.Items != nil { + if parameter.Ref != "" { + like[parameter.Ref] = true + } + if parameter.Items != nil && parameter.Items.Ref != "" { like[parameter.Items.Ref] = true } } @@ -182,6 +181,7 @@ func isValidHTTPStatusCode(code int) bool { } func rpcToPath(pge string, service parser.ServiceRpc, swagger *openapi.Spec, nowDirProtoc []parser.ProtocFileParser, allProtoc map[string][]parser.ProtocFileParser, serviceOpt map[string]parser.Option) { + service.Doc = filterLanguage(service.Doc) for _, options := range service.Opt { for _, option := range options { urlPath := option.Val @@ -198,10 +198,15 @@ func rpcToPath(pge string, service parser.ServiceRpc, swagger *openapi.Spec, now endpoint := &openapi.Endpoint{} switch option.Key { case "http.Get", "http.Put", "http.Post", "http.Patch", "http.Delete": - endpoint.Description = service.Doc + option.Doc - endpoint.Summary = filterTag(service.Doc) + option.Doc + endpoint.Description = parseTag(service.Doc) + endpoint.Summary = filterTag(service.Doc) endpoint.Tags = strings.Split(pge, ".") - endpoint.Parameters = messageToParameters(option.Key, service.Param, nowDirProtoc, allProtoc) + endpoint.Parameters = parseParamInPath(option) + if option.Key == "http.Get" { + endpoint.Parameters = append(endpoint.Parameters, messageToParameters(service.Param, nowDirProtoc, allProtoc)...) + } else { + endpoint.RequestBody = messageToRequestBody(service.Param, nowDirProtoc, allProtoc) + } endpoint.Responses = map[string]*openapi.Response{ "200": messageToResponse(service.Return, nowDirProtoc, allProtoc), } @@ -283,30 +288,25 @@ func messageToResponse(message string, nowDirProtoc []parser.ProtocFileParser, a got := &openapi.Response{ Description: protocMessage.Doc, Schema: &openapi.Schema{ - Ref: "#/definitions/" + pge + "." + protocMessage.Name, + Type: "object", + Format: pge + "." + protocMessage.Name, + Ref: "#/definitions/" + pge + "." + protocMessage.Name, }, } return got } -func messageToParameters(method string, message string, nowDirProtoc []parser.ProtocFileParser, allProtoc map[string][]parser.ProtocFileParser) openapi.Parameters { +func messageToParameters(message string, nowDirProtoc []parser.ProtocFileParser, allProtoc map[string][]parser.ProtocFileParser) openapi.Parameters { protocMessage, pge := findMessage(message, nowDirProtoc, allProtoc) got := openapi.Parameters{} if protocMessage == nil { return got } - in := "formData" - switch method { - case "http.Get": - in = "query" - default: - if isQuery { - in = "query" - } - } + in := "query" for _, option := range protocMessage.Attr { doc, isRequired := filterRequired(option.Doc) + doc, example := filterExample(doc, option.Ty) doc = getTitle(doc) if option.Repeated { if isProtoBaseType(option.Ty) { @@ -318,6 +318,7 @@ func messageToParameters(method string, message string, nowDirProtoc []parser.Pr Format: option.Ty, In: in, Required: isRequired, + Example: example, Items: &openapi.Schema{ Description: doc, Type: getProtoToSwagger(option.Ty), @@ -334,6 +335,7 @@ func messageToParameters(method string, message string, nowDirProtoc []parser.Pr Type: "array", In: in, Required: isRequired, + Example: example, Items: &openapi.Schema{ Ref: getRef(pge, option.Ty), Description: doc, @@ -351,6 +353,7 @@ func messageToParameters(method string, message string, nowDirProtoc []parser.Pr Type: getProtoToSwagger(option.Ty), Format: option.Ty, Required: isRequired, + Example: example, } got = append(got, attr) } else { @@ -360,8 +363,9 @@ func messageToParameters(method string, message string, nowDirProtoc []parser.Pr Description: doc, Type: getProtoToSwagger(option.Ty), Format: option.Ty, - In: "query", // 对象引用只能是query, 不然页面显示错误 + In: in, // 对象引用只能是query, 不然页面显示错误 Required: isRequired, + Example: example, Schema: &openapi.Schema{ Type: "object", Description: getTitle(option.Doc), @@ -376,6 +380,27 @@ func messageToParameters(method string, message string, nowDirProtoc []parser.Pr return got } +func messageToRequestBody(message string, nowDirProtoc []parser.ProtocFileParser, allProtoc map[string][]parser.ProtocFileParser) *openapi.RequestBody { + protocMessage, pge := findMessage(message, nowDirProtoc, allProtoc) + if len(protocMessage.Attr) == 0 { + return nil + } + doc, _ := filterRequired(protocMessage.Doc) + doc = filterLanguage(doc) + got := &openapi.RequestBody{ + Description: parseTag(doc), + Content: openapi.RequestBodyContent{ + // 目前只支持application/json + Json: &openapi.RequestBodyContentType{ + Schema: openapi.Schema{ + Ref: getRef(pge, protocMessage.Name), + }, + }, + }, + } + return got +} + func getRef(pge string, ty string) string { arr := strings.Split(ty, ".") if len(arr) == 1 { @@ -393,6 +418,8 @@ func messageToSchemas(pge string, message parser.Message, swagger *openapi.Spec) var requireArr []string for _, option := range message.Attr { doc, isRequired := filterRequired(option.Doc) + doc, example := filterExample(doc, option.Ty) + doc = filterLanguage(doc) doc = getTitle(doc) if isRequired { requireArr = append(requireArr, option.Name) @@ -408,6 +435,7 @@ func messageToSchemas(pge string, message parser.Message, swagger *openapi.Spec) Type: getProtoToSwagger(option.Ty), Format: option.Ty, }, + Example: example, } properties[option.Name] = attr } else if option.Message != nil { @@ -424,6 +452,7 @@ func messageToSchemas(pge string, message parser.Message, swagger *openapi.Spec) attr := &openapi.Schema{ Type: "array", Description: doc, + Example: example, Items: &openapi.Schema{ Ref: getRef(pge, option.Ty), Description: doc, @@ -438,6 +467,7 @@ func messageToSchemas(pge string, message parser.Message, swagger *openapi.Spec) Description: doc, Type: getProtoToSwagger(option.Ty), Format: option.Ty, + Example: example, } properties[option.Name] = attr } else if option.Message != nil { @@ -450,11 +480,28 @@ func messageToSchemas(pge string, message parser.Message, swagger *openapi.Spec) } properties[option.Name] = attr } else { - attr := &openapi.Schema{ - Description: doc, - Ref: getRef(pge, option.Ty), + if doc == "" { + //使用ref的Description + properties[option.Name] = &openapi.Schema{ + Description: doc, + Ref: getRef(pge, option.Ty), + } + } else { + //使用本地的Description + properties[option.Name] = &openapi.Schema{ + AllOf: []*openapi.Schema{ + { + Description: doc, + }, + { + Format: option.Ty, + }, + { + Ref: getRef(pge, option.Ty), + }, + }, + } } - properties[option.Name] = attr } } @@ -493,6 +540,7 @@ func getTitle(str string) string { var protoToSwagger = map[string]string{ "double": "number", "float": "number", + "int": "integer", "int32": "integer", "int64": "integer", "uint32": "number", @@ -571,17 +619,166 @@ func findMessage(message string, nowDirProtoc []parser.ProtocFileParser, allProt } func filterRequired(doc string) (string, bool) { - re := regexp.MustCompile("(?i)[//\\s]+@(tag)\\(\\\"binding\"([,\\s\\\"]+[^\\)]+)\\)") - arr := re.FindStringSubmatch(doc) - 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 - } - return doc, false + arr := strings.Split(doc, "\n") + var newArr []string + var isRequired bool + re := regexp.MustCompile(`(?i)[/\s]*@tag\("binding"[,\s"]+([^"]+)"\)\s*`) + re2 := regexp.MustCompile(`(?i)required`) + for _, s := range arr { + match := re.FindStringSubmatch(s) + if len(match) == 2 && re2.MatchString(arr[1]) { + isRequired = true + } else { + newArr = append(newArr, s) + } + } + return strings.Join(newArr, "\n"), isRequired +} + +// 仅支持string和number兼容两种写法 @example:xxx xxx 或 @example(xxx xxx) +func filterExample(doc string, ty string) (string, interface{}) { + arr := strings.Split(doc, "\n") + var newArr []string + var example string + re := regexp.MustCompile(`(?i)\s*//\s*@example=(.*)`) + re2 := regexp.MustCompile(`(?i)[/\s]*@example\((.*)\)\s*`) + for _, s := range arr { + matches := re.FindStringSubmatch(s) + if len(matches) == 2 { + example = matches[1] + } else { + matches = re2.FindStringSubmatch(s) + if len(matches) == 2 { + example = matches[1] + } else { + newArr = append(newArr, s) + } + } + } + var result interface{} + if example != "" { + example = strings.Trim(strings.Trim(example, "\""), " ") + t := getProtoToSwagger(ty) + switch t { + case "string": + result = example + case "integer", "number": + result, _ = strconv.ParseFloat(example, 64) + case "boolean": + result, _ = strconv.ParseBool(example) + } + } + return strings.Join(newArr, "\n"), result +} + +// 首行为默认说明,次行如://@lang:zh xxx 中的 xxx 将替换默认说明 +func filterLanguage(doc string) string { + arr := strings.Split(doc, "\n") + if len(arr) < 2 { + return doc + } + var newArr []string + for i, s := range arr { + if i != 0 { + re := regexp.MustCompile(`(?i)\s*//\s*@lang=([a-z]+)\s*(.*)`) + match := re.FindStringSubmatch(s) + if len(match) == 3 { + if language == match[1] { + newArr[0] = match[2] + } + continue + } + } + newArr = append(newArr, s) + } + return strings.Join(newArr, "\n") } func filterTag(str string) string { - re := regexp.MustCompile("@(tag|Tag|TAG)\\(\\\"([a-zA-Z]+)\"[,\\s\\\"]+([^\"]+)\"\\)") - return strings.Trim(re.ReplaceAllString(str, ""), "\r\n") + arr := strings.Split(str, "\n") + var newArr []string + re := regexp.MustCompile(`(?i)\s*//\s*@tag\("([a-zA-Z]+)"[,\s"]+"([^"]+)"\)`) + for _, s := range arr { + if !re.MatchString(s) { + newArr = append(newArr, s) + } + } + return strings.Join(newArr, "\n") +} + +func parseTag(str string) string { + arr := strings.Split(str, "\n") + var newArr []string + re := regexp.MustCompile(`(?i)\s*//\s*@tag\("([a-zA-Z]+)"[,\s"]+"([^"]+)"\)`) + for _, s := range arr { + match := re.FindStringSubmatch(s) + if len(match) == 3 { + newArr = append(newArr, "`"+match[1]+": "+match[2]+"`") + } else { + newArr = append(newArr, s) + } + } + return strings.Join(newArr, " \n") +} + +// 例:@query=id @lang=zh @format:string @example=abc 用户ID +// format默认为int,如format是对象或枚举,即使本地引用,也必须要加上包名,如@format:api.UserStatus +func parseParamInPath(option parser.Option) (params openapi.Parameters) { + re := regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`) + matches := re.FindAllStringSubmatch(option.Val, -1) + for _, match := range matches { + key := match[1] + var doc, example string + format := "int" //默认int(int非protobuf类型) + if option.Doc != "" { + var correctLang, lockDoc bool + for _, s := range strings.Split(option.Doc, "\n") { + r := regexp.MustCompile(`(?i)@([a-z_]*)=([a-z0-9_.]*)`) + ms := r.FindAllStringSubmatch(s, -1) + correctDoc := false + for _, m := range ms { + switch m[1] { + case "query": + if m[2] == key { + correctDoc = true + } + case "lang": + if m[2] == language { + correctLang = true + } + case "format": + if correctDoc { + format = m[2] + } + case "example": + if correctDoc { + example = m[2] + } + } + } + if correctDoc && !lockDoc { + doc = filterTag(strings.Trim(strings.Trim(r.ReplaceAllString(s, ""), "/"), " ")) + if correctLang { + lockDoc = true + } + } + } + } + p := &openapi.Parameter{ + Name: key, + Description: doc, + Format: format, + In: "path", + Required: true, + Type: getProtoToSwagger(format), + Example: example, + } + if p.Type == "object" { + p.Schema = &openapi.Schema{ + Ref: "#/definitions/" + format, + } + } + params = append(params, p) + } + return } diff --git a/console/commands/ts.go b/console/commands/ts.go index 7a08c3d..54b346b 100644 --- a/console/commands/ts.go +++ b/console/commands/ts.go @@ -38,7 +38,7 @@ func (t *Ts) Configure() command.Configure { { Name: "in", Description: "swagger.json路径, 可本地可远程", - Default: "@root/web/swagger.json", + Default: "@root/web/swagger_gen.json", }, { Name: "out", @@ -145,11 +145,16 @@ func (t *Ts) Execute(input command.Input) { 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," + if method.method == "get" && len(method.e.Parameters) > 0 { + typeName := fName + parser.StringToHump(method.method) + "Payload" + paramStr = "data: " + typeName + hasParams = "\n data," t.genTsParams(typeName, method.e.Parameters) + } else if method.e.RequestBody != nil { + ref := strings.Replace(method.e.RequestBody.Content.Json.Schema.Ref, "#/definitions/", "", 1) + ref = strings.ReplaceAll(ref, ".", "_") + paramStr = "data: " + ref + hasParams = "\n data," } //URL参数 for _, urlParam := range urlQuery { @@ -240,6 +245,12 @@ func (t *Ts) genType(ref string) string { for k, schema := range t.swagger.Definitions[def].Properties { if schema.Description != "" { str += fmt.Sprintf(" // %s\n", t.clearEmpty(schema.Description)) + } else if len(schema.AllOf) > 0 { + for _, s := range schema.AllOf { + if s.Description != "" { + str += fmt.Sprintf(" // %s\n", t.clearEmpty(s.Description)) + } + } } str += fmt.Sprintf(" %s: %s;\n", k, t.getTsTypeFromSchema(schema, ref)) } @@ -265,6 +276,14 @@ func (t *Ts) getTsTypeFromParameter(param *openapi.Parameter) string { func (t *Ts) getTsTypeFromSchema(schema *openapi.Schema, ref string) string { ty := schema.Type + _ref := schema.Ref + if len(schema.AllOf) > 0 { + for _, s := range schema.AllOf { + if s.Ref != "" { + _ref = s.Ref + } + } + } switch schema.Type { case "integer", "Number": ty = "number" @@ -274,11 +293,11 @@ func (t *Ts) getTsTypeFromSchema(schema *openapi.Schema, ref string) string { } ty += "[]" case "", "object": - if ref == schema.Ref { + if ref == _ref { //结构引用自己,防止死循环 ty = strings.ReplaceAll(strings.Replace(ref, "#/definitions/", "", 1), ".", "_") - } else if schema.Ref != "" { - ty = t.genType(schema.Ref) + } else if _ref != "" { + ty = t.genType(_ref) } } return ty @@ -316,11 +335,15 @@ func isEnum(schema *openapi.Schema) bool { } 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]) + if tag == "" { + return "" + } + arr := strings.Split(doc, "\n") + re := regexp.MustCompile("`" + tag + ":([^`]+)") + for _, s := range arr { + matches := re.FindStringSubmatch(s) + if matches != nil { + return strings.Trim(matches[1], " ") } } return "" diff --git a/parser/protoc.go b/parser/protoc.go index fd7c8e4..9dccb87 100644 --- a/parser/protoc.go +++ b/parser/protoc.go @@ -90,18 +90,28 @@ func GetProtoFileParser(path string) (ProtocFileParser, error) { l := getWordsWitchFile(path) lastDoc := "" + countDoc := 0 // 超过两次回车复原0 for offset := 0; offset < len(l.list); offset++ { work := l.list[offset] // 原则上, 每个块级别的作用域必须自己处理完, 返回的偏移必须是下一个块的开始 switch work.Ty { case wordT_line: + countDoc = countDoc + 1 + if countDoc == 2 { + countDoc = 0 + lastDoc = "" + } case wordT_division: case wordT_doc: - lastDoc = work.Str + if lastDoc != "" && countDoc == 1 { + lastDoc += "\n" + } + lastDoc += work.Str + countDoc = 0 case wordT_word: switch work.Str { case "syntax": - d.Doc = doc(lastDoc) + d.Doc = filterDoc(lastDoc) d.Syntax, offset = protoSyntax(l.list, offset) lastDoc = "" case "package": @@ -114,7 +124,7 @@ func GetProtoFileParser(path string) (ProtocFileParser, error) { lastDoc = "" case "option": var val Option - val.Doc = doc(lastDoc) + val.Doc = filterDoc(lastDoc) val, offset = protoOption(l.list, offset) d.Option[val.Key] = val lastDoc = "" @@ -122,19 +132,19 @@ func GetProtoFileParser(path string) (ProtocFileParser, error) { var val Service val, offset = protoService(l.list, offset) val.Protoc = &d - val.Doc = doc(lastDoc) + val.Doc = filterDoc(lastDoc) d.Services[val.Name] = val lastDoc = "" case "message": var val Message val, offset = protoMessage(l.list, offset) - val.Doc = doc(lastDoc) + val.Doc = filterDoc(lastDoc) d.Messages[val.Name] = val lastDoc = "" case "enum": var val Enum val, offset = protoEnum(l.list, offset) - val.Doc = doc(lastDoc) + val.Doc = filterDoc(lastDoc) d.Enums[val.Name] = val lastDoc = "" case "extend": @@ -148,7 +158,7 @@ func GetProtoFileParser(path string) (ProtocFileParser, error) { return d, nil } -func doc(doc string) string { +func filterDoc(doc string) string { doc = strings.TrimFunc(doc, IsSpaceAndEspecially) return doc } @@ -262,6 +272,9 @@ func protoService(l []*word, offset int) (Service, int) { } case wordT_division: case wordT_doc: + if doc != "" && countDoc == 1 { + doc += "\n" + } doc += work.Str countDoc = 0 case wordT_word: @@ -269,13 +282,13 @@ func protoService(l []*word, offset int) (Service, int) { case "option": var val Option val, offset = serverOption(nl, offset) - val.Doc = strings.ReplaceAll(doc, "//", "") + val.Doc = filterDoc(doc) got.Opt[val.Key] = val doc = "" case "rpc": var val ServiceRpc val, offset = protoRpc(nl, offset) - val.Doc = strings.ReplaceAll(doc, "//", "") + val.Doc = filterDoc(doc) got.Rpc[val.Name] = val doc = "" } @@ -313,19 +326,29 @@ func protoRpc(l []*word, offset int) (ServiceRpc, int) { newOffset := offset + et + 1 nl := l[offset+st : newOffset] doc := "" + countDoc := 0 // 超过两次回车复原0 for offset := 0; offset < len(nl); offset++ { work := nl[offset] switch work.Ty { case wordT_line: + countDoc = countDoc + 1 + if countDoc == 2 { + countDoc = 0 + doc = "" + } case wordT_division: case wordT_doc: + if doc != "" && countDoc == 1 { + doc += "\n" + } doc += work.Str + countDoc = 0 case wordT_word: switch work.Str { case "option": var val Option val, offset = serverOption(nl, offset) - val.Doc = strings.ReplaceAll(doc, "//", "") + val.Doc = filterDoc(doc) opt[val.Key] = append(opt[val.Key], val) doc = "" } @@ -373,7 +396,7 @@ func protoMessage(l []*word, offset int) (Message, int) { attr.Name, _ = GetFistWord(nl[offset+1:]) st, et := GetBrackets(nl[offset:], "{", "}") attr.Message = protoOtherMessage(attr.Name, nl[offset+st:offset+et+1]) - attr.Doc = doc(attr.Doc) + attr.Doc = filterDoc(attr.Doc) got.Attr = append(got.Attr, attr) attr = Attr{} offset = offset + et + 1 @@ -395,7 +418,7 @@ func protoMessage(l []*word, offset int) (Message, int) { attr.Name = work.Str } else { attr.Num, _ = strconv.Atoi(work.Str) - attr.Doc = doc(attr.Doc) + attr.Doc = filterDoc(attr.Doc) got.Attr = append(got.Attr, attr) attr = Attr{} } @@ -434,7 +457,7 @@ func protoOtherMessage(name string, l []*word) *Message { attr.Name, _ = GetFistWord(nl[offset+1:]) st, et := GetBrackets(nl[offset:], "{", "}") attr.Message = protoOtherMessage(attr.Name, nl[offset+st:offset+et+1]) - attr.Doc = doc(attr.Doc) + attr.Doc = filterDoc(attr.Doc) got.Attr = append(got.Attr, attr) attr = Attr{} offset = offset + et + 1 @@ -456,7 +479,7 @@ func protoOtherMessage(name string, l []*word) *Message { attr.Name = work.Str } else { attr.Num, _ = strconv.Atoi(work.Str) - attr.Doc = doc(attr.Doc) + attr.Doc = filterDoc(attr.Doc) got.Attr = append(got.Attr, attr) attr = Attr{} } @@ -485,7 +508,7 @@ func protoEnum(l []*word, offset int) (Enum, int) { attr.Name = work.Str } else { attr.Num, _ = strconv.Atoi(work.Str) - attr.Doc = doc(attr.Doc) + attr.Doc = filterDoc(attr.Doc) got.Opt = append(got.Opt, attr) attr = Attr{} }