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, }) }