diff --git a/app/apiException/apiException.go b/app/apiException/apiException.go index 6776367..7d3456f 100644 --- a/app/apiException/apiException.go +++ b/app/apiException/apiException.go @@ -36,9 +36,7 @@ var ( FileSizeExceedError = NewError(200515, log.LevelInfo, "文件大小超限") FileNotImageError = NewError(200516, log.LevelInfo, "上传的文件不是图片") - NotInit = NewError(200404, log.LevelWarn, http.StatusText(http.StatusNotFound)) NotFound = NewError(200404, log.LevelWarn, http.StatusText(http.StatusNotFound)) - Unknown = NewError(300500, log.LevelError, "系统异常,请稍后重试!") ) // Error 方法实现了 error 接口,返回错误的消息内容 diff --git a/app/controllers/objectController/upload.go b/app/controllers/objectController/upload.go index f1f424f..ef5764c 100644 --- a/app/controllers/objectController/upload.go +++ b/app/controllers/objectController/upload.go @@ -1,9 +1,8 @@ -//nolint:all package objectController import ( "errors" - "io" + "image" "mime/multipart" "4u-go/app/apiException" @@ -14,8 +13,7 @@ import ( ) type uploadFileData struct { - UploadType string `form:"type" binding:"required"` - File *multipart.FileHeader `form:"file" binding:"required"` + File *multipart.FileHeader `form:"file" binding:"required"` } // UploadFile 上传文件 @@ -26,7 +24,6 @@ func UploadFile(c *gin.Context) { return } - uploadType := data.UploadType fileSize := data.File.Size file, err := data.File.Open() if err != nil { @@ -41,40 +38,25 @@ func UploadFile(c *gin.Context) { }(file) // 获取文件信息 - contentType, fileExt, err := objectService.GetFileInfo(file, fileSize, uploadType) - if errors.Is(err, objectService.ErrSizeExceeded) { - apiException.AbortWithException(c, apiException.FileSizeExceedError, err) + if fileSize > objectService.ImageLimit { + apiException.AbortWithException(c, apiException.FileSizeExceedError, nil) return } - if errors.Is(err, objectService.ErrUnsupportedUploadType) { - apiException.AbortWithException(c, apiException.ParamError, err) + + reader, size, err := objectService.ConvertToWebP(file) + if errors.Is(err, image.ErrFormat) { + apiException.AbortWithException(c, apiException.FileNotImageError, err) return } if err != nil { apiException.AbortWithException(c, apiException.ServerError, err) return } - - var fileReader io.Reader = file - if uploadType == objectService.TypeImage { - reader, size, err := objectService.ConvertToWebP(file) - if err != nil { - if errors.Is(err, objectService.ErrNotImage) { - apiException.AbortWithException(c, apiException.FileNotImageError, err) - return - } - zap.L().Error("转换图片到 WebP 失败", zap.Error(err)) - } else { // 若转换成功则替代原文件 - fileReader = reader - fileSize = size - fileExt = ".webp" - contentType = "image/webp" - } - } + contentType := "image/webp" // 上传文件 - objectKey := objectService.GenerateObjectKey(uploadType, fileExt) - objectUrl, err := objectService.PutObject(objectKey, fileReader, fileSize, contentType) + objectKey := objectService.GenerateObjectKey("image", ".webp") + objectUrl, err := objectService.PutObject(objectKey, reader, size, contentType) if err != nil { apiException.AbortWithException(c, apiException.ServerError, err) return diff --git a/app/services/objectService/objectService.go b/app/services/objectService/objectService.go index 3eeabc0..9e0a2e8 100644 --- a/app/services/objectService/objectService.go +++ b/app/services/objectService/objectService.go @@ -2,101 +2,35 @@ package objectService import ( "bytes" - "errors" "fmt" + "image" + _ "image/gif" // 注册解码器 + _ "image/jpeg" + _ "image/png" "io" - "mime/multipart" "time" "github.com/chai2010/webp" - "github.com/disintegration/imaging" "github.com/dustin/go-humanize" - "github.com/gabriel-vasile/mimetype" uuid "github.com/satori/go.uuid" + _ "golang.org/x/image/bmp" // 注册解码器 + _ "golang.org/x/image/tiff" + _ "golang.org/x/image/webp" ) -var ( - // ErrUnsupportedUploadType 不支持的上传类型 - ErrUnsupportedUploadType = errors.New("unsupported upload type") - - // ErrSizeExceeded 文件大小超限 - ErrSizeExceeded = errors.New("file size exceeded") - - // ErrNotImage 使用 image 类型上传非图片的文件 - ErrNotImage = errors.New("file isn't a image") -) - -const ( - // TypeImage 图片 - TypeImage = "image" - - // TypeAttachment 附件 - TypeAttachment = "attachment" -) - -var uploadTypeLimits = map[string]int64{ - TypeImage: humanize.MByte * 10, - TypeAttachment: humanize.MByte * 100, -} - -// GetFileInfo 获取文件基本信息 -func GetFileInfo( - file multipart.File, - fileSize int64, - uploadType string, -) ( - contentType string, - fileExt string, - err error, -) { - // 检查文件大小 - if err = checkFileSize(uploadType, fileSize); err != nil { - return "", "", err - } - - // 通过文件头获取类型和扩展名 - mimeType, mimeExt, err := getFileTypeAndExt(file) - if err != nil { - return "", "", err - } - return mimeType, mimeExt, nil -} +// ImageLimit 图片上传大小限制 +const ImageLimit = humanize.MByte * 10 // GenerateObjectKey 通过 UUID 作为文件名并生成 ObjectKey func GenerateObjectKey(uploadType string, fileExt string) string { return fmt.Sprintf("%s/%d/%s%s", uploadType, time.Now().Year(), uuid.NewV1().String(), fileExt) } -// checkFileSize 检查文件大小 -func checkFileSize(uploadType string, size int64) error { - maxSize, ok := uploadTypeLimits[uploadType] - if !ok { - return ErrUnsupportedUploadType - } - if size > maxSize { - return ErrSizeExceeded - } - return nil -} - -// getFileTypeAndExt 根据文件头(Magic Number)判断文件类型和扩展名 -func getFileTypeAndExt(file multipart.File) (mimeType string, mimeExt string, err error) { - mime, err := mimetype.DetectReader(file) - if err != nil { - return "", "", err - } - _, err = file.Seek(0, io.SeekStart) - if err != nil { - return "", "", err - } - return mime.String(), mime.Extension(), nil -} - // ConvertToWebP 将图片转换为 WebP 格式 -func ConvertToWebP(file multipart.File) (io.Reader, int64, error) { - img, err := imaging.Decode(file) +func ConvertToWebP(reader io.Reader) (io.Reader, int64, error) { + img, _, err := image.Decode(reader) if err != nil { - return nil, 0, fmt.Errorf("%w: %w", ErrNotImage, err) + return nil, 0, err } var buf bytes.Buffer diff --git a/app/utils/server/server.go b/app/utils/server/server.go index 7545c24..ce66e20 100644 --- a/app/utils/server/server.go +++ b/app/utils/server/server.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "os/signal" + "syscall" "time" "4u-go/config/redis" @@ -29,7 +30,7 @@ func Run(handler http.Handler, addr string) { // 阻塞并监听结束信号 quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit zap.L().Info("Shutdown Server...") diff --git a/config/router/router.go b/config/router/router.go index fac8b3c..83090df 100644 --- a/config/router/router.go +++ b/config/router/router.go @@ -28,7 +28,7 @@ func Init(r *gin.Engine) { user.POST("/login", userController.AuthByPassword) user.POST("/login/session", userController.AuthBySession) - user.POST("/attachment", objectController.UploadFile) + user.POST("/upload", objectController.UploadFile) user.POST("/repass", midwares.CheckLogin, userController.ChangePassword) user.DELETE("/delete", midwares.CheckLogin, userController.DeleteAccount) diff --git a/go.mod b/go.mod index 42970bb..4399651 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,7 @@ go 1.22.9 require ( github.com/chai2010/webp v1.1.1 - github.com/disintegration/imaging v1.6.2 github.com/dustin/go-humanize v1.0.1 - github.com/gabriel-vasile/mimetype v1.4.6 github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/sessions v1.0.1 github.com/gin-gonic/gin v1.10.0 @@ -18,7 +16,8 @@ require ( github.com/silenceper/wechat/v2 v2.1.7 github.com/spf13/viper v1.19.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.29.0 + golang.org/x/crypto v0.31.0 + golang.org/x/image v0.23.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.12 @@ -36,13 +35,14 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.2 // indirect @@ -54,7 +54,7 @@ require ( github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -78,10 +78,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/image v0.22.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ebbcc16..7f526fd 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -72,8 +70,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -131,8 +129,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= @@ -233,13 +231,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= -golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -248,8 +245,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -270,14 +267,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=