140 lines
4.1 KiB
Go
140 lines
4.1 KiB
Go
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
|
|
}
|
|
|
|
// rockUpstreamBound 判断上游「当前」是否已绑定。
|
|
//
|
|
// 只看本次实时返回的绑定信号(status / bind_changed_at / binding),
|
|
// 不用 bound_account_id 判断:上游可能保留上一次绑定的账号残留,
|
|
// 用它会导致刷新按钮把「未扫码」误判成已绑定,也无法把真实绑定同步回本地。
|
|
func rockUpstreamBound(ref *RockRefreshResponse) bool {
|
|
if ref == nil {
|
|
return false
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(ref.Token.Status)) {
|
|
case model.RockStatusBound, model.RockStatusRedeemed:
|
|
return true
|
|
}
|
|
if strings.TrimSpace(ref.Token.BindChangedAt) != "" {
|
|
return true
|
|
}
|
|
return ref.Binding.Bound || strings.TrimSpace(ref.Binding.RoleID) != ""
|
|
}
|
|
|
|
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
|
|
}
|