bindbox-game/internal/service/title/effects_resolver.go
邹方成 8141a47690
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 39s
feat(称号系统): 新增称号管理功能与抽奖效果集成
- 新增系统称号模板与效果配置表及相关CRUD接口
- 实现用户称号分配与抽奖效果应用逻辑
- 优化抽奖接口支持用户ID参数以应用称号效果
- 新增称号管理前端页面与分配功能
- 修复Windows时区错误与JSON字段初始化问题
- 移除无用管理接口代码并更新文档说明
2025-11-16 11:37:40 +08:00

177 lines
5.2 KiB
Go
Raw 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 title
import (
"context"
"encoding/json"
"time"
"bindbox-game/internal/pkg/logger"
"bindbox-game/internal/repository/mysql"
"bindbox-game/internal/repository/mysql/dao"
"bindbox-game/internal/repository/mysql/model"
)
// Service 提供头衔效果解析与领取型权益限流的服务
// Params:
// - ctx: 上下文
// - 依赖通过构造函数注入
// Returns:
// - 头衔相关功能的服务实例
type Service interface {
// ResolveActiveEffects 解析用户在指定上下文(issue/activity/category)下的激活头衔效果
// Params:
// - ctx: 上下文
// - userID: 用户ID
// - scope: 事件作用域(可空字段用于精细过滤)
// Returns:
// - 解析后的效果列表已按scopes过滤并仅保留启用效果
ResolveActiveEffects(ctx context.Context, userID int64, scope EffectScope) ([]*model.SystemTitleEffects, error)
}
type service struct {
logger logger.CustomLogger
readDB *dao.Query
writeDB *dao.Query
}
// New 创建头衔服务实例
// Params:
// - l: 日志器
// - db: 数据库仓库
// Returns:
// - 头衔服务实例
func New(l logger.CustomLogger, db mysql.Repo) Service {
return &service{
logger: l,
readDB: dao.Use(db.GetDbR()),
writeDB: dao.Use(db.GetDbW()),
}
}
// EffectScope 事件上下文作用域
// 用于按活动/期/分类过滤效果生效范围
type EffectScope struct {
ActivityID *int64
IssueID *int64
ActivityCategory *int64
}
// scopesPayload 用于解析SystemTitleEffects.ScopesJSON
type scopesPayload struct {
ActivityIDs []int64 `json:"activity_ids"`
IssueIDs []int64 `json:"issue_ids"`
CategoryIDs []int64 `json:"category_ids"`
Exclude struct {
ActivityIDs []int64 `json:"activity_ids"`
IssueIDs []int64 `json:"issue_ids"`
CategoryIDs []int64 `json:"category_ids"`
} `json:"exclude"`
}
// ResolveActiveEffects 查询用户激活头衔并解析效果返回在给定scope内生效的效果
// Params:
// - ctx: 上下文
// - userID: 用户ID
// - scope: 事件作用域
// Returns:
// - 生效效果列表
func (s *service) ResolveActiveEffects(ctx context.Context, userID int64, scope EffectScope) ([]*model.SystemTitleEffects, error) {
now := time.Now()
titles, err := s.readDB.UserTitles.WithContext(ctx).
Where(s.readDB.UserTitles.UserID.Eq(userID)).
Where(s.readDB.UserTitles.Active.Eq(1)).
Find()
if err != nil {
return nil, err
}
if len(titles) == 0 {
return []*model.SystemTitleEffects{}, nil
}
var selID int64
var selAt time.Time
for _, ut := range titles {
if ut.ExpiresAt.IsZero() || ut.ExpiresAt.After(now) {
if selID == 0 || ut.ObtainedAt.After(selAt) {
selID = ut.TitleID
selAt = ut.ObtainedAt
}
}
}
if selID == 0 {
return []*model.SystemTitleEffects{}, nil
}
effects, err := s.readDB.SystemTitleEffects.WithContext(ctx).
Where(s.readDB.SystemTitleEffects.TitleID.Eq(selID)).
Where(s.readDB.SystemTitleEffects.Status.Eq(1)).
Order(s.readDB.SystemTitleEffects.Sort).
Find()
if err != nil {
return nil, err
}
if len(effects) == 0 {
return []*model.SystemTitleEffects{}, nil
}
var result []*model.SystemTitleEffects
for _, ef := range effects {
if ef.ScopesJSON == "" {
result = append(result, ef)
continue
}
var sc scopesPayload
if err := json.Unmarshal([]byte(ef.ScopesJSON), &sc); err != nil {
// 解析失败时按“全局生效”处理
result = append(result, ef)
continue
}
if !scopeMatch(scope, sc) {
continue
}
result = append(result, ef)
}
return result, nil
}
// scopeMatch 判断效果scope是否命中当前事件上下文
// Params:
// - scope: 事件作用域
// - sc: 效果配置的scope载荷
// Returns:
// - 是否命中
func scopeMatch(scope EffectScope, sc scopesPayload) bool {
// 排除优先
if scope.ActivityID != nil && containsInt64(sc.Exclude.ActivityIDs, *scope.ActivityID) {
return false
}
if scope.IssueID != nil && containsInt64(sc.Exclude.IssueIDs, *scope.IssueID) {
return false
}
if scope.ActivityCategory != nil && containsInt64(sc.Exclude.CategoryIDs, *scope.ActivityCategory) {
return false
}
// 包含判定(未配置即视为全局)
if scope.ActivityID != nil && len(sc.ActivityIDs) > 0 && !containsInt64(sc.ActivityIDs, *scope.ActivityID) {
return false
}
if scope.IssueID != nil && len(sc.IssueIDs) > 0 && !containsInt64(sc.IssueIDs, *scope.IssueID) {
return false
}
if scope.ActivityCategory != nil && len(sc.CategoryIDs) > 0 && !containsInt64(sc.CategoryIDs, *scope.ActivityCategory) {
return false
}
return true
}
// containsInt64 判断切片是否包含指定值
// Params:
// - arr: 切片
// - v: 值
// Returns:
// - 是否包含
func containsInt64(arr []int64, v int64) bool {
for _, x := range arr {
if x == v {
return true
}
}
return false
}