feat(camelOil): 使用智能调度算法优化Token绑定逻辑
- 移除旧的可用Token检查和轮询绑定逻辑 - 新增GetOptimalTokenForBinding方法,按上传时间顺序选择最优Token - 增加isTokenEligible方法,全面校验Token有效性和用户余额 - 绑定卡密操作改为调用智能调度算法选出的最优Token进行绑定 - 完善绑定失败和异常处理,增加日志记录细节信息 - 提升绑卡选Token的准确性和业务鲁棒性
This commit is contained in:
@@ -327,17 +327,6 @@ func (s *sCamelOil) CronCardBindingTask(ctx context.Context) error {
|
||||
failCount := 0
|
||||
|
||||
for _, order := range orders {
|
||||
// 检查是否有可用的 Token
|
||||
availableTokenCount, err2 := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilToken.Columns().Status, consts.CamelOilTokenStatusAvailable).
|
||||
Count()
|
||||
|
||||
if err2 != nil || availableTokenCount == 0 {
|
||||
glog.Warningf(ctx, "无可用 Token,订单 ID: %d 无法绑定", order.Id)
|
||||
failCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查卡号和卡密是否存在
|
||||
if order.CardNumber == "" || order.CardPassword == "" {
|
||||
glog.Warningf(ctx, "订单 %d 卡号或卡密未填写,无法绑定", order.Id)
|
||||
@@ -345,7 +334,7 @@ func (s *sCamelOil) CronCardBindingTask(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// 尝试绑定卡密到 Token
|
||||
// 尝试绑定卡密到 Token(使用智能调度算法)
|
||||
_, err = s.BindCardToToken(ctx, &model.CamelOilCardBindInput{
|
||||
OrderId: order.Id,
|
||||
CardNumber: order.CardNumber,
|
||||
|
||||
@@ -152,6 +152,100 @@ func (s *sCamelOil) ListTokensWithPagination(ctx context.Context, req *model.Cam
|
||||
return tokens, totalCount, nil
|
||||
}
|
||||
|
||||
// GetOptimalTokenForBinding 获取最优Token用于绑卡(按上传时间顺序选择)
|
||||
func (s *sCamelOil) GetOptimalTokenForBinding(ctx context.Context, amount decimal.Decimal) (*entity.V1CamelOilToken, error) {
|
||||
// 获取所有普通用户上传的可用的Token(按上传时间顺序,最早上传的在前)
|
||||
var tokens []*entity.V1CamelOilToken
|
||||
err := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1CamelOilToken.Columns().Status, consts.CamelOilTokenStatusAvailable).
|
||||
WhereNot(dao.V1CamelOilToken.Columns().UserId, ""). // 只获取普通用户上传的Token(UserId不为空)
|
||||
OrderAsc(dao.V1CamelOilToken.Columns().CreatedAt). // 按上传时间排序,最早上传的在前
|
||||
Scan(&tokens)
|
||||
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "查询Token列表失败")
|
||||
}
|
||||
|
||||
if len(tokens) == 0 {
|
||||
return nil, gerror.New("没有可用的用户Token")
|
||||
}
|
||||
|
||||
// 按上传时间顺序筛选第一个符合条件的Token
|
||||
for _, token := range tokens {
|
||||
if s.isTokenEligible(ctx, token, amount) {
|
||||
glog.Infof(ctx, "按上传时间顺序选择用户Token: tokenId=%d, phone=%s, name=%s, userId=%s, 金额=%.2f",
|
||||
token.Id, token.Phone, token.Name, token.UserId, amount.InexactFloat64())
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, gerror.New("没有符合条件的用户Token")
|
||||
}
|
||||
|
||||
// isTokenEligible 检查Token是否符合使用条件(简化版)
|
||||
func (s *sCamelOil) isTokenEligible(ctx context.Context, token *entity.V1CamelOilToken, amount decimal.Decimal) bool {
|
||||
// 基本状态检查
|
||||
if token.Status != int(consts.CamelOilTokenStatusAvailable) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查Token有效性(是否过期)
|
||||
if token.LoginTokenExpiresAt != nil && token.LoginTokenExpiresAt.Before(gtime.Now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查充值限制:总绑卡数量或金额任一超过限制就停止调度
|
||||
// 1. 充值金额限制检查(检查本次充值后是否会超过限制)
|
||||
if token.RechargeLimitAmount.Cmp(decimal.Zero) > 0 {
|
||||
newTotalAmount := token.TotalBindAmount.Add(amount)
|
||||
if newTotalAmount.Cmp(token.RechargeLimitAmount) > 0 {
|
||||
glog.Debugf(ctx, "Token本次充值后将超限,tokenId=%d, 已充值=%.2f, 本次=%.2f, 合计=%.2f, 限制=%.2f",
|
||||
token.Id, token.TotalBindAmount.InexactFloat64(), amount.InexactFloat64(),
|
||||
newTotalAmount.InexactFloat64(), token.RechargeLimitAmount.InexactFloat64())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 充值次数限制检查
|
||||
if token.RechargeLimitCount > 0 {
|
||||
if token.BindCount >= token.RechargeLimitCount {
|
||||
glog.Debugf(ctx, "Token充值次数已达上限,tokenId=%d, 已使用=%d, 限制=%d",
|
||||
token.Id, token.BindCount, token.RechargeLimitCount)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户余额信息和状态检查
|
||||
if token.UserId != "" {
|
||||
// 1. 检查用户状态是否正常
|
||||
isNormal, err := service.SysUser().CheckUserNormal(ctx, token.UserId)
|
||||
if err != nil {
|
||||
glog.Warningf(ctx, "检查用户状态失败,userId=%s: %v", token.UserId, err)
|
||||
return false
|
||||
}
|
||||
if !isNormal {
|
||||
glog.Debugf(ctx, "用户状态异常,userId=%s", token.UserId)
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. 检查用户余额是否充足
|
||||
isEnough, err := service.SysUserPayment().CheckBalanceEnough(ctx, &model.SysUserPaymentCheckBalanceInput{
|
||||
UserId: token.UserId,
|
||||
Amount: amount,
|
||||
})
|
||||
if err != nil {
|
||||
glog.Warningf(ctx, "检查用户余额失败,userId=%s: %v", token.UserId, err)
|
||||
return false
|
||||
}
|
||||
if !isEnough {
|
||||
glog.Debugf(ctx, "用户余额不足,userId=%s, 金额=%.2f", token.UserId, amount.InexactFloat64())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// UpdateTokenInfo 修改 Token 基本信息(不包括 tokenValue)
|
||||
func (s *sCamelOil) UpdateTokenInfo(ctx context.Context, req *model.CamelOilTokenUpdateInput) error {
|
||||
m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1())
|
||||
@@ -269,12 +363,13 @@ func (s *sCamelOil) UpdateTokenStatus(ctx context.Context, req *model.CamelOilTo
|
||||
// 卡密绑定相关方法
|
||||
// ====================================================================================
|
||||
|
||||
// BindCardToToken 绑定卡密到 Token(使用轮询算法选择 Token)
|
||||
// BindCardToToken 绑定卡密到 Token(使用智能调度算法选择 Token)
|
||||
func (s *sCamelOil) BindCardToToken(ctx context.Context, req *model.CamelOilCardBindInput) (bindingId int64, err error) {
|
||||
orderId := req.OrderId
|
||||
cardNumber := req.CardNumber
|
||||
cardPassword := req.CardPassword
|
||||
amount := req.Amount
|
||||
|
||||
// 1. 获取订单信息
|
||||
var order *entity.V1CamelOilOrder
|
||||
err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
@@ -287,26 +382,13 @@ func (s *sCamelOil) BindCardToToken(ctx context.Context, req *model.CamelOilCard
|
||||
return 0, gerror.New("订单不存在")
|
||||
}
|
||||
|
||||
// 2. 获取所有可用的 Token
|
||||
tokens, err := s.ListTokens(ctx)
|
||||
// 2. 使用智能调度算法选择最优 Token
|
||||
selectedToken, err := s.GetOptimalTokenForBinding(ctx, amount)
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "查询Token列表失败")
|
||||
return 0, gerror.Wrap(err, "获取可用Token失败")
|
||||
}
|
||||
|
||||
if len(tokens) == 0 {
|
||||
return 0, gerror.New("没有可用的Token")
|
||||
}
|
||||
|
||||
// 3. 使用轮询算法选择 Token(选择绑定金额最少的 Token)
|
||||
var selectedToken *entity.V1CamelOilToken
|
||||
minAmount := tokens[0].TotalBindAmount
|
||||
selectedToken = tokens[0]
|
||||
|
||||
for _, token := range tokens {
|
||||
if token.TotalBindAmount.Cmp(minAmount) < 0 {
|
||||
minAmount = token.TotalBindAmount
|
||||
selectedToken = token
|
||||
}
|
||||
if selectedToken == nil {
|
||||
return 0, gerror.New("没有符合条件的Token")
|
||||
}
|
||||
|
||||
// 4.2 调用绑卡接口(使用选中 Token 的 LoginToken)
|
||||
@@ -436,7 +518,7 @@ func (s *sCamelOil) GetCardBindingsByToken(ctx context.Context, req *model.Camel
|
||||
return nil, 0, gerror.Wrap(err, "查询绑定记录失败")
|
||||
}
|
||||
|
||||
return bindings, int(count), nil
|
||||
return bindings, count, nil
|
||||
}
|
||||
|
||||
// GetTokenBindingStats 获取 Token 的绑定统计
|
||||
|
||||
Reference in New Issue
Block a user