Add FileCleanupWorker for automatic expiration of chat files with configurable retention period and batch processing. Files uploaded via `/api/v1/uploads/files` are tracked in `uploaded_files` table with expiration timestamps, then deleted from S3 and database upon expiry. Expired file URLs are injected as `"expired": true` in message responses. Extend JPush push notification configuration with vendor-specific channel_id support (xiaomi, huawei, oppo, vivo, meizu, honor, fcm) for differentiating system vs chat notifications. Add iOS APNs thread-id grouping configuration for notification categorization.
131 lines
2.9 KiB
Go
131 lines
2.9 KiB
Go
package handler
|
||
|
||
import (
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"with_you/internal/pkg/response"
|
||
"with_you/internal/service"
|
||
)
|
||
|
||
// UploadHandler 上传处理器
|
||
type UploadHandler struct {
|
||
uploadService service.UploadService
|
||
}
|
||
|
||
// NewUploadHandler 创建上传处理器
|
||
func NewUploadHandler(uploadService service.UploadService) *UploadHandler {
|
||
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
|
||
}
|
||
|
||
url, previewURL, previewURLLarge, err := h.uploadService.UploadImage(c.Request.Context(), file)
|
||
if err != nil {
|
||
response.InternalServerError(c, "failed to upload image")
|
||
return
|
||
}
|
||
|
||
response.Success(c, gin.H{
|
||
"url": url,
|
||
"preview_url": previewURL,
|
||
"preview_url_large": previewURLLarge,
|
||
})
|
||
}
|
||
|
||
// 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})
|
||
}
|
||
|
||
// 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")
|
||
|
||
result, err := h.uploadService.UploadFile(c.Request.Context(), file, folder, userID)
|
||
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,
|
||
})
|
||
}
|