Files
kami_backend/docs/ORDER_REFACTOR_UPDATE.md
danial e6ccd423b7 refactor(otel): 简化OTel配置与错误处理
- 移除不必要的配置字段和复杂错误类型
- 简化trace和log初始化逻辑,保留核心功能
- 使用标准Go错误替代自定义错误结构
- 启用默认批处理和消息丢弃机制- 保留gzip压缩和自动重连功能- 更新相关文档路径引用
- 添加OTel简化增强实现说明文档
2025-11-09 01:09:50 +08:00

14 KiB
Raw Permalink Blame History

订单创建逻辑优化更新说明

📋 更新概述

根据需求对京东Cookie订单创建逻辑进行了进一步优化主要包括以下三个方面的改进

  1. 刷新支付链接传递订单ID替换随机UUID使用实际订单ID
  2. GetPaymentUrl支付链接刷新失败降级:刷新失败时自动创建新京东订单
  3. 移除重试次数限制无限重试直到没有可用Cookie为止

🔧 详细变更

1. 刷新支付链接传递订单ID

文件internal/logic/jd_cookie/order_utils.go

变更前

func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId string) (wxPayUrl string, err error) {
    // ...
    appResp, err := originalJd.NewClient().RefreshPayment(ctx, &originalJd.RefreshPaymentReq{
        Cookies:     cookie.CookieValue,
        OrderId:     jdOrderIdInt,
        PayId:       payId,
        UserOrderId: utils.GenerateRandomUUID(), // 使用随机UUID
    })
    // ...
}

变更后

func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId, orderId string) (wxPayUrl string, err error) {
    // ...
    appResp, err := originalJd.NewClient().RefreshPayment(ctx, &originalJd.RefreshPaymentReq{
        Cookies:     cookie.CookieValue,
        OrderId:     jdOrderIdInt,
        PayId:       payId,
        UserOrderId: orderId, // 使用实际订单ID
    })
    // ...
}

改进点

  • 传递真实订单ID便于追踪
  • 移除对 utils.GenerateRandomUUID() 的依赖
  • 所有调用该方法的地方都需要传递orderId参数

影响范围

  • order_create.go - 订单复用时刷新支付链接
  • order_query.go - 获取支付链接时刷新

2. GetPaymentUrl 刷新失败自动降级

文件internal/logic/jd_cookie/order_query.go

变更前

func (s *sJdCookie) GetPaymentUrl(ctx context.Context, orderId string) (...) {
    // ...
    if jdOrder.WxPayExpireAt != nil && gtime.Now().After(jdOrder.WxPayExpireAt) {
        // 支付链接已过期,尝试刷新
        newWxPayUrl, err2 := s.refreshPaymentUrl(ctx, jdOrder.JdOrderId, jdOrder.PayId, jdOrder.CookieId)
        if err2 != nil {
            return "", "", "", gerror.Wrap(err, "刷新支付链接失败") // 直接返回错误
        }
        wxPayUrl = newWxPayUrl
    }
    // ...
}

变更后

func (s *sJdCookie) GetPaymentUrl(ctx context.Context, orderId string) (...) {
    // ...
    if jdOrder.WxPayExpireAt != nil && gtime.Now().After(jdOrder.WxPayExpireAt) {
        // 支付链接已过期,尝试刷新
        newWxPayUrl, refreshErr := s.refreshPaymentUrl(ctx, jdOrder.JdOrderId, jdOrder.PayId, jdOrder.CookieId, orderId)
        if refreshErr != nil {
            // 刷新失败,标记旧订单为失效
            _ = s.UpdateJdOrderStatus(ctx, jdOrder.JdOrderId, int(consts.JdOrderStatusExpired), "刷新支付链接失败")
            _ = s.RecordJdOrderHistory(ctx, jdOrder.JdOrderId, string(consts.JdOrderChangeTypeInvalid), orderId, jdOrder.WxPayUrl)
            _ = s.RecordCookieHistory(ctx, jdOrder.CookieId, string(consts.CookieChangeTypeRefreshFail), 0, 0, orderId, 0)

            // 解绑旧京东订单
            _ = s.updateJdOrderCurrentOrderId(ctx, jdOrder.JdOrderId, "")

            // 创建新的京东订单(带重试机制)
            newJdOrderId, newCookieId, newWxPayUrl, createErr := s.createNewJdOrderWithRetry(ctx, orderId, gconv.Float64(order.Amount), consts.RedeemOrderCardCategory(order.Category))
            if createErr != nil {
                return "", "", "", gerror.Wrap(createErr, "刷新失败且创建新订单失败")
            }

            // 更新订单关联的京东订单ID和支付链接
            _ = s.updateOrderJdOrderId(ctx, orderId, newJdOrderId, newWxPayUrl)

            // 更新京东订单的当前关联订单ID
            _ = s.updateJdOrderCurrentOrderId(ctx, newJdOrderId, orderId)

            // 记录Cookie使用历史
            _ = s.RecordCookieHistory(ctx, newCookieId, string(consts.CookieChangeTypeUse), 0, 0, orderId, 0)

            // 记录订单重新绑定历史
            _ = s.RecordOrderHistory(ctx, orderId, string(consts.OrderChangeTypeRebind), newJdOrderId)

            // 返回新的支付信息
            jdOrderId = newJdOrderId
            wxPayUrl = newWxPayUrl
        } else {
            // 刷新成功,更新支付链接
            wxPayUrl = newWxPayUrl
            _ = s.updateJdOrderPaymentUrl(ctx, jdOrderId, wxPayUrl)
        }
    }
    // ...
}

改进点

  • 刷新失败时不直接返回错误
  • 自动标记旧订单为失效
  • 自动创建新的京东订单(带重试机制)
  • 更新订单关联关系
  • 记录完整的操作历史(符合变更记录规范)
  • 提升用户体验,保证支付链接可用

新增辅助方法

// updateOrderJdOrderId 更新订单关联的京东订单ID和支付链接
func (s *sJdCookie) updateOrderJdOrderId(ctx context.Context, orderId, jdOrderId, wxPayUrl string) error

历史记录

  • Cookie刷新失败历史
  • 京东订单失效历史
  • 京东订单解绑历史
  • 新Cookie使用历史
  • 订单重新绑定历史

3. 移除重试次数限制

文件internal/logic/jd_cookie/order_create.go

变更前

func (s *sJdCookie) createNewJdOrderWithRetry(...) (jdOrderId, cookieId, wxPayUrl string, err error) {
    const maxRetryCount = 10 // 最大重试次数
    var lastErr error
    var triedCookies []string

    for retryCount := range maxRetryCount {
        // 获取可用的Cookie
        availableCookieId, cookieErr := s.GetAvailableCookie(ctx)
        if cookieErr != nil {
            glog.Warning(ctx, "获取可用Cookie失败", g.Map{
                "orderId":    orderId,
                "retryCount": retryCount,
                "error":      cookieErr,
            })
            lastErr = cookieErr
            break
        }
        
        // ... 其他逻辑
        
        glog.Info(ctx, "创建京东订单成功", g.Map{
            "orderId":    orderId,
            "jdOrderId":  jdOrderId,
            "cookieId":   availableCookieId,
            "retryCount": retryCount,
        })
        
        return jdOrderId, availableCookieId, wxPayUrl, nil
    }
    
    // 所有重试都失败了
    return "", "", "", gerror.Wrapf(lastErr, "创建京东订单失败,已尝试%d个Cookie", len(triedCookies))
}

变更后

func (s *sJdCookie) createNewJdOrderWithRetry(...) (jdOrderId, cookieId, wxPayUrl string, err error) {
    var lastErr error
    var triedCookies []string // 记录已尝试的Cookie

    // 不断尝试直到没有Cookie为止
    for {
        // 获取可用的Cookie
        availableCookieId, cookieErr := s.GetAvailableCookie(ctx)
        if cookieErr != nil {
            glog.Warning(ctx, "获取可用Cookie失败", g.Map{
                "orderId":      orderId,
                "triedCookies": len(triedCookies),
                "error":        cookieErr,
            })
            lastErr = cookieErr
            break // 没有可用Cookie停止重试
        }
        
        // 检查是否已经尝试过这个Cookie
        if s.hasCookieBeenTried(triedCookies, availableCookieId) {
            continue
        }
        
        // 记录已尝试的Cookie
        triedCookies = append(triedCookies, availableCookieId)
        
        // ... 其他逻辑
        
        glog.Info(ctx, "创建京东订单成功", g.Map{
            "orderId":      orderId,
            "jdOrderId":    jdOrderId,
            "cookieId":     availableCookieId,
            "triedCookies": len(triedCookies),
        })
        
        return jdOrderId, availableCookieId, wxPayUrl, nil
    }
    
    // 所有Cookie都失败了
    if lastErr == nil {
        lastErr = gerror.New(consts.ErrCodeCookieNotAvailable)
    }

    glog.Error(ctx, "创建京东订单失败所有Cookie均不可用", g.Map{
        "orderId":      orderId,
        "triedCookies": triedCookies,
        "error":        lastErr,
    })

    return "", "", "", gerror.Wrapf(lastErr, "创建京东订单失败,已尝试%d个Cookie", len(triedCookies))
}

改进点

  • 移除 maxRetryCount = 10 的限制(符合重试机制设计规范)
  • 使用 for 无限循环直到没有可用Cookie为止
  • 日志中改用 triedCookies 数量代替 retryCount
  • 最大化利用所有可用Cookie
  • 提升订单创建成功率

4. 解绑功能增强

文件internal/logic/jd_cookie/order_utils.go

变更前

func (s *sJdCookie) updateJdOrderCurrentOrderId(ctx context.Context, jdOrderId, orderId string) error {
    // 查找订单ID对应的内部ID
    var order *entity.V1JdCookieOrder
    err := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
        Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).
        Scan(&order)
    if err != nil || order == nil {
        return gerror.New("订单不存在")
    }
    
    // 更新京东订单的当前关联订单ID
    _, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
        CurrentOrderId: order.Id,
    })
    // ...
}

变更后

func (s *sJdCookie) updateJdOrderCurrentOrderId(ctx context.Context, jdOrderId, orderId string) error {
    m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())

    // 如果orderId为空表示解绑
    if orderId == "" {
        // 获取更新前的京东订单信息
        var oldJdOrder *entity.V1JdCookieJdOrder
        err := m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&oldJdOrder)
        // ...

        // 解绑设置为null
        _, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
            CurrentOrderId: nil,
        })
        
        // 记录解绑历史
        if oldJdOrder != nil && oldJdOrder.CurrentOrderId > 0 {
            _ = s.RecordJdOrderHistory(ctx, jdOrderId, string(consts.JdOrderChangeTypeUnbind), "", oldJdOrder.WxPayUrl)
        }

        return nil
    }

    // 查找订单ID对应的内部ID绑定操作
    // ...
}

改进点

  • 支持传入空字符串进行解绑操作
  • 解绑时设置 CurrentOrderIdnil
  • 记录解绑历史(符合变更记录规范)
  • 用于 GetPaymentUrl 刷新失败时解绑旧订单

📊 影响范围总结

修改的文件

文件 变更内容 行数变化
order_create.go 1. 调用refreshPaymentUrl时传递orderId
2. 移除重试次数限制
+14/-14
order_query.go 刷新失败时自动创建新订单 +38/-7
order_utils.go 1. refreshPaymentUrl添加orderId参数
2. 新增updateOrderJdOrderId方法
3. updateJdOrderCurrentOrderId支持解绑
+36/-1

新增方法

// updateOrderJdOrderId 更新订单关联的京东订单ID和支付链接
func (s *sJdCookie) updateOrderJdOrderId(ctx context.Context, orderId, jdOrderId, wxPayUrl string) error

方法签名变更

// 变更前
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId string) (wxPayUrl string, err error)

// 变更后
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId, orderId string) (wxPayUrl string, err error)

🧪 测试结果

=== RUN   TestShouldExtractCard
--- PASS: TestShouldExtractCard (0.00s)
=== RUN   TestBatchCheckPaymentStatusConfig
--- PASS: TestBatchCheckPaymentStatusConfig (0.00s)
=== RUN   TestCacheKeyGeneration
--- PASS: TestCacheKeyGeneration (0.00s)
PASS
ok      kami/internal/logic/jd_cookie     0.875s

所有测试通过!

go build -o /dev/null .

编译成功!


🎯 优势分析

1. 提升成功率

  • 无限重试直到所有Cookie用完最大化订单创建成功率
  • GetPaymentUrl刷新失败自动降级保证用户始终能获得有效支付链接

2. 完善追踪

  • 刷新支付链接使用真实订单ID便于日志追踪和问题定位
  • 记录完整的操作历史(刷新失败、解绑、重新绑定等)
  • 符合异常处理与记录规范变更记录规范

3. 用户体验

  • 刷新失败时用户无感知切换到新订单
  • 减少订单创建失败的概率

4. 系统健壮性

  • 自动处理异常情况,减少人工干预
  • 完整的状态流转和历史记录

📝 使用场景

场景1获取支付链接时刷新失败

流程

1. 用户请求支付链接
2. 检测到支付链接已过期
3. 尝试刷新支付链接
4. 刷新失败
   ↓
5. 标记旧京东订单为失效
6. 解绑旧京东订单
7. 创建新京东订单(无限重试)
8. 更新订单关联
9. 返回新的支付链接

历史记录

  • 旧Cookie刷新失败
  • 旧京东订单失效
  • 旧京东订单解绑
  • 新Cookie使用
  • 订单重新绑定

场景2创建订单时Cookie不断失败

流程

1. 尝试Cookie 1 → 失败 → 暂停Cookie 1
2. 尝试Cookie 2 → 失败 → 暂停Cookie 2
3. 尝试Cookie 3 → 失败 → 暂停Cookie 3
   ...
N. 尝试Cookie N → 成功 ✓

日志示例

[WARN] 京东下单失败尝试切换Cookie重试 {"orderId":"ORDER123","cookieId":"COOKIE1","triedCookies":1}
[WARN] 京东下单失败尝试切换Cookie重试 {"orderId":"ORDER123","cookieId":"COOKIE2","triedCookies":2}
[INFO] 创建京东订单成功 {"orderId":"ORDER123","jdOrderId":"JD789","cookieId":"COOKIE3","triedCookies":3}

⚠️ 注意事项

  1. 性能影响:无限重试可能导致响应时间较长,建议配合超时控制使用
  2. 并发控制多个请求同时创建订单时注意Cookie的并发分配
  3. 监控告警建议添加监控当尝试Cookie数量超过阈值时告警

📚 相关文档


更新时间2025-10-11
更新内容:订单创建逻辑优化
测试状态 通过