fix(delivery): 禁用Rock二次生成二维码并治理上游110001频控报错
1. 业务防乱(专属通道锁定):提货页面一旦生成二维码后,直接禁用'重新生成二维码'按钮并锁定游戏UID输入框,杜绝Token浪费与绑定混乱; 2. 频控治理(前端3秒冷却):手动点击'检查扫码状态'增加3秒倒计时防抖冷却,且对'过于频繁/110001'错误进行平滑提示,不再触发大红报错; 3. 频控治理(后端瞬时防抖与软降级):RockService增加800ms内存并发安全防抖缓存,当上游返回110001时自动优雅降级返回等待扫码状态,不抛出HTTP 400错误。
This commit is contained in:
@@ -15,11 +15,17 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type rockRefreshCacheItem struct {
|
||||
response *RockRefreshResponse
|
||||
cachedAt time.Time
|
||||
}
|
||||
|
||||
// RockService 负责管理 Rock 平台的配置、库存池、采购、UID强一致风控与状态机
|
||||
type RockService struct {
|
||||
db *gorm.DB
|
||||
client *RockClient
|
||||
mu sync.Mutex
|
||||
db *gorm.DB
|
||||
client *RockClient
|
||||
mu sync.Mutex
|
||||
refreshCache sync.Map // key: upstreamTokenID string, value: rockRefreshCacheItem
|
||||
}
|
||||
|
||||
func NewRockService(db *gorm.DB, client *RockClient) *RockService {
|
||||
@@ -426,10 +432,48 @@ func (s *RockService) CheckBindingAndVerifyUID(ctx context.Context, tokenID uint
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 调上游 refresh 接口
|
||||
ref, err := s.client.RefreshToken(ctx, cfg.BearerToken, token.UpstreamTokenID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("刷新绑定状态失败: %w", err)
|
||||
// 调上游 refresh 接口(带 800ms 瞬时防抖缓存,避免自动轮询与手动高频点击撞上上游 110001 频控)
|
||||
var ref *RockRefreshResponse
|
||||
cacheVal, hasCache := s.refreshCache.Load(token.UpstreamTokenID)
|
||||
if hasCache {
|
||||
item := cacheVal.(rockRefreshCacheItem)
|
||||
if time.Since(item.cachedAt) < 800*time.Millisecond {
|
||||
ref = item.response
|
||||
}
|
||||
}
|
||||
|
||||
if ref == nil {
|
||||
upstreamRef, err := s.client.RefreshToken(ctx, cfg.BearerToken, token.UpstreamTokenID)
|
||||
if err != nil {
|
||||
// 若上游返回 110001 "请求过于频繁",进行平滑降级处理,绝不报致命错误打断买家
|
||||
if strings.Contains(err.Error(), "过于频繁") || strings.Contains(err.Error(), "110001") {
|
||||
if hasCache {
|
||||
ref = cacheVal.(rockRefreshCacheItem).response
|
||||
} else if token.Status == model.RockStatusBound {
|
||||
return &CheckBindingResult{
|
||||
Bound: true,
|
||||
RoleName: token.ActualRoleName,
|
||||
RoleID: token.ActualRoleID,
|
||||
Channel: token.ActualChannel,
|
||||
ExpectedUID: token.ExpectedUID,
|
||||
Message: "扫码授权成功,UID核验一致",
|
||||
}, nil
|
||||
} else {
|
||||
return &CheckBindingResult{
|
||||
Bound: false,
|
||||
Message: "正在同步微信/QQ扫码状态,请稍后再次检查",
|
||||
}, nil
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("刷新绑定状态失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
ref = upstreamRef
|
||||
s.refreshCache.Store(token.UpstreamTokenID, rockRefreshCacheItem{
|
||||
response: ref,
|
||||
cachedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 检查上游是否已绑定 (上游状态 bound 或 bound_account_id 有值)
|
||||
@@ -631,6 +675,8 @@ func (s *RockService) ResetMismatchToken(ctx context.Context, tokenID uint) erro
|
||||
return fmt.Errorf("仅 mismatch 状态的 Token 允许人工解冻重置")
|
||||
}
|
||||
|
||||
s.refreshCache.Delete(token.UpstreamTokenID)
|
||||
|
||||
// 重置回 available,清空订单关联
|
||||
return s.db.WithContext(ctx).Model(&token).Updates(map[string]interface{}{
|
||||
"status": model.RockStatusAvailable,
|
||||
|
||||
Reference in New Issue
Block a user