feat:Rock刷新去UID拦截,新增绑定错误自动刷新与使用/绑定超时预警

This commit is contained in:
2026-11-08 18:03:48 +08:00
parent 1dba8e4c2e
commit 51dcb9de5a
22 changed files with 768 additions and 182 deletions
@@ -35,10 +35,9 @@ func canRefreshRockBinding(status string) bool {
// RefreshTokenBinding 通过兑换码调用上游 refresh,人工同步一次绑定状态。
//
// 规则:
// 规则(只同步上游实时状态,不再用本地预期 UID 拦截):
// - 上游已发货 → 本地同步 redeemed;
// - 上游已绑定且本地有预期 UID → 一致转 bound,不一致转 mismatch;
// - 上游已绑定但本地没有预期 UID → 只同步角色信息,状态保持不变;
// - 上游已绑定 → 本地直接同步 bound,并清空绑定错误标记;
// - 上游未绑定 → 本地状态保持不变(不自动把 bound 退回 using)。
func (s *RockService) RefreshTokenBinding(ctx context.Context, tokenID uint) (*TokenBindingRefreshResult, error) {
var token model.RockToken
@@ -91,13 +90,13 @@ func (s *RockService) RefreshTokenBinding(ctx context.Context, tokenID uint) (*T
actualRoleName := ref.GetRoleName()
actualRoleID := ref.GetRoleID()
actualChannel := ref.GetChannel()
expectedUID := strings.TrimSpace(token.ExpectedUID)
upstreamBound := rockUpstreamBound(ref)
updates := map[string]interface{}{
"updated_at": now,
}
// 只有上游确实绑定时才同步角色字段;未绑定时 Region 可能只是领取区域,不能当成绑定渠道。
if ref.IsBound() {
if upstreamBound {
if actualRoleName != "" {
updates["actual_role_name"] = actualRoleName
}
@@ -111,9 +110,9 @@ func (s *RockService) RefreshTokenBinding(ctx context.Context, tokenID uint) (*T
result := &TokenBindingRefreshResult{
Status: token.Status,
Bound: ref.IsBound(),
Bound: upstreamBound,
}
if ref.IsBound() {
if upstreamBound {
result.RoleName = actualRoleName
result.RoleID = actualRoleID
result.Channel = actualChannel
@@ -139,19 +138,9 @@ func (s *RockService) RefreshTokenBinding(ctx context.Context, tokenID uint) (*T
result.RedeemedAt = redeemedAt.Format("2006-01-02 15:04:05")
result.Message = "上游已发货,本地状态已同步为已交付"
case ref.IsBound() && actualRoleID == "":
result.Warning = true
result.Message = "上游返回已绑定但缺少角色 UID,未改动本地状态,请人工核对"
case ref.IsBound() && expectedUID == "":
// 没有预期 UID 无法核验,只同步角色信息。
result.Warning = true
result.Message = fmt.Sprintf(
"上游已绑定「%s」(UID %s),但本地缺少预期 UID,已同步角色信息,请人工核对",
displayRockRole(actualRoleName, actualRoleID), actualRoleID,
)
case ref.IsBound() && token.Status == model.RockStatusMismatch && actualRoleID == expectedUID:
case upstreamBound:
// 上游已绑定即同步为 bound:不以本地预期 UID 做拦截,
// 否则历史 UID 不一致的 Token 永远无法把真实状态刷新回本地。
updates["status"] = model.RockStatusBound
updates["mismatch_at"] = nil
updates["mismatch_reason"] = ""
@@ -159,44 +148,24 @@ func (s *RockService) RefreshTokenBinding(ctx context.Context, tokenID uint) (*T
updates["bound_at"] = resolveRockBoundAt(ref, now)
}
result.Status = model.RockStatusBound
result.Message = "上游绑定 UID 与预期一致,已解除绑定错误"
case ref.IsBound() && token.Status == model.RockStatusUsing && !rockBuyerNewlyBound(&token, ref):
result.Warning = true
result.Message = "检测到历史绑定残留(非本次扫码),已同步角色信息,状态保持不变"
case ref.IsBound() && actualRoleID != expectedUID:
reason := fmt.Sprintf("预期UID(%s) 与 扫码UID(%s/%s) 不一致", expectedUID, actualRoleName, actualRoleID)
updates["status"] = model.RockStatusMismatch
updates["mismatch_reason"] = reason
if token.MismatchAt == nil {
updates["mismatch_at"] = now
if actualRoleID == "" {
result.Warning = true
result.Message = "上游已绑定,但未返回角色 UID,已同步为已绑定,请人工核对"
} else {
result.Message = fmt.Sprintf(
"上游已绑定「%s」(UID %s),已同步为已绑定",
displayRockRole(actualRoleName, actualRoleID), actualRoleID,
)
}
result.Status = model.RockStatusMismatch
result.Warning = true
result.Message = fmt.Sprintf(
"上游绑定「%s」(UID %s) 与预期 UID(%s) 不一致,已标记绑定错误",
displayRockRole(actualRoleName, actualRoleID), actualRoleID, expectedUID,
)
case ref.IsBound():
updates["status"] = model.RockStatusBound
updates["mismatch_at"] = nil
updates["mismatch_reason"] = ""
if token.BoundAt == nil {
updates["bound_at"] = resolveRockBoundAt(ref, now)
}
result.Status = model.RockStatusBound
result.Message = "上游已绑定,UID 核验一致"
default:
result.Message = "上游仍未绑定,本地状态保持不变"
}
result.Changed = result.Status != originalStatus ||
(ref.IsBound() && actualRoleName != "" && actualRoleName != originalRoleName) ||
(ref.IsBound() && actualRoleID != "" && actualRoleID != originalRoleID) ||
(ref.IsBound() && actualChannel != "" && actualChannel != originalChannel)
(upstreamBound && actualRoleName != "" && actualRoleName != originalRoleName) ||
(upstreamBound && actualRoleID != "" && actualRoleID != originalRoleID) ||
(upstreamBound && actualChannel != "" && actualChannel != originalChannel)
if err := s.db.WithContext(ctx).Model(&token).Updates(updates).Error; err != nil {
return nil, err