优化首页分页和图片上传

This commit is contained in:
yml
2026-06-02 13:24:04 +08:00
parent a094901b72
commit 6f4c8b59f9
23 changed files with 1232 additions and 275 deletions
+61 -12
View File
@@ -1,9 +1,12 @@
package file
import (
"bytes"
"context"
"errors"
"io"
"mime/multipart"
"net/http"
"net/url"
"strings"
)
@@ -34,32 +37,78 @@ func (s *Service) Upload(req uploadRequest) (*UploadDTO, error) {
if s.storage == nil {
return nil, ErrDependencyUnavailable
}
if req.Header == nil || req.Reader == nil || req.Header.Size <= 0 || req.Header.Size > maxUploadSize {
if req.Header == nil || req.Reader == nil || req.Header.Size <= 0 {
return nil, ErrInvalidFile
}
contentType := req.ContentType
if contentType == "" {
contentType = req.Header.Header.Get("Content-Type")
data, err := readUploadData(req.Reader)
if err != nil || len(data) == 0 || len(data) > maxUploadSize {
return nil, ErrInvalidFile
}
contentType := normalizeContentType(req.ContentType, data)
scene := normalizeScene(req.Scene)
if !allowedContentTypes[contentType] || (scene == "chat" && !strings.HasPrefix(contentType, "image/")) {
return nil, ErrInvalidFile
}
key, err := s.storage.Put(req.Context, scene, req.Header, req.Reader, contentType)
key, err := newObjectKey(scene, req.Header.Filename)
if err != nil {
return nil, err
}
if err := s.storage.PutObject(req.Context, key, bytes.NewReader(data), int64(len(data)), contentType, map[string]string{
"original-filename": req.Header.Filename,
}); err != nil {
return nil, err
}
var thumbnailURL string
var mediumURL string
for _, variant := range generateImageVariants(key, data, contentType) {
err := s.storage.PutObject(req.Context, variant.Key, bytes.NewReader(variant.Content), int64(len(variant.Content)), variant.ContentType, map[string]string{
"source-object": key,
})
if err != nil {
continue
}
if strings.Contains(variant.Key, "."+ImageVariantThumb+".") {
thumbnailURL = fileURLForScene(scene, variant.Key)
}
if strings.Contains(variant.Key, "."+ImageVariantMedium+".") {
mediumURL = fileURLForScene(scene, variant.Key)
}
}
fileURL := fileURLForScene(scene, key)
return &UploadDTO{
ObjectKey: key,
URL: fileURL,
ThumbnailURL: thumbnailURL,
MediumURL: mediumURL,
Filename: req.Header.Filename,
ContentType: contentType,
Size: int64(len(data)),
}, nil
}
func readUploadData(reader multipart.File) ([]byte, error) {
limited := io.LimitReader(reader, maxUploadSize+1)
return io.ReadAll(limited)
}
func normalizeContentType(contentType string, data []byte) string {
contentType = strings.ToLower(strings.TrimSpace(strings.Split(contentType, ";")[0]))
if allowedContentTypes[contentType] {
return contentType
}
detected := strings.ToLower(strings.TrimSpace(strings.Split(http.DetectContentType(data), ";")[0]))
if allowedContentTypes[detected] {
return detected
}
return contentType
}
func fileURLForScene(scene string, key string) string {
fileURL := "/api/files/object?key=" + url.QueryEscape(key)
if scene == "home-banner" || scene == "avatar" {
fileURL = "/api/public/files/object?key=" + url.QueryEscape(key)
}
return &UploadDTO{
ObjectKey: key,
URL: fileURL,
Filename: req.Header.Filename,
ContentType: contentType,
Size: req.Header.Size,
}, nil
return fileURL
}
func normalizeScene(scene string) string {