feat: 赋能溢出截断 + 前端按已赋能量禁用按钮

- 单篇赋能上限由硬拒绝改为溢出截断,类似每日经验处理
- 前端弹窗时查询文章已赋能总量,>=10 禁用重赋,>=20 全部禁用
- GetEnergyInfo 支持 ?post_id= 返回 post_energize_total
- 文案精简为'赋能成功!+N 经验'等短句
This commit is contained in:
2026-06-03 00:13:03 +08:00
parent 4442da31c3
commit 1af9fba291
6 changed files with 90 additions and 30 deletions

View File

@ -37,7 +37,7 @@ func (ec *EnergyController) Energize(c *gin.Context) {
return
}
expGained, err := ec.energySvc.Energize(uid, req.PostID, req.Amount)
expGained, actualAmount, err := ec.energySvc.Energize(uid, req.PostID, req.Amount)
if err != nil {
switch err {
case common.ErrPostNotFound:
@ -56,21 +56,30 @@ func (ec *EnergyController) Energize(c *gin.Context) {
return
}
truncated := actualAmount != req.Amount
common.Ok(c, gin.H{
"exp_gained": expGained,
"message": buildEnergizeMessage(expGained),
"exp_gained": expGained,
"actual_amount": actualAmount,
"truncated": truncated,
"message": buildEnergizeMessage(expGained, truncated),
})
}
// buildEnergizeMessage 根据获得的经验生成提示消息
func buildEnergizeMessage(expGained int) string {
if expGained > 0 {
return "赋能成功!" + strconv.Itoa(expGained) + "经验已加入你的旅程。"
func buildEnergizeMessage(expGained int, truncated bool) string {
if truncated {
if expGained > 0 {
return "单篇额度已满,实际赋能部分额度,+" + strconv.Itoa(expGained) + " 经验"
}
return "单篇额度已满,实际赋能部分额度"
}
return "赋能成功!今日赋能经验奖励已达上限,对方仍会获得激励。"
if expGained > 0 {
return "赋能成功!+" + strconv.Itoa(expGained) + " 经验"
}
return "赋能成功!今日经验已满,对方仍获得激励"
}
// GetEnergyInfo 获取域能余额和经验上限状态
// GetEnergyInfo 获取域能余额和经验上限状态,支持 ?post_id= 查询文章赋能总量
func (ec *EnergyController) GetEnergyInfo(c *gin.Context) {
uid, _, ok := common.GetGinUser(c)
if !ok {
@ -78,7 +87,10 @@ func (ec *EnergyController) GetEnergyInfo(c *gin.Context) {
return
}
info, err := ec.energySvc.GetEnergyInfo(uid)
postIDStr := c.DefaultQuery("post_id", "0")
postID, _ := strconv.ParseUint(postIDStr, 10, 64)
info, err := ec.energySvc.GetEnergyInfo(uid, uint(postID))
if err != nil {
common.Error(c, http.StatusInternalServerError, "获取域能信息失败")
return

View File

@ -85,8 +85,8 @@ type levelUseCase interface {
// energyUseCase EnergyController 对 EnergyService 的最小依赖ISP3 个方法)
type energyUseCase interface {
Energize(energizerID uint, postID uint, amount int) (int, error)
GetEnergyInfo(userID uint) (*service.EnergyInfo, error)
Energize(energizerID uint, postID uint, amount int) (int, int, error)
GetEnergyInfo(userID uint, postID uint) (*service.EnergyInfo, error)
GetEnergyLogs(userID uint, days, page, pageSize int) ([]model.EnergyLog, int64, error)
}

View File

@ -9,11 +9,11 @@ import (
)
// Energize 赋能文章
// 返回:获得的经验值、错误
func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int, error) {
// 返回:获得的经验值、实际赋能量DB乘10错误
func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int, int, error) {
// 验证赋能量
if amount != EnergyEnergize1 && amount != EnergyEnergize2 {
return 0, common.ErrInvalidEnergyAmount
return 0, 0, common.ErrInvalidEnergyAmount
}
var expGained int
@ -21,6 +21,9 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
var expToday time.Time
var expTotal int
// 实际使用的赋能量(可能因上限被截断)
actualAmount := amount
err := s.repo.Transaction(func(txRepo EnergyStore, txFundRepo FundStore) error {
// 1. 检查帖子存在,获取作者
post, err := txRepo.FindPostByID(postID)
@ -42,14 +45,21 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
return common.ErrInsufficientEnergy
}
// 4. 检查单用户单文章赋能总量 ≤ MaxEnergizePerPost
// 4. 检查单用户单文章赋能上限,超出部分截断(类似每日经验溢出截断)
total, err := txRepo.SumUserPostEnergy(energizerID, postID)
if err != nil {
return fmt.Errorf("查询赋能记录失败: %w", err)
}
if total+amount > MaxEnergizePerPost {
room := MaxEnergizePerPost - total
if room <= 0 {
return common.ErrEnergizeLimitReached
}
if amount > room {
actualAmount = room // 截断到剩余空间
}
// 重新赋值 amount 用于后续扣域能、写日志等
amount = actualAmount
// 5. 扣赋能者域能
if err := txRepo.AddEnergy(energizerID, -amount); err != nil {
@ -155,14 +165,14 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
return nil
})
if err != nil {
return 0, err
return 0, 0, err
}
// 12. 事务提交后,在事务外增加经验 + 更新每日汇总
if needExpUpdate {
if s.expSvc != nil {
if _, _, err := s.expSvc.AddExp(energizerID, expGained); err != nil {
return expGained, fmt.Errorf("赋能成功,但增加经验失败: %w", err)
return expGained, actualAmount, fmt.Errorf("赋能成功,但增加经验失败: %w", err)
}
}
newSummary := &model.DailyExpSummary{
@ -171,9 +181,9 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
ExpEarned: expTotal,
}
if err := s.repo.UpsertDailyExp(newSummary); err != nil {
return expGained, fmt.Errorf("赋能成功,但更新经验汇总失败: %w", err)
return expGained, actualAmount, fmt.Errorf("赋能成功,但更新经验汇总失败: %w", err)
}
}
return expGained, nil
return expGained, actualAmount, nil
}

View File

@ -8,7 +8,8 @@ import (
)
// GetEnergyInfo 获取用户域能余额和每日赋能经验状态
func (s *EnergyService) GetEnergyInfo(userID uint) (*EnergyInfo, error) {
// postID 为 0 时不查文章赋能总量
func (s *EnergyService) GetEnergyInfo(userID uint, postID uint) (*EnergyInfo, error) {
user, err := s.repo.FindUserByID(userID)
if err != nil {
return nil, err
@ -24,12 +25,21 @@ func (s *EnergyService) GetEnergyInfo(userID uint) (*EnergyInfo, error) {
dailyExp = summary.ExpEarned
}
return &EnergyInfo{
info := &EnergyInfo{
Energy: user.Energy,
DailyEnergizeExp: dailyExp,
DailyExpCap: DailyEnergizeExpCap,
DailyExpCapReached: dailyExp >= DailyEnergizeExpCap,
}, nil
}
if postID != 0 {
total, err := s.repo.SumUserPostEnergy(userID, postID)
if err == nil {
info.PostEnergizeTotal = total
}
}
return info, nil
}
// GetEnergyLogs 分页查询用户域能流水(近 N 天)

View File

@ -30,10 +30,11 @@ func (s *EnergyService) SetFundRepo(repo FundStore) {
// EnergyInfo 域能信息(含每日赋能经验状态)
type EnergyInfo struct {
Energy int // 域能余额乘10存储
DailyEnergizeExp int // 当日已通过赋能获得的经验值
DailyExpCap int // 每日赋能经验上限
DailyExpCapReached bool // 是否已达上限
Energy int // 域能余额乘10存储
DailyEnergizeExp int // 当日已通过赋能获得的经验值
DailyExpCap int // 每日赋能经验上限
DailyExpCapReached bool // 是否已达上限
PostEnergizeTotal int // 当前文章已赋能量DB乘10仅在传 postID 时返回)
}
// 域能相关常量乘10存储DB值

View File

@ -32,6 +32,25 @@
capHint.style.display = 'none';
lightBtn.disabled = false;
heavyBtn.disabled = false;
heavyBtn.title = '';
heavyBtn.style.opacity = '';
lightBtn.title = '';
lightBtn.style.opacity = '';
}
function disableHeavy() {
heavyBtn.disabled = true;
heavyBtn.title = '已达单篇上限1/2';
heavyBtn.style.opacity = '0.4';
}
function disableAll() {
lightBtn.disabled = true;
heavyBtn.disabled = true;
lightBtn.title = '已达单篇上限2/2';
lightBtn.style.opacity = '0.4';
heavyBtn.title = '已达单篇上限2/2';
heavyBtn.style.opacity = '0.4';
}
btn.addEventListener('click', function() {
@ -39,11 +58,19 @@
showToast('无法给自己赋能');
return;
}
fetch('/api/energy/info')
fetch('/api/energy/info?post_id=' + postId)
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success && d.data && d.data.daily_exp_cap_reached) {
capHint.style.display = 'block';
if (d.success && d.data) {
if (d.data.daily_exp_cap_reached) {
capHint.style.display = 'block';
}
var total = d.data.post_energize_total || 0;
if (total >= 20) {
disableAll();
} else if (total >= 10) {
disableHeavy();
}
}
showOverlay();
})