feat: 用户徽章与等级体系,积分经济管理重构

- 新增徽章系统:badge 服务与 handler、管理端配置页、BadgeChip/LevelBadge 展示组件
- 新增等级模型:model/level 及前端 levels 元数据
- 管理端经济模块拆分为 points/badges/levels 独立页面,EconomySettings 迁入设置
- 积分、签到、评论等服务适配等级与徽章逻辑
This commit is contained in:
2026-09-25 16:38:29 +08:00
parent 7b327bc8cc
commit 4ae57d9392
57 changed files with 3939 additions and 147 deletions

90
backend/model/level.go Normal file
View File

@@ -0,0 +1,90 @@
package model
import (
"sync/atomic"
)
// 用户等级:按「累计获得积分」(User.TotalPoints)定级,消费不减经验。
// 等级体系(数量/阈值/颜色)由管理员在后台配置,持久化于 site_settings 的 levels_config 键;
// 进程内持有一份原子快照供 LevelOf 纯算术计算,配置保存时刷新,无需重启。
// LevelDef 单级配置:Threshold 为该级累计积分下限(Lv1 恒为 0),Color 为展示色板 key
type LevelDef struct {
Threshold int `json:"threshold"`
Color string `json:"color"`
}
const (
// MinLevelCount / MaxLevelCount 等级数量边界(前端 CSS 变量按 MaxLevelCount 档固定下发)
MinLevelCount = 2
MaxLevelCount = 12
)
// DefaultLevelDefs 默认 7 级(与历史硬编码阶梯一致),缺行/配置非法时回落
func DefaultLevelDefs() []LevelDef {
return []LevelDef{
{Threshold: 0, Color: "neutral"},
{Threshold: 50, Color: "info"},
{Threshold: 200, Color: "teal"},
{Threshold: 500, Color: "ok"},
{Threshold: 1200, Color: "accent"},
{Threshold: 3000, Color: "purple"},
{Threshold: 8000, Color: "gold"},
}
}
// levelSnapshot 当前生效的等级配置([]LevelDef)
var levelSnapshot atomic.Value
func init() {
levelSnapshot.Store(DefaultLevelDefs())
}
// ValidLevelDefs 结构校验:数量 2–12、阈值非负且严格递增、首级为 0(色板合法性由 service 层校验)
func ValidLevelDefs(defs []LevelDef) bool {
if len(defs) < MinLevelCount || len(defs) > MaxLevelCount {
return false
}
if defs[0].Threshold != 0 {
return false
}
for i, d := range defs {
if d.Threshold < 0 {
return false
}
if i > 0 && d.Threshold <= defs[i-1].Threshold {
return false
}
}
return true
}
// ApplyLevelDefs 刷新进程内等级快照;非法/空配置回落默认表
func ApplyLevelDefs(defs []LevelDef) {
if !ValidLevelDefs(defs) {
defs = DefaultLevelDefs()
}
levelSnapshot.Store(append([]LevelDef(nil), defs...))
}
// LevelDefs 当前生效的等级配置
func LevelDefs() []LevelDef {
if v, ok := levelSnapshot.Load().([]LevelDef); ok && len(v) > 0 {
return v
}
return DefaultLevelDefs()
}
// LevelOf 由累计获得积分计算等级,恒返回 1..len(当前配置)
func LevelOf(totalPoints int) int {
defs := LevelDefs()
level := 1
for i := 1; i < len(defs); i++ {
if totalPoints >= defs[i].Threshold {
level = i + 1
} else {
break
}
}
return level
}