refactor(server): decouple services and improve architecture
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s

- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
This commit is contained in:
2026-06-15 03:41:59 +08:00
parent 9951043034
commit d9aa4b46c3
78 changed files with 2156 additions and 1640 deletions

View File

@@ -16,6 +16,7 @@ import (
"path/filepath"
"strings"
"with_you/internal/model"
"with_you/internal/pkg/s3"
"github.com/disintegration/imaging"
@@ -25,8 +26,20 @@ import (
_ "golang.org/x/image/tiff"
)
// UploadService 上传服务
type UploadService struct {
// UploadService 上传服务接口
type UploadService interface {
UploadImage(ctx context.Context, file *multipart.FileHeader) (string, string, string, error)
UploadImageBytes(ctx context.Context, raw []byte, filename string) (url, previewURL, previewURLLarge string, err error)
UploadAvatar(ctx context.Context, userID string, file *multipart.FileHeader) (string, error)
UploadCover(ctx context.Context, userID string, file *multipart.FileHeader) (string, error)
GetURL(ctx context.Context, objectName string) (string, error)
Delete(ctx context.Context, objectName string) error
GeneratePreviewImages(ctx context.Context, originalData []byte, hash string) (previewURL, previewURLLarge string, err error)
ValidateChatMessageImageSegments(segments model.MessageSegments) error
}
// uploadService 上传服务实现
type uploadService struct {
s3Client *s3.Client
userService UserService
}
@@ -39,15 +52,15 @@ const (
)
// NewUploadService 创建上传服务
func NewUploadService(s3Client *s3.Client, userService UserService) *UploadService {
return &UploadService{
func NewUploadService(s3Client *s3.Client, userService UserService) UploadService {
return &uploadService{
s3Client: s3Client,
userService: userService,
}
}
// UploadImage 上传图片返回原图URL和预览图URL
func (s *UploadService) UploadImage(ctx context.Context, file *multipart.FileHeader) (string, string, string, error) {
func (s *uploadService) UploadImage(ctx context.Context, file *multipart.FileHeader) (string, string, string, error) {
processedData, contentType, ext, err := prepareImageForUpload(file)
if err != nil {
return "", "", "", err
@@ -78,7 +91,7 @@ func (s *UploadService) UploadImage(ctx context.Context, file *multipart.FileHea
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) {
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")
}
@@ -136,7 +149,7 @@ func getExtFromContentType(contentType string) string {
}
// UploadAvatar 上传头像
func (s *UploadService) UploadAvatar(ctx context.Context, userID string, file *multipart.FileHeader) (string, error) {
func (s *uploadService) UploadAvatar(ctx context.Context, userID string, file *multipart.FileHeader) (string, error) {
processedData, contentType, ext, err := prepareImageForUpload(file)
if err != nil {
return "", err
@@ -170,7 +183,7 @@ func (s *UploadService) UploadAvatar(ctx context.Context, userID string, file *m
}
// UploadCover 上传头图(个人主页封面)
func (s *UploadService) UploadCover(ctx context.Context, userID string, file *multipart.FileHeader) (string, error) {
func (s *uploadService) UploadCover(ctx context.Context, userID string, file *multipart.FileHeader) (string, error) {
processedData, contentType, ext, err := prepareImageForUpload(file)
if err != nil {
return "", err
@@ -204,12 +217,12 @@ func (s *UploadService) UploadCover(ctx context.Context, userID string, file *mu
}
// GetURL 获取文件URL
func (s *UploadService) GetURL(ctx context.Context, objectName string) (string, error) {
func (s *uploadService) GetURL(ctx context.Context, objectName string) (string, error) {
return s.s3Client.GetURL(ctx, objectName)
}
// Delete 删除文件
func (s *UploadService) Delete(ctx context.Context, objectName string) error {
func (s *uploadService) Delete(ctx context.Context, objectName string) error {
return s.s3Client.Delete(ctx, objectName)
}
@@ -313,7 +326,7 @@ func normalizeImageContentType(contentType string) string {
}
// GeneratePreviewImages 生成预览图
func (s *UploadService) GeneratePreviewImages(ctx context.Context, originalData []byte, hash string) (previewURL, previewURLLarge string, err error) {
func (s *uploadService) GeneratePreviewImages(ctx context.Context, originalData []byte, hash string) (previewURL, previewURLLarge string, err error) {
img, _, err := image.Decode(bytes.NewReader(originalData))
if err != nil {
return "", "", fmt.Errorf("failed to decode image: %w", err)
@@ -343,7 +356,7 @@ func (s *UploadService) GeneratePreviewImages(ctx context.Context, originalData
}
// generatePreview 生成单个预览图
func (s *UploadService) generatePreview(ctx context.Context, img image.Image, originalWidth, originalHeight int, mode string, maxDim int, hash string) (string, error) {
func (s *uploadService) generatePreview(ctx context.Context, img image.Image, originalWidth, originalHeight int, mode string, maxDim int, hash string) (string, error) {
var targetWidth, targetHeight int
// 计算目标尺寸