2025-09-24 01:07:21 +08:00
|
|
|
|
package article
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-09-24 20:57:23 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
"gorm.io/gorm"
|
2025-09-24 01:07:21 +08:00
|
|
|
|
"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"`
|
2025-09-27 18:25:46 +08:00
|
|
|
|
Options []OptionItem `json:"options" binding:"required,min=1,max=5"`
|
2025-09-24 01:07:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// ArticleList 数据库文章记录结构体
|
|
|
|
|
|
|
|
|
|
|
|
// UserPoint 用户积分记录表结构
|
|
|
|
|
|
|
|
|
|
|
|
// UserInfo 用户信息表结构
|
|
|
|
|
|
type UserInfo struct {
|
|
|
|
|
|
UID string `gorm:"column:uid;primaryKey"`
|
|
|
|
|
|
TotalPoints int `gorm:"column:total_points"`
|
2025-09-24 01:07:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// 自定义表名,GORM会根据这些结构体进行操作
|
2025-09-24 01:07:21 +08:00
|
|
|
|
func (ArticleList) TableName() string {
|
|
|
|
|
|
return "article_list"
|
|
|
|
|
|
}
|
2025-09-24 20:57:23 +08:00
|
|
|
|
func (UserPoint) TableName() string {
|
|
|
|
|
|
return "user_points"
|
|
|
|
|
|
}
|
|
|
|
|
|
func (UserInfo) TableName() string {
|
|
|
|
|
|
return "user_info"
|
|
|
|
|
|
}
|
2025-09-24 01:07:21 +08:00
|
|
|
|
|
|
|
|
|
|
// CreateArticle 创建文章(包含选项)
|
|
|
|
|
|
func CreateArticle(c *gin.Context) {
|
2025-09-24 20:57:23 +08:00
|
|
|
|
const (
|
|
|
|
|
|
POST_ARTICLE_POINTS = 2 // 发表文章获得的积分
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-24 01:07:21 +08:00
|
|
|
|
var req CreateArticleRequest
|
|
|
|
|
|
|
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
|
|
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
|
|
|
|
"error": "参数解析失败",
|
|
|
|
|
|
"detail": err.Error(),
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// 开启数据库事务
|
2025-09-24 01:07:21 +08:00
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// 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. 创建文章主记录
|
2025-09-24 01:07:21 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// 3. 批量创建选项
|
2025-09-24 01:07:21 +08:00
|
|
|
|
var options []map[string]interface{}
|
|
|
|
|
|
for _, opt := range req.Options {
|
|
|
|
|
|
options = append(options, map[string]interface{}{
|
2025-09-24 20:57:23 +08:00
|
|
|
|
"vote_article_id": newArticle.ArticleID,
|
2025-09-24 01:07:21 +08:00
|
|
|
|
"option_content": opt.Content,
|
|
|
|
|
|
"option_votes_num": 0,
|
|
|
|
|
|
"sort_order": opt.SortOrder,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-09-24 20:57:23 +08:00
|
|
|
|
if err := tx.Table("article_options").CreateInBatches(options, len(options)).Error; err != nil {
|
2025-09-24 01:07:21 +08:00
|
|
|
|
tx.Rollback()
|
2025-09-24 20:57:23 +08:00
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建选项失败: " + err.Error()})
|
2025-09-24 01:07:21 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-09-24 20:57:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 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 {
|
2025-09-24 01:07:21 +08:00
|
|
|
|
tx.Rollback()
|
2025-09-24 20:57:23 +08:00
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "记录积分变动失败: " + err.Error()})
|
2025-09-24 01:07:21 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// 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 {
|
2025-09-24 01:07:21 +08:00
|
|
|
|
tx.Rollback()
|
2025-09-24 20:57:23 +08:00
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户总积分失败: " + err.Error()})
|
2025-09-24 01:07:21 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:57:23 +08:00
|
|
|
|
// 6. 提交事务
|
2025-09-24 01:07:21 +08:00
|
|
|
|
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,
|
2025-09-24 20:57:23 +08:00
|
|
|
|
"message": "文章及选项创建成功,您获得了" + fmt.Sprintf("%d", POST_ARTICLE_POINTS) + "积分",
|
2025-09-24 01:07:21 +08:00
|
|
|
|
"data": gin.H{
|
|
|
|
|
|
"article_id": newArticle.ArticleID,
|
|
|
|
|
|
"option_count": len(req.Options),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|