GetConfig 从不把 rock_configs.base_url 回写到客户端,main.go 又硬编码了 https://0666.pro,导致后台改上游地址「保存那一刻生效、进程重启后失效」, 数据库里的值被静默忽略且无任何提示。 - 新增 model.RockDefaultBaseURL 统一默认值来源 - 新增 RockService.ApplyConfigToClient,启动时把 DB 地址同步到客户端 - UpdateConfig 改用 client.SetBaseURL 立即生效,不再直接写字段 - RockClient.baseURL 原为裸字段,被 HTTP 请求线程与库存巡检 goroutine 并发 读写(数据竞争),改为 sync.RWMutex 保护并新增 BaseURL/SetBaseURL - ClaimTokens/RefreshToken/RedeemSkin 的 Origin/Referer 跟随配置地址 - 空值回落默认地址,末尾斜杠自动裁剪 为保持该提交可编译,同一次改动把 rock_service.go(1177 行)按域拆分为 8 个文件(最大 221 行):rock_service / rock_bindlink / rock_stock / rock_assign / rock_bindcheck / rock_redeem / rock_recover / rock_snapshot。 35 个声明块全部有归属,无遗漏无重复;迁移文件 012 受 checksum 保护故未改动。 验证:TestRockServiceBaseURLFromConfig 覆盖「改地址→立即生效→重启→仍生效」; 并发用例在 -race 下通过;全量测试通过。
117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
// 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()
|
|
}
|
|
|
|
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, time.Local); 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
|
|
}
|