feat(worker): add image conversion functionality with Magick and support for additional formats

This commit is contained in:
keven1024
2026-02-27 11:23:48 +08:00
parent 3d8d1ccd3f
commit 22637bcf6e
4 changed files with 88 additions and 3 deletions

View File

@@ -3,8 +3,20 @@ package services
import (
"errors"
"worker/internal/utils"
"github.com/samber/lo"
)
var (
ErrUnsupportedMimeType = errors.New("UnsupportedMimeType")
)
// 支持的图片扩展名列表
var supportedImageExts = []string{
".jpg", ".jpeg", ".png", ".gif", ".webp",
// ".bmp", ".tiff", ".tif", ".svg", ".ico",
}
func CompressImage(filePath string, mimeType string) (string, error) {
compressedPath := filePath + "_compressed"
switch mimeType {
@@ -23,7 +35,23 @@ func CompressImage(filePath string, mimeType string) (string, error) {
return "", err
}
default:
return "", errors.New("不支持的文件类型")
return "", ErrUnsupportedMimeType
}
return compressedPath, nil
}
func ConvertImageWithMagick(filePath, mimeType, targetExt string) (string, error) {
// 验证目标扩展名是否合法
if !lo.Contains(supportedImageExts, targetExt) {
return "", ErrUnsupportedMimeType
}
outputPath := filePath + "_converted" + targetExt
_, err := utils.RunCommand("magick", filePath, outputPath)
if err != nil {
return "", err
}
return outputPath, nil
}

View File

@@ -21,9 +21,19 @@ func RemoveFile(ctx context.Context, task *asynq.Task) error {
if err != nil {
return err
}
if fileInfo == nil || fileInfo.FileType == models.FileTypeUpload {
if fileInfo == nil {
return nil
}
// 如果文件是上传文件,则需要检查是否还有分享,考虑到比如文件转换这些一次性任务产生的文件需要销毁
if fileInfo.FileType == models.FileTypeUpload {
shareIDs, err := models.GetRedisFileShareRelational(payload.FileId)
if err != nil {
return err
}
if len(shareIDs) > 0 {
return nil
}
}
rdb, rctx := u.GetRedisClient()
uploadPath, err := utils.GetUploadDirPath()

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"mime"
"path/filepath"
"pkg/models"
"worker/internal/services"
@@ -19,7 +20,7 @@ func CompressImage(ctx context.Context, task *asynq.Task) error {
}
originalFileInfo, _ := models.GetRedisFileInfo(payload.FileId)
if originalFileInfo == nil || originalFileInfo.FileType != models.FileTypeUpload {
return errors.New("文件不存在")
return ErrNotFoundFile
}
uploadPath, err := utils.GetUploadDirPath()
if err != nil {

View File

@@ -64,3 +64,49 @@ func TestCompressJPEGHappyPath(t *testing.T) {
assert.NotEqual(t, origInfo.Size(), compInfo.Size())
fmt.Printf("原图: %d | 压缩后: %d | 压缩率: %f%%\n", origInfo.Size(), compInfo.Size(), float64(compInfo.Size())/float64(origInfo.Size())*100)
}
func TestConvertImageWithMagickPNGToJPG(t *testing.T) {
tmp := t.TempDir()
filePath := filepath.Join(tmp, "test.png")
// 从 test/resource 复制真实 PNG
_, self, _, _ := runtime.Caller(0)
srcPath := filepath.Join(filepath.Dir(self), "..", "resource", "test.png")
err := utils.CopyFile(srcPath, filePath)
if err != nil {
t.Fatal(err)
}
// 测试 PNG 转 JPEG
got, err := services.ConvertImageWithMagick(filePath, "image/png", ".jpg")
if err != nil {
t.Fatal(err)
}
// 验证输出文件路径
assert.Equal(t, got, filePath+"_converted.jpg")
// 验证输出文件存在
info, err := os.Stat(got)
if err != nil {
t.Fatal(err)
}
assert.True(t, info.Size() > 0, "转换后的文件应该有内容")
fmt.Printf("PNG 转 JPEG 成功: %s (大小: %d bytes)\n", got, info.Size())
}
func TestConvertImageWithMagickInvalidExt(t *testing.T) {
tmp := t.TempDir()
filePath := filepath.Join(tmp, "test.png")
_, self, _, _ := runtime.Caller(0)
srcPath := filepath.Join(filepath.Dir(self), "..", "resource", "test.png")
err := utils.CopyFile(srcPath, filePath)
if err != nil {
t.Fatal(err)
}
// 测试非法扩展名(防止注入)
_, err = services.ConvertImageWithMagick(filePath, "image/png", ".exe")
assert.Error(t, err, "应该返回错误")
assert.Equal(t, services.ErrInvalidImageExt, err, "应该返回 ErrInvalidImageExt 错误")
}