2026-04-28 14:53:04 +08:00
|
|
|
|
package handler
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
|
2026-04-22 16:01:59 +08:00
|
|
|
|
"with_you/internal/pkg/response"
|
|
|
|
|
|
"with_you/internal/service"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// UploadHandler 上传处理器
|
|
|
|
|
|
type UploadHandler struct {
|
refactor(server): decouple services and improve architecture
- 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.
2026-06-15 03:41:59 +08:00
|
|
|
|
uploadService service.UploadService
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewUploadHandler 创建上传处理器
|
refactor(server): decouple services and improve architecture
- 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.
2026-06-15 03:41:59 +08:00
|
|
|
|
func NewUploadHandler(uploadService service.UploadService) *UploadHandler {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return &UploadHandler{
|
|
|
|
|
|
uploadService: uploadService,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UploadImage 上传图片
|
|
|
|
|
|
func (h *UploadHandler) UploadImage(c *gin.Context) {
|
|
|
|
|
|
userID := c.GetString("user_id")
|
|
|
|
|
|
if userID == "" {
|
|
|
|
|
|
response.Unauthorized(c, "")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file, err := c.FormFile("image")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.BadRequest(c, "image file is required")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 02:25:10 +08:00
|
|
|
|
url, previewURL, previewURLLarge, err := h.uploadService.UploadImage(c.Request.Context(), file)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.InternalServerError(c, "failed to upload image")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 02:25:10 +08:00
|
|
|
|
response.Success(c, gin.H{
|
|
|
|
|
|
"url": url,
|
|
|
|
|
|
"preview_url": previewURL,
|
|
|
|
|
|
"preview_url_large": previewURLLarge,
|
|
|
|
|
|
})
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UploadAvatar 上传头像
|
|
|
|
|
|
func (h *UploadHandler) UploadAvatar(c *gin.Context) {
|
|
|
|
|
|
userID := c.GetString("user_id")
|
|
|
|
|
|
if userID == "" {
|
|
|
|
|
|
response.Unauthorized(c, "")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file, err := c.FormFile("image")
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.BadRequest(c, "avatar file is required")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
url, err := h.uploadService.UploadAvatar(c.Request.Context(), userID, file)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.InternalServerError(c, "failed to upload avatar")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
response.Success(c, gin.H{"url": url})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UploadCover 上传头图(个人主页封面)
|
|
|
|
|
|
func (h *UploadHandler) UploadCover(c *gin.Context) {
|
|
|
|
|
|
userID := c.GetString("user_id")
|
|
|
|
|
|
if userID == "" {
|
|
|
|
|
|
response.Unauthorized(c, "")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file, err := c.FormFile("image")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.BadRequest(c, "image file is required")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
url, err := h.uploadService.UploadCover(c.Request.Context(), userID, file)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.InternalServerError(c, "failed to upload cover")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
response.Success(c, gin.H{"url": url})
|
|
|
|
|
|
}
|
2026-06-17 20:41:55 +08:00
|
|
|
|
|
|
|
|
|
|
// UploadFile 上传聊天/通用文件(非图片类资源)
|
|
|
|
|
|
// form 字段:file(必填)、folder(可选,默认 chat)
|
|
|
|
|
|
func (h *UploadHandler) UploadFile(c *gin.Context) {
|
|
|
|
|
|
userID := c.GetString("user_id")
|
|
|
|
|
|
if userID == "" {
|
|
|
|
|
|
response.Unauthorized(c, "")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file, err := c.FormFile("file")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
response.BadRequest(c, "file is required")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
folder := c.DefaultPostForm("folder", "chat")
|
2026-07-02 23:22:24 +08:00
|
|
|
|
// 前端(尤其原生端 expo-file-system)无法自定义 multipart filename,
|
|
|
|
|
|
// 文件常被复制到缓存目录并以 UUID 命名,导致 header Filename 丢失真实名。
|
|
|
|
|
|
// 因此前端额外把真实文件名通过 "name" 字段回传,作为权威展示名。
|
|
|
|
|
|
displayName := c.PostForm("name")
|
2026-06-17 20:41:55 +08:00
|
|
|
|
|
2026-07-02 23:22:24 +08:00
|
|
|
|
result, err := h.uploadService.UploadFile(c.Request.Context(), file, folder, userID, displayName)
|
2026-06-17 20:41:55 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 参数类错误(大小/类型)返回 400,其余返回 500
|
|
|
|
|
|
if file.Size > 0 && file.Size <= 100<<20 {
|
|
|
|
|
|
response.BadRequest(c, err.Error())
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
response.InternalServerError(c, "failed to upload file")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
response.Success(c, gin.H{
|
|
|
|
|
|
"url": result.URL,
|
|
|
|
|
|
"name": result.Name,
|
|
|
|
|
|
"size": result.Size,
|
|
|
|
|
|
"mime_type": result.MimeType,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|