评论系统概述
评论系统是辣评平台的核心功能之一,用于用户对投稿作品的评价和讨论。系统需要支持评论的创建、展示、统计、排序等多种功能。
系统目标
- 支持灵活的评论创建和管理
- 提供高效的评论查询和展示
- 实现准确的评论统计
- 支持评论排行榜功能
评论数据模型设计
核心数据结构
// Comment 评论模型
type Comment struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"index"`
SubmissionID uint `gorm:"index"`
CompetitionID uint `gorm:"index"`
Content string `gorm:"type:text"`
Score int // 评分
Status string // 评论状态
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
// 关联
User *User
Submission *Submission
Competition *Competition
}
// CommentStatistics 评论统计模型
type CommentStatistics struct {
ID uint
UserID uint
CompetitionID uint
TotalComments int // 总评论数
AverageScore float64 // 平均评分
HighQualityCount int // 高质量评论数
CreatedAt time.Time
UpdatedAt time.Time
}
索引设计
// 创建复合索引以提升查询性能
db.Model(&Comment{}).
AddIndex("idx_user_competition", "user_id", "competition_id").
AddIndex("idx_submission_competition", "submission_id", "competition_id")
评论收集与处理逻辑
评论创建流程
// CreateComment 创建评论
func (s *CommentService) CreateComment(ctx context.Context, req *CreateCommentRequest) error {
// 1. 验证权限
if err := s.validateUserPermission(ctx, req.UserID); err != nil {
return err
}
// 2. 验证投稿存在
submission, err := s.getSubmission(ctx, req.SubmissionID)
if err != nil {
return err
}
// 3. 检查重复评论
if err := s.checkDuplicateComment(ctx, req); err != nil {
return err
}
// 4. 创建评论
comment := &Comment{
UserID: req.UserID,
SubmissionID: req.SubmissionID,
CompetitionID: submission.CompetitionID,
Content: req.Content,
Score: req.Score,
Status: "active",
}
// 5. 保存到数据库
if err := s.db.Create(comment).Error; err != nil {
return err
}
// 6. 更新统计数据
s.updateCommentStatistics(ctx, req.UserID, submission.CompetitionID)
return nil
}
评论验证规则
// ValidateComment 验证评论内容
func (s *CommentService) ValidateComment(comment *Comment) error {
// 1. 检查内容长度
if len(comment.Content) < 10 {
return errors.New("评论内容过短")
}
if len(comment.Content) > 5000 {
return errors.New("评论内容过长")
}
// 2. 检查评分范围
if comment.Score < 0 || comment.Score > 5 {
return errors.New("评分范围不正确")
}
// 3. 检查用户是否为投稿者
if s.isAuthor(comment.UserID, comment.SubmissionID) {
return errors.New("投稿者不能评论自己的作品")
}
return nil
}
评论统计与排行榜功能
统计计算
// CalculateCommentStatistics 计算评论统计
func (s *CommentService) CalculateCommentStatistics(ctx context.Context, userID, competitionID uint) error {
// 1. 查询用户的所有评论
var comments []Comment
if err := s.db.Where("user_id = ? AND competition_id = ?", userID, competitionID).
Find(&comments).Error; err != nil {
return err
}
// 2. 计算统计数据
totalComments := len(comments)
var totalScore int
highQualityCount := 0
for _, comment := range comments {
totalScore += comment.Score
if comment.Score >= 4 {
highQualityCount++
}
}
averageScore := float64(totalScore) / float64(totalComments)
// 3. 保存统计数据
stats := &CommentStatistics{
UserID: userID,
CompetitionID: competitionID,
TotalComments: totalComments,
AverageScore: averageScore,
HighQualityCount: highQualityCount,
}
return s.db.Save(stats).Error
}
排行榜生成
// GetLeaderboard 获取排行榜
func (s *CommentService) GetLeaderboard(ctx context.Context, competitionID uint, limit int) ([]LeaderboardEntry, error) {
var entries []LeaderboardEntry
// 按平均评分和评论数排序
err := s.db.Table("comment_statistics").
Select("user_id, total_comments, average_score").
Where("competition_id = ?", competitionID).
Order("average_score DESC, total_comments DESC").
Limit(limit).
Scan(&entries).Error
return entries, err
}
重复评论处理机制
重复评论检测
// CheckDuplicateComment 检查重复评论
func (s *CommentService) CheckDuplicateComment(ctx context.Context, req *CreateCommentRequest) error {
var count int64
// 检查同一用户在同一投稿上是否已有评论
err := s.db.Model(&Comment{}).
Where("user_id = ? AND submission_id = ?", req.UserID, req.SubmissionID).
Count(&count).Error
if err != nil {
return err
}
if count > 0 {
return errors.New("您已经评论过此作品")
}
return nil
}
重复提交防护
// 使用幂等性 token 防止重复提交
type CreateCommentRequest struct {
UserID uint `json:"user_id"`
SubmissionID uint `json:"submission_id"`
Content string `json:"content"`
Score int `json:"score"`
IdempotencyKey string `json:"idempotency_key"` // 幂等性 key
}
// 检查幂等性 key
func (s *CommentService) CheckIdempotency(ctx context.Context, key string) error {
var count int64
err := s.db.Model(&Comment{}).
Where("idempotency_key = ?", key).
Count(&count).Error
if err != nil {
return err
}
if count > 0 {
return errors.New("请求已处理")
}
return nil
}
评论撤回与修复
评论撤回
// WithdrawComment 撤回评论
func (s *CommentService) WithdrawComment(ctx context.Context, commentID, userID uint) error {
// 1. 验证权限
comment := &Comment{}
if err := s.db.First(comment, commentID).Error; err != nil {
return err
}
if comment.UserID != userID {
return errors.New("无权撤回此评论")
}
// 2. 检查撤回时间限制(例如:24小时内)
if time.Since(comment.CreatedAt) > 24*time.Hour {
return errors.New("超过撤回时间限制")
}
// 3. 标记为已撤回
return s.db.Model(comment).Update("status", "withdrawn").Error
}
评论编辑
// EditComment 编辑评论
func (s *CommentService) EditComment(ctx context.Context, commentID, userID uint, newContent string) error {
comment := &Comment{}
// 1. 获取评论
if err := s.db.First(comment, commentID).Error; err != nil {
return err
}
// 2. 验证权限
if comment.UserID != userID {
return errors.New("无权编辑此评论")
}
// 3. 验证新内容
if err := s.ValidateCommentContent(newContent); err != nil {
return err
}
// 4. 更新评论
return s.db.Model(comment).Update("content", newContent).Error
}
评论查询优化
高效查询
// GetCommentsBySubmission 获取投稿的所有评论
func (s *CommentService) GetCommentsBySubmission(ctx context.Context, submissionID uint, page, pageSize int) ([]Comment, int64, error) {
var comments []Comment
var total int64
offset := (page - 1) * pageSize
// 使用预加载优化查询
query := s.db.Preload("User").
Where("submission_id = ? AND status = ?", submissionID, "active").
Order("created_at DESC")
// 获取总数
if err := query.Model(&Comment{}).Count(&total).Error; err != nil {
return nil, 0, err
}
// 获取分页数据
if err := query.Offset(offset).Limit(pageSize).Find(&comments).Error; err != nil {
return nil, 0, err
}
return comments, total, nil
}
缓存策略
// 使用缓存减少数据库查询
func (s *CommentService) GetCommentStatisticsWithCache(ctx context.Context, userID, competitionID uint) (*CommentStatistics, error) {
// 1. 尝试从缓存获取
cacheKey := fmt.Sprintf("comment_stats:%d:%d", userID, competitionID)
if cached, err := s.cache.Get(ctx, cacheKey); err == nil {
return cached.(*CommentStatistics), nil
}
// 2. 从数据库查询
stats := &CommentStatistics{}
if err := s.db.Where("user_id = ? AND competition_id = ?", userID, competitionID).
First(stats).Error; err != nil {
return nil, err
}
// 3. 存入缓存(1小时过期)
s.cache.Set(ctx, cacheKey, stats, time.Hour)
return stats, nil
}
评论系统的 API 接口
创建评论
POST /api/comments
{
"submission_id": 1,
"content": "这是一条评论",
"score": 4
}
获取评论列表
GET /api/submissions/{id}/comments?page=1&page_size=10
编辑评论
PUT /api/comments/{id}
{
"content": "更新后的评论内容"
}
删除评论
DELETE /api/comments/{id}
获取排行榜
GET /api/competitions/{id}/leaderboard?limit=10
总结
评论系统的构建涉及多个方面的考虑:
关键设计原则
- 数据一致性: 确保评论数据的准确性
- 性能优化: 使用索引和缓存提升查询速度
- 用户体验: 提供灵活的评论管理功能
- 系统稳定性: 防止重复提交和异常情况
后续优化方向
- 实现评论的点赞和回复功能
- 添加评论内容审核机制
- 实现评论的实时通知
- 优化大数据量下的查询性能
评论系统的完善为辣评平台提供了强大的用户互动能力,是平台成功的重要基础。