diff --git a/README.md b/README.md index 46daa0a..69ad827 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,26 @@ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/goctl-swagger UserSearchReq { KeyWord string `form:"keyWord"` } + + UploadReq { + Type int `form:"type"` + Key string `form:"key,optional"` + } + + UploadResp { + Name string `json:"name"` + Age int `json:"age"` + Birthday string `json:"birthday"` + Description string `json:"description"` + Tag []string `json:"tag"` + } ) + @server( + // 这里是为了给service添加swagger的security,用于使用Authorize添加到接口的header + // 如果添加了jwt,这个描述可以省略 + security: true + ) service user-api { @doc( summary: "注册" @@ -77,6 +95,13 @@ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/goctl-swagger ) @handler searchUser get /api/user/search (UserSearchReq) returns (UserInfoReply) + + @doc( + summary: "上传文件,body修改为formData,并添加一个file参数" + inject_formdata_param: "file" + ) + @handler UploadHandler + post /file/upload (UploadReq) returns (UploadResp) } ``` diff --git a/generate/generate_test.go b/generate/generate_test.go new file mode 100644 index 0000000..fe87b2f --- /dev/null +++ b/generate/generate_test.go @@ -0,0 +1,57 @@ +package generate + +import ( + "testing" + + "github.com/zeromicro/go-zero/tools/goctl/api/parser" + "github.com/zeromicro/go-zero/tools/goctl/plugin" +) + +func TestDo(t *testing.T) { + + type args struct { + apiFile string + filename string + host string + basePath string + schemes string + in *plugin.Plugin + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "", + args: args{ + apiFile: "./upload.api", + filename: "upload.json", + host: "127.0.0.1:8890", + basePath: "/", + schemes: "http", + in: &plugin.Plugin{ + Api: nil, + ApiFilePath: "./upload.api", + Style: "", + Dir: "./", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + // 解析api文件 + api, err := parser.Parse(tt.args.apiFile) + if err != nil { + t.Errorf("Parse() error = %v", err) + } + tt.args.in.Api = api + + t.Run(tt.name, func(t *testing.T) { + if err := Do(tt.args.filename, tt.args.host, tt.args.basePath, tt.args.schemes, tt.args.in); (err != nil) != tt.wantErr { + t.Errorf("Do() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/generate/parser.go b/generate/parser.go index 147ee18..f320be1 100644 --- a/generate/parser.go +++ b/generate/parser.go @@ -241,30 +241,82 @@ func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swagge } } else { - reqRef := fmt.Sprintf("#/definitions/%s", route.RequestType.Name()) - if len(route.RequestType.Name()) > 0 { - schema := swaggerSchemaObject{ - schemaCore: schemaCore{ - Ref: reqRef, - }, - } + // If there is a file key in the @doc, add the file parameter, and the parameter will be changed to formData + // Example: + // @doc( + // inject_formdata_param: "file" + // ) + if fileKey, ok := route.AtDoc.Properties["inject_formdata_param"]; ok { + // First, add a file parameter to the form data + fileParameter := swaggerParameterObject{ + Name: strings.Trim(fileKey, "\""), + Type: "file", + In: "formData", + Required: true, + } - parameter := swaggerParameterObject{ - Name: "body", - In: "body", - Required: true, - Schema: &schema, - } + parameters = append(parameters, fileParameter) - doc := strings.Join(route.RequestType.Documents(), ",") - doc = strings.Replace(doc, "//", "", -1) + // Construct the remaining parameters + for _, member := range defineStruct.Members { + required := true + // Whether the parameter is mandatory + for _, tag := range member.Tags() { + for _, option := range tag.Options { + if strings.HasPrefix(option, optionalOption) || strings.HasPrefix(option, omitemptyOption) { + required = false + } + } + } + + // Obtain the parameter type + tempKind := swaggerMapTypes[strings.Replace(member.Type.Name(), "[]", "", -1)] + ftype, format, ok := primitiveSchema(tempKind, member.Type.Name()) + if !ok { + ftype = tempKind.String() + format = "UNKNOWN" + } + + // Construction parameters + fileParameter := swaggerParameterObject{ + Name: strings.ToLower(strings.Trim(member.Name, "\"")), + Type: ftype, + Format: format, + In: "formData", + Required: required, + Description: member.GetComment(), + } + + parameters = append(parameters, fileParameter) + } + } else { + reqRef := fmt.Sprintf("#/definitions/%s", route.RequestType.Name()) + + schema := swaggerSchemaObject{ + schemaCore: schemaCore{ + Ref: reqRef, + }, + } + + parameter := swaggerParameterObject{ + Name: "body", + In: "body", + Required: true, + Schema: &schema, + } + + doc := strings.Join(route.RequestType.Documents(), ",") + doc = strings.Replace(doc, "//", "", -1) + + if doc != "" { + parameter.Description = doc + } + + parameters = append(parameters, parameter) - if doc != "" { - parameter.Description = doc } - parameters = append(parameters, parameter) } } } @@ -384,6 +436,8 @@ func renderServiceRoutes(service spec.Service, groups []spec.Group, paths swagge if group.Annotation.Properties["jwt"] != "" { operationObject.Security = &[]swaggerSecurityRequirementObject{{"apiKey": []string{}}} + } else if group.Annotation.Properties["security"] == "true" { + operationObject.Security = &[]swaggerSecurityRequirementObject{{"apiKey": []string{}}} } switch strings.ToUpper(route.Method) { @@ -572,7 +626,7 @@ func schemaOfField(member spec.Member) swaggerSchemaObject { comment = strings.Replace(comment, "//", "", -1) switch ft := kind; ft { - case reflect.Invalid: //[]Struct 也有可能是 Struct + case reflect.Invalid: // []Struct 也有可能是 Struct // []Struct // map[ArrayType:map[Star:map[StringExpr:UserSearchReq] StringExpr:*UserSearchReq] StringExpr:[]*UserSearchReq] refTypeName := strings.Replace(member.Type.Name(), "[", "", 1) diff --git a/generate/upload.api b/generate/upload.api new file mode 100644 index 0000000..9ee4e5b --- /dev/null +++ b/generate/upload.api @@ -0,0 +1,34 @@ +syntax = "v1" + +// 上传文件 +type UploadRequest { + Type int `form:"type"` // Type 1视频 2音频 3图片 4文档 5附件 + Key string `form:"key,optional"` // Key 用于自定义保存到COS的文件名称,会自动加上传的文件后缀 +} + +type UploadResp { + Msg string `json:"msg"` + ID string `json:"id"` + Src string `json:"src"` +} + +type Resp { + Data UploadResp `json:"data"` + Msg string `json:"msg"` + Code int `json:"code"` +} + +// 文件 +@server ( + middleware: Auth + group: api + security: true +) +service upload { + @doc ( + summary : "上传文件" + inject_formdata_param: "file" + ) + @handler UploadHandler + post /file/upload (UploadRequest) returns (Resp) +} diff --git a/generate/upload.json b/generate/upload.json new file mode 100644 index 0000000..16a6cdf --- /dev/null +++ b/generate/upload.json @@ -0,0 +1,137 @@ +{ + "swagger": "2.0", + "info": { + "title": "", + "version": "" + }, + "host": "127.0.0.1:8890", + "basePath": "/", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/file/upload": { + "post": { + "summary": "上传文件", + "operationId": "UploadHandler", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/Resp" + } + } + }, + "parameters": [ + { + "name": "file", + "in": "formData", + "required": true, + "type": "file" + }, + { + "name": "type", + "description": "// Type 1视频 2音频 3图片 4文档 5附件", + "in": "formData", + "required": true, + "type": "integer", + "format": "int32" + }, + { + "name": "key", + "description": "// Key 用于自定义保存到COS的文件名称,会自动加上传的文件后缀", + "in": "formData", + "required": false, + "type": "string" + } + ], + "tags": [ + "api" + ], + "consumes": [ + "multipart/form-data" + ], + "security": [ + { + "apiKey": [] + } + ] + } + } + }, + "definitions": { + "Resp": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/UploadResp" + }, + "msg": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int32" + } + }, + "title": "Resp", + "required": [ + "data", + "msg", + "code" + ] + }, + "UploadRequest": { + "type": "object", + "properties": { + "type": { + "type": "integer", + "format": "int32", + "description": " Type 1视频 2音频 3图片 4文档 5附件" + }, + "key": { + "type": "string", + "description": " Key 用于自定义保存到COS的文件名称,会自动加上传的文件后缀" + } + }, + "title": "UploadRequest", + "required": [ + "type" + ] + }, + "UploadResp": { + "type": "object", + "properties": { + "msg": { + "type": "string" + }, + "id": { + "type": "string" + }, + "src": { + "type": "string" + } + }, + "title": "UploadResp", + "required": [ + "msg", + "id", + "src" + ] + } + }, + "securityDefinitions": { + "apiKey": { + "type": "apiKey", + "description": "Enter JWT Bearer token **_only_**", + "name": "Authorization", + "in": "header" + } + } +} diff --git a/goctl-swagger b/goctl-swagger new file mode 100644 index 0000000..d5a99a6 Binary files /dev/null and b/goctl-swagger differ diff --git a/goctl-swagger.exe b/goctl-swagger.exe new file mode 100644 index 0000000..d4bafed Binary files /dev/null and b/goctl-swagger.exe differ