Files
kami_backend/internal/logic/card_apple_order/push_redeem.go
danial 2d317037b0 feat(merchant): 集成OpenAI接口实现订单记录自动总结功能
- 在订单查询接口增加调用OpenAI聊天模型生成中文总结的功能
- 修改OrderQueryRes结构体,新增Summary字段用于返回总结信息
- 添加queryAppleResult方法,通过OpenAI接口生成订单记录总结文本
- 在查询订单接口聚合苹果充值和兑换记录后,调用该方法获取总结
- 修改推送兑换逻辑优化充值成功余额显示,简化错误失败注释
- 调整订单状态修改时的备注信息,增强日志一致性和清晰度
- 更新go.mod引入OpenAI官方Go SDK及相关依赖
- 新增单元测试验证OpenAI接口调用正确性
- 添加AppleOrderOperation的String方法及对应测试实现,提高代码整洁度
2025-11-28 21:30:01 +08:00

430 lines
18 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"
"errors"
"fmt"
"github.com/gogf/gf/v2/net/gtrace"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"kami/internal/consts"
"kami/internal/dao"
"kami/internal/errHandler"
"kami/internal/model"
"kami/internal/model/entity"
"kami/internal/service"
"kami/utility/cache"
"kami/utility/config"
"kami/utility/integration/apple"
"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/glog"
"github.com/shopspring/decimal"
)
// checkAccountConsecutiveRevoke 检查账号连续撤销次数
func (h *sAppleOrder) checkAccountConsecutiveRevoke(ctx context.Context, accountInfo *entity.V1CardAppleAccountInfo, orderInfo *entity.V1CardAppleRechargeInfo) error {
// 获取该账号最近的历史记录,多查询几条以便准确判断连续性
revokeCount, err := dao.V1CardAppleHistoryInfo.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CardAppleHistoryInfo.Columns().AccountId, accountInfo.Id).
Where(dao.V1CardAppleHistoryInfo.Columns().OrderNo, orderInfo.OrderNo).
Where(dao.V1CardAppleHistoryInfo.Columns().Operation, consts.AppleRechargeOperationItunesRefund).
Count()
if err != nil {
return nil // 查询失败不影响主流程
}
// 如果历史记录太少,不进行检查
if revokeCount <= consts.AppleAccountMaxConsecutiveRevokeCount {
return nil
}
_ = service.AppleAccount().ModifyStatus(ctx, accountInfo.Id, consts.AppleAccountForbiddenBySafetyReason, nil)
return nil
}
func (h *sAppleOrder) queryBalance(ctx context.Context, orderInfo *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo) (balance float64, err error) {
ctx, span := gtrace.NewSpan(ctx, "queryBalance")
defer span.End()
queryResp, err := apple.NewClient().QueryBalance(ctx, &apple.QueryBalanceReq{
Account: accountInfo.Account,
Password: accountInfo.Password,
OrderId: orderInfo.OrderNo,
})
span.AddEvent("查询订单")
if err != nil {
_ = h.handleRedeemUnknown(ctx, orderInfo, accountInfo, "订单查询网络错误"+err.Error())
return 0, err
}
// 3. 苹果账户原因错误8001-8005
if queryResp.Code >= 8001 && queryResp.Code <= 8005 {
_ = h.handleAccountError(ctx, orderInfo, accountInfo, queryResp.Code)
return 0, errors.New(queryResp.Code.String())
}
if queryResp.Code != apple.CodeSuccess {
return 0, errors.New(queryResp.Code.String())
}
return queryResp.Data.Balance, nil
}
// handleRedeemResult 处理核销结果,根据状态码分类进行对应处理
func (h *sAppleOrder) handleRedeemResult(ctx context.Context, orderInfo *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo) error {
ctx, span := gtrace.NewSpan(ctx, "handleRedeemResult")
defer span.End()
// 准备推送请求
redeemReq := &apple.RedeemReq{
Account: accountInfo.Account,
Password: accountInfo.Password,
OrderId: orderInfo.OrderNo,
RedemptionCode: orderInfo.CardPass,
}
balance, err := h.queryBalance(ctx, orderInfo, accountInfo)
if err != nil {
return err
}
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
RechargeId: int(orderInfo.Id),
OrderNo: orderInfo.OrderNo,
AccountID: accountInfo.Id,
AccountName: accountInfo.Account,
Operation: consts.AppleRechargeOperationQuery,
Remark: fmt.Sprintf("账号【%s】充值前余额%.2f", accountInfo.Account, balance),
}, nil)
defer func() {
balance, err = h.queryBalance(ctx, orderInfo, accountInfo)
if err != nil {
return
}
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
RechargeId: int(orderInfo.Id),
OrderNo: orderInfo.OrderNo,
AccountID: accountInfo.Id,
AccountName: accountInfo.Account,
Operation: consts.AppleRechargeOperationQuery,
Remark: fmt.Sprintf("账号【%s】充值后余额%.2f", accountInfo.Account, balance),
}, nil)
}()
resp, err := apple.NewClient().Redeem(ctx, redeemReq)
span.AddEvent("兑换订单")
if err != nil {
return h.handleRedeemUnknown(ctx, orderInfo, accountInfo, "苹果充值网络错误"+err.Error())
}
// 根据状态码分类处理
switch {
// 1. 成功状态CodeSuccess = 0
case resp.Code == apple.CodeSuccess:
return h.handleRedeemSuccess(ctx, orderInfo, accountInfo, resp.Data.Amount, balance, resp.Data.BalanceAfter)
// 2. 网络请求或系统资源错误5000-5999
case resp.Code >= 5000 && resp.Code < 6000:
return h.handleSystemError(ctx, orderInfo, accountInfo, resp.Code)
// 3. 苹果账户原因错误8001-8005
case resp.Code >= 8001 && resp.Code <= 8005:
return h.handleAccountError(ctx, orderInfo, accountInfo, resp.Code)
// 4. 充值限制错误8010-8012
case resp.Code >= 8010 && resp.Code <= 8012:
return h.handleRedeemLimitError(ctx, orderInfo, accountInfo, resp.Code)
// 5. 卡密错误8013-8014
case resp.Code >= 8013 && resp.Code <= 8014:
return h.handleCardCodeError(ctx, orderInfo, accountInfo, resp.Code)
// 未知错误
default:
return h.handleRedeemUnknown(ctx, orderInfo, accountInfo, resp.Code.String())
}
}
// handleRedeemSuccess 处理核销成功的情况
func (h *sAppleOrder) handleRedeemSuccess(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, amount, balanceBefore, balanceAfter float64) error {
// 判断金额是否一致
var orderStatus consts.AppleRechargeOrderStatus
var historyOperation consts.AppleOrderOperation
var remark string
if amount == orderEntity.CardAmount {
orderStatus = consts.AppleRechargeOrderSuccess
historyOperation = consts.AppleRechargeOperationItunesSucceed
remark = fmt.Sprintf("充值成功:使用卡密【%s】为账号【%s】充值【%.2f】元,卡密已核销成功,余额【%.2f】", orderEntity.CardPass, accountInfo.Account, amount, balanceAfter)
} else {
orderStatus = consts.AppleRechargeOrderAmountDifferent
historyOperation = consts.AppleRechargeOperationItunesSucceedButWrongAmount
remark = fmt.Sprintf("充值失败:使用卡密【%s】为账号【%s】充值时卡密金额【%.2f】与订单金额【%.2f】不一致,卡密已核销成功,因为金额不一致,所以订单不给回调.", orderEntity.CardPass, accountInfo.Account, orderEntity.CardAmount, amount)
}
// 更新订单和账户余额
err := h.UpdateActualAmountAndHistoryAndWallet(ctx, &model.AppleAccountUpdateAmountAndHistoryRecord{
OrderInfo: &model.AppleAccountUpdateAmountRecord{
OrderNo: orderEntity.OrderNo,
Amount: amount,
Status: orderStatus,
Remark: historyOperation.String(),
},
AccountId: accountInfo.Id,
HistoryRemark: remark,
HistoryOperation: historyOperation,
BalanceFromItunes: balanceAfter,
})
if err != nil {
glog.Error(ctx, fmt.Sprintf("更新订单成功记录失败 - 订单号: %s, 错误: %v", orderEntity.OrderNo, err))
return err
}
glog.Info(ctx, fmt.Sprintf("订单核销成功处理完毕 - 订单号: %s", orderEntity.OrderNo))
return nil
}
// handleRedeemUnknown 处理核销失败的情况
func (h *sAppleOrder) handleRedeemUnknown(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, errMsg string) error {
// 订单退回待处理状态
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.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
AccountID: accountInfo.Id,
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountName: accountInfo.Account,
Operation: consts.AppleRechargeOperationItunesRefund,
Remark: fmt.Sprintf("使用卡密【%s】为账号【%s】充值失败核销失败%s", orderEntity.CardPass, accountInfo.Account, errMsg),
}, nil)
glog.Info(ctx, fmt.Sprintf("订单核销失败,已退回待处理 - 订单号: %s", orderEntity.OrderNo))
return nil
}
// handleSystemError 处理系统资源错误5000-5999
// 订单退回待处理,等待系统重新调度重试
func (h *sAppleOrder) handleSystemError(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, code apple.Code) error {
// 订单退回待处理状态
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.AppleRechargeOperationItunesFail,
Remark: fmt.Sprintf("使用卡密【%s】为账号【%s】充值失败网络原因错误%s订单退回重新处理", orderEntity.CardPass, accountInfo.Account, code.String()),
}, 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) 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("使用卡密【%s】为账号【%s】充值失败订单退回重新充值因为苹果账户错误原因%s", orderEntity.CardPass, accountInfo.Account, code.String()),
}, nil)
glog.Warning(ctx, fmt.Sprintf("苹果账户原因导致失效,订单已退回待重新分配 - 订单号: %s, 账号: %s", orderEntity.OrderNo, accountInfo.Account))
return nil
}
// handleRedeemLimitError 处理充值限制错误8010-8012
// 根据限制类型标记账户,订单根据业务规则决定重试或失败
func (h *sAppleOrder) handleRedeemLimitError(ctx context.Context, orderEntity *entity.V1CardAppleRechargeInfo, accountInfo *entity.V1CardAppleAccountInfo, code apple.Code) error {
// 根据限制类型标记账户状态
var accountStatus consts.AppleAccountStatus
var accountRemark string
switch code {
case apple.CodeAppleRedeemLimitExceeded:
// 充值次数限制 - 永久受限
accountStatus = consts.AppleAccountForbiddenByTooManyRecharge
accountRemark = "苹果账户充值次数限制(永久)"
case apple.CodeAppleRedeemLimitExceededTemporarily:
// 临时限制 - 标记为临时冻结,暂停兑换
accountStatus = consts.AppleAccountTmpStoppedByTooManyRequest
accountRemark = "苹果账户临时充值限制2分钟后恢复"
case apple.CodeAppleRedeemLimitExceededPerMinute:
accountStatus = consts.AppleAccountTmpLimited
accountRemark = "苹果账户一分钟充值限制1分钟后恢复"
default:
accountStatus = consts.AppleAccountLimited
accountRemark = fmt.Sprintf("苹果账户充值限制")
}
// 标记账户限制状态
_ = 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.AppleRechargeOperationItunesFail,
Remark: fmt.Sprintf("使用卡密【%s】为账号【%s】充值失败订单退回重新充值账户受限%s", orderEntity.CardPass, accountInfo.Account, accountRemark),
}, 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) error {
// 根据卡密错误类型确定订单失败状态
var orderStatus = consts.AppleRechargeOrderFail
var operationRemark string
switch code {
case apple.CodeAppleRedeemAlreadyClaimed:
operationRemark = "卡密已被兑换"
case apple.CodeAppleRedeemNotFound:
operationRemark = "卡密不存在"
default:
operationRemark = "卡密错误"
}
// 更新订单为失败状态
err := h.ModifyOrderStatus(ctx, orderEntity.OrderNo, orderStatus, operationRemark, nil)
if err != nil {
glog.Error(ctx, fmt.Sprintf("更新订单失败状态失败 - 订单号: %s, 错误: %v", orderEntity.OrderNo, err))
return err
}
operationRemark = fmt.Sprintf("使用卡密【%s】为账号【%s】充值失败%s", orderEntity.CardPass, accountInfo.Account, operationRemark)
// 添加历史记录
_ = h.AddHistory(ctx, &model.AppleCardRechargeHistoryInput{
AccountID: accountInfo.Id,
OrderNo: orderEntity.OrderNo,
RechargeId: int(orderEntity.Id),
AccountName: accountInfo.Account,
Operation: consts.AppleRechargeOperationItunesFail,
Remark: operationRemark,
}, nil)
glog.Warning(ctx, fmt.Sprintf("卡密错误,订单已失败 - 订单号: %s, 账号: %s, 错误: %s", orderEntity.OrderNo, accountInfo.Account, operationRemark))
return nil
}
func (h *sAppleOrder) processOrderWithAccount(ctx context.Context, orderInfo *entity.V1CardAppleRechargeInfo) (err error) {
ctx, span := gtrace.NewSpan(ctx, "processOrderWithAccount", trace.WithAttributes(attribute.String("orderId", orderInfo.OrderNo)))
defer span.End()
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 any) string {
return item.(string)
})
accountInfo, err := service.AppleAccount().GetAccordingAccount(ctx, decimal.NewFromFloat(orderInfo.Balance), keysStr)
if err != nil || accountInfo == nil {
return
}
// 检查账号连续撤销次数
if err = h.checkAccountConsecutiveRevoke(ctx, accountInfo, orderInfo); err != nil {
glog.Warning(ctx, fmt.Sprintf("账号连续iTunes退回次数超限 - 账号: %s, 错误: %v", accountInfo.Account, err))
// 禁用账号
_ = service.AppleAccount().ModifyStatus(ctx, accountInfo.Id, consts.AppleAccountForbidden, nil)
return
}
span.AddEvent("获取账号", trace.WithAttributes(attribute.String("account", accountInfo.Account)))
cacheVar, err := cache.NewCache().Get(ctx, cache.PrefixAppleAccount.Key(accountInfo.Id))
if err == nil && !cacheVar.IsNil() {
span.AddEvent("账户正在处理中")
return
}
//3分钟超时
_ = cache.NewCache().Set(ctx, cache.PrefixAppleAccount.Key(accountInfo.Id), 1, time.Minute*3)
defer func() {
span.AddEvent("处理完成")
_, _ = cache.NewCache().Remove(ctx, cache.PrefixAppleAccount.Key(accountInfo.Id))
}()
span.AddEvent("处理订单")
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", accountInfo.Account),
}, tx)
return err
})
if err != nil {
return err
}
return h.handleRedeemResult(ctx, orderInfo, accountInfo)
}