refactor: Gitea 式目录改组,移除本分支 SPA 与杂项产物
将 model/service/handler/middleware 迁至 models/services/routers/api/modules/auth,并删除 frontend、embed_static、scripts 及误入库缓存/二进制。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
272
services/badge.go
Normal file
272
services/badge.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// BadgeService 徽章定义与发放
|
||||
type BadgeService struct{}
|
||||
|
||||
func NewBadgeService() *BadgeService { return &BadgeService{} }
|
||||
|
||||
// ListDefs 列出徽章定义
|
||||
func (s *BadgeService) ListDefs(includeDisabled bool) ([]model.BadgeDef, error) {
|
||||
q := model.DB.Order("sort_order asc, id asc")
|
||||
if !includeDisabled {
|
||||
q = q.Where("enabled = ?", true)
|
||||
}
|
||||
var rows []model.BadgeDef
|
||||
err := q.Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
// UpsertDef 创建或更新徽章定义(按 code)
|
||||
func (s *BadgeService) UpsertDef(def *model.BadgeDef) error {
|
||||
if def.Code == "" || def.Name == "" {
|
||||
return errors.New("徽章代码与名称不能为空")
|
||||
}
|
||||
if def.Kind != model.BadgeKindAuto && def.Kind != model.BadgeKindLimited {
|
||||
return errors.New("无效的徽章类型")
|
||||
}
|
||||
var existing model.BadgeDef
|
||||
err := model.DB.Where("code = ?", def.Code).Limit(1).Find(&existing).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing.ID == 0 {
|
||||
return model.DB.Create(def).Error
|
||||
}
|
||||
def.ID = existing.ID
|
||||
return model.DB.Model(&existing).Updates(map[string]interface{}{
|
||||
"name": def.Name,
|
||||
"description": def.Description,
|
||||
"icon": def.Icon,
|
||||
"kind": def.Kind,
|
||||
"metric": def.Metric,
|
||||
"threshold": def.Threshold,
|
||||
"sort_order": def.SortOrder,
|
||||
"enabled": def.Enabled,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// AwardLimited 站长颁发限定徽章
|
||||
func (s *BadgeService) AwardLimited(userID, badgeID, adminID uint) error {
|
||||
var def model.BadgeDef
|
||||
if err := model.DB.First(&def, badgeID).Error; err != nil {
|
||||
return errors.New("徽章不存在")
|
||||
}
|
||||
if def.Kind != model.BadgeKindLimited {
|
||||
return errors.New("仅可颁发限定徽章")
|
||||
}
|
||||
if !def.Enabled {
|
||||
return errors.New("徽章已停用")
|
||||
}
|
||||
var n int64
|
||||
model.DB.Model(&model.UserBadge{}).Where("user_id = ? AND badge_id = ?", userID, badgeID).Count(&n)
|
||||
if n > 0 {
|
||||
return errors.New("用户已拥有该徽章")
|
||||
}
|
||||
return model.DB.Create(&model.UserBadge{
|
||||
UserID: userID,
|
||||
BadgeID: badgeID,
|
||||
AwardedAt: time.Now(),
|
||||
AwardedBy: adminID,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// Revoke 收回徽章
|
||||
func (s *BadgeService) Revoke(userID, badgeID uint) error {
|
||||
res := model.DB.Where("user_id = ? AND badge_id = ?", userID, badgeID).Delete(&model.UserBadge{})
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.New("用户未拥有该徽章")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUserBadges 用户已获徽章(含定义)
|
||||
func (s *BadgeService) ListUserBadges(userID uint) ([]model.UserBadge, error) {
|
||||
var rows []model.UserBadge
|
||||
err := model.DB.Preload("Badge").Where("user_id = ?", userID).
|
||||
Order("awarded_at desc").Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
// BadgeViews 转为展示视图(最多 limit 枚,0=全部)
|
||||
func BadgeViews(rows []model.UserBadge, limit int) []model.UserBadgeView {
|
||||
out := make([]model.UserBadgeView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
if r.Badge.ID == 0 || !r.Badge.Enabled {
|
||||
continue
|
||||
}
|
||||
out = append(out, model.UserBadgeView{
|
||||
Code: r.Badge.Code,
|
||||
Name: r.Badge.Name,
|
||||
Description: r.Badge.Description,
|
||||
Icon: r.Badge.Icon,
|
||||
Kind: r.Badge.Kind,
|
||||
})
|
||||
if limit > 0 && len(out) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// EvaluateAuto 检查并授予符合条件的自动徽章
|
||||
func (s *BadgeService) EvaluateAuto(userID uint) error {
|
||||
var user model.User
|
||||
if err := model.DB.First(&user, userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var defs []model.BadgeDef
|
||||
if err := model.DB.Where("kind = ? AND enabled = ?", model.BadgeKindAuto, true).Find(&defs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
tenureDays := int(time.Since(user.CreatedAt).Hours() / 24)
|
||||
var likes int64
|
||||
_ = model.DB.Model(&model.Post{}).
|
||||
Select("COALESCE(SUM(like_count), 0)").
|
||||
Where("user_id = ? AND status = ?", userID, model.ContentStatusPublished).
|
||||
Scan(&likes).Error
|
||||
income := user.CreatorIncomeTotal
|
||||
|
||||
owned := map[uint]bool{}
|
||||
var existing []model.UserBadge
|
||||
_ = model.DB.Where("user_id = ?", userID).Find(&existing).Error
|
||||
for _, e := range existing {
|
||||
owned[e.BadgeID] = true
|
||||
}
|
||||
|
||||
for _, d := range defs {
|
||||
if owned[d.ID] {
|
||||
continue
|
||||
}
|
||||
ok := false
|
||||
switch d.Metric {
|
||||
case model.BadgeMetricTenureDays:
|
||||
ok = tenureDays >= d.Threshold
|
||||
case model.BadgeMetricLikesReceived:
|
||||
ok = int(likes) >= d.Threshold
|
||||
case model.BadgeMetricCreatorIncome:
|
||||
ok = income >= d.Threshold
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_ = model.DB.Create(&model.UserBadge{
|
||||
UserID: userID,
|
||||
BadgeID: d.ID,
|
||||
AwardedAt: time.Now(),
|
||||
AwardedBy: 0,
|
||||
}).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachBadgeSummaries 批量为用户填充展示用徽章(最多 perUser 枚)
|
||||
func (s *BadgeService) AttachBadgeSummaries(users []*model.User, perUser int) {
|
||||
if len(users) == 0 {
|
||||
return
|
||||
}
|
||||
if perUser <= 0 {
|
||||
perUser = 3
|
||||
}
|
||||
ids := make([]uint, 0, len(users))
|
||||
seen := map[uint]bool{}
|
||||
for _, u := range users {
|
||||
if u == nil || u.ID == 0 {
|
||||
continue
|
||||
}
|
||||
u.Level = model.LevelFromExp(u.Exp)
|
||||
if !seen[u.ID] {
|
||||
seen[u.ID] = true
|
||||
ids = append(ids, u.ID)
|
||||
}
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return
|
||||
}
|
||||
var rows []model.UserBadge
|
||||
_ = model.DB.Preload("Badge").Where("user_id IN ?", ids).
|
||||
Order("awarded_at desc").Find(&rows).Error
|
||||
grouped := map[uint][]model.UserBadgeView{}
|
||||
for _, r := range rows {
|
||||
if r.Badge.ID == 0 || !r.Badge.Enabled {
|
||||
continue
|
||||
}
|
||||
list := grouped[r.UserID]
|
||||
if len(list) >= perUser {
|
||||
continue
|
||||
}
|
||||
list = append(list, model.UserBadgeView{
|
||||
Code: r.Badge.Code,
|
||||
Name: r.Badge.Name,
|
||||
Description: r.Badge.Description,
|
||||
Icon: r.Badge.Icon,
|
||||
Kind: r.Badge.Kind,
|
||||
})
|
||||
grouped[r.UserID] = list
|
||||
}
|
||||
for _, u := range users {
|
||||
if u == nil || u.ID == 0 {
|
||||
continue
|
||||
}
|
||||
u.Badges = grouped[u.ID]
|
||||
}
|
||||
}
|
||||
|
||||
// AttachBadgeSummariesOnPosts 给帖子作者填充徽章摘要
|
||||
func (s *BadgeService) AttachBadgeSummariesOnPosts(posts []model.Post, perUser int) {
|
||||
users := make([]*model.User, 0, len(posts))
|
||||
for i := range posts {
|
||||
if posts[i].User.ID > 0 {
|
||||
users = append(users, &posts[i].User)
|
||||
}
|
||||
}
|
||||
s.AttachBadgeSummaries(users, perUser)
|
||||
}
|
||||
|
||||
// AttachBadgeSummariesOnComments 给评论作者填充徽章摘要
|
||||
func (s *BadgeService) AttachBadgeSummariesOnComments(comments []model.Comment, perUser int) {
|
||||
users := make([]*model.User, 0, len(comments))
|
||||
for i := range comments {
|
||||
if comments[i].User.ID > 0 {
|
||||
users = append(users, &comments[i].User)
|
||||
}
|
||||
}
|
||||
s.AttachBadgeSummaries(users, perUser)
|
||||
}
|
||||
|
||||
// AddExp 增加经验(不可为负消耗;delta<=0 忽略)
|
||||
func AddExp(userID uint, delta int) {
|
||||
if userID == 0 || delta <= 0 {
|
||||
return
|
||||
}
|
||||
_ = model.DB.Model(&model.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("exp", gorm.Expr("exp + ?", delta)).Error
|
||||
}
|
||||
|
||||
// SetUserLevel 站长设等级(调整 Exp 到门槛)
|
||||
func SetUserLevel(userID uint, level int) error {
|
||||
if level < 1 || level > model.MaxLevel() {
|
||||
return errors.New("等级须在 1–10")
|
||||
}
|
||||
exp := model.ExpForLevel(level)
|
||||
return model.DB.Model(&model.User{}).Where("id = ?", userID).Update("exp", exp).Error
|
||||
}
|
||||
|
||||
// SetVerified 设置认证
|
||||
func SetVerified(userID uint, verified bool) error {
|
||||
var user model.User
|
||||
if err := model.DB.First(&user, userID).Error; err != nil {
|
||||
return errors.New("用户不存在")
|
||||
}
|
||||
return model.DB.Model(&user).Update("verified", verified).Error
|
||||
}
|
||||
Reference in New Issue
Block a user