Files
2025-09-25 18:17:41 +08:00

212 lines
6.2 KiB
Go
Raw Permalink 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 publishComments
import (
"database/sql"
"fmt"
"net/http"
"time"
"toutoukan/init/databaseInit"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// PublishReq 定义发布评论的请求结构体
type PublishReq struct {
ArticleID int64 `json:"articleId" binding:"required,min=1"` // 必须是有效的文章ID
ParentID *int64 `json:"parent_id"` // 父评论ID指针类型nil表示NULL
Content string `json:"content" binding:"required,min=1,max=500"`
UserID string `json:"user_id" binding:"required,min=1,max=40"` // 用户ID
}
// CommentModel 对应 article_comments 表的 GORM 模型
type CommentModel struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement"`
ArticleID int64 `gorm:"column:articleId"`
UserID string `gorm:"column:user_id"`
ParentID sql.NullInt64 `gorm:"column:parent_id"`
Content string `gorm:"column:content"`
LikesCount int `gorm:"column:likes_count"`
CreatedAt time.Time `gorm:"column:created_time"`
UpdatedAt time.Time `gorm:"column:update_time"`
}
// UserPoint 用户积分记录表结构
type UserPoint struct {
UserID string `gorm:"column:user_id"`
PointsChange int `gorm:"column:points_change"`
Source string `gorm:"column:source"`
CreateTime time.Time `gorm:"column:create_time"`
}
// TableName 为 CommentModel 结构体指定数据库表名
func (CommentModel) TableName() string {
return "article_comments"
}
// TableName 为 UserPoint 结构体指定数据库表名
func (UserPoint) TableName() string {
return "user_points"
}
// PublishComment 处理发布新评论的请求
func PublishComment(c *gin.Context) {
const (
COMMENTER_POINTS = 1 // 评论者获得的积分
AUTHOR_COMMENT_POINTS = 2 // 文章发布者获得的积分
)
var req PublishReq
// 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()
}
}()
now := time.Now()
// 3. 检查文章发布者是否存在并获取发布者ID
var articleInfo struct {
AuthorID string `gorm:"column:publish_user_id"`
}
if err := tx.Table("article_list").
Where("articleId = ?", req.ArticleID).
Select("publish_user_id").
First(&articleInfo).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
}
authorID := articleInfo.AuthorID
// 3.1 检查评论者UID是否存在避免外键约束失败
// 这里的检查只需要确保 req.UserID 存在即可articleInfo.AuthorID 存在性已在上面检查
var userCheck struct{ UID string }
if err := tx.Table("user_info").Select("uid").Where("uid = ?", req.UserID).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
}
// 4. 插入评论主记录
newComment := CommentModel{
ArticleID: req.ArticleID,
UserID: req.UserID,
Content: req.Content,
LikesCount: 0,
CreatedAt: now,
UpdatedAt: now,
}
// 处理 ParentID 的 NULL 值
if req.ParentID != nil && *req.ParentID > 0 {
newComment.ParentID = sql.NullInt64{Int64: *req.ParentID, Valid: true}
} else {
newComment.ParentID = sql.NullInt64{Valid: false} // 设为 NULL
}
if err := tx.Create(&newComment).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("评论发布失败: %s", err.Error()),
})
return
}
// --- 5. 积分处理 (评论者 & 文章作者) ---
// 5.1 记录评论者积分变动 (user_points)
commenterPoint := UserPoint{
UserID: req.UserID,
PointsChange: COMMENTER_POINTS,
Source: "publish_comment",
CreateTime: now,
}
if err := tx.Table("user_points").Create(&commenterPoint).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "记录评论者积分变动失败: " + err.Error()})
return
}
// 5.2 更新评论者总积分
if err := tx.Table("user_info").
Where("uid = ?", req.UserID).
Update("total_points", gorm.Expr("total_points + ?", COMMENTER_POINTS)).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新评论者总积分失败: " + err.Error()})
return
}
// 5.3 记录并更新文章发布者积分(如果不是自己评论自己)
if authorID != req.UserID {
// 记录作者积分变动
authorPoint := UserPoint{
UserID: authorID,
PointsChange: AUTHOR_COMMENT_POINTS,
Source: "comment_received",
CreateTime: now,
}
if err := tx.Table("user_points").Create(&authorPoint).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "记录作者积分变动失败: " + err.Error()})
return
}
// 更新作者总积分
if err := tx.Table("user_info").
Where("uid = ?", authorID).
Update("total_points", gorm.Expr("total_points + ?", AUTHOR_COMMENT_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
}
// 7. 返回成功响应
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": fmt.Sprintf("评论发布成功,您获得了 %d 积分", COMMENTER_POINTS),
"data": gin.H{
"commentId": newComment.ID,
"articleId": newComment.ArticleID,
"author_points_awarded": func() int {
if authorID != req.UserID {
return AUTHOR_COMMENT_POINTS
}
return 0
}(),
},
})
}