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

351 lines
11 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/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"
)
// ====================================================================================
// Cookie轮询分配相关方法
// ====================================================================================
// GetAvailableCookie 获取可用的Cookie轮询分配
func (s *sJdCookie) GetAvailableCookie(ctx context.Context) (cookieId string, err error) {
// 首先尝试解锁过期的暂停Cookie
s.unlockExpiredCookies(ctx)
// 获取所有可用的Cookie
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
var availableCookies []*entity.V1JdCookieAccount
err = m.Where(dao.V1JdCookieAccount.Columns().Status, int(consts.JdCookieStatusNormal)).
OrderAsc(dao.V1JdCookieAccount.Columns().LastUsedAt).
Scan(&availableCookies)
if err != nil {
return "", gerror.Wrap(err, "查询可用Cookie失败")
}
if len(availableCookies) == 0 {
return "", gerror.New(consts.ErrCodeCookieNotAvailable)
}
// 选择最久未使用的Cookie
selectedCookie := availableCookies[0]
// 更新最后使用时间
_, err = m.Where(dao.V1JdCookieAccount.Columns().CookieId, selectedCookie.CookieId).
Update(&do.V1JdCookieAccount{
LastUsedAt: gtime.Now(),
})
if err != nil {
glog.Error(ctx, "更新Cookie最后使用时间失败", err)
}
return selectedCookie.CookieId, nil
}
// unlockExpiredCookies 解锁过期的暂停Cookie
func (s *sJdCookie) unlockExpiredCookies(ctx context.Context) {
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
// 先查询即将解锁的Cookie用于记录历史
var expiredCookies []*entity.V1JdCookieAccount
err := m.Where(dao.V1JdCookieAccount.Columns().Status, int(consts.JdCookieStatusSuspend)).
WhereLTE(dao.V1JdCookieAccount.Columns().SuspendUntil, gtime.Now()).
WhereNotNull(dao.V1JdCookieAccount.Columns().SuspendUntil).
Scan(&expiredCookies)
if err != nil {
glog.Error(ctx, "查询过期暂停Cookie失败", err)
return
}
// 批量解锁暂停时间已过的Cookie
_, err = m.Where(dao.V1JdCookieAccount.Columns().Status, int(consts.JdCookieStatusSuspend)).
WhereLTE(dao.V1JdCookieAccount.Columns().SuspendUntil, gtime.Now()).
WhereNotNull(dao.V1JdCookieAccount.Columns().SuspendUntil).
Update(&do.V1JdCookieAccount{
Status: int(consts.JdCookieStatusNormal),
SuspendUntil: nil,
})
if err != nil {
glog.Error(ctx, "解锁暂停Cookie失败", err)
return
}
// 为每个解锁的Cookie记录历史
for _, cookie := range expiredCookies {
_ = s.RecordCookieHistory(ctx, &model.RecordCookieHistoryReq{
CookieId: cookie.CookieId,
ChangeType: consts.CookieChangeTypeResume,
StatusBefore: consts.JdCookieStatusSuspend,
StatusAfter: consts.JdCookieStatusNormal,
UserOrderId: "",
FailureCount: cookie.FailureCount,
Remark: "自动解锁过期暂停Cookie",
})
}
if len(expiredCookies) > 0 {
glog.Info(ctx, "自动解锁过期暂停Cookie", g.Map{
"unlockedCount": len(expiredCookies),
})
}
}
// ====================================================================================
// Cookie状态管理相关方法
// ====================================================================================
// UpdateCookieStatus 更新Cookie状态
func (s *sJdCookie) UpdateCookieStatus(ctx context.Context, cookieId string, status consts.JdCookieStatus, failureCount int, remark string) (err error) {
if cookieId == "" {
return gerror.New("Cookie ID不能为空")
}
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
// 获取更新前的Cookie信息
var oldCookie *entity.V1JdCookieAccount
err = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Scan(&oldCookie)
if err != nil {
return gerror.Wrap(err, "查询Cookie信息失败")
}
if oldCookie == nil {
return gerror.New("Cookie不存在")
}
updateData := &do.V1JdCookieAccount{
Status: int(status),
FailureCount: failureCount,
}
// 如果是暂停状态,设置暂停时间
if status == consts.JdCookieStatusSuspend {
updateData.SuspendUntil = gtime.Now().Add(time.Minute * consts.JdCookieSuspendDuration)
}
// 如果是恢复正常状态,清除暂停时间
if status == consts.JdCookieStatusNormal {
updateData.SuspendUntil = nil
}
_, err = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Update(updateData)
if err != nil {
return gerror.Wrap(err, "更新Cookie状态失败")
}
// 确定变更类型
var changeType consts.CookieChangeType
switch status {
case consts.JdCookieStatusNormal:
changeType = consts.CookieChangeTypeResume
case consts.JdCookieStatusSuspend:
changeType = consts.CookieChangeTypeSuspend
case consts.JdCookieStatusExpired:
changeType = consts.CookieChangeTypeFail
default:
changeType = consts.CookieChangeTypeUpdate
}
// 记录状态变更历史
_ = s.RecordCookieHistory(ctx, &model.RecordCookieHistoryReq{
CookieId: cookieId,
ChangeType: changeType,
StatusBefore: consts.JdCookieStatus(oldCookie.Status),
StatusAfter: status,
UserOrderId: "",
FailureCount: failureCount,
Remark: remark,
})
return
}
// ====================================================================================
// 京东订单管理相关方法
// ====================================================================================
// CreateJdOrder 创建京东订单
func (s *sJdCookie) CreateJdOrder(ctx context.Context, jdOrderId, payId, cookieId, category string, amount float64) (err error) {
if jdOrderId == "" || payId == "" || cookieId == "" {
return gerror.New("京东订单参数不能为空")
}
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err = m.Insert(&do.V1JdCookieJdOrder{
JdOrderId: jdOrderId,
PayId: payId,
Amount: amount,
Category: category,
CookieId: cookieId,
Status: int(consts.JdOrderStatusPending),
OrderExpireAt: gtime.Now().Add(time.Hour * consts.JdOrderExpireDuration),
})
if err != nil {
return gerror.Wrap(err, "创建京东订单失败")
}
// 记录京东订单创建历史
_ = s.RecordJdOrderHistory(ctx, jdOrderId, consts.JdOrderChangeTypeCreate, payId, "", "")
return
}
// UpdateJdOrderStatus 更新京东订单状态
func (s *sJdCookie) UpdateJdOrderStatus(ctx context.Context, jdOrderId string, status consts.JdOrderStatus, wxPayUrl string, remark string) (err error) {
if jdOrderId == "" {
return gerror.New("京东订单号不能为空")
}
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 {
return gerror.Wrap(err, "查询京东订单失败")
}
if oldOrder == nil {
return gerror.New("京东订单不存在")
}
updateData := &do.V1JdCookieJdOrder{
Status: int(status),
}
if wxPayUrl != "" {
updateData.WxPayUrl = wxPayUrl
updateData.WxPayExpireAt = gtime.Now().Add(time.Minute * consts.WxPayUrlExpireDuration)
}
_, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(updateData)
if err != nil {
return gerror.Wrap(err, "更新京东订单状态失败")
}
// 记录状态变更历史
if oldOrder.Status != int(status) {
var changeType consts.JdOrderChangeType
switch status {
case consts.JdOrderStatusPaid:
changeType = consts.JdOrderChangeTypePay
case consts.JdOrderStatusExpired:
changeType = consts.JdOrderChangeTypeExpire
case consts.JdOrderStatusCanceled:
changeType = consts.JdOrderChangeTypeInvalid
case consts.JdOrderStatusCkFailed:
changeType = consts.JdOrderChangeTypeInvalid
default:
changeType = consts.JdOrderChangeTypeSend
}
// 获取关联的订单ID
orderId := oldOrder.OrderId
payUrl := wxPayUrl
if payUrl == "" {
payUrl = oldOrder.WxPayUrl
}
_ = s.RecordJdOrderHistory(ctx, jdOrderId, changeType, orderId, payUrl, remark)
}
return
}
// ====================================================================================
// 历史记录相关方法
// ====================================================================================
// RecordCookieHistory 记录Cookie变更历史
func (s *sJdCookie) RecordCookieHistory(ctx context.Context, req *model.RecordCookieHistoryReq) (err error) {
m := dao.V1JdCookieChangeHistory.Ctx(ctx).DB(config.GetDatabaseV1())
historyUuid := utils.GenerateRandomUUID()
_, err = m.Insert(&do.V1JdCookieChangeHistory{
HistoryUuid: historyUuid,
CookieId: req.CookieId,
ChangeType: req.ChangeType,
StatusBefore: int(req.StatusBefore),
StatusAfter: int(req.StatusAfter),
UserOrderId: req.UserOrderId,
FailureCount: req.FailureCount,
Remark: req.Remark,
})
if err != nil {
glog.Error(ctx, "记录Cookie变更历史失败", g.Map{
"cookieId": req.CookieId,
"changeType": req.ChangeType,
"statusBefore": req.StatusBefore,
"statusAfter": req.StatusAfter,
"userOrderId": req.UserOrderId,
"failureCount": req.FailureCount,
"remark": req.Remark,
"error": err,
})
}
return
}
// RecordJdOrderHistory 记录京东订单变更历史
func (s *sJdCookie) RecordJdOrderHistory(ctx context.Context, jdOrderId string, changeType consts.JdOrderChangeType, orderId, wxPayUrl, remark string) (err error) {
m := dao.V1JdCookieJdOrderChangeHistory.Ctx(ctx).DB(config.GetDatabaseV1())
_, err = m.Insert(&do.V1JdCookieJdOrderChangeHistory{
HistoryUuid: utils.GenerateRandomUUID(),
JdOrderId: jdOrderId,
ChangeType: changeType,
OrderId: orderId,
WxPayUrl: wxPayUrl,
Remark: remark,
})
if err != nil {
glog.Error(ctx, "记录京东订单变更历史失败", g.Map{
"jdOrderId": jdOrderId,
"changeType": changeType,
"orderId": orderId,
"error": err,
})
}
return
}
// RecordOrderHistory 记录订单变更历史
func (s *sJdCookie) RecordOrderHistory(ctx context.Context, orderId string, changeType consts.OrderChangeType, jdOrderId string) (err error) {
m := dao.V1JdCookieOrderChangeHistory.Ctx(ctx).DB(config.GetDatabaseV1())
historyUuid := utils.GenerateRandomUUID()
_, err = m.Insert(&do.V1JdCookieOrderChangeHistory{
HistoryUuid: historyUuid,
OrderId: orderId,
ChangeType: changeType,
JdOrderId: jdOrderId,
})
if err != nil {
glog.Error(ctx, "记录订单变更历史失败", g.Map{
"orderId": orderId,
"changeType": changeType,
"jdOrderId": jdOrderId,
"error": err,
})
}
return
}