fix: 修复文章赋能死锁导致请求卡住的问题

- 事务内操作 users 表(扣域能)后,调用 LevelService.AddExp 也操作同一 users 表行,不同 DB 连接间产生行锁死锁
- 将 AddExp 和 UpsertDailyExp 移出事务,等事务提交释放行锁后再执行
- 经验计算仍在事务内读取以保证一致性
This commit is contained in:
2026-06-01 14:18:28 +08:00
parent 43c35ddaf2
commit 683c9e02d8

View File

@ -63,6 +63,9 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
}
var expGained int
var needExpUpdate bool
var expToday time.Time
var expTotal int
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
@ -153,7 +156,7 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
return fmt.Errorf("创建被赋能者流水失败: %w", err)
}
// 10. 计算经验值(溢出截断)
// 10. 计算经验值(溢出截断)—— 在事务内读取以保证一致性
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
@ -173,30 +176,38 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
expGain = expRoom
}
// 11. 增加经验 + 更新每日经验汇总
if expGain > 0 {
if s.expSvc != nil {
_, _, err = s.expSvc.AddExp(energizerID, expGain)
if err != nil {
return fmt.Errorf("增加经验失败: %w", err)
}
}
newSummary := &model.DailyExpSummary{
UserID: energizerID,
Date: today,
ExpEarned: currentDaily + expGain,
}
if err := txRepo.UpsertDailyExp(newSummary); err != nil {
return fmt.Errorf("更新每日经验汇总失败: %w", err)
}
}
expGained = expGain
if expGain > 0 {
needExpUpdate = true
expToday = today
expTotal = currentDaily + expGain
}
return nil
})
if err != nil {
return 0, err
}
return expGained, err
// 11. 事务提交后,在事务外增加经验 + 更新每日汇总
// 必须在事务外执行LevelService.AddExp 也操作 users 表,
// 若在事务内调用会造成连接池不同连接间行锁死锁。
if needExpUpdate {
if s.expSvc != nil {
if _, _, err := s.expSvc.AddExp(energizerID, expGained); err != nil {
return expGained, fmt.Errorf("赋能成功,但增加经验失败: %w", err)
}
}
newSummary := &model.DailyExpSummary{
UserID: energizerID,
Date: expToday,
ExpEarned: expTotal,
}
if err := s.repo.UpsertDailyExp(newSummary); err != nil {
return expGained, fmt.Errorf("赋能成功,但更新经验汇总失败: %w", err)
}
}
return expGained, nil
}
// CheckInEnergy 签到获得域能LV0 用户不调用此方法)