收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -15,10 +15,12 @@ import (
|
||||
|
||||
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
|
||||
packagedomain "github.com/break/junhong_cmp_fiber/internal/domain/package"
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/packageprice"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
@@ -44,6 +46,7 @@ type AutoPurchaseHandler struct {
|
||||
asynqClient *asynq.Client // 用于事务提交成功后触发佣金计算任务
|
||||
logger *zap.Logger
|
||||
observationSeriesEvents cardObservationApp.SeriesEventWriter
|
||||
auditWriter *audit.Writer
|
||||
}
|
||||
|
||||
// NewAutoPurchaseHandler 创建充值后自动购包处理器
|
||||
@@ -59,6 +62,7 @@ func NewAutoPurchaseHandler(
|
||||
asynqClient *asynq.Client,
|
||||
logger *zap.Logger,
|
||||
observationSeriesEvents cardObservationApp.SeriesEventWriter,
|
||||
auditWriter *audit.Writer,
|
||||
) *AutoPurchaseHandler {
|
||||
if orderStore == nil {
|
||||
orderStore = postgres.NewOrderStore(db, redisClient)
|
||||
@@ -94,6 +98,7 @@ func NewAutoPurchaseHandler(
|
||||
asynqClient: asynqClient,
|
||||
logger: logger,
|
||||
observationSeriesEvents: observationSeriesEvents,
|
||||
auditWriter: auditWriter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +123,11 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
h.logger.Error("查询充值订单失败", zap.Uint("recharge_order_id", payload.RechargeOrderID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeAutoPurchaseAfterRecharge,
|
||||
ActorName: "充值后自动购包任务", Source: constants.AuditSourceWorker,
|
||||
CorrelationID: rechargeOrder.RechargeOrderNo,
|
||||
})
|
||||
|
||||
if rechargeOrder.AutoPurchaseStatus == constants.AutoPurchaseStatusSuccess {
|
||||
return nil
|
||||
@@ -276,8 +286,10 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
Update("auto_purchase_status", constants.AutoPurchaseStatusSuccess).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if h.auditWriter == nil {
|
||||
return pkgerrors.New(pkgerrors.CodeInvalidStatus, "自动购包统一审计接缝未配置")
|
||||
}
|
||||
return h.appendAutoPurchaseAudit(ctx, tx, rechargeOrder, order, payment, wallet, walletTx, packages)
|
||||
}); err != nil {
|
||||
h.logger.Error("自动购包任务执行失败",
|
||||
zap.Uint("recharge_record_id", rechargeOrder.ID),
|
||||
@@ -311,6 +323,61 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *AutoPurchaseHandler) appendAutoPurchaseAudit(ctx context.Context, tx *gorm.DB, recharge *model.RechargeOrder, order *model.Order, payment *model.Payment, wallet *model.AssetWallet, walletTx *model.AssetWalletTransaction, packages []*model.Package) error {
|
||||
rechargeID := strconv.FormatUint(uint64(recharge.ID), 10)
|
||||
resources := []audit.ResourceInput{{
|
||||
Type: constants.AuditResourceRechargeOrder, ID: &rechargeID, Key: recharge.RechargeOrderNo, DisplayName: recharge.RechargeOrderNo,
|
||||
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleRechargeTarget,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": recharge.ID, "recharge_order_no": recharge.RechargeOrderNo, "user_id": recharge.UserID,
|
||||
"asset_wallet_id": recharge.AssetWalletID, "resource_type": recharge.ResourceType,
|
||||
"resource_id": recharge.ResourceID, "amount": recharge.Amount, "status": recharge.Status,
|
||||
},
|
||||
BeforeData: map[string]any{"auto_purchase_status": recharge.AutoPurchaseStatus},
|
||||
AfterData: map[string]any{"auto_purchase_status": constants.AutoPurchaseStatusSuccess},
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "充值后自动购包已完成",
|
||||
}}
|
||||
orderResource := audit.OrderResource(order, constants.AuditResourceRelationAffected, constants.AuditResourceRoleRechargeAutoPurchaseOrder)
|
||||
orderResource.SubjectVisibility = constants.AuditSubjectResult
|
||||
orderResource.SubjectSummary = "充值后自动购包已完成"
|
||||
resources = append(resources, orderResource)
|
||||
walletID := strconv.FormatUint(uint64(wallet.ID), 10)
|
||||
resources = append(resources, audit.ResourceInput{
|
||||
Type: constants.AuditResourceAssetWallet, ID: &walletID, Key: walletID, DisplayName: "资产钱包 " + walletID,
|
||||
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleRechargeWallet,
|
||||
IdentitySnapshot: map[string]any{"id": wallet.ID, "resource_type": wallet.ResourceType, "resource_id": wallet.ResourceID, "currency": wallet.Currency},
|
||||
BeforeData: map[string]any{"balance": walletTx.BalanceBefore}, AfterData: map[string]any{"balance": walletTx.BalanceAfter},
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "充值后自动购包已完成",
|
||||
})
|
||||
walletTxID := strconv.FormatUint(uint64(walletTx.ID), 10)
|
||||
resources = append(resources, audit.ResourceInput{
|
||||
Type: constants.AuditResourceAssetWalletTransaction, ID: &walletTxID, Key: walletTxID, DisplayName: "资产钱包流水 " + walletTxID,
|
||||
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleRechargeWalletTransaction,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": walletTx.ID, "asset_wallet_id": walletTx.AssetWalletID, "resource_type": walletTx.ResourceType,
|
||||
"resource_id": walletTx.ResourceID, "transaction_type": walletTx.TransactionType,
|
||||
"reference_type": walletTx.ReferenceType, "reference_no": walletTx.ReferenceNo, "status": walletTx.Status,
|
||||
},
|
||||
AfterData: map[string]any{"amount": walletTx.Amount, "balance_before": walletTx.BalanceBefore, "balance_after": walletTx.BalanceAfter},
|
||||
})
|
||||
resources = append(resources, audit.PaymentResource(payment, constants.AuditResourceRelationReference, constants.AuditResourceRoleOrderPayment, nil, nil))
|
||||
for _, pkg := range packages {
|
||||
resources = append(resources, audit.PackageResource(pkg, constants.AuditResourceRelationReference, constants.AuditResourceRoleOrderPackage, nil, nil))
|
||||
}
|
||||
var usages []model.PackageUsage
|
||||
if err := tx.WithContext(ctx).Where("order_id = ?", order.ID).Order("id ASC").Find(&usages).Error; err != nil {
|
||||
return pkgerrors.Wrap(pkgerrors.CodeDatabaseError, err, "查询自动购包套餐权益审计快照失败")
|
||||
}
|
||||
for i := range usages {
|
||||
resources = append(resources, audit.PackageUsageResource(&usages[i], constants.AuditResourceRelationAffected, constants.AuditResourceRolePackageUsageTarget, nil, nil))
|
||||
}
|
||||
return h.auditWriter.Append(ctx, tx, audit.AppendInput{
|
||||
ActionCode: constants.AuditActionAssetRechargeAutoPurchased, Summary: "充值后自动购包已完成",
|
||||
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
|
||||
CorrelationID: recharge.RechargeOrderNo, Resources: resources,
|
||||
})
|
||||
}
|
||||
|
||||
func orderObservationResource(order *model.Order) (string, uint) {
|
||||
if order != nil && order.DeviceID != nil {
|
||||
return constants.CardObservationResourceTypeDevice, *order.DeviceID
|
||||
@@ -348,10 +415,39 @@ func (h *AutoPurchaseHandler) markAutoPurchaseFailedIfFinalRetry(ctx context.Con
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.WithContext(ctx).
|
||||
Model(&model.RechargeOrder{}).
|
||||
Where("id = ?", rechargeOrderID).
|
||||
Update("auto_purchase_status", constants.AutoPurchaseStatusFailed).Error; err != nil {
|
||||
if err := h.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var recharge model.RechargeOrder
|
||||
if err := tx.WithContext(ctx).First(&recharge, rechargeOrderID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
result := tx.WithContext(ctx).Model(&model.RechargeOrder{}).
|
||||
Where("id = ? AND auto_purchase_status <> ?", rechargeOrderID, constants.AutoPurchaseStatusFailed).
|
||||
Update("auto_purchase_status", constants.AutoPurchaseStatusFailed)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
return result.Error
|
||||
}
|
||||
if h.auditWriter == nil {
|
||||
return pkgerrors.New(pkgerrors.CodeInvalidStatus, "自动购包统一审计接缝未配置")
|
||||
}
|
||||
rechargeID := strconv.FormatUint(uint64(recharge.ID), 10)
|
||||
return h.auditWriter.Append(ctx, tx, audit.AppendInput{
|
||||
ActionCode: constants.AuditActionAssetRechargeAutoPurchased, Summary: "充值后自动购包失败",
|
||||
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultFailed,
|
||||
CorrelationID: recharge.RechargeOrderNo,
|
||||
Resources: []audit.ResourceInput{{
|
||||
Type: constants.AuditResourceRechargeOrder, ID: &rechargeID, Key: recharge.RechargeOrderNo, DisplayName: recharge.RechargeOrderNo,
|
||||
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleRechargeTarget,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": recharge.ID, "recharge_order_no": recharge.RechargeOrderNo, "user_id": recharge.UserID,
|
||||
"asset_wallet_id": recharge.AssetWalletID, "resource_type": recharge.ResourceType,
|
||||
"resource_id": recharge.ResourceID, "amount": recharge.Amount, "status": recharge.Status,
|
||||
},
|
||||
BeforeData: map[string]any{"auto_purchase_status": recharge.AutoPurchaseStatus},
|
||||
AfterData: map[string]any{"auto_purchase_status": constants.AutoPurchaseStatusFailed},
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "充值后自动购包失败",
|
||||
}},
|
||||
})
|
||||
}); err != nil {
|
||||
h.logger.Error("更新自动购包失败状态失败",
|
||||
zap.Uint("recharge_record_id", rechargeOrderID),
|
||||
zap.Error(err),
|
||||
|
||||
Reference in New Issue
Block a user