Files
affiliate_dash/backend/internal/service/rock_bindlink.go
T
yml2213 80e2bbf875 fix(rock): 上游地址改为以数据库配置为准,并消除 baseURL 数据竞争
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 下通过;全量测试通过。
2026-10-06 19:42:58 +08:00

78 lines
2.2 KiB
Go

package service
import (
"context"
"strings"
"time"
"affiliate_dash/internal/model"
)
type rockRefreshCacheItem struct {
response *RockRefreshResponse
cachedAt time.Time
}
// 腾讯活动链接按生成时间签名。同一提货号过期后重取链接,不另发新号。
const rockBindLinkMaxAge = 8 * time.Minute
type rockBindLinkCacheItem struct {
bindURL string
qrURL string
cachedAt time.Time
}
func (s *RockService) rememberBindLink(tokenID uint, bindURL, qrURL string) {
bindURL = strings.TrimSpace(bindURL)
if s == nil || tokenID == 0 || bindURL == "" {
return
}
qrURL = strings.TrimSpace(qrURL)
if qrURL == "" {
qrURL = qrCodeURL(bindURL)
}
s.bindLinkCache.Store(tokenID, rockBindLinkCacheItem{
bindURL: bindURL,
qrURL: qrURL,
cachedAt: time.Now(),
})
}
// freshBindLink 只为仍在扫码的同一提货号重取活动链接。过期前复用缓存,避免每轮轮询都打 0666。
func (s *RockService) freshBindLink(ctx context.Context, tokenID uint, orderNo string) (string, string) {
if s == nil || s.client == nil || tokenID == 0 {
return "", ""
}
var token model.RockToken
if err := s.db.WithContext(ctx).First(&token, tokenID).Error; err != nil {
return "", ""
}
if token.Status != model.RockStatusUsing || strings.TrimSpace(token.RedeemCode) == "" {
return "", ""
}
if orderNo = strings.TrimSpace(orderNo); orderNo != "" && token.AssignedOrderNo != "" && token.AssignedOrderNo != orderNo {
return "", ""
}
if item, ok := s.bindLinkCache.Load(token.ID); ok {
cached := item.(rockBindLinkCacheItem)
if cached.bindURL != "" && time.Since(cached.cachedAt) < rockBindLinkMaxAge {
return cached.bindURL, cached.qrURL
}
}
detail, err := s.client.GetRedeemDetail(ctx, token.RedeemCode)
if err != nil || detail == nil || strings.TrimSpace(detail.BindURL) == "" {
if item, ok := s.bindLinkCache.Load(token.ID); ok {
cached := item.(rockBindLinkCacheItem)
return cached.bindURL, cached.qrURL
}
return "", ""
}
bindURL := strings.TrimSpace(detail.BindURL)
qrURL := strings.TrimSpace(detail.QRURL)
s.rememberBindLink(token.ID, bindURL, qrURL)
if qrURL == "" {
qrURL = qrCodeURL(bindURL)
}
return bindURL, qrURL
}