[feature/backend] overall enhancement of image uploading
All checks were successful
Build Backend / Build Docker Image (push) Successful in 5m3s
All checks were successful
Build Backend / Build Docker Image (push) Successful in 5m3s
This commit is contained in:
parent
6e1be3d513
commit
3e6181e578
13 changed files with 740 additions and 314 deletions
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue