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