[feature/backend] overall enhancement of image uploading
All checks were successful
Build Backend / Build Docker Image (push) Successful in 5m3s

This commit is contained in:
CDN 2025-02-23 04:42:48 +08:00
parent 6e1be3d513
commit 3e6181e578
Signed by: CDN
GPG key ID: 0C656827F9F80080
13 changed files with 740 additions and 314 deletions

View file

@ -1,11 +1,14 @@
package service
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
@ -26,6 +29,8 @@ import (
"tss-rocks-be/ent/user"
"tss-rocks-be/internal/storage"
"github.com/chai2010/webp"
"github.com/disintegration/imaging"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
@ -419,21 +424,71 @@ func (s *serviceImpl) Upload(ctx context.Context, file *multipart.FileHeader, us
// Open the uploaded file
src, err := openFile(file)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer src.Close()
// 获取文件类型和扩展名
contentType := file.Header.Get("Content-Type")
ext := strings.ToLower(filepath.Ext(file.Filename))
if contentType == "" {
// 如果 Content-Type 为空,尝试从文件扩展名判断
switch ext {
case ".jpg", ".jpeg":
contentType = "image/jpeg"
case ".png":
contentType = "image/png"
case ".gif":
contentType = "image/gif"
case ".webp":
contentType = "image/webp"
case ".mp4":
contentType = "video/mp4"
case ".webm":
contentType = "video/webm"
case ".mp3":
contentType = "audio/mpeg"
case ".ogg":
contentType = "audio/ogg"
case ".wav":
contentType = "audio/wav"
case ".pdf":
contentType = "application/pdf"
case ".doc":
contentType = "application/msword"
case ".docx":
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
}
}
// 如果是图片,检查是否需要转换为 WebP
var fileToSave multipart.File = src
var finalContentType = contentType
if strings.HasPrefix(contentType, "image/") && contentType != "image/webp" {
// 转换为 WebP
webpFile, err := convertToWebP(src)
if err != nil {
return nil, fmt.Errorf("failed to convert image to WebP: %v", err)
}
fileToSave = webpFile
finalContentType = "image/webp"
ext = ".webp"
}
// 生成带扩展名的存储文件名
storageFilename := uuid.New().String() + ext
// Save the file to storage
fileInfo, err := s.storage.Save(ctx, file.Filename, file.Header.Get("Content-Type"), src)
fileInfo, err := s.storage.Save(ctx, storageFilename, finalContentType, fileToSave)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to save file: %v", err)
}
// Create media record
return s.client.Media.Create().
SetStorageID(fileInfo.ID).
SetOriginalName(file.Filename).
SetMimeType(fileInfo.ContentType).
SetMimeType(finalContentType).
SetSize(fileInfo.Size).
SetURL(fileInfo.URL).
SetCreatedBy(strconv.Itoa(userID)).
@ -444,13 +499,8 @@ func (s *serviceImpl) GetMedia(ctx context.Context, id int) (*ent.Media, error)
return s.client.Media.Get(ctx, id)
}
func (s *serviceImpl) GetFile(ctx context.Context, id int) (io.ReadCloser, *storage.FileInfo, error) {
media, err := s.GetMedia(ctx, id)
if err != nil {
return nil, nil, err
}
return s.storage.Get(ctx, media.StorageID)
func (s *serviceImpl) GetFile(ctx context.Context, storageID string) (io.ReadCloser, *storage.FileInfo, error) {
return s.storage.Get(ctx, storageID)
}
func (s *serviceImpl) DeleteMedia(ctx context.Context, id int, userID int) error {
@ -1065,3 +1115,47 @@ func (s *serviceImpl) DeleteDaily(ctx context.Context, id string, currentUserID
return s.client.Daily.DeleteOneID(id).Exec(ctx)
}
// convertToWebP 将图片转换为 WebP 格式
func convertToWebP(src multipart.File) (multipart.File, error) {
// 读取原始图片
img, err := imaging.Decode(src)
if err != nil {
return nil, fmt.Errorf("failed to decode image: %v", err)
}
// 创建一个新的缓冲区来存储 WebP 图片
buf := new(bytes.Buffer)
// 将图片编码为 WebP 格式
// 设置较高的质量以保持图片质量
err = webp.Encode(buf, img, &webp.Options{
Lossless: false,
Quality: 90,
})
if err != nil {
return nil, fmt.Errorf("failed to encode image to WebP: %v", err)
}
// 创建一个新的临时文件来存储转换后的图片
tmpFile, err := os.CreateTemp("", "webp-*.webp")
if err != nil {
return nil, fmt.Errorf("failed to create temp file: %v", err)
}
// 写入转换后的数据
if _, err := io.Copy(tmpFile, buf); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
return nil, fmt.Errorf("failed to write WebP data: %v", err)
}
// 将文件指针移回开始位置
if _, err := tmpFile.Seek(0, 0); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
return nil, fmt.Errorf("failed to seek file: %v", err)
}
return tmpFile, nil
}