fix(appleOrder): 优化苹果订单处理与回调逻辑

- 简化临时冻结账号恢复逻辑,缩短暂停时长并批量更新状态
- 修改苹果接口请求地址为本地地址,方便开发调试
- 增加苹果订单最大回调次数限制,完善回调失败重试逻辑
- 新增定时任务处理订单回调及超时重调度,提高系统稳定性
- 改进订单处理并发控制,防止订单重复处理
- 统一错误处理,替换部分错误包装为gerror返回
- 调整苹果礼品卡充值请求面值类型为float64,兼容金额精度
- 删除冗余已废弃代码,优化充值成功处理逻辑日志输出
- 添加缓存相关字符串转换函数,修正缓存前缀定义
- 调整订单分配逻辑,增加创建时间过滤与状态更新操作
This commit is contained in:
danial
2025-11-25 15:43:27 +08:00
parent 9a8f0c28bb
commit 80110cd160
24 changed files with 211 additions and 189 deletions

View File

@@ -13,14 +13,14 @@ import (
// RechargeSubmitReq 礼品卡充值
type RechargeSubmitReq struct {
g.Meta `path:"/cardInfo/appleCard/submit" tags:"苹果礼品卡充值" method:"post" summary:"充值礼品卡"`
CardNo string `json:"cardNo" description:"卡号"`
CardPass string `json:"cardPass" description:"密码" v:"required#卡密不能为空"`
FaceValue int64 `json:"faceValue" description:"面值" v:"required#面值不能为空"`
CallbackUrl string `json:"callbackUrl" description:"回调地址"`
Attach string `json:"attach" description:"附加信息(目前是上游订单号)"`
TimeStamp int `json:"timeStamp" description:"时间戳"`
Sign string `json:"sign" description:"签名"`
MerchantId string `json:"merchantId" description:"商户ID"`
CardNo string `json:"cardNo" description:"卡号"`
CardPass string `json:"cardPass" description:"密码" v:"required#卡密不能为空"`
FaceValue float64 `json:"faceValue" description:"面值" v:"required#面值不能为空"`
CallbackUrl string `json:"callbackUrl" description:"回调地址"`
Attach string `json:"attach" description:"附加信息(目前是上游订单号)"`
TimeStamp int `json:"timeStamp" description:"时间戳"`
Sign string `json:"sign" description:"签名"`
MerchantId string `json:"merchantId" description:"商户ID"`
}
type RechargeSubmitRes struct {

View File

@@ -7,6 +7,7 @@ const (
AppleOrderMaxDistributionCount int = 5
AppleTmpStoppedMaxCount int = 5
AppleOrderMaxCallbackCount int = 3 // 最大回调次数
)
// AppleAccountStatus 账号状态编码
@@ -87,7 +88,9 @@ const (
type AppleOrderOperation string
const (
AppleRechargeOperationCreated AppleOrderOperation = "创建订单"
AppleRechargeOperationCreated AppleOrderOperation = "创建订单"
AppleRechargeOperationTimeout AppleOrderOperation = "订单超时"
AppleRechargeOperationWrongPassword AppleOrderOperation = "代充值账户密码错误,等待重新调度"
AppleRechargeOperationDuplicatedOrder AppleOrderOperation = "创建订单(人工处理订单,需人工介入)"

View File

@@ -2,12 +2,13 @@ package card_info_apple
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"kami/internal/consts"
"kami/internal/errHandler"
"kami/internal/model"
"kami/internal/service"
"github.com/gogf/gf/v2/errors/gerror"
v1 "kami/api/card_info_apple/v1"
"github.com/gogf/gf/v2/errors/gcode"
@@ -22,7 +23,7 @@ func (c *ControllerV1) CallBackOrderManual(ctx context.Context, req *v1.CallBack
}
if orderEntity.Status != int(consts.AppleRechargeOrderSuccess) &&
orderEntity.Status != int(consts.AppleRechargeOrderFail) {
err = errHandler.WrapError(ctx, gcode.CodeInvalidRequest, nil, "订单正在处理中,请稍后重试~")
err = gerror.NewCode(gcode.CodeInvalidRequest, "订单正在处理中,请稍后重试~")
_ = rechargeOrderService.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),

View File

@@ -9,6 +9,7 @@ import (
"kami/api/card_info_apple/v1"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
func (c *ControllerV1) CardInfoSuspendOrContinue(ctx context.Context, req *v1.CardInfoSuspendOrContinueReq) (res *v1.CardInfoSuspendOrContinueRes, err error) {
@@ -24,7 +25,7 @@ func (c *ControllerV1) CardInfoSuspendOrContinue(ctx context.Context, req *v1.Ca
case int(consts.AppleAccountForbidden):
err = appleAccountService.ModifyStatus(ctx, req.ID, consts.AppleAccountNormal, nil)
default:
err = errHandler.WrapError(ctx, gcode.CodeInvalidParameter, nil, "苹果账号未处于正常状态")
err = gerror.NewCode(gcode.CodeInvalidParameter, "苹果账号未处于正常状态")
return
}
if err != nil {

View File

@@ -3,27 +3,27 @@ package card_info_apple
import (
"context"
"kami/internal/consts"
"kami/internal/errHandler"
"kami/internal/service"
"kami/api/card_info_apple/v1"
v1 "kami/api/card_info_apple/v1"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
func (c *ControllerV1) RechargeDuplicatedCardPass(ctx context.Context, req *v1.RechargeDuplicatedCardPassReq) (res *v1.RechargeDuplicatedCardPassRes, err error) {
orderService := service.AppleOrder()
data, err := orderService.GetOneByOrderNo(ctx, req.OrderNo, nil)
if err != nil || data.OrderNo == "" {
return nil, errHandler.WrapError(ctx, gcode.CodeNotFound, nil, "当前订单不存在")
return nil, gerror.NewCode(gcode.CodeNotFound, "当前订单不存在")
}
if data.Status != int(consts.AppleRechargeOrderCardNoOrCardPassDuplicated) {
return nil, errHandler.WrapError(ctx, gcode.CodeNotFound, nil, "当前订单状态不正确")
return nil, gerror.NewCode(gcode.CodeNotFound, "当前订单状态不正确")
}
t := consts.AppleRechargeOrderAmountDifferent
previousOrder, err := orderService.QueryOneByCardPass(ctx, data.CardPass, &t)
if err != nil || previousOrder.OrderNo == "" {
return nil, errHandler.WrapError(ctx, gcode.CodeNotFound, nil, "金额异议订单不存在")
return nil, gerror.NewCode(gcode.CodeNotFound, "金额异议订单不存在")
}
//err = orderService.UpdateActualAmountAndHistory(ctx, &model.UpdateAmountAndHistoryRecord{
// OrderInfo: &model.UpdateAmountRecord{
@@ -38,6 +38,6 @@ func (c *ControllerV1) RechargeDuplicatedCardPass(ctx context.Context, req *v1.R
//if err != nil {
// return nil, errHandler.WrapError(ctx, gcode.CodeInternalError, err, "修改订单失败")
//}
_ = orderService.CallBackOrderToUpstream(ctx, req.OrderNo)
// _ = orderService.CallBackOrderToUpstream(ctx, req.OrderNo)
return
}

View File

@@ -8,7 +8,7 @@ import (
"kami/internal/model"
"kami/internal/service"
"kami/api/card_info_apple/v1"
v1 "kami/api/card_info_apple/v1"
"github.com/gogf/gf/v2/errors/gcode"
)

View File

@@ -14,6 +14,7 @@ import (
v1 "kami/api/card_info_apple/v1"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
func (c *ControllerV1) RechargeOrderResetStatus(ctx context.Context, req *v1.RechargeOrderResetStatusReq) (res *v1.RechargeOrderResetStatusRes, err error) {
@@ -24,7 +25,7 @@ func (c *ControllerV1) RechargeOrderResetStatus(ctx context.Context, req *v1.Rec
return err2
}
if orderInfo.Status != int(consts.AppleRechargeOrderFreeze) {
return errHandler.WrapError(ctx, gcode.CodeInternalError, nil, "只有冻结订单才可以重置")
return gerror.NewCode(gcode.CodeInternalError, "只有冻结订单才可以重置")
}
if err2 = rechargeService.ModifyOrderStatus(ctx, req.OrderNo, consts.AppleRechargeOrderWaiting, fmt.Sprintf("人工重置订单:%s", req.Remark), tx); err2 != nil {
return err2

View File

@@ -53,8 +53,8 @@ func (c *ControllerV1) RechargeSubmit(ctx context.Context, req *v1.RechargeSubmi
// return
//}
orderNo, err := service.AppleOrder().AddRechargeOrder(ctx, &model.AppleCardRechargeInput{
Amount: float64(req.FaceValue),
Balance: float64(req.FaceValue),
Amount: req.FaceValue,
Balance: req.FaceValue,
RechargeSubmitReq: req,
})
if err != nil {

View File

@@ -13,6 +13,7 @@ import (
"kami/api/card_info_t_mall_game/v1"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
func (c *ControllerV1) CallBackOrderManual(ctx context.Context, req *v1.CallBackOrderManualReq) (res *v1.CallBackOrderManualRes, err error) {
@@ -58,7 +59,7 @@ func (c *ControllerV1) CallBackOrderManual(ctx context.Context, req *v1.CallBack
}, nil)
err = tMallGameOrderService.CallBackOrderToUpstream(ctx, req.OrderNo)
default:
err = errHandler.WrapError(ctx, gcode.CodeInvalidRequest, nil, "订单正在处理中,请稍后重试~")
err = gerror.NewCode(gcode.CodeInvalidRequest, "订单正在处理中,请稍后重试~")
_ = tMallGameOrderService.AddOrderHistory(ctx, &model.AddAgisoCallBackHistoryInput{
OrderNo: req.OrderNo,
Status: consts.RechargeTMallGameOrderCallbackFailedManual,

View File

@@ -16,6 +16,8 @@ func (c *ControllerV1) TMallGameAccountCreate(ctx context.Context, req *v1.TMall
err = accountService.Add(ctx, &model.RechargeTMallGameAccountAddInput{
TMallGameAccountCreateReq: req,
})
err = errHandler.WrapError(ctx, gcode.CodeInternalError, nil, "创建账户失败")
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "创建账户失败")
}
return
}

View File

@@ -13,6 +13,8 @@ import (
func (c *ControllerV1) TMallGameAccountDelete(ctx context.Context, req *v1.TMallGameAccountDeleteReq) (res *v1.TMallGameAccountDeleteRes, err error) {
accountService := service.RechargeTMallGameAccount()
err = accountService.DeleteOne(ctx, req.ID)
err = errHandler.WrapError(ctx, gcode.CodeInternalError, nil, "删除账户错误")
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "删除账户错误")
}
return
}

View File

@@ -38,7 +38,7 @@ func (c *ControllerV1) SysPaymentAdd(ctx context.Context, req *v1.SysPaymentAddR
return
}
if !isEnough {
err = errHandler.WrapError(ctx, gcode.CodeValidationFailed, nil, "余额不足")
err = gerror.NewCode(gcode.CodeValidationFailed, "余额不足")
return
}
_, err = userPaymentService.Consumption(ctx, &model.SysUserPaymentRechargeOrConsumeInput{

View File

@@ -7,6 +7,9 @@ import (
)
func WrapError(ctx context.Context, code gcode.Code, err error, message ...string) error {
if err == nil {
return nil
}
newMsg := ""
if len(message) != 0 {
newMsg = message[0]

View File

@@ -18,7 +18,6 @@ import (
"kami/utility/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtime"
)
@@ -314,26 +313,20 @@ func (a *sAppleAccount) CheckIsNormal(ctx context.Context, accountId string) {
// HandleTmpStoppedList 处理临时暂停账号
func (a *sAppleAccount) HandleTmpStoppedList(ctx context.Context) (err error) {
dataList := make([]entity.V1CardAppleAccountInfo, 0)
err = dao.V1CardAppleAccountInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
//临时暂停的账号处理
_, err = dao.V1CardAppleAccountInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleAccountInfo.Columns().Status, consts.AppleAccountTmpStoppedByTooManyRequest).
WhereLT(dao.V1CardAppleAccountInfo.Columns().UpdatedAt, gtime.Now().Add(-10*gtime.M)).
Scan(&dataList)
if err != nil {
glog.Error(ctx, dataList, err)
return
}
for _, info := range dataList {
err = config.GetDatabaseV1().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
if err2 := a.ModifyStatus(ctx, info.Id, consts.AppleAccountNormal, tx); err2 != nil {
return err2
}
return nil
WhereLT(dao.V1CardAppleAccountInfo.Columns().UpdatedAt, gtime.Now().Add(-3*gtime.M)).
Update(do.V1CardAppleAccountInfo{
Status: consts.AppleAccountNormal,
})
//充值过快的账号处理
_, err = dao.V1CardAppleAccountInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleAccountInfo.Columns().Status, consts.AppleAccountTmpLimited).
WhereLT(dao.V1CardAppleAccountInfo.Columns().UpdatedAt, gtime.Now().Add(-gtime.M)).
Update(do.V1CardAppleAccountInfo{
Status: consts.AppleAccountNormal,
})
if err != nil {
glog.Error(ctx, "苹果账号:"+info.Id+info.Account, err)
}
}
return
}

View File

@@ -17,9 +17,11 @@ import (
"github.com/gogf/gf/v2/util/gconv"
"kami/internal/consts"
"kami/internal/dao"
"kami/internal/errHandler"
"kami/internal/model"
"kami/internal/model/entity"
"kami/utility/config"
"kami/utility/pool"
"kami/utility/utils"
)
@@ -56,31 +58,32 @@ func (h *sAppleOrder) CallBackOrderToUpstream(ctx context.Context, orderNo strin
return
}
err = pool.New(pool.AppleCardCallBack, 20).Add(gctx.GetInitCtx(), func(ctx context.Context) {
isCallbackSucceed := false
for i := 1; i < 4; i++ {
isOk, err2 := h.CallbackOrder(ctx, &orderEntity)
if isOk {
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountID: orderEntity.AccountId,
Operation: consts.AppleRechargeOperationCallBackSuccess,
}, nil)
isCallbackSucceed = true
break
} else {
glog.Error(ctx, "回调上游失败,原因:\n", err2)
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountID: orderEntity.AccountId,
Operation: consts.AppleRechargeOperationCallBackFailed,
Remark: fmt.Sprintf("第%d次回调失败原因回调接口返回失败", i),
}, nil)
}
time.Sleep(time.Duration(i*2) * time.Second)
isOk, err2 := h.CallbackOrder(ctx, &orderEntity)
// 增加回调次数
_, err = dao.V1CardAppleRechargeInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleRechargeInfo.Columns().OrderNo, orderNo).
Increment(dao.V1CardAppleRechargeInfo.Columns().CallbackCount, 1)
if isOk {
_ = h.ModifyCallBackStatus(ctx, orderEntity.OrderNo, true, nil)
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountID: orderEntity.AccountId,
Operation: consts.AppleRechargeOperationCallBackSuccess,
}, nil)
return
}
glog.Error(ctx, "回调上游失败,原因:\n", err2)
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountID: orderEntity.AccountId,
Operation: consts.AppleRechargeOperationCallBackFailed,
Remark: fmt.Sprintf("回调订单%d失败原因回调接口返回失败", orderEntity.CallbackCount+1),
}, nil)
if orderEntity.CallbackCount+1 >= consts.AppleOrderMaxCallbackCount {
_ = h.ModifyCallBackStatus(ctx, orderEntity.OrderNo, false, nil)
}
_ = h.ModifyCallBackStatus(ctx, orderEntity.OrderNo, isCallbackSucceed, nil)
})
return
}

View File

@@ -1 +1,71 @@
package card_apple_order
import (
"context"
"github.com/gogf/gf/v2/os/gtime"
"kami/internal/consts"
"kami/internal/dao"
"kami/internal/model"
"kami/internal/model/entity"
"kami/utility/config"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
// CronCallbackPendingOrders 定时任务:回调处于失败和成功状态的订单
func (h *sAppleOrder) CronCallbackPendingOrders(ctx context.Context) error {
glog.Info(ctx, "开始执行苹果订单回调任务")
// 查询需要回调的订单(失败和成功状态)
var orders []*entity.V1CardAppleRechargeInfo
err := dao.V1CardAppleRechargeInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
WhereIn(dao.V1CardAppleRechargeInfo.Columns().Status, g.Slice{
consts.AppleRechargeOrderFail, // 充值失败
consts.AppleRechargeOrderSuccess, // 充值成功
}).
WhereNot(dao.V1CardAppleRechargeInfo.Columns().NotifyStatus, consts.CardAppleNotifyStatusSuccess). // 只回调未成功的
WhereLT(dao.V1CardAppleRechargeInfo.Columns().CallbackCount, consts.AppleOrderMaxCallbackCount). // 未达到最大回调次数
Limit(50). // 每次处理50个订单
Scan(&orders)
if err != nil {
glog.Error(ctx, "查询待回调订单失败:", err)
return err
}
if len(orders) == 0 {
glog.Debug(ctx, "无待回调订单")
return nil
}
// 逐个处理订单回调
for _, order := range orders {
// 执行回调
_ = h.CallBackOrderToUpstream(ctx, order.OrderNo)
}
return nil
}
// CronFiledScheduleTask 定时任务:回调处于失败和成功状态的订单
func (h *sAppleOrder) CronFailedScheduleTask(ctx context.Context) error {
var orders []*entity.V1CardAppleRechargeInfo
_ = dao.V1CardAppleRechargeInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleRechargeInfo.Columns().Status, consts.AppleRechargeOrderProcessing). // 只回调未成功的
WhereLT(dao.V1CardAppleRechargeInfo.Columns().UpdatedAt, gtime.Now().Add(-gtime.M*10)).
Scan(&orders)
for _, order := range orders {
if err := h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
RechargeId: int(order.Id),
OrderNo: order.OrderNo,
AccountID: order.AccountId,
AccountName: order.AccountName,
Operation: consts.AppleRechargeOperationTimeout,
Remark: "订单调度超时,重新调度",
}, nil); err != nil {
glog.Error(ctx, "添加订单操作记录失败:", err)
}
_ = h.ModifyOrderStatus(ctx, order.OrderNo, consts.AppleRechargeOrderWaiting, "订单调度超时,重新调度", nil)
}
return nil
}

View File

@@ -2,7 +2,6 @@ package card_apple_order
import (
"context"
"github.com/gogf/gf/v2/os/gmlock"
"kami/api/commonApi"
"kami/internal/consts"
"kami/internal/dao"
@@ -15,10 +14,11 @@ import (
"kami/utility/utils"
"slices"
"github.com/gogf/gf/v2/os/gmlock"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
@@ -161,12 +161,7 @@ func (h *sAppleOrder) DistributionAccordingAccount(ctx context.Context, account
if err2 != nil {
return err2
}
_, err2 = m.TX(tx).Update(g.Map{
dao.V1CardAppleRechargeInfo.Columns().DistributionCount: gdb.Counter{
Field: dao.V1CardAppleRechargeInfo.Columns().DistributionCount,
Value: 1,
},
})
_, err2 = m.TX(tx).Increment(dao.V1CardAppleRechargeInfo.Columns().DistributionCount, 1)
return err2
})
return err
@@ -349,9 +344,10 @@ func (h *sAppleOrder) GetAccordingOrders(ctx context.Context) (list []*entity.V1
defer gmlock.Unlock("sAppleOrder_GetAccordingOrders")
_ = dao.V1CardAppleRechargeInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleRechargeInfo.Columns().Status, consts.AppleRechargeOrderWaiting).
WhereLT(dao.V1CardAppleRechargeInfo.Columns().CreatedAt, gtime.Now().Add(-gtime.M)).
OrderAsc(dao.V1CardAppleRechargeInfo.Columns().CreatedAt).Scan(&list)
slices.DeleteFunc(list, func(v *entity.V1CardAppleRechargeInfo) bool {
list = slices.DeleteFunc(list, func(v *entity.V1CardAppleRechargeInfo) bool {
if v.Id == 0 || v.DistributionCount < consts.AppleOrderMaxDistributionCount {
return false
}
@@ -366,7 +362,7 @@ func (h *sAppleOrder) GetAccordingOrders(ctx context.Context) (list []*entity.V1
AccountID: v.AccountId,
AccountName: v.AccountName,
Operation: consts.AppleRechargeOperationCallBackTimeout,
Remark: "itunes调用订单超过最大次数",
Remark: "调用订单超过最大次数",
}, tx)
return err
})

View File

@@ -12,21 +12,18 @@ import (
"kami/utility/config"
"kami/utility/integration/apple"
"kami/utility/pool"
"sync"
"time"
"github.com/duke-git/lancet/v2/slice"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/os/gcron"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/glog"
"github.com/shopspring/decimal"
)
// handleRedeemResult 处理核销结果,根据状态码分类进行对应处理
func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderInfo *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo) error {
// 调用 Apple 服务进行核销(同步等待)
redeemClient := apple.NewClient()
// 准备推送请求
redeemReq := &apple.RedeemReq{
Account: accountInfo.Account,
@@ -34,7 +31,7 @@ func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderInfo *entity.
OrderId: orderInfo.OrderNo,
RedemptionCode: orderInfo.CardPass,
}
resp, err := redeemClient.Redeem(ctx, redeemReq)
resp, err := apple.NewClient().Redeem(ctx, redeemReq)
if err != nil {
return err
}
@@ -69,15 +66,13 @@ func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderInfo *entity.
// handleRedeemSuccess 处理核销成功的情况
func (h *sAppleOrder) handleRedeemSuccess(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, amount, balanceBefore, balanceAfter float64) error {
// 将返回的金额字符串转换为 float64假数据处理
actualAmount := orderEntity.CardAmount // 使用卡密面额作为默认金额
// 判断金额是否一致
var orderStatus consts.AppleRechargeOrderStatus
var historyOperation consts.AppleOrderOperation
var remark string
if actualAmount == orderEntity.CardAmount {
if amount == orderEntity.CardAmount {
orderStatus = consts.AppleRechargeOrderSuccess
historyOperation = consts.AppleRechargeOperationItunesSucceed
} else {
@@ -90,9 +85,9 @@ func (h *sAppleOrder) handleRedeemSuccess(ctx context.Context, orderEntity *enti
err := h.UpdateActualAmountAndHistoryAndWallet(ctx, &model.AppleAccountUpdateAmountAndHistoryRecord{
OrderInfo: &model.AppleAccountUpdateAmountRecord{
OrderNo: orderEntity.OrderNo,
Amount: actualAmount,
Amount: amount,
Status: orderStatus,
Remark: fmt.Sprintf("卡密:%s面额%.2f,实际充值:%.2f,充值账户:%s%s", orderEntity.CardPass, orderEntity.CardAmount, actualAmount, accountInfo.Account, remark),
Remark: fmt.Sprintf("卡密:%s面额%.2f,实际充值:%.2f,充值账户:%s充值前余额:%.2f,充值后余额:%.2f%s", orderEntity.CardPass, orderEntity.CardAmount, amount, accountInfo.Account, balanceBefore, balanceAfter, remark),
},
AccountId: accountInfo.Id,
HistoryRemark: remark,
@@ -105,9 +100,6 @@ func (h *sAppleOrder) handleRedeemSuccess(ctx context.Context, orderEntity *enti
return err
}
// 异步回调上游
_ = h.CallBackOrderToUpstream(ctx, orderEntity.OrderNo)
glog.Info(ctx, fmt.Sprintf("订单核销成功处理完毕 - 订单号: %s", orderEntity.OrderNo))
return nil
}
@@ -138,35 +130,6 @@ func (h *sAppleOrder) handleRedeemFailed(ctx context.Context, orderEntity *entit
return nil
}
// handleAccountInvalid 处理账号失效的情况
func (h *sAppleOrder) handleAccountInvalid(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, errMsg string) error {
// 标记当前账号为失效(密码错误或被禁用)
_ = service.AppleAccount().ModifyStatus(ctx, accountInfo.Id, consts.AppleAccountWrongPassword, nil)
// 订单重新设为待调度状态
err := h.ModifyOrderStatus(ctx, orderEntity.OrderNo, consts.AppleRechargeOrderWaiting, "账号失效,待重新分配", nil)
if err != nil {
glog.Error(ctx, fmt.Sprintf("更新订单待处理状态失败 - 订单号: %s, 错误: %v", orderEntity.OrderNo, err))
return err
}
// 减少分配计数,允许重新分配
_ = h.DecrementDistributionCount(ctx, orderEntity.OrderNo)
// 添加历史记录
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
AccountID: accountInfo.Id,
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountName: accountInfo.Account,
Operation: consts.AppleRechargeOperationWrongPassword,
Remark: fmt.Sprintf("账号失效:%s", errMsg),
}, nil)
glog.Info(ctx, fmt.Sprintf("账号失效,订单已退回待重新分配 - 订单号: %s, 账号: %s", orderEntity.OrderNo, accountInfo.Account))
return nil
}
// handleSystemError 处理系统资源错误5000-5999
// 订单退回待处理,等待系统重新调度重试
func (h *sAppleOrder) handleSystemError(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, code apple.Code, message string) error {
@@ -254,17 +217,8 @@ func (h *sAppleOrder) handleRedeemLimitError(ctx context.Context, orderEntity *e
case apple.CodeAppleRedeemLimitExceededTemporarily:
// 临时限制 - 标记为临时冻结,暂停兑换
accountStatus = consts.AppleAccountTmpStoppedByTooManyRequest
_, _ = gcron.AddOnce(gctx.GetInitCtx(), "@every 2m", func(ctx2 context.Context) {
// 获取追踪能力
_ = service.AppleAccount().ModifyStatus(ctx2, orderEntity.AccountId, consts.AppleAccountNormal, nil)
})
accountRemark = "苹果账户临时充值限制2分钟后恢复"
case apple.CodeAppleRedeemLimitExceededPerMinute:
// 一分钟限制 - 标记为临时冻结
_, _ = gcron.AddOnce(gctx.GetInitCtx(), "@every 1m", func(ctx2 context.Context) {
// 获取追踪能力
_ = service.AppleAccount().ModifyStatus(ctx2, orderEntity.AccountId, consts.AppleAccountNormal, nil)
})
accountStatus = consts.AppleAccountTmpLimited
accountRemark = "苹果账户一分钟充值限制1分钟后恢复"
default:
@@ -303,7 +257,7 @@ func (h *sAppleOrder) handleRedeemLimitError(ctx context.Context, orderEntity *e
// 直接按订单失败处理,更新订单状态为失败
func (h *sAppleOrder) handleCardCodeError(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, code apple.Code, message string) error {
// 根据卡密错误类型确定订单失败状态
var orderStatus consts.AppleRechargeOrderStatus = consts.AppleRechargeOrderFail
var orderStatus = consts.AppleRechargeOrderFail
var operationRemark string
switch code {
@@ -322,9 +276,6 @@ func (h *sAppleOrder) handleCardCodeError(ctx context.Context, orderEntity *enti
return err
}
// 减少分配计数(卡密错误不再重试)
_ = h.DecrementDistributionCount(ctx, orderEntity.OrderNo)
// 添加历史记录
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
AccountID: accountInfo.Id,
@@ -349,24 +300,39 @@ func (h *sAppleOrder) ProcessOrderWithPush(ctx context.Context) (err error) {
if len(orderEntities) == 0 {
return
}
poolClient := pool.New("apple_order_process_push_pool", 1)
poolClient := pool.New(pool.AppleOrderProcessPushPool, 20)
wg := sync.WaitGroup{}
for _, orderInfo := range orderEntities {
wg.Add(1)
_ = poolClient.Add(ctx, func(ctx context.Context) {
defer wg.Done()
orderLock, _ := cache.NewCache().Get(ctx, cache.PrefixAppleOrder.Key(orderInfo.OrderNo))
if orderLock != nil && !orderLock.IsNil() {
glog.Warning(ctx, "订单正在处理中", orderInfo.OrderNo)
return
}
_ = cache.NewCache().Set(ctx, cache.PrefixAppleOrder.Key(orderInfo.OrderNo), 1, time.Minute)
defer func() {
_, _ = cache.NewCache().Remove(ctx, cache.PrefixAppleOrder.Key(orderInfo.OrderNo))
}()
err = h.processOrderWithAccount(ctx, orderInfo)
if err != nil {
glog.Error(ctx, "处理订单失败", err)
}
})
}
wg.Wait()
return
}
func (h *sAppleOrder) processOrderWithAccount(ctx context.Context, orderInfo *entity.V1CardAppleRechargeInfo) (err error) {
keys := cache.NewCache().GetPrefixKey(ctx, cache.PrefixAppleAccount)
keysStr := slice.Map(slice.Filter(keys, func(index int, item interface{}) bool {
keys := cache.NewCache().GetPrefixKey(ctx, cache.PrefixAppleAccount.String())
keysStr := slice.Map(slice.Filter(keys, func(index int, item any) bool {
_, ok := item.(string)
return ok
}), func(index int, item interface{}) string {
}), func(index int, item any) string {
return item.(string)
})
accountInfo, err := service.AppleAccount().GetAccordingAccount(ctx, decimal.NewFromFloat(orderInfo.Balance), keysStr)

View File

@@ -16,7 +16,6 @@ import (
"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"
)
// AddHistory 添加一条充值历史记录
@@ -152,36 +151,3 @@ func (h *sAppleOrder) GetRechargeDetails(ctx context.Context, orderNo string) (t
}
return
}
// HandleWaitingList 处理待充值的订单
func (h *sAppleOrder) HandleWaitingList(ctx context.Context) (err error) {
dataList := make([]entity.V1CardAppleRechargeInfo, 0)
err = dao.V1CardAppleRechargeInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleRechargeInfo.Columns().Status, consts.AppleRechargeOrderProcessing).
WhereLT(dao.V1CardAppleRechargeInfo.Columns().UpdatedAt, gtime.Now().Add(-10*gtime.M)).
Scan(&dataList)
if err != nil {
glog.Error(ctx, dataList, err)
return
}
for _, info := range dataList {
err = config.GetDatabaseV1().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
if err2 := h.ModifyOrderStatus(ctx, info.OrderNo, consts.AppleRechargeOrderWaiting, "历史订单处理", tx); err2 != nil {
return err2
}
err2 := h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
RechargeId: int(info.Id),
OrderNo: info.OrderNo,
AccountID: info.AccountId,
AccountName: info.AccountName,
Operation: consts.AppleRechargeOperationTimeOut,
Remark: info.Remark,
}, tx)
return err2
})
if err != nil {
glog.Error(ctx, "订单编号:"+info.OrderNo, err)
}
}
return
}

View File

@@ -21,6 +21,10 @@ type (
// CallbackOrder 回调订单给第三方
CallbackOrder(ctx context.Context, data *entity.V1CardAppleRechargeInfo) (bool, error)
CallBackOrderToUpstream(ctx context.Context, orderNo string) (err error)
// CronCallbackPendingOrders 定时任务:回调处于失败和成功状态的订单
CronCallbackPendingOrders(ctx context.Context) error
// CronFiledScheduleTask 定时任务:回调处于失败和成功状态的订单
CronFailedScheduleTask(ctx context.Context) error
// List 获取充值列表
List(ctx context.Context, input model.AppleCardRechargeParamsInput) (total int, data []entity.V1CardAppleRechargeInfo, err error)
// ListWithAccount 获取充值列表(包含账号信息)
@@ -69,8 +73,6 @@ type (
QueryOneByCardPass(ctx context.Context, cardPass string, status *consts.AppleRechargeOrderStatus) (data entity.V1CardAppleRechargeInfo, err error)
// GetRechargeDetails 获取充值操作记录
GetRechargeDetails(ctx context.Context, orderNo string) (total int, data []entity.V1CardAppleHistoryInfo, err error)
// HandleWaitingList 处理待充值的订单
HandleWaitingList(ctx context.Context) (err error)
// ModifyOrderStatus 修改充值订单状态
ModifyOrderStatus(ctx context.Context, orderNo string, status consts.AppleRechargeOrderStatus, remark string, tx gdb.TX) (err error)
// ResetDistributionCount 重置调用次数

View File

@@ -48,6 +48,7 @@ const (
PrefixWalmartAccountQueryBalanceWithCookie PrefixEnum = "walmart_account_query_cache_with_cookie"
PrefixAppleMachineAccount PrefixEnum = "MachineCurrentAccountId"
PrefixAppleAccount PrefixEnum = "apple_account"
PrefixAppleOrder PrefixEnum = "apple_order"
PrefixAppleDuplicatedOrder PrefixEnum = "apple_duplicated_order"
PrefixJdPaymentCheck PrefixEnum = "jd_payment_check" // 支付状态检查缓存
PrefixJdCardExtract PrefixEnum = "jd_card_extract" // 卡密提取锁定缓存
@@ -60,6 +61,10 @@ func (e PrefixEnum) Key(key interface{}) string {
return gconv.String(e) + ":" + gconv.String(key)
}
func (e PrefixEnum) String() string {
return string(e)
}
func NewCache() *Cache {
if cache == nil {
sync.OnceFunc(func() {

View File

@@ -37,15 +37,21 @@ func registerMainTasks(ctx context.Context) {
glog.Error(ctx, "每日账户充值统计失败", err)
}
})
_, _ = gcron.AddSingleton(ctx, "@every 10m", func(ctx context.Context) {
//处理正在等待充值的订单
_ = service.AppleOrder().HandleWaitingList(ctx)
_, _ = gcron.AddSingleton(ctx, "@every 1m", func(ctx context.Context) {
//处理临时停止的订单
_ = service.AppleAccount().HandleTmpStoppedList(ctx)
_ = service.AppleOrder().CronFailedScheduleTask(ctx)
//if err := tmall.NewClient().CronAuthTask(ctx); err != nil {
// glog.Error(ctx, "遍历天猫授权失败", err)
//}
})
// 苹果订单回调定时任务每5分钟执行一次
_, _ = gcron.AddSingleton(ctx, "@every 1s", func(ctx context.Context) {
_ = service.AppleOrder().ProcessOrderWithPush(ctx)
})
_, _ = gcron.AddSingleton(ctx, "@every 3s", func(ctx context.Context) {
_ = service.AppleOrder().CronCallbackPendingOrders(ctx)
})
//_, _ = gcron.AddSingleton(ctx, "@every 3m", func(ctx context.Context) {
// //处理大多数轮询订单
// _ = service.CardRedeemOrder().TriggerConsumeWithContext(ctx)

View File

@@ -24,7 +24,7 @@ func NewClient() *Client {
}
func (c *Client) Redeem(ctx context.Context, req *RedeemReq) (resp *Resp[RedeemResp], err error) {
response, err := c.Client.Post(ctx, "http://kami-spider-monorepo:8000/api/apple/redeem", req)
response, err := c.Client.Post(ctx, "http://127.0.0.1:8000/api/apple/redeem", req)
if err != nil {
return
}
@@ -34,7 +34,7 @@ func (c *Client) Redeem(ctx context.Context, req *RedeemReq) (resp *Resp[RedeemR
}
func (c *Client) QueryBalance(ctx context.Context, req *QueryBalanceReq) (resp *Resp[QueryBalanceResp], err error) {
response, err := c.Client.Post(ctx, "http://kami-spider-monorepo:8000/api/apple/query-balance", req)
response, err := c.Client.Post(ctx, "http://127.0.0.1:8000/api/apple/query-balance", req)
if err != nil {
return
}
@@ -44,7 +44,7 @@ func (c *Client) QueryBalance(ctx context.Context, req *QueryBalanceReq) (resp *
}
func (c *Client) Heartbeat(ctx context.Context, req *HeartBeatReq) (resp *Resp[HeartBeatResp], err error) {
response, err := c.Client.Post(ctx, "http://kami-spider-monorepo:8000/api/apple/heartbeat", req)
response, err := c.Client.Post(ctx, "http://127.0.0.1:8000/api/apple/heartbeat", req)
if err != nil {
return
}

View File

@@ -12,15 +12,16 @@ import (
type Key string
const (
AppleCardCallBack Key = "apple_card_callback"
AppleCardTMallCallback Key = "apple_card_t_mall_callback"
AppleAccountCheckWallet Key = "apple_account_check_wallet"
JDCardCallBack Key = "jd_card_callback"
JDCardConsume Key = "jd_card_consume"
RedeemCardConsume Key = "redeem_card_consume"
RedeemCardCallback Key = "redeem_card_callback"
TMallAccountCallback Key = "t_mall_game_account_callback"
AccountDetect Key = "account_detect"
AppleCardCallBack Key = "apple_card_callback"
AppleOrderProcessPushPool Key = "apple_order_process_push_pool"
AppleCardTMallCallback Key = "apple_card_t_mall_callback"
AppleAccountCheckWallet Key = "apple_account_check_wallet"
JDCardCallBack Key = "jd_card_callback"
JDCardConsume Key = "jd_card_consume"
RedeemCardConsume Key = "redeem_card_consume"
RedeemCardCallback Key = "redeem_card_callback"
TMallAccountCallback Key = "t_mall_game_account_callback"
AccountDetect Key = "account_detect"
)
func (k *Key) Key(key string) Key {