package service import ( "encoding/json" "strings" "time" "affiliate_dash/internal/model" "affiliate_dash/internal/pkg/timeutil" ) // rockBuyerNewlyBound 判断上游展示的绑定是不是本次领取之后新扫上的。 // 和分配快照一致,或者绑定时间仍为空且发货记录都早于本次领取,都是历史残留,链接可以再次使用。 // 没有快照、也没有更早的发货记录时,上游已有角色按新绑定处理,避免把刚扫上的号放回库存。 func rockBuyerNewlyBound(token *model.RockToken, ref *RockRefreshResponse) bool { if ref == nil || !ref.IsBound() || strings.TrimSpace(ref.GetRoleID()) == "" { return false } snap, hasSnap := bindingSnapshot(token) if hasSnap && snap.Bound && snap.RoleID == ref.GetRoleID() && snap.BindChangedAt == strings.TrimSpace(ref.Token.BindChangedAt) { return false } if deliveryAfterClaim(token, ref) || bindChangedAfterClaim(token, ref.Token.BindChangedAt) { return true } if hasSnap && snap.RoleID != "" && snap.RoleID != ref.GetRoleID() { return true } if strings.TrimSpace(ref.Token.BindChangedAt) == "" && historicalDeliveriesOnly(token, ref) { return false } if hasSnap { return false } return true } func historicalDeliveriesOnly(token *model.RockToken, ref *RockRefreshResponse) bool { if ref == nil || len(ref.DeliveryList) == 0 { return false } return !deliveryAfterClaim(token, ref) } func bindingSnapshot(token *model.RockToken) (BindingSnapshot, bool) { if token == nil || strings.TrimSpace(token.InitialBoundSnapshot) == "" { return BindingSnapshot{}, false } var snap BindingSnapshot if err := json.Unmarshal([]byte(token.InitialBoundSnapshot), &snap); err != nil { return BindingSnapshot{}, false } snap.RoleID = strings.TrimSpace(snap.RoleID) snap.BindChangedAt = strings.TrimSpace(snap.BindChangedAt) return snap, true } func rockClaimBaseline(token *model.RockToken) time.Time { if token == nil { return time.Time{} } if token.ClaimedAt != nil && !token.ClaimedAt.IsZero() { return token.ClaimedAt.UTC() } return token.CreatedAt.UTC() } // parseRockTime 解析上游返回的时间串。 // 上游(0666.pro)返回的是不带偏移的北京时间,必须以业务时区解析, // 不能依赖 time.Local——否则进程时区一变,绑定时序判断就会整体偏移 8 小时。 func parseRockTime(raw string) (time.Time, bool) { raw = strings.TrimSpace(raw) if raw == "" { return time.Time{}, false } layouts := []string{ time.RFC3339Nano, "2006-01-02T15:04:05.999999999", "2006-01-02T15:04:05", "2006-01-02 15:04:05", } for _, layout := range layouts { if t, err := time.ParseInLocation(layout, raw, timeutil.Location()); err == nil { return t.UTC(), true } } return time.Time{}, false } func bindChangedAfterClaim(token *model.RockToken, changedAt string) bool { changed, ok := parseRockTime(changedAt) if !ok { return false } baseline := rockClaimBaseline(token) if baseline.IsZero() { return true } return !changed.Before(baseline.Add(-time.Minute)) } func deliveryAfterClaim(token *model.RockToken, ref *RockRefreshResponse) bool { if ref == nil { return false } baseline := rockClaimBaseline(token) if baseline.IsZero() { return false } for _, item := range ref.DeliveryList { deliveredAt, ok := parseRockTime(item.Time) if !ok { continue } if !deliveredAt.Before(baseline.Add(-time.Minute)) { return true } } return false }