feat: 类型帖互动、积分经济与发帖体验
补齐问答采纳/重开、投票匿名与有票禁编、悬赏过期退回、抽奖回帖开奖,并接入积分账本与发帖附件可见性。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,6 +22,8 @@ type Handlers struct {
|
||||
Checkin *service.CheckinService
|
||||
Announcement *service.AnnouncementService
|
||||
Upload *service.UploadService
|
||||
PostFile *service.PostFileService
|
||||
Points *service.PointsService
|
||||
Setting *service.SettingService
|
||||
AdminUser *service.AdminUserService
|
||||
Moderation *service.ModerationService
|
||||
|
||||
@@ -2,7 +2,10 @@ package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/middleware"
|
||||
@@ -43,7 +46,6 @@ func (h *Handlers) Posts(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
// 填充点赞状态(仅登录用户)
|
||||
if claims := middleware.CurrentUser(c); claims != nil {
|
||||
ids := make([]uint, 0, len(items))
|
||||
for _, p := range items {
|
||||
@@ -74,14 +76,13 @@ func (h *Handlers) PostDetail(c *gin.Context) {
|
||||
var loadActor func() *service.Actor
|
||||
if claims != nil {
|
||||
viewerID = claims.ID
|
||||
loadActor = h.actorLoader(claims.ID) // 懒加载:仅非已发布帖才查 DB
|
||||
loadActor = h.actorLoader(claims.ID)
|
||||
}
|
||||
post, err := h.Post.GetByIDForViewer(uint(id), viewerID, loadActor)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
|
||||
return
|
||||
}
|
||||
// 填充点赞状态(仅登录用户)
|
||||
if claims != nil {
|
||||
post.Liked = h.Like.HasLiked(post.ID, claims.ID)
|
||||
}
|
||||
@@ -90,11 +91,15 @@ func (h *Handlers) PostDetail(c *gin.Context) {
|
||||
|
||||
// CreatePostRequest 发帖请求
|
||||
type CreatePostRequest struct {
|
||||
BoardID uint `json:"board_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required,min=1,max=256"`
|
||||
Content string `json:"content" binding:"required,min=1"`
|
||||
Tags string `json:"tags"`
|
||||
PostType string `json:"post_type"`
|
||||
BoardID uint `json:"board_id" binding:"required"`
|
||||
Title string `json:"title" binding:"required,min=1,max=256"`
|
||||
Content string `json:"content" binding:"required,min=1"`
|
||||
Tags string `json:"tags"`
|
||||
PostType string `json:"post_type"`
|
||||
ContentAccess string `json:"content_access"`
|
||||
AccessPoints int `json:"access_points"`
|
||||
TypeMeta string `json:"type_meta"`
|
||||
AttachmentIDs []uint `json:"attachment_ids"`
|
||||
}
|
||||
|
||||
// CreatePost 创建帖子
|
||||
@@ -105,17 +110,23 @@ func (h *Handlers) CreatePost(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
postType := req.PostType
|
||||
if postType == "" {
|
||||
postType = "normal"
|
||||
}
|
||||
// 管理团队成员发帖免审直发;普通用户进入待审核队列。
|
||||
// 角色变更会强制 JWT 失效(token_version 递增),claims.Role 可视为实时值
|
||||
status := model.ContentStatusPending
|
||||
if model.IsStaff(model.Role(claims.Role)) {
|
||||
status = model.ContentStatusPublished
|
||||
}
|
||||
post, err := h.Post.Create(claims.ID, req.BoardID, req.Title, req.Content, req.Tags, postType, status)
|
||||
post, err := h.Post.Create(service.CreatePostInput{
|
||||
UserID: claims.ID,
|
||||
BoardID: req.BoardID,
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Tags: req.Tags,
|
||||
PostType: req.PostType,
|
||||
ContentAccess: req.ContentAccess,
|
||||
AccessPoints: req.AccessPoints,
|
||||
TypeMeta: req.TypeMeta,
|
||||
Status: status,
|
||||
AttachmentIDs: req.AttachmentIDs,
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -130,9 +141,13 @@ func (h *Handlers) CreatePost(c *gin.Context) {
|
||||
|
||||
// UpdatePostRequest 更新帖子请求
|
||||
type UpdatePostRequest struct {
|
||||
Title string `json:"title" binding:"omitempty,min=1,max=256"`
|
||||
Content string `json:"content" binding:"omitempty,min=1"`
|
||||
Tags string `json:"tags"`
|
||||
Title string `json:"title" binding:"omitempty,min=1,max=256"`
|
||||
Content string `json:"content" binding:"omitempty,min=1"`
|
||||
Tags string `json:"tags"`
|
||||
ContentAccess *string `json:"content_access"`
|
||||
AccessPoints *int `json:"access_points"`
|
||||
TypeMeta *string `json:"type_meta"`
|
||||
AttachmentIDs *[]uint `json:"attachment_ids"`
|
||||
}
|
||||
|
||||
// UpdatePost 编辑帖子
|
||||
@@ -149,7 +164,15 @@ func (h *Handlers) UpdatePost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
actor := h.loadActor(claims.ID)
|
||||
post, err := h.Post.Update(actor, uint(id), claims.ID, req.Title, req.Content, req.Tags)
|
||||
post, err := h.Post.Update(actor, uint(id), claims.ID, service.UpdatePostInput{
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Tags: req.Tags,
|
||||
ContentAccess: req.ContentAccess,
|
||||
AccessPoints: req.AccessPoints,
|
||||
TypeMeta: req.TypeMeta,
|
||||
AttachmentIDs: req.AttachmentIDs,
|
||||
})
|
||||
if err != nil {
|
||||
respondPostModError(c, err)
|
||||
return
|
||||
@@ -157,6 +180,190 @@ func (h *Handlers) UpdatePost(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// UnlockPostContent 积分解锁正文
|
||||
func (h *Handlers) UnlockPostContent(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
|
||||
}
|
||||
post, err := h.Post.UnlockContent(claims.ID, uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrInsufficientPoints) {
|
||||
c.JSON(http.StatusPaymentRequired, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// GetPointsBalance 当前用户积分余额
|
||||
func (h *Handlers) GetPointsBalance(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
bal, err := h.Points.Balance(claims.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"points": bal})
|
||||
}
|
||||
|
||||
// GetPointsLedger 本人积分流水
|
||||
func (h *Handlers) GetPointsLedger(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||
items, total, err := h.Points.Ledger(claims.ID, page, size)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"items": nonNilSlice(items),
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
})
|
||||
}
|
||||
|
||||
// AdminPointsStats 管理端积分与类型帖看板
|
||||
func (h *Handlers) AdminPointsStats(c *gin.Context) {
|
||||
st, err := h.Points.AdminEconomyStats()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, st)
|
||||
}
|
||||
|
||||
// UploadPostFile 上传帖子附件(草稿态)
|
||||
func (h *Handlers) UploadPostFile(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请选择文件"})
|
||||
return
|
||||
}
|
||||
price, _ := strconv.Atoi(c.DefaultPostForm("price_points", "0"))
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无法读取文件"})
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
data, err := io.ReadAll(io.LimitReader(f, service.FileMaxBytes+1))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "读取文件失败"})
|
||||
return
|
||||
}
|
||||
att, err := h.PostFile.SaveDraftFile(claims.ID, file.Filename, data, price)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"attachment": gin.H{
|
||||
"id": att.ID,
|
||||
"name": att.Name,
|
||||
"size": att.Size,
|
||||
"mime": att.MIME,
|
||||
"price_points": att.PricePoints,
|
||||
"download_count": att.DownloadCount,
|
||||
"unlocked": true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// DeletePostFile 删除本人附件草稿
|
||||
func (h *Handlers) DeletePostFile(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.PostFile.DeleteOwn(claims.ID, uint(id)); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// UpdatePostFilePrice 更新附件积分定价
|
||||
func (h *Handlers) UpdatePostFilePrice(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
|
||||
}
|
||||
var req struct {
|
||||
PricePoints int `json:"price_points"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
if err := h.PostFile.UpdatePrice(claims.ID, uint(id), req.PricePoints); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// DownloadPostAttachment 下载帖子附件(鉴权 + 积分)
|
||||
func (h *Handlers) DownloadPostAttachment(c *gin.Context) {
|
||||
postID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"})
|
||||
return
|
||||
}
|
||||
attID, err := strconv.ParseUint(c.Param("aid"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的附件 ID"})
|
||||
return
|
||||
}
|
||||
claims := middleware.CurrentUser(c)
|
||||
var viewerID uint
|
||||
var loadActor func() *service.Actor
|
||||
if claims != nil {
|
||||
viewerID = claims.ID
|
||||
loadActor = h.actorLoader(claims.ID)
|
||||
}
|
||||
if err := h.Post.EnsurePostVisible(uint(postID), viewerID, loadActor); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
|
||||
return
|
||||
}
|
||||
att, err := h.PostFile.Get(uint(attID))
|
||||
if err != nil || att.PostID != uint(postID) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "附件不存在"})
|
||||
return
|
||||
}
|
||||
if att.PricePoints > 0 {
|
||||
if claims == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||||
return
|
||||
}
|
||||
if err := h.PostFile.EnsureAttachmentUnlocked(claims.ID, att); err != nil {
|
||||
if errors.Is(err, service.ErrInsufficientPoints) {
|
||||
c.JSON(http.StatusPaymentRequired, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
path := h.PostFile.FilePath(att)
|
||||
h.PostFile.IncDownload(att.ID)
|
||||
c.Header("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape(att.Name))
|
||||
c.Header("Content-Type", att.MIME)
|
||||
c.File(path)
|
||||
_ = filepath.Base(path)
|
||||
}
|
||||
|
||||
// DeletePost 删除帖子
|
||||
func (h *Handlers) DeletePost(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
@@ -200,12 +407,12 @@ func (h *Handlers) ToggleRecommend(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"})
|
||||
return
|
||||
}
|
||||
recommended, err := h.Post.ToggleRecommend(uint(id))
|
||||
rec, err := h.Post.ToggleRecommend(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"recommended": recommended})
|
||||
c.JSON(http.StatusOK, gin.H{"recommended": rec})
|
||||
}
|
||||
|
||||
// requireAdminOrAbove 置顶/加精仅管理员及以上可用;校验失败已写响应,返回 false
|
||||
|
||||
197
backend/handler/post_interact.go
Normal file
197
backend/handler/post_interact.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/middleware"
|
||||
"github.com/freefire/jiang13-bbs/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (h *Handlers) respondInteractError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrPostNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrPostForbidden):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrInsufficientPoints):
|
||||
c.JSON(http.StatusPaymentRequired, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrAlreadyVoted),
|
||||
errors.Is(err, service.ErrPollClosed),
|
||||
errors.Is(err, service.ErrPollOptionsLocked),
|
||||
errors.Is(err, service.ErrLotteryDrawn),
|
||||
errors.Is(err, service.ErrLotteryClosed),
|
||||
errors.Is(err, service.ErrBountySettled),
|
||||
errors.Is(err, service.ErrBountyExpired):
|
||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
// VotePoll 投票
|
||||
func (h *Handlers) VotePoll(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
|
||||
}
|
||||
var req struct {
|
||||
Options []int `json:"options"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
post, err := h.Post.VotePoll(claims.ID, uint(id), req.Options)
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// ClosePoll 结束投票
|
||||
func (h *Handlers) ClosePoll(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
|
||||
}
|
||||
post, err := h.Post.ClosePoll(h.loadActor(claims.ID), claims.ID, uint(id))
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// AcceptQuestion 采纳问答答案
|
||||
func (h *Handlers) AcceptQuestion(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
|
||||
}
|
||||
var req struct {
|
||||
CommentID uint `json:"comment_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请指定评论"})
|
||||
return
|
||||
}
|
||||
post, err := h.Post.AcceptQuestion(h.loadActor(claims.ID), claims.ID, uint(id), req.CommentID)
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// SolveQuestion 手动标已解决
|
||||
func (h *Handlers) SolveQuestion(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
|
||||
}
|
||||
post, err := h.Post.SolveQuestion(h.loadActor(claims.ID), claims.ID, uint(id))
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// ReopenQuestion 重新打开问答
|
||||
func (h *Handlers) ReopenQuestion(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
|
||||
}
|
||||
post, err := h.Post.ReopenQuestion(h.loadActor(claims.ID), claims.ID, uint(id))
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// AcceptBounty 采纳评论并发放悬赏
|
||||
func (h *Handlers) AcceptBounty(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
|
||||
}
|
||||
var req struct {
|
||||
CommentID uint `json:"comment_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请指定评论"})
|
||||
return
|
||||
}
|
||||
post, err := h.Post.AcceptBounty(h.loadActor(claims.ID), claims.ID, uint(id), req.CommentID)
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// RefundBounty 退回悬赏
|
||||
func (h *Handlers) RefundBounty(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
|
||||
}
|
||||
post, err := h.Post.RefundBounty(h.loadActor(claims.ID), claims.ID, uint(id))
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// CloseLottery 截止抽奖
|
||||
func (h *Handlers) CloseLottery(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
|
||||
}
|
||||
post, err := h.Post.CloseLotteryEntries(h.loadActor(claims.ID), claims.ID, uint(id))
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
|
||||
// DrawLottery 开奖
|
||||
func (h *Handlers) DrawLottery(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
|
||||
}
|
||||
post, err := h.Post.DrawLottery(h.loadActor(claims.ID), claims.ID, uint(id))
|
||||
if err != nil {
|
||||
h.respondInteractError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"post": post})
|
||||
}
|
||||
@@ -49,6 +49,46 @@ func (h *Handlers) UploadAvatar(c *gin.Context) {
|
||||
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"`
|
||||
|
||||
Reference in New Issue
Block a user