Files
kami_backend/internal/logic/card_apple_order/push_redeem.go
danial 80f605877f docs(camel_oil): 新增骆驼加油相关功能文档
- 添加骆驼加油API端点设计及系统架构文档
- 新增定时任务模块详细说明及任务流程图
- 完成订单管理服务功能及架构介绍
- 增加账号管理模块设计与状态机说明
- 集成Pig接码平台文档,介绍验证码检测流程
- 详细列出各组件的依赖关系及性能优化措施
- 提供故障排除指南及系统扩展性总结
2025-11-21 15:04:55 +08:00

410 lines
17 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 card_apple_order
import (
"context"
"fmt"
"kami/internal/consts"
"kami/internal/errHandler"
"kami/internal/model"
"kami/internal/model/entity"
"kami/internal/service"
"kami/utility/cache"
"kami/utility/config"
"kami/utility/integration/apple"
"kami/utility/pool"
"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, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo) error {
// 调用 Apple 服务进行核销(同步等待)
redeemClient := apple.NewClient()
// 准备推送请求
redeemReq := &apple.RedeemReq{
Account: accountInfo.Account,
Password: accountInfo.Password,
OrderId: orderEntity.OrderNo,
RedemptionCode: orderEntity.CardPass,
}
resp, err := redeemClient.Redeem(ctx, redeemReq)
if err != nil {
return err
}
// 根据状态码分类处理
switch {
// 1. 成功状态CodeSuccess = 0
case resp.Code == apple.CodeSuccess:
return h.handleRedeemSuccess(ctx, orderEntity, accountInfo, resp.Data.Amount, resp.Data.BalanceBefore, resp.Data.BalanceAfter)
// 2. 网络请求或系统资源错误5000-5999
case resp.Code >= 5000 && resp.Code < 6000:
return h.handleSystemError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
// 3. 苹果账户原因错误8001-8005
case resp.Code >= 8001 && resp.Code <= 8005:
return h.handleAccountError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
// 4. 充值限制错误8010-8012
case resp.Code >= 8010 && resp.Code <= 8012:
return h.handleRedeemLimitError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
// 5. 卡密错误8013-8014
case resp.Code >= 8013 && resp.Code <= 8014:
return h.handleCardCodeError(ctx, orderEntity, accountInfo, resp.Code, resp.Message)
// 未知错误
default:
return h.handleRedeemFailed(ctx, orderEntity, accountInfo, resp.Message)
}
}
// 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 {
orderStatus = consts.AppleRechargeOrderSuccess
historyOperation = consts.AppleRechargeOperationItunesSucceed
} else {
orderStatus = consts.AppleRechargeOrderAmountDifferent
historyOperation = consts.AppleRechargeOperationItunesSucceedButWrongAmount
remark = "金额异议"
}
// 更新订单和账户余额
err := h.UpdateActualAmountAndHistoryAndWallet(ctx, &model.AppleAccountUpdateAmountAndHistoryRecord{
OrderInfo: &model.AppleAccountUpdateAmountRecord{
OrderNo: orderEntity.OrderNo,
Amount: actualAmount,
Status: orderStatus,
Remark: fmt.Sprintf("卡密:%s面额%.2f,实际充值:%.2f,充值账户:%s%s", orderEntity.CardPass, orderEntity.CardAmount, actualAmount, accountInfo.Account, remark),
},
AccountId: accountInfo.Id,
HistoryRemark: remark,
HistoryOperation: historyOperation,
BalanceFromItunes: balanceAfter,
})
if err != nil {
glog.Error(ctx, fmt.Sprintf("更新订单成功记录失败 - 订单号: %s, 错误: %v", orderEntity.OrderNo, err))
return err
}
// 异步回调上游
_ = h.CallBackOrderToUpstream(ctx, orderEntity.OrderNo)
glog.Info(ctx, fmt.Sprintf("订单核销成功处理完毕 - 订单号: %s", orderEntity.OrderNo))
return nil
}
// handleRedeemFailed 处理核销失败的情况
func (h *sAppleOrder) handleRedeemFailed(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, errMsg string) error {
// 订单退回待处理状态
err := h.ModifyOrderStatus(ctx, orderEntity.OrderNo, consts.AppleRechargeOrderWaiting, fmt.Sprintf("核销失败:%s", errMsg), 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.AppleRechargeOperationItunesFail,
Remark: fmt.Sprintf("核销失败:%s", errMsg),
}, nil)
glog.Info(ctx, fmt.Sprintf("订单核销失败,已退回待处理 - 订单号: %s", orderEntity.OrderNo))
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 {
// 订单退回待处理状态
err := h.ModifyOrderStatus(ctx, orderEntity.OrderNo, consts.AppleRechargeOrderWaiting, fmt.Sprintf("系统错误:%s", message), 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.AppleRechargeOperationItunesFail,
Remark: fmt.Sprintf("系统错误(状态码: %d%s", code, message),
}, nil)
glog.Warning(ctx, fmt.Sprintf("系统错误,订单已退回待处理 - 订单号: %s, 状态码: %d", orderEntity.OrderNo, code))
return nil
}
// handleAccountError 处理苹果账户原因错误8001-8005
// 标记账户失效,订单退回待处理,等待重新分配其他有效账户
func (h *sAppleOrder) handleAccountError(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, code apple.Code, message string) error {
// 根据错误码标记不同的账户失效状态
var accountStatus consts.AppleAccountStatus
switch code {
case apple.CodeApplePasswordError, apple.CodeApplePasswordExpired:
accountStatus = consts.AppleAccountWrongPassword // 账号密码错误
case apple.CodeAppleAccountForbidden:
accountStatus = consts.AppleAccountForbidden // 账号被禁用
case apple.CodeAppleAccountLocked:
accountStatus = consts.AppleAccountLimited // 账号被封锁(作为受限处理)
case apple.CodeAppleStatusUnknown:
accountStatus = consts.AppleAccountNormal // 状态未知,保持正常以便后续重试
default:
accountStatus = consts.AppleAccountWrongPassword
}
// 标记账户为失效状态
_ = service.AppleAccount().ModifyStatus(ctx, accountInfo.Id, accountStatus, 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("苹果账户错误(状态码: %d%s", code, message),
}, nil)
glog.Warning(ctx, fmt.Sprintf("苹果账户原因导致失效,订单已退回待重新分配 - 订单号: %s, 账号: %s, 状态码: %d", orderEntity.OrderNo, accountInfo.Account, code))
return nil
}
// handleRedeemLimitError 处理充值限制错误8010-8012
// 根据限制类型标记账户,订单根据业务规则决定重试或失败
func (h *sAppleOrder) handleRedeemLimitError(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, code apple.Code, message string) error {
// 根据限制类型标记账户状态
var accountStatus consts.AppleAccountStatus
var accountRemark string
switch code {
case apple.CodeAppleRedeemLimitExceeded:
// 充值次数限制 - 永久受限
accountStatus = consts.AppleAccountForbiddenByTooManyRecharge
accountRemark = "苹果账户充值次数限制(永久)"
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:
accountStatus = consts.AppleAccountLimited
accountRemark = fmt.Sprintf("苹果账户充值限制(状态码: %d", code)
}
// 标记账户限制状态
_ = service.AppleAccount().ModifyStatus(ctx, accountInfo.Id, accountStatus, nil)
// 订单退回待处理,后续可根据限制类型进行重试或转移到其他账户
err := h.ModifyOrderStatus(ctx, orderEntity.OrderNo, consts.AppleRechargeOrderWaiting, fmt.Sprintf("账户受限:%s", message), 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.AppleRechargeOperationItunesFail,
Remark: fmt.Sprintf("%s - %s", accountRemark, message),
}, nil)
glog.Warning(ctx, fmt.Sprintf("账户充值限制,订单已退回待处理 - 订单号: %s, 账号: %s, 限制类型: %d", orderEntity.OrderNo, accountInfo.Account, code))
return nil
}
// handleCardCodeError 处理卡密错误8013-8014
// 直接按订单失败处理,更新订单状态为失败
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 operationRemark string
switch code {
case apple.CodeAppleRedeemAlreadyClaimed:
operationRemark = "卡密已被兑换"
case apple.CodeAppleRedeemNotFound:
operationRemark = "卡密不存在"
default:
operationRemark = fmt.Sprintf("卡密错误(状态码: %d", code)
}
// 更新订单为失败状态
err := h.ModifyOrderStatus(ctx, orderEntity.OrderNo, orderStatus, operationRemark, 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.AppleRechargeOperationItunesFail,
Remark: fmt.Sprintf("卡密错误 - %s", message),
}, nil)
glog.Warning(ctx, fmt.Sprintf("卡密错误,订单已失败 - 订单号: %s, 账号: %s, 错误: %s", orderEntity.OrderNo, accountInfo.Account, operationRemark))
return nil
}
// ProcessOrderWithPush 处理订单:根据订单类型判断是立即进行核销处理还是创建定时任务异步处理
// 参数说明:
// - orderEntity: 待处理的订单信息
// - accountInfo: 分配的苹果账号信息
// - immediate: 是否立即处理true: 立即核销false: 异步定时处理)
func (h *sAppleOrder) ProcessOrderWithPush(ctx context.Context) (err error) {
orderEntities, _ := h.GetAccordingOrders(ctx)
if len(orderEntities) == 0 {
return
}
poolClient := pool.New("apple_order_process_push_pool", 1)
for _, orderInfo := range orderEntities {
_ = poolClient.Add(ctx, func(ctx context.Context) {
err = h.processOrderWithAccount(ctx, orderInfo)
if err != nil {
glog.Error(ctx, "处理订单失败", err)
}
})
}
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 {
_, ok := item.(string)
return ok
}), func(index int, item interface{}) string {
return item.(string)
})
accountInfo, err := service.AppleAccount().GetAccordingAccount(ctx, decimal.NewFromFloat(orderInfo.Balance), keysStr)
if err != nil {
glog.Error(ctx, "获取订单账户失败", err)
return
}
cacheVar, err := cache.NewCache().Get(ctx, cache.PrefixAppleAccount.Key(accountInfo.Id))
if err == nil && !cacheVar.IsNil() {
glog.Warning(ctx, "账户正在处理中", accountInfo.Account)
}
//3分钟超时
_ = cache.NewCache().Set(ctx, cache.PrefixAppleAccount.Key(accountInfo.Id), 1, time.Minute*3)
defer func() {
_, _ = cache.NewCache().Remove(ctx, cache.PrefixAppleAccount.Key(accountInfo.Id))
}()
if err = h.DistributionAccordingAccount(ctx, accountInfo, orderInfo); err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "分配订单账户失败")
return
}
err = config.GetDatabaseV1().Transaction(ctx, func(ctx2 context.Context, tx gdb.TX) error {
err = service.AppleOrder().ModifyOrderStatus(ctx2, orderInfo.OrderNo, consts.AppleRechargeOrderProcessing, "", tx)
if err != nil {
return err
}
err = service.AppleOrder().AddHistory(ctx2, &model.AppleCardRechargeHistoryInput{
AccountID: accountInfo.Id,
OrderNo: orderInfo.OrderNo,
RechargeId: int(orderInfo.Id),
AccountName: accountInfo.Account,
Operation: consts.AppleRechargeOperationStartRechargeByItunes,
Remark: fmt.Sprintf("分配账户:%s账户余额%.2f", accountInfo.Account, accountInfo.BalanceItunes),
}, tx)
return err
})
if err != nil {
return err
}
return h.handleRedeemResult(ctx, orderInfo, accountInfo)
}