diff --git a/internal/controller/energy_controller.go b/internal/controller/energy_controller.go index de7b10a..688b9ec 100644 --- a/internal/controller/energy_controller.go +++ b/internal/controller/energy_controller.go @@ -78,16 +78,19 @@ func (ec *EnergyController) GetEnergyInfo(c *gin.Context) { return } - energy, err := ec.energySvc.GetEnergyInfo(uid) + info, err := ec.energySvc.GetEnergyInfo(uid) if err != nil { common.Error(c, http.StatusInternalServerError, "获取域能信息失败") return } - energyDisplay := float64(energy) / 10 + energyDisplay := float64(info.Energy) / 10 common.Ok(c, gin.H{ - "energy": energy, - "energy_display": energyDisplay, + "energy": info.Energy, + "energy_display": energyDisplay, + "daily_energize_exp": info.DailyEnergizeExp, + "daily_exp_cap": info.DailyExpCap, + "daily_exp_cap_reached": info.DailyExpCapReached, }) } diff --git a/internal/controller/interfaces.go b/internal/controller/interfaces.go index e739cee..e117609 100644 --- a/internal/controller/interfaces.go +++ b/internal/controller/interfaces.go @@ -72,7 +72,7 @@ type levelUseCase interface { // energyUseCase EnergyController 对 EnergyService 的最小依赖(ISP:3 个方法) type energyUseCase interface { Energize(energizerID uint, postID uint, amount int) (int, error) - GetEnergyInfo(userID uint) (int, error) + GetEnergyInfo(userID uint) (*service.EnergyInfo, error) GetEnergyLogs(userID uint, days, page, pageSize int) ([]model.EnergyLog, int64, error) } diff --git a/internal/controller/settings_api_profile.go b/internal/controller/settings_api_profile.go index 9bd6af8..d1d4853 100644 --- a/internal/controller/settings_api_profile.go +++ b/internal/controller/settings_api_profile.go @@ -69,6 +69,29 @@ func (sc *SettingsController) UpdateProfile(c *gin.Context) { } // 管理员及以上直接更新 + // 先获取当前用户信息,判断是否改名 + currentUser, err := sc.authService.GetProfile(userID) + if err != nil { + common.Error(c, http.StatusInternalServerError, "操作失败") + return + } + + // 改名:首次免费,非首次扣 6 域能 + if req.Username != "" && req.Username != currentUser.Username && sc.energySvc != nil { + hasCompleted, _ := sc.levelSvc.HasCompletedTask(userID, "username") + if hasCompleted { + if err := sc.energySvc.DeductOnRename(userID); err != nil { + switch err { + case common.ErrInsufficientEnergy: + common.Error(c, http.StatusBadRequest, "域能不足,无法改名。可通过每日签到或创作被赋能获取") + default: + common.Error(c, http.StatusInternalServerError, "操作失败") + } + return + } + } + } + if err := sc.authService.UpdateProfile(userID, req.Username, req.Bio); err != nil { handleSettingsError(c, err) return diff --git a/internal/repository/energy_repo.go b/internal/repository/energy_repo.go index 8f2b260..8720622 100644 --- a/internal/repository/energy_repo.go +++ b/internal/repository/energy_repo.go @@ -4,6 +4,7 @@ import ( "time" "metazone.cc/metalab/internal/model" + "metazone.cc/metalab/internal/service" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -19,6 +20,11 @@ func NewEnergyRepo(db *gorm.DB) *EnergyRepo { return &EnergyRepo{db: db} } +// WithTx 基于给定事务连接创建新的 EnergyRepo(确保事务内操作原子性) +func (r *EnergyRepo) WithTx(tx *gorm.DB) service.EnergyStore { + return &EnergyRepo{db: tx} +} + // FindUserByID 查找用户(获取能量余额等) func (r *EnergyRepo) FindUserByID(userID uint) (*model.User, error) { var user model.User diff --git a/internal/service/energy_service.go b/internal/service/energy_service.go index 17aae7e..768270b 100644 --- a/internal/service/energy_service.go +++ b/internal/service/energy_service.go @@ -17,17 +17,25 @@ type expAdder interface { // EnergyService 域能业务逻辑 type EnergyService struct { - db *gorm.DB - repo energyStore - expSvc expAdder + db *gorm.DB + repo EnergyStore + expSvc expAdder notifSvc energyNotifier } // NewEnergyService 构造函数 -func NewEnergyService(db *gorm.DB, repo energyStore, expSvc expAdder, notifSvc energyNotifier) *EnergyService { +func NewEnergyService(db *gorm.DB, repo EnergyStore, expSvc expAdder, notifSvc energyNotifier) *EnergyService { return &EnergyService{db: db, repo: repo, expSvc: expSvc, notifSvc: notifSvc} } +// EnergyInfo 域能信息(含每日赋能经验状态) +type EnergyInfo struct { + Energy int // 域能余额(乘10存储) + DailyEnergizeExp int // 当日已通过赋能获得的经验值 + DailyExpCap int // 每日赋能经验上限 + DailyExpCapReached bool // 是否已达上限 +} + // SetExpAdder 链式注入经验服务(用于赋能获得经验) func (s *EnergyService) SetExpAdder(svc expAdder) { s.expSvc = svc @@ -57,8 +65,10 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int var expGained int err := s.db.Transaction(func(tx *gorm.DB) error { + txRepo := s.repo.WithTx(tx) + // 1. 检查帖子存在,获取作者 - post, err := s.repo.FindPostByID(postID) + post, err := txRepo.FindPostByID(postID) if err != nil { if err == gorm.ErrRecordNotFound { return common.ErrPostNotFound @@ -72,7 +82,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int } // 3. 检查赋能者域能余额(owner 跳过) - energizer, err := s.repo.FindUserByID(energizerID) + energizer, err := txRepo.FindUserByID(energizerID) if err != nil { return fmt.Errorf("查询用户失败: %w", err) } @@ -81,7 +91,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int } // 4. 检查单用户单文章赋能总量 ≤ MaxEnergizePerPost - total, err := s.repo.SumUserPostEnergy(energizerID, postID) + total, err := txRepo.SumUserPostEnergy(energizerID, postID) if err != nil { return fmt.Errorf("查询赋能记录失败: %w", err) } @@ -90,18 +100,18 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int } // 5. 扣赋能者域能 - if err := s.repo.AddEnergy(energizerID, -amount); err != nil { + if err := txRepo.AddEnergy(energizerID, -amount); err != nil { return fmt.Errorf("扣减域能失败: %w", err) } // 6. 增加被赋能者(文章作者)域能(赋能量的十分之一) receivedAmount := amount / 10 // 1 域能→0.1,2 域能→0.2 - if err := s.repo.AddEnergy(post.UserID, receivedAmount); err != nil { + if err := txRepo.AddEnergy(post.UserID, receivedAmount); err != nil { return fmt.Errorf("增加被赋能者域能失败: %w", err) } // 7. 更新 Post.total_energy_received - if err := s.repo.IncrPostEnergy(postID, amount); err != nil { + if err := txRepo.IncrPostEnergy(postID, amount); err != nil { return fmt.Errorf("更新文章赋能计数失败: %w", err) } @@ -111,7 +121,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int PostID: postID, Amount: amount, } - if err := s.repo.CreateEnergizeLog(eLog); err != nil { + if err := txRepo.CreateEnergizeLog(eLog); err != nil { return fmt.Errorf("创建赋能记录失败: %w", err) } @@ -127,7 +137,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int RelatedID: postID, Description: fmt.Sprintf("给文章 %d 赋能 %.1f 域能", postID, amountDisplay), } - if err := s.repo.CreateEnergyLog(energizerLog); err != nil { + if err := txRepo.CreateEnergyLog(energizerLog); err != nil { return fmt.Errorf("创建赋能者流水失败: %w", err) } @@ -139,7 +149,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int RelatedID: postID, Description: fmt.Sprintf("文章 %d 被赋能 +%.1f 域能", postID, receivedDisplay), } - if err := s.repo.CreateEnergyLog(energizedLog); err != nil { + if err := txRepo.CreateEnergyLog(energizedLog); err != nil { return fmt.Errorf("创建被赋能者流水失败: %w", err) } @@ -148,7 +158,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int now := time.Now().In(loc) today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc) - summary, err := s.repo.GetDailyEnergizeExp(energizerID, today) + summary, err := txRepo.GetDailyEnergizeExp(energizerID, today) currentDaily := 0 if err == nil { currentDaily = summary.ExpEarned @@ -177,7 +187,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int Date: today, ExpEarned: currentDaily + expGain, } - if err := s.repo.UpsertDailyExp(newSummary); err != nil { + if err := txRepo.UpsertDailyExp(newSummary); err != nil { return fmt.Errorf("更新每日经验汇总失败: %w", err) } } @@ -282,13 +292,29 @@ func (s *EnergyService) AdminAdjust(operatorUID uint, userIDs []uint, amount int return nil } -// GetEnergyInfo 获取用户域能余额 -func (s *EnergyService) GetEnergyInfo(userID uint) (int, error) { +// GetEnergyInfo 获取用户域能余额和每日赋能经验状态 +func (s *EnergyService) GetEnergyInfo(userID uint) (*EnergyInfo, error) { user, err := s.repo.FindUserByID(userID) if err != nil { - return 0, err + return nil, err } - return user.Energy, 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) + + dailyExp := 0 + summary, err := s.repo.GetDailyEnergizeExp(userID, today) + if err == nil { + dailyExp = summary.ExpEarned + } + + return &EnergyInfo{ + Energy: user.Energy, + DailyEnergizeExp: dailyExp, + DailyExpCap: DailyEnergizeExpCap, + DailyExpCapReached: dailyExp >= DailyEnergizeExpCap, + }, nil } // GetEnergyLogs 分页查询用户域能流水(近 N 天) diff --git a/internal/service/repository.go b/internal/service/repository.go index 9986524..fadf170 100644 --- a/internal/service/repository.go +++ b/internal/service/repository.go @@ -4,6 +4,8 @@ import ( "time" "metazone.cc/metalab/internal/model" + + "gorm.io/gorm" ) // userAuthStore AuthService 所需的最小仓储接口(ISP:7 个方法) @@ -87,8 +89,8 @@ type levelNotifier interface { Create(userID uint, notifyType, title, content string, relatedID *uint) error } -// energyStore EnergyService 所需的完整仓储接口 -type energyStore interface { +// EnergyStore EnergyService 所需的完整仓储接口 +type EnergyStore interface { FindUserByID(userID uint) (*model.User, error) FindPostByID(postID uint) (*model.Post, error) AddEnergy(userID uint, amount int) error @@ -102,8 +104,13 @@ type energyStore interface { ListEnergyLogsByUser(userID uint, days int, offset, limit int) ([]model.EnergyLog, error) CountEnergyLogsByUser(userID uint, days int) (int64, error) ListAllEnergyLogs(energyType string, offset, limit int) ([]model.EnergyLog, int64, error) + // WithTx 基于给定事务连接创建新的 EnergyStore(确保事务内操作原子性) + WithTx(tx *gorm.DB) EnergyStore } +// energyStore 向后兼容别名 +type energyStore = EnergyStore + // energyNotifier EnergyService 所需的通知接口(ISP) type energyNotifier interface { Create(userID uint, notifyType, title, content string, relatedID *uint) error diff --git a/templates/MetaLab-2026/html/posts/show.html b/templates/MetaLab-2026/html/posts/show.html index cc64640..be2d850 100644 --- a/templates/MetaLab-2026/html/posts/show.html +++ b/templates/MetaLab-2026/html/posts/show.html @@ -43,6 +43,33 @@ {{end}} + + +
{{end}} @@ -112,45 +139,83 @@ diff --git a/templates/MetaLab-2026/html/settings/index.html b/templates/MetaLab-2026/html/settings/index.html index 4be9ea1..fbab797 100644 --- a/templates/MetaLab-2026/html/settings/index.html +++ b/templates/MetaLab-2026/html/settings/index.html @@ -159,7 +159,7 @@域能流水和余额
+域能余额和经验