Files
jiang13-bbs/backend/handler/upload.go
freefire b9ba8cb8c9 feat: 类型帖互动、积分经济与发帖体验
补齐问答采纳/重开、投票匿名与有票禁编、悬赏过期退回、抽奖回帖开奖,并接入积分账本与发帖附件可见性。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:17:07 +08:00

141 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"errors"
"io"
"net/http"
"strconv"
"github.com/freefire/jiang13-bbs/middleware"
"github.com/freefire/jiang13-bbs/service"
"github.com/gin-gonic/gin"
)
// UploadAvatar 上传裁剪后的头像(multipart 字段 file,内容必须是 WebP)
func (h *Handlers) UploadAvatar(c *gin.Context) {
claims := middleware.CurrentUser(c)
// 限制请求体:以裁剪后文件为依据,允许少量 multipart 边界开销
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, service.AvatarMaxBytes+4096)
fh, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "图片过大(裁剪后不能超过 2MB)或格式不正确"})
return
}
if fh.Size > service.AvatarMaxBytes {
c.JSON(http.StatusBadRequest, gin.H{"error": "裁剪后的图片不能超过 2MB"})
return
}
f, err := fh.Open()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "读取图片失败"})
return
}
defer f.Close()
// fh.Size 已受 MaxBytesReader 约束;读取时二次防御
data, err := io.ReadAll(io.LimitReader(f, service.AvatarMaxBytes+1))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "读取图片失败"})
return
}
att, err := h.Upload.SaveAvatar(claims.ID, data)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"url": att.URL, "attachment": att})
}
// UploadImage 上传帖子插图(multipart 字段 file:JPEG / PNG / WebP)
func (h *Handlers) UploadImage(c *gin.Context) {
claims := middleware.CurrentUser(c)
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, service.ImageMaxBytes+4096)
fh, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "图片过大(不能超过 5MB)或格式不正确"})
return
}
if fh.Size > service.ImageMaxBytes {
c.JSON(http.StatusBadRequest, gin.H{"error": "图片不能超过 5MB"})
return
}
f, err := fh.Open()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "读取图片失败"})
return
}
defer f.Close()
data, err := io.ReadAll(io.LimitReader(f, service.ImageMaxBytes+1))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "读取图片失败"})
return
}
if len(data) > service.ImageMaxBytes {
c.JSON(http.StatusBadRequest, gin.H{"error": "图片不能超过 5MB"})
return
}
att, err := h.Upload.SaveImage(claims.ID, data)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"url": att.URL, "attachment": att})
}
// UseAvatarRequest 选用历史头像请求
type UseAvatarRequest struct {
URL string `json:"url"`
}
// UseAvatar 选用本人历史上传的头像
func (h *Handlers) UseAvatar(c *gin.Context) {
claims := middleware.CurrentUser(c)
var req UseAvatarRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数格式错误"})
return
}
if err := h.Upload.UseAvatar(claims.ID, req.URL); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"url": req.URL})
}
// MyMedia 媒体库:本人上传的全部图片(头像、帖子插图等)
func (h *Handlers) MyMedia(c *gin.Context) {
claims := middleware.CurrentUser(c)
list, err := h.Upload.ListMedia(claims.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"attachments": nonNilSlice(list)})
}
// DeleteAttachment 彻底删除本人附件(DB + 物理文件)
func (h *Handlers) DeleteAttachment(c *gin.Context) {
claims := middleware.CurrentUser(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的附件 ID"})
return
}
if err := h.Upload.DeleteAttachment(claims.ID, uint(id)); err != nil {
if errors.Is(err, service.ErrAttachmentInUse) {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}