排行榜系统概述
排行榜系统是辣评平台的重要功能,用于展示用户的评论质量和活跃度排名。通过星级评定机制,系统能够识别和奖励高质量的评论者。
系统目标
- 提供公平的排名机制
- 实现灵活的排序功能
- 支持多维度的筛选
- 激励用户参与评论
排行榜数据模型
核心数据结构
// Leaderboard 排行榜模型
type Leaderboard struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"uniqueIndex:idx_user_competition"`
CompetitionID uint `gorm:"uniqueIndex:idx_user_competition"`
Rank int // 排名
TotalComments int // 总评论数
AverageScore float64 // 平均评分
HighQualityCount int // 高质量评论数(评分>=4)
TotalScore int // 总评分
Points int // 积分(用于排名)
LastUpdatedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
// 关联
User *User
Competition *Competition
}
// LeaderboardHistory 排行榜历史记录
type LeaderboardHistory struct {
ID uint
LeaderboardID uint
Rank int
Points int
RecordedAt time.Time
}
// UserRating 用户评分记录
type UserRating struct {
ID uint
UserID uint `gorm:"index"`
CompetitionID uint `gorm:"index"`
TotalRatings int // 被评分的次数
AverageRating float64 // 平均被评分
FiveStarCount int // 五星评分数
FourStarCount int // 四星评分数
ThreeStarCount int // 三星评分数
TwoStarCount int // 二星评分数
OneStarCount int // 一星评分数
UpdatedAt time.Time
}
排行榜计算逻辑
积分计算
// CalculateLeaderboardPoints 计算排行榜积分
func (s *LeaderboardService) CalculateLeaderboardPoints(ctx context.Context, userID, competitionID uint) (int, error) {
// 1. 获取用户的评论统计
var comments []Comment
if err := s.db.Where("user_id = ? AND competition_id = ?", userID, competitionID).
Find(&comments).Error; err != nil {
return 0, err
}
if len(comments) == 0 {
return 0, nil
}
// 2. 计算基础积分
points := 0
// 评论数积分:每条评论1分
points += len(comments)
// 评分积分:根据评分等级加分
for _, comment := range comments {
switch {
case comment.Score >= 4:
points += 5 // 高质量评论加5分
case comment.Score >= 3:
points += 2 // 中等质量评论加2分
case comment.Score >= 2:
points += 1 // 低质量评论加1分
}
}
// 3. 计算平均评分奖励
var totalScore int
for _, comment := range comments {
totalScore += comment.Score
}
averageScore := float64(totalScore) / float64(len(comments))
// 平均评分>=4.5分额外加10分
if averageScore >= 4.5 {
points += 10
} else if averageScore >= 4.0 {
points += 5
}
// 4. 计算高质量评论比例奖励
highQualityCount := 0
for _, comment := range comments {
if comment.Score >= 4 {
highQualityCount++
}
}
qualityRatio := float64(highQualityCount) / float64(len(comments))
if qualityRatio >= 0.8 {
points += 15 // 高质量评论比例>=80%加15分
} else if qualityRatio >= 0.6 {
points += 10
} else if qualityRatio >= 0.4 {
points += 5
}
return points, nil
}
// UpdateLeaderboard 更新排行榜
func (s *LeaderboardService) UpdateLeaderboard(ctx context.Context, competitionID uint) error {
// 1. 获取所有参与该比赛的用户
var userIDs []uint
if err := s.db.Model(&Comment{}).
Distinct("user_id").
Where("competition_id = ?", competitionID).
Pluck("user_id", &userIDs).Error; err != nil {
return err
}
// 2. 为每个用户计算积分
leaderboardEntries := make([]Leaderboard, 0)
for _, userID := range userIDs {
// 计算积分
points, err := s.CalculateLeaderboardPoints(ctx, userID, competitionID)
if err != nil {
continue
}
// 获取评论统计
var comments []Comment
s.db.Where("user_id = ? AND competition_id = ?", userID, competitionID).
Find(&comments)
totalComments := len(comments)
var totalScore int
highQualityCount := 0
for _, comment := range comments {
totalScore += comment.Score
if comment.Score >= 4 {
highQualityCount++
}
}
averageScore := 0.0
if totalComments > 0 {
averageScore = float64(totalScore) / float64(totalComments)
}
entry := Leaderboard{
UserID: userID,
CompetitionID: competitionID,
TotalComments: totalComments,
AverageScore: averageScore,
HighQualityCount: highQualityCount,
TotalScore: totalScore,
Points: points,
LastUpdatedAt: time.Now(),
}
leaderboardEntries = append(leaderboardEntries, entry)
}
// 3. 按积分排序
sort.Slice(leaderboardEntries, func(i, j int) bool {
if leaderboardEntries[i].Points != leaderboardEntries[j].Points {
return leaderboardEntries[i].Points > leaderboardEntries[j].Points
}
return leaderboardEntries[i].AverageScore > leaderboardEntries[j].AverageScore
})
// 4. 分配排名
for i, entry := range leaderboardEntries {
entry.Rank = i + 1
// 查询是否已存在
existing := &Leaderboard{}
result := s.db.Where("user_id = ? AND competition_id = ?", entry.UserID, entry.CompetitionID).
First(existing)
if result.Error == gorm.ErrRecordNotFound {
// 创建新记录
s.db.Create(&entry)
} else {
// 更新现有记录
s.db.Model(existing).Updates(entry)
// 记录排名变化历史
s.recordLeaderboardHistory(ctx, existing.ID, entry.Rank, entry.Points)
}
}
return nil
}
// recordLeaderboardHistory 记录排行榜历史
func (s *LeaderboardService) recordLeaderboardHistory(ctx context.Context, leaderboardID uint, rank, points int) error {
history := &LeaderboardHistory{
LeaderboardID: leaderboardID,
Rank: rank,
Points: points,
RecordedAt: time.Now(),
}
return s.db.Create(history).Error
}
星级评定机制
评分系统
// RatingSystem 评分系统
type RatingSystem struct {
MinScore int // 最低评分
MaxScore int // 最高评分
Step int // 评分步长
}
// ValidateScore 验证评分
func (s *LeaderboardService) ValidateScore(score int) error {
if score < 1 || score > 5 {
return errors.New("评分必须在1-5之间")
}
return nil
}
// GetScoreDescription 获取评分描述
func (s *LeaderboardService) GetScoreDescription(score int) string {
descriptions := map[int]string{
1: "很差 - 内容不相关或有严重问题",
2: "差 - 内容有问题或不够深入",
3: "一般 - 内容基本合理但缺乏深度",
4: "好 - 内容充分且有一定深度",
5: "很好 - 内容优秀且有很高价值",
}
if desc, ok := descriptions[score]; ok {
return desc
}
return "未知"
}
// CalculateUserRating 计算用户评分统计
func (s *LeaderboardService) CalculateUserRating(ctx context.Context, userID, competitionID uint) (*UserRating, error) {
// 1. 获取用户收到的所有评分
var comments []Comment
if err := s.db.Where("user_id = ? AND competition_id = ?", userID, competitionID).
Find(&comments).Error; err != nil {
return nil, err
}
// 2. 统计各等级评分
rating := &UserRating{
UserID: userID,
CompetitionID: competitionID,
TotalRatings: len(comments),
}
var totalScore int
for _, comment := range comments {
totalScore += comment.Score
switch comment.Score {
case 5:
rating.FiveStarCount++
case 4:
rating.FourStarCount++
case 3:
rating.ThreeStarCount++
case 2:
rating.TwoStarCount++
case 1:
rating.OneStarCount++
}
}
// 3. 计算平均评分
if rating.TotalRatings > 0 {
rating.AverageRating = float64(totalScore) / float64(rating.TotalRatings)
}
rating.UpdatedAt = time.Now()
return rating, nil
}
排序与筛选
排序功能
// SortOption 排序选项
type SortOption struct {
Field string // 排序字段:points, average_score, total_comments
Direction string // 排序方向:asc, desc
}
// GetLeaderboard 获取排行榜
func (s *LeaderboardService) GetLeaderboard(ctx context.Context, competitionID uint, page, pageSize int, sortOption *SortOption) ([]Leaderboard, int64, error) {
var leaderboard []Leaderboard
var total int64
query := s.db.Where("competition_id = ?", competitionID)
// 应用排序
if sortOption != nil {
direction := "DESC"
if sortOption.Direction == "asc" {
direction = "ASC"
}
switch sortOption.Field {
case "average_score":
query = query.Order("average_score " + direction)
case "total_comments":
query = query.Order("total_comments " + direction)
default:
query = query.Order("points " + direction)
}
} else {
query = query.Order("points DESC")
}
// 获取总数
if err := query.Model(&Leaderboard{}).Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Find(&leaderboard).Error; err != nil {
return nil, 0, err
}
return leaderboard, total, nil
}
// FilterLeaderboard 筛选排行榜
func (s *LeaderboardService) FilterLeaderboard(ctx context.Context, competitionID uint, filter *LeaderboardFilter) ([]Leaderboard, error) {
query := s.db.Where("competition_id = ?", competitionID)
// 按平均评分筛选
if filter.MinAverageScore > 0 {
query = query.Where("average_score >= ?", filter.MinAverageScore)
}
// 按评论数筛选
if filter.MinComments > 0 {
query = query.Where("total_comments >= ?", filter.MinComments)
}
// 按高质量评论比例筛选
if filter.MinQualityRatio > 0 {
query = query.Where("high_quality_count / total_comments >= ?", filter.MinQualityRatio)
}
var leaderboard []Leaderboard
if err := query.Order("points DESC").Find(&leaderboard).Error; err != nil {
return nil, err
}
return leaderboard, nil
}
前端展示
排行榜组件
<template>
<div class="leaderboard-container">
<div class="leaderboard-header">
<h2>评论排行榜</h2>
<div class="leaderboard-controls">
<el-select v-model="sortBy" @change="handleSortChange">
<el-option label="按积分排序" value="points"></el-option>
<el-option label="按平均评分排序" value="average_score"></el-option>
<el-option label="按评论数排序" value="total_comments"></el-option>
</el-select>
</div>
</div>
<el-table :data="leaderboard" stripe>
<el-table-column prop="rank" label="排名" width="80"></el-table-column>
<el-table-column prop="user.username" label="用户名" width="150"></el-table-column>
<el-table-column prop="points" label="积分" width="100"></el-table-column>
<el-table-column prop="totalComments" label="评论数" width="100"></el-table-column>
<el-table-column prop="averageScore" label="平均评分" width="120">
<template #default="{ row }">
<el-rate v-model="row.averageScore" disabled></el-rate>
</template>
</el-table-column>
<el-table-column prop="highQualityCount" label="高质量评论" width="120"></el-table-column>
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button type="text" @click="viewUserProfile(row.userId)">查看</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
></el-pagination>
</div>
</template>
<script>
export default {
props: {
competitionId: Number
},
data() {
return {
leaderboard: [],
sortBy: 'points',
currentPage: 1,
pageSize: 10,
total: 0
}
},
methods: {
async fetchLeaderboard() {
try {
const response = await this.$api.getLeaderboard(this.competitionId, {
page: this.currentPage,
page_size: this.pageSize,
sort_by: this.sortBy
})
this.leaderboard = response.data
this.total = response.total
} catch (error) {
this.$message.error('获取排行榜失败')
}
},
handleSortChange() {
this.currentPage = 1
this.fetchLeaderboard()
},
handlePageChange(page) {
this.currentPage = page
this.fetchLeaderboard()
},
viewUserProfile(userId) {
this.$router.push(`/user/${userId}`)
}
},
mounted() {
this.fetchLeaderboard()
}
}
</script>
<style scoped>
.leaderboard-container {
padding: 20px;
}
.leaderboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.leaderboard-controls {
display: flex;
gap: 10px;
}
</style>
API 接口
GET /api/competitions/{id}/leaderboard - 获取排行榜
GET /api/competitions/{id}/leaderboard/filter - 筛选排行榜
GET /api/users/{id}/rating - 获取用户评分统计
POST /api/leaderboard/update - 更新排行榜
GET /api/leaderboard/history - 获取排行榜历史
总结
排行榜与星级评定系统为辣评平台提供了完整的用户激励机制,通过公平的积分计算和排名展示,激励用户提交高质量的评论。
关键特性
- 多维度的积分计算
- 灵活的排序和筛选
- 详细的评分统计
- 排名变化历史记录
- 用户激励机制
这个系统提升了平台的竞争性和用户参与度。