Files
kami_backend/internal/logic/jd_cookie/order_utils.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

379 lines
13 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/dao"
"kami/internal/model"
"kami/internal/model/do"
"kami/internal/model/entity"
"kami/utility/config"
"kami/utility/integration/originalJd"
"kami/utility/utils"
"strings"
"time"
"github.com/duke-git/lancet/v2/convertor"
"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"
)
// ====================================================================================
// 数据库查询辅助方法
// ====================================================================================
// getOrderByOrderId 根据订单号查询订单
func (s *sJdCookie) getOrderByOrderId(ctx context.Context, orderId string) (order *entity.V1JdCookieOrder, err error) {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Scan(&order)
return
}
// getOrderByUserOrderId 根据用户订单号查询订单
func (s *sJdCookie) getOrderByUserOrderId(ctx context.Context, userOrderId string) (order *entity.V1JdCookieOrder, err error) {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieOrder.Columns().UserOrderId, userOrderId).Scan(&order)
err = utils.HandleNoRowsError(err)
return
}
// getJdOrderByJdOrderId 根据京东订单号查询京东订单
func (s *sJdCookie) getJdOrderByJdOrderId(ctx context.Context, jdOrderId string) (jdOrder *entity.V1JdCookieJdOrder, err error) {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&jdOrder)
return
}
// getCookieById 根据Cookie ID获取Cookie信息
func (s *sJdCookie) getCookieById(ctx context.Context, cookieId string) (cookie *entity.V1JdCookieAccount, err error) {
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Scan(&cookie)
return
}
// ====================================================================================
// 数据库更新辅助方法
// ====================================================================================
// createJdOrderRecord 创建京东订单记录
func (s *sJdCookie) createJdOrderRecord(ctx context.Context, req *model.CreateJdOrderRecordReq) error {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Insert(&do.V1JdCookieJdOrder{
JdOrderId: req.JdOrderId,
RealJdOrderId: req.RealJdOrderId,
PayId: req.PayId,
Amount: req.Amount,
Category: req.Category,
CookieId: req.CookieId,
Status: int(consts.JdOrderStatusPending),
WxPayUrl: req.WxPayUrl,
WxPayExpireAt: gtime.Now().Add(time.Minute * consts.WxPayUrlExpireDuration),
OrderExpireAt: gtime.Now().Add(time.Hour * consts.JdOrderExpireDuration),
})
return err
}
// createOrderRecord 创建订单记录(带并发冲突处理)
func (s *sJdCookie) createOrderRecord(ctx context.Context, internalOrderId, userOrderId string, amount float64, category consts.RedeemOrderCardCategory, jdOrderId, wxPayUrl string) error {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 尝试插入订单记录
_, err := m.Insert(&do.V1JdCookieOrder{
OrderId: internalOrderId,
UserOrderId: userOrderId,
Amount: amount,
Category: category,
JdOrderId: jdOrderId,
Status: int(consts.OrderStatusPending),
})
if err != nil {
// 检查是否是唯一索引冲突错误MySQL duplicate entry error
// 这里可以根据数据库类型调整错误检测逻辑
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
glog.Warning(ctx, "检测到重复订单,可能是并发创建导致", g.Map{
"userOrderId": userOrderId,
"internalOrderId": internalOrderId,
"error": err,
})
return gerror.New("订单已存在,请勿重复创建")
}
// 其他类型的错误
glog.Error(ctx, "创建订单记录失败", g.Map{
"userOrderId": userOrderId,
"internalOrderId": internalOrderId,
"error": err,
})
return gerror.Wrap(err, "创建订单记录失败")
}
return nil
}
// updateOrderLastRequest 更新订单最后请求时间
func (s *sJdCookie) updateOrderLastRequest(ctx context.Context, orderId string) error {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Update(&do.V1JdCookieOrder{
LastRequestAt: gtime.Now(),
})
return err
}
// updateOrderJdOrderId 更新订单关联的京东订单ID
func (s *sJdCookie) updateOrderJdOrderId(ctx context.Context, orderId, jdOrderId, wxPayUrl string) error {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Update(&do.V1JdCookieOrder{
JdOrderId: jdOrderId,
})
return err
}
// updateJdOrderPaymentUrl 更新京东订单的支付链接
func (s *sJdCookie) updateJdOrderPaymentUrl(ctx context.Context, jdOrderId, wxPayUrl string) error {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 获取更新前的订单信息
var oldOrder *entity.V1JdCookieJdOrder
err := m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&oldOrder)
if err != nil || oldOrder == nil {
glog.Warning(ctx, "查询京东订单失败,无法记录历史", err)
// 即使查询失败也继续更新
}
_, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
WxPayUrl: wxPayUrl,
WxPayExpireAt: gtime.Now().Add(time.Minute * consts.WxPayUrlExpireDuration),
})
if err != nil {
return err
}
// 记录支付链接更新历史
if oldOrder != nil && oldOrder.WxPayUrl != wxPayUrl {
_ = s.RecordJdOrderHistory(ctx, jdOrderId, consts.JdOrderChangeTypeSend, oldOrder.OrderId, wxPayUrl, "")
}
return err
}
// updateJdOrderId 更新京东订单关联的用户订单ID
func (s *sJdCookie) updateJdOrderId(ctx context.Context, jdOrderId, orderId string) error {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 获取更新前的京东订单信息
var oldJdOrder *entity.V1JdCookieJdOrder
err := m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&oldJdOrder)
if err != nil || oldJdOrder == nil {
glog.Warning(ctx, "查询京东订单失败,无法记录历史", err)
// 即使查询失败也继续更新
}
// 更新订单ID
_, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
OrderId: orderId,
})
if err != nil {
return err
}
// 记录订单绑定历史
var changeType consts.JdOrderChangeType
if oldJdOrder != nil {
if oldJdOrder.OrderId == "" {
changeType = consts.JdOrderChangeTypeBind // 首次绑定
} else if oldJdOrder.OrderId != orderId {
changeType = consts.JdOrderChangeTypeReplace // 更换绑定
}
} else {
changeType = consts.JdOrderChangeTypeBind // 首次绑定
}
// 获取京东订单的支付链接
wxPayUrl := ""
if oldJdOrder != nil {
wxPayUrl = oldJdOrder.WxPayUrl
}
_ = s.RecordJdOrderHistory(ctx, jdOrderId, changeType, orderId, wxPayUrl, "")
return err
}
// ====================================================================================
// 订单复用相关方法
// ====================================================================================
// findReusableJdOrder 查找可复用的京东订单
func (s *sJdCookie) findReusableJdOrder(ctx context.Context, amount float64, category consts.RedeemOrderCardCategory) (jdOrder *entity.V1JdCookieJdOrder, err error) {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 查找符合条件的京东订单:
// 1. 金额和品类相同
// 2. 状态为待支付
// 3. 没有关联订单
// 4. 订单未过期
err = m.Where(dao.V1JdCookieJdOrder.Columns().Amount, amount).
Where(dao.V1JdCookieJdOrder.Columns().Category, category).
Where(dao.V1JdCookieJdOrder.Columns().Status, int(consts.JdOrderStatusPending)).
WhereNull(dao.V1JdCookieJdOrder.Columns().OrderId).
WhereGT(dao.V1JdCookieJdOrder.Columns().OrderExpireAt, gtime.Now()).
OrderAsc(dao.V1JdCookieJdOrder.Columns().CreatedAt).
Limit(1).
Scan(&jdOrder)
return
}
// ====================================================================================
// 京东接口调用方法
// ====================================================================================
// callJdCreateOrder 调用京东下单接口
func (s *sJdCookie) callJdCreateOrder(ctx context.Context, req *model.CallJdCreateOrderReq) (res *model.CallJdCreateOrderRes, err error) {
res = &model.CallJdCreateOrderRes{
IsCkFailed: false,
}
cookie, err := s.getCookieById(ctx, req.CookieId)
if err != nil {
return
}
appResp, err := originalJd.NewClient().AppleRecharge(ctx, &originalJd.AppleRechargeReq{
FacePrice: req.Amount,
Category: req.Category,
OrderNum: req.JdOrderId,
Cookies: cookie.CookieValue,
})
glog.Info(ctx, "下单接口返回", appResp)
if err != nil {
return
}
res.Remark = appResp.Msg
if appResp.Code == consts.CardCookieJDStatusCkFailed {
res.IsCkFailed = true
return res, gerror.New("Cookie已失效")
}
if appResp.Code != consts.CardCookieJDStatusSuccess || appResp.Deeplink == "" {
return res, gerror.New("下单失败," + appResp.Msg)
}
res = &model.CallJdCreateOrderRes{
RealJdOrderId: appResp.OrderId, // 京东客户端返回的真实订单ID
PayId: appResp.PayId,
WxPayUrl: appResp.Deeplink,
}
return
}
// refreshPaymentUrl 刷新支付链接
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, req *model.RefreshPaymentUrlReq) (wxPayUrl string, isCkFailed bool, err error) {
cookie, err := s.getCookieById(ctx, req.CookieId)
if err != nil {
return
}
jdOrder, err := s.getJdOrderByJdOrderId(ctx, req.JdOrderId)
if err != nil {
return
}
jdOrderInt, _ := convertor.ToInt(jdOrder.RealJdOrderId)
appResp, err := originalJd.NewClient().RefreshPayment(ctx, &originalJd.RefreshPaymentReq{
Cookies: cookie.CookieValue,
OrderId: jdOrderInt,
PayId: req.PayId,
UserOrderId: req.JdOrderId,
})
glog.Info(ctx, "刷新支付链接", g.Map{
"appResp": appResp,
"cookie": cookie,
"jdOrderId": req.JdOrderId,
})
if err != nil {
return
}
if appResp.Code == consts.CardCookieJDStatusExpired {
return "", true, gerror.New(appResp.Msg)
}
if appResp.Code != consts.CardCookieJDStatusSuccess || appResp.Deeplink == "" {
return "", false, gerror.New("刷新链接," + appResp.Msg)
}
wxPayUrl = appResp.Deeplink
return
}
// callJdCheckPayment 调用京东接口检查支付状态
func (s *sJdCookie) callJdCheckPayment(ctx context.Context, req *model.CallJdCheckPaymentReq) (*originalJd.AppleRechargeCardInfoResp, error) {
// 创建京东客户端
client := originalJd.NewClient()
// 调用检查支付状态接口使用京东客户端返回的真实订单ID
appReq := &originalJd.AppleRechargeCardInfoReq{
OrderId: req.JdOrderId,
JdOrderId: req.RealJdOrderId,
Cookies: req.CookieValue,
Category: req.Category,
}
resp, err := client.GetCardInfo(ctx, appReq)
if err != nil {
glog.Error(ctx, "调用京东检查支付状态接口失败", g.Map{
"realJdOrderId": req.RealJdOrderId,
"error": err,
})
return resp, err
}
glog.Info(ctx, "京东检查支付状态成功", resp)
return resp, nil
}
// ====================================================================================
// Cookie处理方法
// ====================================================================================
// handleCookieFailure 处理Cookie失败
func (s *sJdCookie) handleCookieFailure(ctx context.Context, cookieId, orderId string, isCKFailed bool, remark string) {
// 获取当前Cookie信息
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
var account *entity.V1JdCookieAccount
err := m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Scan(&account)
if err != nil || account == nil {
return
}
newFailureCount := account.FailureCount + 1
statusBefore := consts.JdCookieStatus(account.Status)
var statusAfter consts.JdCookieStatus
if newFailureCount >= consts.JdCookieMaxFailureCount || isCKFailed {
// 失败次数过多,标记为失效
statusAfter = consts.JdCookieStatusExpired
_, _ = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Update(&do.V1JdCookieAccount{
Status: int(statusAfter),
FailureCount: newFailureCount,
})
} else {
// 暂停Cookie
statusAfter = consts.JdCookieStatusSuspend
suspendUntil := gtime.Now().Add(time.Minute * consts.JdCookieSuspendDuration)
_, _ = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Update(&do.V1JdCookieAccount{
Status: int(statusAfter),
FailureCount: newFailureCount,
SuspendUntil: suspendUntil,
})
}
// 记录失败历史
_ = s.RecordCookieHistory(ctx, &model.RecordCookieHistoryReq{
CookieId: cookieId,
ChangeType: consts.CookieChangeTypeFail,
StatusBefore: statusBefore,
StatusAfter: statusAfter,
UserOrderId: orderId,
FailureCount: newFailureCount,
Remark: remark,
})
}