diff --git a/docs/docs.go b/docs/docs.go index 7dd4612..8a8665a 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -260,6 +260,44 @@ const docTemplate = `{ } } } + }, + "/admin/upload/image": { + "post": { + "description": "上传图片", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "通用" + ], + "summary": "上传图片", + "parameters": [ + { + "type": "file", + "description": "选择文件", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/upload.uploadImageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } } }, "definitions": { @@ -446,6 +484,19 @@ const docTemplate = `{ "type": "string" } } + }, + "upload.uploadImageResponse": { + "type": "object", + "properties": { + "preview_image_url": { + "description": "可预览图片地址", + "type": "string" + }, + "real_image_url": { + "description": "真实图片地址", + "type": "string" + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.json b/docs/swagger.json index c98da0f..eefdf21 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -252,6 +252,44 @@ } } } + }, + "/admin/upload/image": { + "post": { + "description": "上传图片", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "通用" + ], + "summary": "上传图片", + "parameters": [ + { + "type": "file", + "description": "选择文件", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/upload.uploadImageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } } }, "definitions": { @@ -438,6 +476,19 @@ "type": "string" } } + }, + "upload.uploadImageResponse": { + "type": "object", + "properties": { + "preview_image_url": { + "description": "可预览图片地址", + "type": "string" + }, + "real_image_url": { + "description": "真实图片地址", + "type": "string" + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 745d792..97fceb7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -129,6 +129,15 @@ definitions: description: 描述信息 type: string type: object + upload.uploadImageResponse: + properties: + preview_image_url: + description: 可预览图片地址 + type: string + real_image_url: + description: 真实图片地址 + type: string + type: object info: contact: {} title: mini-chat 接口文档 @@ -291,6 +300,31 @@ paths: summary: 管理员登录 tags: - 管理端.登录 + /admin/upload/image: + post: + consumes: + - multipart/form-data + description: 上传图片 + parameters: + - description: 选择文件 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/upload.uploadImageResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/code.Failure' + summary: 上传图片 + tags: + - 通用 securityDefinitions: LoginVerifyToken: in: header diff --git a/internal/api/upload/upload.go b/internal/api/upload/upload.go new file mode 100644 index 0000000..21cbacb --- /dev/null +++ b/internal/api/upload/upload.go @@ -0,0 +1,21 @@ +package upload + +import ( + "mini-chat/internal/pkg/logger" + "mini-chat/internal/repository/mysql" + "mini-chat/internal/repository/mysql/dao" +) + +type handler struct { + logger logger.CustomLogger + writeDB *dao.Query + readDB *dao.Query +} + +func New(logger logger.CustomLogger, db mysql.Repo) *handler { + return &handler{ + logger: logger, + writeDB: dao.Use(db.GetDbW()), + readDB: dao.Use(db.GetDbR()), + } +} diff --git a/internal/api/upload/upload_image.go b/internal/api/upload/upload_image.go new file mode 100755 index 0000000..5751f28 --- /dev/null +++ b/internal/api/upload/upload_image.go @@ -0,0 +1,90 @@ +package upload + +import ( + "fmt" + "net/http" + "strings" + + "mini-chat/configs" + "mini-chat/internal/code" + "mini-chat/internal/pkg/core" + "mini-chat/internal/pkg/idgen" +) + +type uploadImageResponse struct { + RealImageUrl string `json:"real_image_url"` // 真实图片地址 + PreviewImageUrl string `json:"preview_image_url"` // 可预览图片地址 +} + +// UploadImage 上传图片 +// @Summary 上传图片 +// @Description 上传图片 +// @Tags 通用 +// @Accept multipart/form-data +// @Produce json +// @Param file formData file true "选择文件" +// @Success 200 {object} uploadImageResponse +// @Failure 400 {object} code.Failure +// @Router /admin/upload/image [post] +func (h *handler) UploadImage() core.HandlerFunc { + return func(ctx core.Context) { + file, err := ctx.FormFile("file") + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.UploadError, + fmt.Sprintf("%s: %s", code.Text(code.UploadError), err.Error()), + )) + return + } + + if file == nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.UploadError, + fmt.Sprintf("%s: %s", code.Text(code.UploadError), "缺少 file 文件"), + )) + return + } + + // 校验文件后缀 + extension := "" + if dot := strings.LastIndexByte(file.Filename, '.'); dot != -1 { + extension = file.Filename[dot+1:] + } + + allowedExtensions := map[string]bool{ + "jpg": true, + "jpeg": true, + "png": true, + "gif": true, + } + + if !allowedExtensions[strings.ToLower(extension)] { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.UploadError, + fmt.Sprintf("%s: %s", code.Text(code.UploadError), "文件后缀应为 .jpg、.jpeg、.png、.gif"), + )) + return + } + + // 保存文件 + imagePath := fmt.Sprintf("image/%s.%s", idgen.GenerateUniqueID(), strings.ToLower(extension)) + filePath := fmt.Sprintf("%s/%s", configs.GetResourcesFilePath(), imagePath) + if err := ctx.SaveUploadedFile(file, filePath); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.UploadError, + fmt.Sprintf("%s: %s", code.Text(code.UploadError), err.Error()), + )) + return + } + + res := new(uploadImageResponse) + res.RealImageUrl = filePath + res.PreviewImageUrl = fmt.Sprintf("resources/%s", imagePath) + + ctx.Payload(res) + } +} diff --git a/internal/code/code.go b/internal/code/code.go index f6c217d..df25c71 100644 --- a/internal/code/code.go +++ b/internal/code/code.go @@ -19,6 +19,7 @@ const ( ServerError = 10101 ParamBindError = 10102 JWTAuthVerifyError = 10103 + UploadError = 10104 AdminLoginError = 20101 CreateAppError = 20201 diff --git a/internal/code/zh-cn.go b/internal/code/zh-cn.go index 24a9bd1..29cac1e 100644 --- a/internal/code/zh-cn.go +++ b/internal/code/zh-cn.go @@ -4,6 +4,7 @@ var zhCNText = map[int]string{ ServerError: "内部服务器错误", ParamBindError: "参数错误", JWTAuthVerifyError: "JWT 授权验证错误", + UploadError: "上传失败", AdminLoginError: "登录失败", diff --git a/internal/pkg/core/core.go b/internal/pkg/core/core.go index 0de94c5..673a518 100644 --- a/internal/pkg/core/core.go +++ b/internal/pkg/core/core.go @@ -3,6 +3,7 @@ package core import ( "encoding/json" "fmt" + "mini-chat/internal/pkg/cors" "net/http" "net/url" "runtime/debug" @@ -11,7 +12,6 @@ import ( "mini-chat/configs" _ "mini-chat/docs" "mini-chat/internal/code" - "mini-chat/internal/pkg/cors" "mini-chat/internal/pkg/env" "mini-chat/internal/pkg/errors" "mini-chat/internal/pkg/logger" @@ -231,6 +231,7 @@ func New(logger logger.CustomLogger, options ...Option) (Mux, error) { // 启动信息 startup.PrintInfo() + mux.engine.Use(cors.New()) mux.engine.StaticFS("resources", gin.Dir(configs.GetResourcesFilePath(), true)) // withoutTracePaths 这些请求,默认不记录日志 @@ -275,9 +276,9 @@ func New(logger logger.CustomLogger, options ...Option) (Mux, error) { mux.engine.GET("/metrics", gin.WrapH(promhttp.Handler())) // register prometheus } - if opt.enableCors { - mux.engine.Use(cors.New()) - } + //if opt.enableCors { + // mux.engine.Use(cors.New()) + //} // recover 两次,防止 recover 过程中时发生 panic mux.engine.Use(func(ctx *gin.Context) { diff --git a/internal/router/router.go b/internal/router/router.go index 7bd6216..5be21dd 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -4,6 +4,7 @@ import ( "mini-chat/internal/alert" "mini-chat/internal/api/admin" "mini-chat/internal/api/app" + "mini-chat/internal/api/upload" "mini-chat/internal/cron" "mini-chat/internal/dblogger" "mini-chat/internal/pkg/core" @@ -41,6 +42,7 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co adminHandler := admin.New(logger, db) appHandler := app.New(logger, db) + uploadHandler := upload.New(logger, db) // 管理端非认证接口路由组 adminNonAuthApiRouter := mux.Group("/admin") @@ -49,7 +51,8 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co ctx.Payload(startup.Info()) }) - adminNonAuthApiRouter.POST("/login", adminHandler.Login()) // 登录 + adminNonAuthApiRouter.POST("/login", adminHandler.Login()) // 登录 + adminNonAuthApiRouter.POST("/upload/image", uploadHandler.UploadImage()) // 上传图片 } // 管理端认证接口路由组