fix(dlc): 发货重试不再改写订单号

代练仓按原单号回查能不能发货。随机后缀查不到,正式单会失败。已有队列时应续发原单,不能换号。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-10-06 18:45:08 +08:00
co-authored by Cursor
parent c85b9f4173
commit a2c4eb59b6
2 changed files with 47 additions and 52 deletions
+13 -50
View File
@@ -220,9 +220,10 @@ func (p *DLCProvider) Submit(ctx context.Context, order *model.FulfillmentOrder,
partial := map[string]interface{}{
"provider": ProviderDLC,
}
effectiveOrderSN := order.OrderNo
if savedSN := resultDataString(order.ResultData, "order_sn"); savedSN != "" {
effectiveOrderSN = savedSN
// 代练仓下正式单前会按这个单号回查。随机后缀在库里不存在,不能再拿去下单。
effectiveOrderSN := strings.TrimSpace(order.OrderNo)
if savedSN := resultDataString(order.ResultData, "order_sn"); savedSN != "" && savedSN != effectiveOrderSN {
queueOrderID = ""
}
if queueOrderID == "" {
@@ -263,29 +264,7 @@ func (p *DLCProvider) Submit(ctx context.Context, order *model.FulfillmentOrder,
upstreamOrder, err := p.createUpstreamOrder(queueOrderID, gameAccount, goodID, effectiveOrderSN)
if err != nil {
if isQueueDeadError(err) {
// 创建正式单阶段出现订单不可操作或重复提交,尝试换新重试单号重建队列重试一次
delete(partial, "queue_order_id")
retryBaseSN := fmt.Sprintf("%s_%x", strings.SplitN(order.OrderNo, "_", 2)[0], time.Now().UnixNano()%0xffff)
newQueueID, newSN, createErr := p.createOrderQueue(goodID, retryBaseSN, goodsNo)
if createErr == nil {
queueOrderID = newQueueID
effectiveOrderSN = newSN
partial["queue_order_id"] = queueOrderID
partial["order_sn"] = effectiveOrderSN
if patchErr := p.patchOrderQueue(queueOrderID, gameAccount, bindUUID, goodsNo, goodsName); patchErr == nil {
if retriedOrder, retriedErr := p.createUpstreamOrder(queueOrderID, gameAccount, goodID, effectiveOrderSN); retriedErr == nil {
upstreamOrder = retriedOrder
err = nil
} else {
err = retriedErr
}
}
}
}
if err != nil {
return &ProviderSubmitResult{PatchResultData: partial}, fmt.Errorf("创建上游正式订单失败: %w", err)
}
return &ProviderSubmitResult{PatchResultData: partial}, fmt.Errorf("创建上游正式订单失败: %w", err)
}
providerOrderNo := firstNonEmpty(
@@ -398,43 +377,28 @@ func (p *DLCProvider) createOrderQueue(goodID, orderNo, goodsNo string) (string,
var out struct {
Orders []map[string]interface{} `json:"orders"`
}
usedSN := orderNo
orderNo = strings.TrimSpace(orderNo)
payload := map[string]interface{}{
"good_id": goodID,
"quantity": 1,
"order_sn": usedSN,
"order_sn": orderNo,
"h5_prefix": p.channel,
}
if goodsNo != "" {
payload["goods_no"] = goodsNo
}
err := p.signProxy("/public/users/orders-queue", "POST", payload, &out)
if err != nil {
if strings.Contains(err.Error(), "正在处理中") ||
strings.Contains(err.Error(), "请勿重复提交") ||
strings.Contains(err.Error(), "已有发货记录") ||
strings.Contains(err.Error(), "已处理") {
// DLC 已存在此 order_sn 的队列/发货记录,追加随机后缀重新创建队列
retrySN := fmt.Sprintf("%s_%x", strings.SplitN(orderNo, "_", 2)[0], time.Now().UnixNano()%0xffff)
usedSN = retrySN
payload["order_sn"] = usedSN
out.Orders = nil
if retryErr := p.signProxy("/public/users/orders-queue", "POST", payload, &out); retryErr == nil && len(out.Orders) > 0 {
err = nil
}
}
}
if err != nil {
return "", "", err
// 代练仓已有这条单号时,原文是「请续发未完成项或重试失败项」。换一个带后缀的单号会让回查 404。
if err := p.signProxy("/public/users/orders-queue", "POST", payload, &out); err != nil {
return "", orderNo, err
}
if len(out.Orders) == 0 {
return "", "", errors.New("发货服务未返回队列订单")
return "", orderNo, errors.New("发货服务未返回队列订单")
}
orderID := firstNonEmpty(stringFromMap(out.Orders[0], "_id"), stringFromMap(out.Orders[0], "id"))
if orderID == "" {
return "", "", errors.New("发货服务未返回队列订单 ID")
return "", orderNo, errors.New("发货服务未返回队列订单 ID")
}
return orderID, usedSN, nil
return orderID, orderNo, nil
}
func (p *DLCProvider) patchOrderQueue(orderID, gameAccount, bindUUID, goodsNo, goodsName string) error {
@@ -632,4 +596,3 @@ func (p *DLCProvider) signProxy(path, method string, data interface{}, out inter
return lastErr
}
+34 -2
View File
@@ -5,6 +5,7 @@ import (
"errors"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
@@ -179,6 +180,39 @@ func TestSignProxyDoesNotRetryOnRateLimit(t *testing.T) {
}
}
func TestDLCCreateOrderQueueKeepsOriginalOrderSN(t *testing.T) {
const orderNo = "FO20261006180010b19fbe66ec3"
var requestedSN string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body struct {
Path string `json:"path"`
Data map[string]interface{} `json:"data"`
}
_ = json.NewDecoder(r.Body).Decode(&body)
requestedSN, _ = body.Data["order_sn"].(string)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"code": 1,
"message": "该订单已有发货记录,请续发未完成项或重试失败项",
})
}))
defer server.Close()
p := NewDLCProvider(server.URL, "dlc", &http.Client{Timeout: 5 * time.Second})
_, sn, err := p.createOrderQueue("good-1", orderNo, "suit_romantic_destiny")
if err == nil {
t.Fatal("已有发货记录时应返回代练仓原文,不能换成新单号重试")
}
if !strings.Contains(err.Error(), "请续发未完成项或重试失败项") {
t.Fatalf("应保留代练仓原文,实际: %v", err)
}
if requestedSN != orderNo || sn != orderNo || strings.Contains(requestedSN, "_") {
t.Fatalf("请求单号被改写: requested=%s returned=%s", requestedSN, sn)
}
}
func TestSignProxyDoesNotRetryOnFormalOrderCreation(t *testing.T) {
var callCount int32
@@ -211,5 +245,3 @@ func TestSignProxyDoesNotRetryOnFormalOrderCreation(t *testing.T) {
t.Fatalf("正式下单接口预期只调用 1 次且不触发重试,实际调用: %d", finalCount)
}
}