package article import ( "fmt" "github.com/gin-gonic/gin" "gorm.io/gorm" "net/http" "time" "toutoukan/init/databaseInit" ) // OptionItem 选项子结构 type OptionItem struct { Content string `json:"content" binding:"required,min=1,max=200"` // 选项内容 SortOrder int `json:"sort_order" binding:"required,min=0"` // 排序值 } // CreateArticleRequest 创建文章的请求参数结构 type CreateArticleRequest struct { PublishUserID string `json:"publish_user_id" binding:"required,min=1,max=40"` Title string `json:"title" binding:"required,min=1,max=255"` VoteType string `json:"vote_type" binding:"required,min=1,max=60"` EndTime time.Time `json:"end_time" binding:"required"` Options []OptionItem `json:"options" binding:"required,min=1,max=5"` } // ArticleList 数据库文章记录结构体 // UserPoint 用户积分记录表结构 // UserInfo 用户信息表结构 type UserInfo struct { UID string `gorm:"column:uid;primaryKey"` TotalPoints int `gorm:"column:total_points"` } // 自定义表名,GORM会根据这些结构体进行操作 func (ArticleList) TableName() string { return "article_list" } func (UserPoint) TableName() string { return "user_points" } func (UserInfo) TableName() string { return "user_info" } // CreateArticle 创建文章(包含选项) func CreateArticle(c *gin.Context) { const ( POST_ARTICLE_POINTS = 2 // 发表文章获得的积分 ) var req CreateArticleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "参数解析失败", "detail": err.Error(), }) return } // 开启数据库事务 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() } }() // 1. 检查文章发布者是否存在 var userCheck UserInfo if err := tx.Table("user_info"). Where("uid = ?", req.PublishUserID). First(&userCheck).Error; err != nil { tx.Rollback() if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "文章发布者UID不存在"}) } else { c.JSON(http.StatusInternalServerError, gin.H{"error": "查询用户失败: " + err.Error()}) } return } // 2. 创建文章主记录 newArticle := ArticleList{ PublishUserID: req.PublishUserID, Title: req.Title, VoteType: req.VoteType, EndTime: req.EndTime, IsEnded: false, TotalVotersNum: 0, CreateTime: time.Now(), } if err := tx.Create(&newArticle).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "创建文章失败: " + err.Error()}) return } if newArticle.ArticleID <= 0 { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "无法获取文章ID,创建失败"}) return } // 3. 批量创建选项 var options []map[string]interface{} for _, opt := range req.Options { options = append(options, map[string]interface{}{ "vote_article_id": newArticle.ArticleID, "option_content": opt.Content, "option_votes_num": 0, "sort_order": opt.SortOrder, }) } if err := tx.Table("article_options").CreateInBatches(options, len(options)).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "创建选项失败: " + err.Error()}) return } // 4. 记录文章发布者积分变动 userPoint := UserPoint{ UserID: req.PublishUserID, PointsChange: POST_ARTICLE_POINTS, Source: "publish_article", CreateTime: time.Now(), } if err := tx.Table("user_points").Create(&userPoint).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "记录积分变动失败: " + err.Error()}) return } // 5. 更新 user_info 表中的 total_points 字段 // 使用 UpdateColumn 来确保原子性,并避免 GORM 的其他回调 if err := tx.Table("user_info"). Where("uid = ?", req.PublishUserID). UpdateColumn("total_points", gorm.Expr("total_points + ?", POST_ARTICLE_POINTS)).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户总积分失败: " + err.Error()}) return } // 6. 提交事务 if err := tx.Commit().Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": "提交数据失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "文章及选项创建成功,您获得了" + fmt.Sprintf("%d", POST_ARTICLE_POINTS) + "积分", "data": gin.H{ "article_id": newArticle.ArticleID, "option_count": len(req.Options), }, }) }