Files
mce/internal/service/level_service.go

174 lines
4.6 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 service
import (
"fmt"
"time"
"metazone.cc/mce/internal/model"
)
// LevelService 积分与等级业务逻辑
type LevelService struct {
userRepo userLevelStore
checkRepo checkInStore
taskRepo taskStore
notifSvc levelNotifier
energyCheckInSvc energyCheckIner
}
// energyCheckIner LevelService 对域能签到的最小依赖ISP
type energyCheckIner interface {
CheckInEnergy(userID uint) error
}
// NewLevelService 构造函数
func NewLevelService(userRepo userLevelStore, checkRepo checkInStore, taskRepo taskStore, notifSvc levelNotifier) *LevelService {
return &LevelService{userRepo: userRepo, checkRepo: checkRepo, taskRepo: taskRepo, notifSvc: notifSvc}
}
// WithEnergyService 链式注入域能服务(用于签到域能奖励)
func (s *LevelService) WithEnergyService(svc energyCheckIner) *LevelService {
s.energyCheckInSvc = svc
return s
}
// GetLevel 获取用户当前等级、经验值和今日签到状态
func (s *LevelService) GetLevel(userID uint) (level int, exp int, checkedIn bool, err error) {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return 0, 0, false, err
}
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
_, err = s.checkRepo.FindCheckIn(userID, today)
checkedIn = err == nil
return model.GetLevelByExp(user.Exp), user.Exp, checkedIn, nil
}
// AddExp 增加经验值,自动检查升级并发送通知
// 返回:新经验值、是否升级、错误
func (s *LevelService) AddExp(userID uint, delta int) (newExp int, levelUp bool, err error) {
if delta <= 0 {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return 0, false, err
}
return user.Exp, false, nil
}
// 先查旧等级
user, err := s.userRepo.FindByID(userID)
if err != nil {
return 0, false, err
}
oldLevel := model.GetLevelByExp(user.Exp)
// 原子增加经验值
if err := s.userRepo.AddExp(userID, delta); err != nil {
return 0, false, err
}
// 查新等级
user, err = s.userRepo.FindByID(userID)
if err != nil {
return 0, false, err
}
newLevel := model.GetLevelByExp(user.Exp)
if newLevel > oldLevel {
levelUp = true
if s.notifSvc != nil {
_ = s.notifSvc.Create(userID, model.NotifyLevelUp,
"等级提升",
fmt.Sprintf("恭喜您!您的等级已提升至 %s", model.GetLevelName(newLevel)),
nil, nil)
}
}
return user.Exp, levelUp, nil
}
// CheckIn 执行签到(基于 Asia/Shanghai 自然日严格防重)
// 返回:是否成功签到、新经验值、是否升级、错误
// LV0 用户不触发签到
func (s *LevelService) CheckIn(userID uint) (checkedIn bool, newExp int, levelUp bool, err error) {
// LV0 用户不触发签到
user, err := s.userRepo.FindByID(userID)
if err != nil {
return false, 0, false, err
}
if user.Exp < 1 {
return false, user.Exp, false, nil
}
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
// 检查今日是否已签到
_, err = s.checkRepo.FindCheckIn(userID, today)
if err == nil {
// 已签到
return false, user.Exp, false, nil
}
// 创建签到记录
ci := &model.UserCheckIn{
UserID: userID,
Date: today,
}
if err := s.checkRepo.CreateCheckIn(ci); err != nil {
// 唯一索引冲突 → 已签到
return false, user.Exp, false, nil
}
// 增加域能(静默失败不影响签到经验)
if s.energyCheckInSvc != nil {
_ = s.energyCheckInSvc.CheckInEnergy(userID)
}
// 增加经验值
newExp, levelUp, err = s.AddExp(userID, 5)
return true, newExp, levelUp, err
}
// HasCompletedTask 检查用户是否已完成过指定任务(无副作用查询)
func (s *LevelService) HasCompletedTask(userID uint, taskType string) (bool, error) {
return s.taskRepo.HasTask(userID, taskType)
}
// CompleteTask 完成首次任务(严格防重)
// 返回:新经验值、是否升级、错误
func (s *LevelService) CompleteTask(userID uint, taskType string) (newExp int, levelUp bool, err error) {
// 检查是否已完成
_, err = s.taskRepo.FindTask(userID, taskType)
if err == nil {
// 已完成过,不重复奖励
user, _ := s.userRepo.FindByID(userID)
if user != nil {
return user.Exp, false, nil
}
return 0, false, nil
}
// 创建任务记录
task := &model.UserTask{
UserID: userID,
TaskType: taskType,
}
if err := s.taskRepo.CreateTask(task); err != nil {
// 唯一索引冲突 → 已完成
user, _ := s.userRepo.FindByID(userID)
if user != nil {
return user.Exp, false, nil
}
return 0, false, nil
}
// 增加经验值
return s.AddExp(userID, 10)
}