Files
toutoukan/controllers/article/votearticle.go
2025-09-24 15:34:09 +08:00

193 lines
5.6 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 (
"net/http"
"time"
"toutoukan/init/databaseInit"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// 投票请求结构体,同时支持单选(optionId)和多选(optionIds)
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:"omitempty,min=1"` // 单个选项ID单选时使用
OptionIDs []int64 `json:"optionIds" binding:"omitempty,dive,min=1"` // 多个选项ID多选时使用
}
// 用户投票记录表结构
type UserVote struct {
UserID string `gorm:"column:user_id"`
VoteArticleID int64 `gorm:"column:vote_article_id"`
OptionID int64 `gorm:"column:option_id"`
VoteTime time.Time `gorm:"column:vote_time"`
}
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 articleStatus struct {
IsEnded bool `gorm:"column:is_ended"`
EndTime time.Time `gorm:"column:end_time"`
VoteType string `gorm:"column:vote_type"` // 投票类型single-单选multiple-多选
}
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
}
// 3.1 检查投票是否已结束
now := time.Now()
if articleStatus.IsEnded || articleStatus.EndTime.Before(now) {
tx.Rollback()
c.JSON(http.StatusForbidden, gin.H{"error": "该文评投票已结束或已过截止时间,无法投票"})
return
}
// 4. 处理投票选项统一转换为选项ID列表
var selectedOptions []int64
if articleStatus.VoteType == "single" {
if req.OptionID == 0 || len(req.OptionIDs) > 0 {
tx.Rollback()
c.JSON(http.StatusForbidden, gin.H{
"error": "该文评为单选投票请使用optionId提交单个选项",
})
return
}
selectedOptions = []int64{req.OptionID}
} else {
if len(req.OptionIDs) == 0 || req.OptionID != 0 {
tx.Rollback()
c.JSON(http.StatusForbidden, gin.H{
"error": "该文评为多选投票请使用optionIds提交多个选项",
})
return
}
selectedOptions = req.OptionIDs
}
// 5. 检查用户是否已投票
var voteCount int64
if err := tx.Table("user_votes").
Where("user_id = ? AND vote_article_id = ?", req.Uid, req.ArticleID).
Count(&voteCount).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "检查投票记录失败: " + err.Error()})
return
}
if voteCount > 0 {
tx.Rollback()
c.JSON(http.StatusForbidden, gin.H{"error": "您已对该文评投过票,无法重复投票"})
return
}
// 6. 批量检查所有选项是否属于该文评
var existingOptionsCount int64
if err := tx.Table("article_options").
Where("vote_article_id = ? AND id IN ?", req.ArticleID, selectedOptions).
Count(&existingOptionsCount).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询选项失败: " + err.Error()})
return
}
if existingOptionsCount != int64(len(selectedOptions)) {
tx.Rollback()
c.JSON(http.StatusNotFound, gin.H{"error": "部分选项ID不存在或不属于该文评"})
return
}
// 7. 执行批量投票事务
voteTime := time.Now()
// 7.1 批量插入用户投票记录
userVotes := make([]UserVote, len(selectedOptions))
for i, optionID := range selectedOptions {
userVotes[i] = UserVote{
UserID: req.Uid,
VoteArticleID: req.ArticleID,
OptionID: optionID,
VoteTime: voteTime,
}
}
if err := tx.Table("user_votes").Create(&userVotes).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "批量记录投票失败: " + err.Error()})
return
}
// 7.2 批量更新选项票数
if err := tx.Table("article_options").
Where("id IN ?", selectedOptions).
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
}
// 7.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
}
// 8. 提交事务
if err := tx.Commit().Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "提交投票失败: " + err.Error()})
return
}
// 9. 返回成功响应
responseData := gin.H{
"articleId": req.ArticleID,
"voteTime": voteTime.Format("2006-01-02 15:04:05"),
}
if articleStatus.VoteType == "single" {
responseData["optionId"] = req.OptionID
} else {
responseData["optionIds"] = req.OptionIDs
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "投票成功",
"data": responseData,
})
}