Files
toutoukan/controllers/article/votearticle.go
2025-09-24 01:07:21 +08:00

173 lines
5.0 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 article
import (
"fmt"
"net/http"
"time"
"toutoukan/init/databaseInit"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type VoteArticleRequest struct {
Uid string `json:"uid" binding:"required,min=1,max=50"` // 用户ID非空且长度限制
ArticleID int64 `json:"articleId" binding:"required,min=1"` // 文评ID非空且为正整数
OptionID int64 `json:"optionId" binding:"required,min=1"` // 选项ID投票必需
}
func VoteArticle(c *gin.Context) {
var req VoteArticleRequest
// 1. 解析并验证请求参数
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "参数解析失败",
"detail": err.Error(),
})
return
}
// 2. 开启数据库事务确保数据一致性
tx := databaseInit.UserDB.Begin()
if tx.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "开启事务失败: " + tx.Error.Error(),
})
return
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 3. 检查用户是否已投票(防重复投票)
var existingVote struct{ ID int64 }
voteCheckErr := tx.Table("user_votes").
Where("user_id = ? AND vote_article_id = ?", req.Uid, req.ArticleID).
First(&existingVote).Error
if voteCheckErr == nil {
// 已存在投票记录
tx.Rollback()
c.JSON(http.StatusForbidden, gin.H{
"error": "您已对该文评投过票,无法重复投票",
})
return
} else if voteCheckErr != gorm.ErrRecordNotFound {
// 其他查询错误
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{
"error": "检查投票记录失败: " + voteCheckErr.Error(),
})
return
}
var articleStatus struct {
IsEnded bool `gorm:"column:is_ended"`
EndTime time.Time `gorm:"column:end_time"` // 新增:获取截止时间
}
if err := tx.Table("article_list").
Where("articleId = ?", req.ArticleID).
First(&articleStatus).Error; err != nil {
tx.Rollback()
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "文评不存在"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询文评失败: " + err.Error()})
}
return
}
// 双重检查:已标记结束 或 已过截止时间,均禁止投票
now := time.Now()
if articleStatus.IsEnded || articleStatus.EndTime.Before(now) {
tx.Rollback()
// 区分提示信息(可选,增强用户体验)
var errMsg string
if articleStatus.IsEnded {
errMsg = "该文评投票已结束,无法投票"
} else {
errMsg = "该文评投票已过截止时间,无法投票"
}
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
return
}
// 额外优化如果已过截止时间但is_ended未更新主动更新状态可选
if !articleStatus.IsEnded && articleStatus.EndTime.Before(now) {
if err := tx.Table("article_list").
Where("articleId = ?", req.ArticleID).
Update("is_ended", true).Error; err != nil {
// 此处不回滚,仅记录警告,不影响主要逻辑
fmt.Printf("警告更新过期文评状态失败articleId=%d, err=%v\n", req.ArticleID, err)
}
}
// 5. 检查选项是否属于该文评
var optionCheck struct{ ID int64 }
if err := tx.Table("article_options").
Where("id = ? AND vote_article_id = ?", req.OptionID, req.ArticleID).
First(&optionCheck).Error; err != nil {
tx.Rollback()
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "选项不存在或不属于该文评"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询选项失败: " + err.Error()})
}
return
}
// 6. 执行投票事务(三表操作)
// 6.1 记录用户投票记录
if err := tx.Table("user_votes").Create(map[string]interface{}{
"user_id": req.Uid,
"vote_article_id": req.ArticleID,
"option_id": req.OptionID,
"vote_time": time.Now(),
}).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "记录投票失败: " + err.Error()})
return
}
// 6.2 更新选项票数
if err := tx.Table("article_options").
Where("id = ?", req.OptionID).
Update("option_votes_num", gorm.Expr("option_votes_num + 1")).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新选项票数失败: " + err.Error()})
return
}
// 6.3 更新文评总投票人数
if err := tx.Table("article_list").
Where("articleId = ?", req.ArticleID).
Update("total_voters_num", gorm.Expr("total_voters_num + 1")).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新总投票人数失败: " + err.Error()})
return
}
// 7. 提交事务
if err := tx.Commit().Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "提交投票失败: " + err.Error()})
return
}
// 8. 返回成功响应
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "投票成功",
"data": gin.H{
"articleId": req.ArticleID,
"optionId": req.OptionID,
"voteTime": time.Now().Format("2006-01-02 15:04:05"),
},
})
}