173 lines
5.0 KiB
Go
173 lines
5.0 KiB
Go
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"),
|
||
},
|
||
})
|
||
}
|