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 下通过;全量测试通过。
128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// CorrectMismatchExpectedUID 客服修改买家填写的 UID,提货号继续留在原订单上。
|
|
// 改成当前扫码角色的 UID 时解除阻断并记为已绑定;改成其他 UID 只更新期望值,仍保持阻断。
|
|
func (s *RockService) CorrectMismatchExpectedUID(ctx context.Context, tokenID uint, expectedUID string) (*model.RockToken, error) {
|
|
trimmedUID := strings.TrimSpace(expectedUID)
|
|
if trimmedUID == "" || !isDigits(trimmedUID) || len(trimmedUID) > 15 {
|
|
return nil, errors.New("请填写 15 位以内的纯数字 UID")
|
|
}
|
|
|
|
var token model.RockToken
|
|
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&token, tokenID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("提货号不存在")
|
|
}
|
|
return err
|
|
}
|
|
if token.Status != model.RockStatusMismatch {
|
|
return errors.New("仅绑定错误的提货号可以修改买家 UID")
|
|
}
|
|
if strings.TrimSpace(token.AssignedOrderNo) == "" {
|
|
return errors.New("该提货号没有关联订单,不能修改 UID")
|
|
}
|
|
|
|
now := time.Now()
|
|
actualUID := strings.TrimSpace(token.ActualRoleID)
|
|
updates := map[string]interface{}{
|
|
"expected_uid": trimmedUID,
|
|
"updated_at": now,
|
|
}
|
|
if actualUID != "" && trimmedUID == actualUID {
|
|
updates["status"] = model.RockStatusBound
|
|
updates["mismatch_reason"] = ""
|
|
updates["mismatch_at"] = nil
|
|
updates["bound_at"] = now
|
|
token.Status = model.RockStatusBound
|
|
token.MismatchReason = ""
|
|
token.MismatchAt = nil
|
|
token.BoundAt = &now
|
|
} else if actualUID != "" {
|
|
updates["mismatch_reason"] = fmt.Sprintf("预期UID(%s) 与 扫码UID(%s/%s) 不一致", trimmedUID, token.ActualRoleName, actualUID)
|
|
token.MismatchReason = updates["mismatch_reason"].(string)
|
|
}
|
|
if err := tx.Model(&token).Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
token.ExpectedUID = trimmedUID
|
|
|
|
var order model.FulfillmentOrder
|
|
if err := tx.Where("order_no = ?", token.AssignedOrderNo).First(&order).Error; err == nil {
|
|
data := resultDataMap(order.ResultData)
|
|
data["expected_uid"] = trimmedUID
|
|
raw, marshalErr := json.Marshal(data)
|
|
if marshalErr != nil {
|
|
return marshalErr
|
|
}
|
|
if err := tx.Model(&order).Update("result_data", string(raw)).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return writeAudit(tx, nil, nil, nil, "rock_token.correct_expected_uid", "rock_token", fmt.Sprintf("%d", token.ID), map[string]interface{}{
|
|
"assigned_order_no": token.AssignedOrderNo,
|
|
"expected_uid": trimmedUID,
|
|
"actual_role_id": actualUID,
|
|
"status": token.Status,
|
|
})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &token, nil
|
|
}
|
|
|
|
func isDigits(value string) bool {
|
|
if value == "" {
|
|
return false
|
|
}
|
|
for _, r := range value {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ResetMismatchToken 客服人工重置解冻绑定错误的 Token
|
|
func (s *RockService) ResetMismatchToken(ctx context.Context, tokenID uint) error {
|
|
var token model.RockToken
|
|
if err := s.db.WithContext(ctx).First(&token, tokenID).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if token.Status != model.RockStatusMismatch {
|
|
return fmt.Errorf("仅 mismatch 状态的 Token 允许人工解冻重置")
|
|
}
|
|
|
|
s.refreshCache.Delete(token.UpstreamTokenID)
|
|
|
|
// 重置回 available,清空订单关联
|
|
return s.db.WithContext(ctx).Model(&token).Updates(map[string]interface{}{
|
|
"status": model.RockStatusAvailable,
|
|
"assigned_order_no": "",
|
|
"expected_uid": "",
|
|
"actual_role_name": "",
|
|
"actual_role_id": "",
|
|
"actual_channel": "",
|
|
"initial_bound_snapshot": "",
|
|
"mismatch_at": nil,
|
|
"mismatch_reason": "",
|
|
"updated_at": time.Now(),
|
|
}).Error
|
|
}
|