Files
kami_backend/internal/logic/jd_cookie/order_create.go
danial bc2d58753b feat(jd_cookie):重构订单创建逻辑并优化相关模型
- 新增 CreateOrderReq 结构体用于统一订单创建参数- 修改 CreateOrder 方法签名,使用结构体传参替代多个参数
- 更新 jd_cookie 相关枚举值,增加 JdCookieStatusUnknown 状态
- 调整 OrderInfo 和 JdOrderInfo 模型字段,增强数据一致性
-优化订单与京东订单关联逻辑,移除冗余的 CurrentOrderId 字段
- 移除 ShouldExtractCard 方法,改为内部私有方法 shouldExtractCard- 精简 Callback 方法参数,移除不必要的 userOrderId 和 amount 参数
- 修复订单历史记录中订单号关联问题,直接使用 orderId 字段查询
- 更新控制器层参数传递方式,适配新的服务层接口定义
- 调整卡密提取逻辑,去除对用户订单实体的依赖
- 完善订单状态检查机制,提高卡密提取安全性
- 优化数据库查询逻辑,减少不必要的关联查询操作
2025-10-18 14:13:40 +08:00

298 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package jd_cookie
import (
"context"
"kami/internal/consts"
"kami/internal/model"
"kami/utility/cache"
"kami/utility/utils"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtime"
)
// CreateOrder 创建订单
func (s *sJdCookie) CreateOrder(ctx context.Context, req *model.CreateOrderReq) (result *model.CreateOrderResult, err error) {
_ = s.ReleaseExpiredJdOrders(ctx)
if req.UserOrderId == "" {
return nil, gerror.New("用户订单号不能为空")
}
if req.Amount <= 0 {
return nil, gerror.New("订单金额必须大于0")
}
// 获取用户订单分布式锁,防止并发创建重复订单
lockKey := consts.OrderLockKeyPrefix + req.UserOrderId
lockValue, err := cache.NewCache().Lock(ctx, lockKey, time.Minute*3, time.Second*60)
if err != nil {
return nil, gerror.Wrap(err, "系统繁忙,请稍后重试")
}
// 确保锁会被释放
defer func() {
if unlockErr := cache.NewCache().Unlock(ctx, lockKey, lockValue); unlockErr != nil {
glog.Warning(ctx, "释放分布式锁失败", g.Map{
"lockKey": lockKey,
"error": unlockErr,
})
}
}()
// 在锁保护下再次检查用户订单是否已存在(双重检查)
existingOrder, err := s.getOrderByUserOrderId(ctx, req.UserOrderId)
if err != nil {
return nil, gerror.Wrap(err, "检查用户订单是否存在失败")
}
if existingOrder != nil {
// 订单已存在,尝试获取支付链接
paymentResult, err := s.GetPaymentUrl(ctx, existingOrder.OrderId)
if err != nil {
return nil, err
}
// 转换为CreateOrderResult
return &model.CreateOrderResult{
WxPayUrl: paymentResult.WxPayUrl,
JdOrderId: paymentResult.JdOrderId,
OrderId: paymentResult.OrderId,
}, nil
}
// 优先尝试复用现有的京东订单
reusableJdOrder, err := s.findReusableJdOrder(ctx, req.Amount, req.Category)
if err != nil {
glog.Warning(ctx, "查找可复用京东订单失败", err)
}
// 生成内部订单ID
internalOrderId := "JD_" + utils.GenerateRandomUUID()
var cookieId, jdOrderId, wxPayUrl string
var isReused = false
if reusableJdOrder != nil {
// 尝试使用可复用的京东订单
jdOrderId = reusableJdOrder.JdOrderId
cookieId = reusableJdOrder.CookieId
wxPayUrl = reusableJdOrder.WxPayUrl
// 检查支付链接是否过期
if reusableJdOrder.WxPayExpireAt != nil && gtime.Now().After(reusableJdOrder.WxPayExpireAt) {
// 支付链接已过期,尝试刷新
newWxPayUrl, isCkFailed, refreshErr := s.refreshPaymentUrl(ctx, &model.RefreshPaymentUrlReq{
JdOrderId: jdOrderId,
PayId: reusableJdOrder.PayId,
CookieId: cookieId,
})
if isCkFailed {
s.handleCookieFailure(ctx, cookieId, jdOrderId, isCkFailed, refreshErr.Error())
}
if refreshErr != nil {
glog.Warning(ctx, "刷新支付链接失败,将创建新订单", g.Map{
"jdOrderId": jdOrderId,
"error": refreshErr,
})
// 刷新失败,标记为不可复用
_ = s.UpdateJdOrderStatus(ctx, jdOrderId, consts.JdOrderStatusExpired, "", refreshErr.Error())
// 记录Cookie刷新失败历史
_ = s.RecordCookieHistory(ctx, &model.RecordCookieHistoryReq{
CookieId: cookieId,
ChangeType: consts.CookieChangeTypeRefreshFail,
StatusBefore: consts.JdCookieStatusUnknown,
StatusAfter: consts.JdCookieStatusExpired,
UserOrderId: internalOrderId,
FailureCount: 0,
Remark: "刷新支付链接失败",
})
// 清空,准备创建新订单
jdOrderId = ""
cookieId = ""
wxPayUrl = ""
} else {
wxPayUrl = newWxPayUrl
// 更新京东订单的支付链接和过期时间
_ = s.updateJdOrderPaymentUrl(ctx, jdOrderId, wxPayUrl)
isReused = true
}
} else {
isReused = true
}
if isReused {
glog.Info(ctx, "复用现有京东订单", g.Map{
"orderId": internalOrderId,
"jdOrderId": jdOrderId,
"cookieId": cookieId,
})
}
}
// 如果没有成功复用,创建新的京东订单
if jdOrderId == "" {
retryRes, err := s.createNewJdOrderWithRetry(ctx, &model.CreateNewJdOrderWithRetryReq{
OrderId: internalOrderId,
Amount: req.Amount,
Category: req.Category,
})
if err != nil {
return nil, err
}
jdOrderId = retryRes.JdOrderId
cookieId = retryRes.CookieId
wxPayUrl = retryRes.WxPayUrl
}
// 创建订单记录
err = s.createOrderRecord(ctx, internalOrderId, req.UserOrderId, req.Amount, req.Category, jdOrderId, wxPayUrl)
if err != nil {
return nil, gerror.Wrap(err, "创建订单记录失败")
}
// 更新京东订单的当前关联订单ID
_ = s.updateJdOrderId(ctx, jdOrderId, internalOrderId)
// 记录Cookie使用历史
_ = s.RecordCookieHistory(ctx, &model.RecordCookieHistoryReq{
CookieId: cookieId,
ChangeType: consts.CookieChangeTypeUse,
StatusBefore: consts.JdCookieStatusUnknown,
StatusAfter: consts.JdCookieStatusNormal,
UserOrderId: internalOrderId,
FailureCount: 0,
Remark: "Cookie用于创建订单",
})
// 记录订单创建历史
_ = s.RecordOrderHistory(ctx, internalOrderId, consts.OrderChangeTypeCreate, jdOrderId)
return &model.CreateOrderResult{
WxPayUrl: wxPayUrl,
JdOrderId: jdOrderId,
OrderId: internalOrderId,
}, nil
}
// createNewJdOrderWithRetry 创建新的京东订单(带重试机制)
func (s *sJdCookie) createNewJdOrderWithRetry(ctx context.Context, req *model.CreateNewJdOrderWithRetryReq) (res *model.CreateNewJdOrderWithRetryRes, 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": req.OrderId,
"triedCookies": len(triedCookies),
"error": cookieErr,
})
lastErr = cookieErr
break // 没有可用Cookie停止重试
}
// 检查是否已经尝试过这个Cookie
if s.hasCookieBeenTried(triedCookies, availableCookieId) {
glog.Debug(ctx, "Cookie已被尝试过跳过", g.Map{
"cookieId": availableCookieId,
})
continue
}
// 记录已尝试的Cookie
triedCookies = append(triedCookies, availableCookieId)
jdOrderId := utils.GenerateRandomUUID()
// 调用京东下单接口
createOrderRes, lastErr := s.callJdCreateOrder(ctx, &model.CallJdCreateOrderReq{
JdOrderId: jdOrderId,
CookieId: availableCookieId,
Amount: req.Amount,
Category: req.Category,
})
if lastErr != nil {
glog.Warning(ctx, "京东下单失败尝试切换Cookie重试", g.Map{
"orderId": req.OrderId,
"cookieId": availableCookieId,
"triedCookies": len(triedCookies),
"error": lastErr,
})
isCkFailed := false
remark := ""
if createOrderRes != nil {
remark = createOrderRes.Remark
isCkFailed = createOrderRes.IsCkFailed
}
// Cookie失败更新状态
s.handleCookieFailure(ctx, availableCookieId, "", isCkFailed, remark)
// 继续下一次重试
continue
}
// 下单成功,创建京东订单记录
err = s.createJdOrderRecord(ctx, &model.CreateJdOrderRecordReq{
JdOrderId: jdOrderId, // 内部订单号
RealJdOrderId: createOrderRes.RealJdOrderId, // 京东客户端返回的真实订单ID
PayId: createOrderRes.PayId,
CookieId: availableCookieId,
Category: req.Category,
Amount: req.Amount,
WxPayUrl: createOrderRes.WxPayUrl,
})
if err != nil {
glog.Error(ctx, "创建京东订单记录失败", g.Map{
"jdOrderId": jdOrderId,
"error": err,
})
return nil, gerror.Wrap(err, "创建京东订单记录失败")
}
// 记录京东订单创建历史
_ = s.RecordJdOrderHistory(ctx, jdOrderId, consts.JdOrderChangeTypeCreate, req.OrderId, createOrderRes.WxPayUrl, "")
glog.Info(ctx, "创建京东订单成功", g.Map{
"orderId": req.OrderId,
"jdOrderId": jdOrderId,
"cookieId": availableCookieId,
"triedCookies": len(triedCookies),
})
// 返回成功结果
return &model.CreateNewJdOrderWithRetryRes{
JdOrderId: jdOrderId,
CookieId: availableCookieId,
WxPayUrl: createOrderRes.WxPayUrl,
}, nil
}
// 所有重试都失败了
if lastErr == nil {
lastErr = gerror.New(consts.ErrCodeCookieNotAvailable)
}
glog.Error(ctx, "创建京东订单失败所有Cookie均不可用", g.Map{
"orderId": req.OrderId,
"triedCookies": triedCookies,
"error": lastErr,
})
return nil, gerror.Wrapf(lastErr, "创建京东订单失败,已尝试%d个Cookie", len(triedCookies))
}
// hasCookieBeenTried 检查Cookie是否已经尝试过
func (s *sJdCookie) hasCookieBeenTried(triedCookies []string, cookieId string) bool {
for _, tried := range triedCookies {
if tried == cookieId {
return true
}
}
return false
}