收口审计治理与套餐任务进展

Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
2026-08-05 14:30:54 +08:00
parent b3499adfca
commit 5e552d99bc
178 changed files with 16797 additions and 5674 deletions

View File

@@ -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),