refactor(utils): 修复密码哈希比较逻辑错误 feat(user): 新增按状态筛选优惠券接口 docs: 添加虚拟发货与任务中心相关文档 fix(wechat): 修正Code2Session上下文传递问题 test: 补充订单折扣与积分转换测试用例 build: 更新配置文件与构建脚本 style: 清理多余的空行与注释
67 lines
2.0 KiB
Go
67 lines
2.0 KiB
Go
package user
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
|
||
"bindbox-game/internal/repository/mysql/dao"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
|
||
"gorm.io/gorm/clause"
|
||
)
|
||
|
||
// ConsumePointsFor 扣减用户积分(通用)
|
||
// 功能:按有效期优先扣减用户积分,并记录积分流水到 `user_points_ledger`
|
||
// 参数:
|
||
// - ctx:上下文
|
||
// - userID:用户ID
|
||
// - points:扣减积分数量
|
||
// - refTable:业务关联表名(例如 products、system_coupons)
|
||
// - refID:业务关联ID字符串(例如 product_id、coupon_id)
|
||
// - remark:备注信息
|
||
// - action:流水动作标识(例如 redeem_product、redeem_coupon)
|
||
//
|
||
// 返回:ledgerID(积分流水ID),error(不足或写入失败时)
|
||
func (s *service) ConsumePointsFor(ctx context.Context, userID int64, points int64, refTable string, refID string, remark string, action string) (int64, error) {
|
||
if points <= 0 {
|
||
return 0, nil
|
||
}
|
||
var ledgerID int64
|
||
err := s.writeDB.Transaction(func(tx *dao.Query) error {
|
||
rows, err := tx.UserPoints.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where(tx.UserPoints.UserID.Eq(userID)).Order(tx.UserPoints.ValidEnd.Asc()).Find()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
remain := points
|
||
now := time.Now()
|
||
for _, r := range rows {
|
||
if !r.ValidEnd.IsZero() && r.ValidEnd.Before(now) {
|
||
continue
|
||
}
|
||
if remain <= 0 {
|
||
break
|
||
}
|
||
use := r.Points
|
||
if use > remain {
|
||
use = remain
|
||
}
|
||
_, err := tx.UserPoints.WithContext(ctx).Where(tx.UserPoints.ID.Eq(r.ID)).Updates(map[string]any{"points": r.Points - use})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
remain -= use
|
||
}
|
||
if remain > 0 {
|
||
return errors.New("insufficient_points")
|
||
}
|
||
led := &model.UserPointsLedger{UserID: userID, Action: action, Points: -points, RefTable: refTable, RefID: refID, Remark: remark}
|
||
if err := tx.UserPointsLedger.WithContext(ctx).Create(led); err != nil {
|
||
return err
|
||
}
|
||
ledgerID = led.ID
|
||
return nil
|
||
})
|
||
return ledgerID, err
|
||
}
|