feat(upload): integrate upload service for chat image validation and S3 uploads
All checks were successful
Build Backend / build (push) Successful in 13m35s
Build Backend / build-docker (push) Successful in 1m11s

- Added UploadService to handle image uploads and validation for chat messages.
- Updated ChatService and MessageService to utilize UploadService for validating image segments in messages.
- Enhanced wire generation to inject UploadService into relevant services.
- Introduced UploadImageBytes method in UploadService for uploading images directly from memory.
- Added TrustedPublicURLPrefix method in S3 Client for generating public URLs for uploaded images.
This commit is contained in:
lafay
2026-03-25 03:57:40 +08:00
parent a887e8ea23
commit 28a45caad3
7 changed files with 150 additions and 13 deletions

View File

@@ -75,6 +75,41 @@ func (s *UploadService) UploadImage(ctx context.Context, file *multipart.FileHea
return url, previewURL, previewURLLarge, nil
}
const maxUploadImageBytesFromBuffer = 15 << 20 // 与聊天内联解码上限一致,略大于 upload.max_file_size 时可接受
// UploadImageBytes 从内存字节上传图片(与 multipart 上传走同一套压缩与存储逻辑)。
func (s *UploadService) UploadImageBytes(ctx context.Context, raw []byte, filename string) (url, previewURL, previewURLLarge string, err error) {
if len(raw) == 0 {
return "", "", "", fmt.Errorf("empty image data")
}
if len(raw) > maxUploadImageBytesFromBuffer {
return "", "", "", fmt.Errorf("image data too large")
}
detectedType := normalizeImageContentType(http.DetectContentType(raw))
compressedData, compressedType, cerr := compressImageData(raw, detectedType)
if cerr != nil {
compressedData = raw
compressedType = detectedType
}
compressedType = cmp.Or(compressedType, http.DetectContentType(compressedData))
ext := cmp.Or(getExtFromContentType(compressedType), strings.ToLower(filepath.Ext(filename)), ".jpg")
hash := sha256.Sum256(compressedData)
hashStr := fmt.Sprintf("%x", hash)
objectName := fmt.Sprintf("images/%s%s", hashStr, ext)
url, err = s.s3Client.UploadData(ctx, objectName, compressedData, compressedType)
if err != nil {
return "", "", "", fmt.Errorf("failed to upload to S3: %w", err)
}
previewURL, previewURLLarge, prevErr := s.GeneratePreviewImages(ctx, compressedData, hashStr)
if prevErr != nil {
zap.L().Warn("UploadImageBytes: preview generation failed", zap.Error(prevErr))
}
return url, previewURL, previewURLLarge, nil
}
// getExtFromContentType 根据Content-Type获取文件扩展名
func getExtFromContentType(contentType string) string {
baseType, _, err := mime.ParseMediaType(contentType)