Files
kami_backend/internal/logic/jd_cookie/rotation.go
danial f358aa0745 feat(jd-cookie): 引入用户订单号支持并重构订单创建逻辑
- 新增用户订单号字段以区分内部订单号
- 修改订单表结构添加 user_order_id 字段及索引
- 更新 CreateOrder 接口支持用户订单号参数-重构 CreateOrder 和 GetPaymentUrl 方法返回统一结果对象
- 新增模型定义用于封装订单创建与支付结果
- 调整相关逻辑方法签名与调用方式适配新结构- 优化订单创建流程增加内部订单号生成逻辑
- 完善订单查询逻辑确保正确关联用户订单号- 更新控制器层对接新版服务接口- 升级 Cookie 状态及订单状态管理枚举类型使用
2025-10-13 15:00:11 +08:00

341 lines
10 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/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"
"github.com/gogf/gf/v2/util/guid"
)
// ====================================================================================
// 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, cookie.CookieId, consts.CookieChangeTypeResume,
int(consts.JdCookieStatusSuspend), int(consts.JdCookieStatusNormal), "", cookie.FailureCount)
}
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) (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状态失败")
}
// 记录状态变更历史
if oldCookie.Status != int(status) || oldCookie.FailureCount != failureCount {
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, cookieId, changeType, oldCookie.Status, int(status), "", failureCount)
}
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) (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
default:
changeType = consts.JdOrderChangeTypeReplace
}
// 获取当前关联的订单ID
orderId := ""
if oldOrder.CurrentOrderId > 0 {
// 查询订单表获取订单号
var order *entity.V1JdCookieOrder
_ = dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1JdCookieOrder.Columns().Id, oldOrder.CurrentOrderId).
Scan(&order)
if order != nil {
orderId = order.OrderId
}
}
payUrl := wxPayUrl
if payUrl == "" {
payUrl = oldOrder.WxPayUrl
}
_ = s.RecordJdOrderHistory(ctx, jdOrderId, changeType, orderId, payUrl)
}
return
}
// ====================================================================================
// 历史记录相关方法
// ====================================================================================
// RecordCookieHistory 记录Cookie变更历史
func (s *sJdCookie) RecordCookieHistory(ctx context.Context, cookieId string, changeType consts.CookieChangeType, statusBefore, statusAfter int, userOrderId string, failureCount int) (err error) {
m := dao.V1JdCookieChangeHistory.Ctx(ctx).DB(config.GetDatabaseV1())
historyUuid := utils.GenerateRandomUUID()
_, err = m.Insert(&do.V1JdCookieChangeHistory{
HistoryUuid: historyUuid,
CookieId: cookieId,
ChangeType: changeType,
StatusBefore: statusBefore,
StatusAfter: statusAfter,
UserOrderId: userOrderId,
FailureCount: failureCount,
})
if err != nil {
glog.Error(ctx, "记录Cookie变更历史失败", g.Map{
"cookieId": cookieId,
"changeType": changeType,
"statusBefore": statusBefore,
"statusAfter": statusAfter,
"error": err,
})
}
return
}
// RecordJdOrderHistory 记录京东订单变更历史
func (s *sJdCookie) RecordJdOrderHistory(ctx context.Context, jdOrderId string, changeType consts.JdOrderChangeType, orderId, wxPayUrl string) (err error) {
m := dao.V1JdCookieJdOrderChangeHistory.Ctx(ctx).DB(config.GetDatabaseV1())
historyUuid := guid.S()
_, err = m.Insert(&do.V1JdCookieJdOrderChangeHistory{
HistoryUuid: historyUuid,
JdOrderId: jdOrderId,
ChangeType: changeType,
OrderId: orderId,
WxPayUrl: wxPayUrl,
})
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 := guid.S()
_, 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
}