- 新增 CreateOrderReq 结构体用于统一订单创建参数- 修改 CreateOrder 方法签名,使用结构体传参替代多个参数 - 更新 jd_cookie 相关枚举值,增加 JdCookieStatusUnknown 状态 - 调整 OrderInfo 和 JdOrderInfo 模型字段,增强数据一致性 -优化订单与京东订单关联逻辑,移除冗余的 CurrentOrderId 字段 - 移除 ShouldExtractCard 方法,改为内部私有方法 shouldExtractCard- 精简 Callback 方法参数,移除不必要的 userOrderId 和 amount 参数 - 修复订单历史记录中订单号关联问题,直接使用 orderId 字段查询 - 更新控制器层参数传递方式,适配新的服务层接口定义 - 调整卡密提取逻辑,去除对用户订单实体的依赖 - 完善订单状态检查机制,提高卡密提取安全性 - 优化数据库查询逻辑,减少不必要的关联查询操作
298 lines
8.8 KiB
Go
298 lines
8.8 KiB
Go
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
|
||
}
|