收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
293
internal/infrastructure/audit/wallet.go
Normal file
293
internal/infrastructure/audit/wallet.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// WriteAgentWalletBalanceAdjustment 将人工余额调整写入统一 Audit Event。
|
||||
func (w *Writer) WriteAgentWalletBalanceAdjustment(ctx context.Context, tx *gorm.DB, event walletapp.CreditedEvent) error {
|
||||
if event.WalletID == 0 || event.ReferenceType != constants.ReferenceTypeManualAdjustment ||
|
||||
event.ReferenceID == 0 || event.TransactionType != constants.AgentTransactionTypeAdjustment ||
|
||||
event.Amount <= 0 || strings.TrimSpace(event.Remark) == "" {
|
||||
return errors.New(errors.CodeInvalidParam, "代理主钱包人工调整审计事实不完整")
|
||||
}
|
||||
var wallet model.AgentWallet
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&wallet, event.WalletID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询人工调整代理主钱包审计快照失败")
|
||||
}
|
||||
var transaction model.AgentWalletTransaction
|
||||
if err := tx.WithContext(ctx).Unscoped().Where(
|
||||
"agent_wallet_id = ? AND reference_type = ? AND reference_id = ? AND transaction_type = ? AND status = ?",
|
||||
event.WalletID, event.ReferenceType, event.ReferenceID, event.TransactionType, constants.TransactionStatusSuccess,
|
||||
).First(&transaction).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询代理主钱包人工调整流水失败")
|
||||
}
|
||||
walletResource := agentWalletAuditResource(&wallet, constants.AuditResourceRelationPrimary, constants.AuditResourceRoleWalletTarget)
|
||||
walletResource.BeforeData = map[string]any{"balance": transaction.BalanceBefore, "frozen_balance": wallet.FrozenBalance}
|
||||
walletResource.AfterData = map[string]any{"balance": transaction.BalanceAfter, "frozen_balance": wallet.FrozenBalance}
|
||||
walletResource.SubjectVisibility = constants.AuditSubjectResult
|
||||
walletResource.SubjectSummary = "代理主钱包余额已人工调整"
|
||||
transactionResource := agentWalletTransactionResource(&transaction)
|
||||
transactionResource.Role = constants.AuditResourceRoleWalletTransaction
|
||||
return w.Append(ctx, tx, AppendInput{
|
||||
ActionCode: constants.AuditActionAgentWalletBalanceAdjusted, Summary: "人工调整代理主钱包余额",
|
||||
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
|
||||
CorrelationID: event.CorrelationID,
|
||||
Metadata: map[string]any{
|
||||
"amount": event.Amount, "reason": strings.TrimSpace(event.Remark),
|
||||
"reference_type": event.ReferenceType, "reference_id": event.ReferenceID,
|
||||
},
|
||||
Resources: []ResourceInput{walletResource, transactionResource},
|
||||
})
|
||||
}
|
||||
|
||||
// WriteAgentWalletCreditChange 将实际信用额度变化写入统一 Audit Event。
|
||||
func (w *Writer) WriteAgentWalletCreditChange(ctx context.Context, tx *gorm.DB, change walletapp.CreditChangeAudit) error {
|
||||
if change.Wallet == nil || change.Wallet.ID == 0 || change.Wallet.ShopID == 0 || change.Wallet.WalletType != constants.AgentWalletTypeMain {
|
||||
return errors.New(errors.CodeInvalidParam, "代理主钱包信用额度审计事实不完整")
|
||||
}
|
||||
walletResource := agentWalletAuditResource(change.Wallet, constants.AuditResourceRelationPrimary, constants.AuditResourceRoleWalletTarget)
|
||||
walletResource.BeforeData = change.BeforeData
|
||||
walletResource.AfterData = change.AfterData
|
||||
walletResource.SubjectVisibility = constants.AuditSubjectResult
|
||||
var shop model.Shop
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&shop, change.Wallet.ShopID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询信用额度关联店铺审计快照失败")
|
||||
}
|
||||
shopResource := ShopResource(&shop, constants.AuditResourceRelationReference, constants.AuditResourceRoleWalletShop)
|
||||
result := change.Result
|
||||
if result == "" {
|
||||
result = constants.AuditResultSuccess
|
||||
}
|
||||
walletResource.SubjectSummary = "代理主钱包信用额度已更新"
|
||||
if result != constants.AuditResultSuccess {
|
||||
walletResource.SubjectSummary = "代理主钱包信用额度更新未完成"
|
||||
}
|
||||
return w.Append(ctx, tx, AppendInput{
|
||||
ActionCode: constants.AuditActionAgentWalletCreditChanged, Summary: "调整代理主钱包信用额度",
|
||||
ScopeType: constants.AuditScopePlatform, Result: result,
|
||||
ErrorCode: change.ErrorCode, ErrorSummary: change.ErrorSummary,
|
||||
Resources: []ResourceInput{walletResource, shopResource},
|
||||
})
|
||||
}
|
||||
|
||||
// WriteAgentWalletDebit 将代理主钱包订单扣款写入统一 Audit Event。
|
||||
func (w *Writer) WriteAgentWalletDebit(ctx context.Context, tx *gorm.DB, event walletapp.DebitedEvent) error {
|
||||
if event.WalletID == 0 || event.ReferenceType != constants.ReferenceTypeOrder || event.ReferenceID == 0 || event.Amount <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "代理主钱包订单扣款审计事实不完整")
|
||||
}
|
||||
completed, err := completedReservationExists(ctx, tx, event.ReferenceID)
|
||||
if err != nil || completed {
|
||||
return err
|
||||
}
|
||||
order, wallet, transaction, err := loadAgentWalletDebitFacts(ctx, tx, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources := []ResourceInput{
|
||||
walletOrderResource(order, "代理主钱包订单扣款"),
|
||||
agentWalletResource(wallet, transaction.BalanceBefore, wallet.FrozenBalance, transaction.BalanceAfter, wallet.FrozenBalance),
|
||||
agentWalletTransactionResource(transaction),
|
||||
}
|
||||
return w.Append(ctx, tx, AppendInput{
|
||||
ActionCode: constants.AuditActionAgentWalletOrderDebited, Summary: "代理主钱包完成订单扣款",
|
||||
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
|
||||
CorrelationID: event.CorrelationID, Metadata: map[string]any{"amount": event.Amount}, Resources: resources,
|
||||
})
|
||||
}
|
||||
|
||||
// WriteAgentWalletReservation 将代理主钱包订单预占状态变化写入统一 Audit Event。
|
||||
func (w *Writer) WriteAgentWalletReservation(ctx context.Context, tx *gorm.DB, event walletapp.ReservationEvent) error {
|
||||
actionCode, summary, err := reservationAuditAction(event.Status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if event.ReservationID == 0 || event.WalletID == 0 || event.ReferenceType != constants.ReferenceTypeOrder || event.ReferenceID == 0 || event.Amount <= 0 {
|
||||
return errors.New(errors.CodeInvalidParam, "代理主钱包订单预占审计事实不完整")
|
||||
}
|
||||
order, wallet, reservation, transaction, err := loadAgentWalletReservationFacts(ctx, tx, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
beforeBalance, beforeFrozen, afterBalance, afterFrozen := reservationWalletState(wallet, transaction, event)
|
||||
resources := []ResourceInput{
|
||||
walletOrderResource(order, summary),
|
||||
agentWalletResource(wallet, beforeBalance, beforeFrozen, afterBalance, afterFrozen),
|
||||
agentWalletReservationResource(reservation, event.Status),
|
||||
}
|
||||
if transaction != nil {
|
||||
resources = append(resources, agentWalletTransactionResource(transaction))
|
||||
}
|
||||
return w.Append(ctx, tx, AppendInput{
|
||||
ActionCode: actionCode, Summary: summary, ScopeType: constants.AuditScopePlatform,
|
||||
Result: constants.AuditResultSuccess, CorrelationID: event.CorrelationID,
|
||||
Metadata: map[string]any{"amount": event.Amount}, Resources: resources,
|
||||
})
|
||||
}
|
||||
|
||||
func completedReservationExists(ctx context.Context, tx *gorm.DB, orderID uint) (bool, error) {
|
||||
var count int64
|
||||
err := tx.WithContext(ctx).Model(&model.AgentWalletReservation{}).
|
||||
Where("reference_type = ? AND reference_id = ? AND status = ?", constants.ReferenceTypeOrder, orderID, constants.AgentWalletReservationStatusCompleted).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return false, errors.Wrap(errors.CodeDatabaseError, err, "查询订单钱包预占终态失败")
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func loadAgentWalletDebitFacts(ctx context.Context, tx *gorm.DB, event walletapp.DebitedEvent) (*model.Order, *model.AgentWallet, *model.AgentWalletTransaction, error) {
|
||||
var order model.Order
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&order, event.ReferenceID).Error; err != nil {
|
||||
return nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询钱包扣款订单审计快照失败")
|
||||
}
|
||||
var wallet model.AgentWallet
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&wallet, event.WalletID).Error; err != nil {
|
||||
return nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理主钱包审计快照失败")
|
||||
}
|
||||
transaction, err := loadOrderDebitTransaction(ctx, tx, event.WalletID, event.ReferenceID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return &order, &wallet, transaction, nil
|
||||
}
|
||||
|
||||
func loadAgentWalletReservationFacts(ctx context.Context, tx *gorm.DB, event walletapp.ReservationEvent) (*model.Order, *model.AgentWallet, *model.AgentWalletReservation, *model.AgentWalletTransaction, error) {
|
||||
var order model.Order
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&order, event.ReferenceID).Error; err != nil {
|
||||
return nil, nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询钱包预占订单审计快照失败")
|
||||
}
|
||||
var wallet model.AgentWallet
|
||||
if err := tx.WithContext(ctx).Unscoped().First(&wallet, event.WalletID).Error; err != nil {
|
||||
return nil, nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理主钱包审计快照失败")
|
||||
}
|
||||
var reservation model.AgentWalletReservation
|
||||
if err := tx.WithContext(ctx).First(&reservation, event.ReservationID).Error; err != nil {
|
||||
return nil, nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理主钱包预占审计快照失败")
|
||||
}
|
||||
var transaction *model.AgentWalletTransaction
|
||||
if event.Status == constants.AgentWalletReservationStatusCompleted {
|
||||
loaded, err := loadOrderDebitTransaction(ctx, tx, event.WalletID, event.ReferenceID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
transaction = loaded
|
||||
}
|
||||
return &order, &wallet, &reservation, transaction, nil
|
||||
}
|
||||
|
||||
func loadOrderDebitTransaction(ctx context.Context, tx *gorm.DB, walletID, orderID uint) (*model.AgentWalletTransaction, error) {
|
||||
var transaction model.AgentWalletTransaction
|
||||
err := tx.WithContext(ctx).Unscoped().Where(
|
||||
"agent_wallet_id = ? AND reference_type = ? AND reference_id = ? AND transaction_type = ? AND status = ?",
|
||||
walletID, constants.ReferenceTypeOrder, orderID, constants.AgentTransactionTypeDeduct, constants.TransactionStatusSuccess,
|
||||
).First(&transaction).Error
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理主钱包订单扣款流水失败")
|
||||
}
|
||||
return &transaction, nil
|
||||
}
|
||||
|
||||
func walletOrderResource(order *model.Order, summary string) ResourceInput {
|
||||
resource := OrderResource(order, constants.AuditResourceRelationPrimary, constants.AuditResourceRoleOrderTarget)
|
||||
resource.SubjectVisibility = constants.AuditSubjectResult
|
||||
resource.SubjectSummary = summary
|
||||
return resource
|
||||
}
|
||||
|
||||
func agentWalletResource(wallet *model.AgentWallet, beforeBalance, beforeFrozen, afterBalance, afterFrozen int64) ResourceInput {
|
||||
id := strconv.FormatUint(uint64(wallet.ID), 10)
|
||||
return ResourceInput{
|
||||
Type: constants.AuditResourceAgentWallet, ID: &id, Key: id, DisplayName: "代理主钱包 " + id,
|
||||
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleOrderWallet,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": wallet.ID, "shop_id": wallet.ShopID, "wallet_type": wallet.WalletType,
|
||||
"currency": wallet.Currency, "status": wallet.Status,
|
||||
},
|
||||
BeforeData: map[string]any{"balance": beforeBalance, "frozen_balance": beforeFrozen},
|
||||
AfterData: map[string]any{"balance": afterBalance, "frozen_balance": afterFrozen},
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "代理主钱包订单资金已更新",
|
||||
}
|
||||
}
|
||||
|
||||
func agentWalletAuditResource(wallet *model.AgentWallet, relation, role string) ResourceInput {
|
||||
id := strconv.FormatUint(uint64(wallet.ID), 10)
|
||||
return ResourceInput{
|
||||
Type: constants.AuditResourceAgentWallet, ID: &id, Key: id, DisplayName: "代理主钱包 " + id,
|
||||
Relation: relation, Role: role,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": wallet.ID, "shop_id": wallet.ShopID, "wallet_type": wallet.WalletType,
|
||||
"currency": wallet.Currency, "status": wallet.Status,
|
||||
"credit_enabled": wallet.CreditEnabled, "credit_limit": wallet.CreditLimit,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func agentWalletReservationResource(reservation *model.AgentWalletReservation, status int) ResourceInput {
|
||||
id := strconv.FormatUint(uint64(reservation.ID), 10)
|
||||
return ResourceInput{
|
||||
Type: constants.AuditResourceAgentWalletReservation, ID: &id, Key: reservation.ReferenceType + ":" + strconv.FormatUint(uint64(reservation.ReferenceID), 10),
|
||||
DisplayName: "订单钱包预占 " + id, Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleOrderWalletReservation,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": reservation.ID, "agent_wallet_id": reservation.AgentWalletID, "shop_id": reservation.ShopID,
|
||||
"amount": reservation.Amount, "status": status, "reference_type": reservation.ReferenceType, "reference_id": reservation.ReferenceID,
|
||||
},
|
||||
BeforeData: map[string]any{"status": reservationStatusBefore(status)}, AfterData: map[string]any{"status": status},
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "订单钱包预占状态已更新",
|
||||
}
|
||||
}
|
||||
|
||||
func agentWalletTransactionResource(transaction *model.AgentWalletTransaction) ResourceInput {
|
||||
id := strconv.FormatUint(uint64(transaction.ID), 10)
|
||||
return ResourceInput{
|
||||
Type: constants.AuditResourceAgentWalletTransaction, ID: &id, Key: id, DisplayName: "代理钱包流水 " + id,
|
||||
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleOrderWalletTransaction,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": transaction.ID, "agent_wallet_id": transaction.AgentWalletID, "shop_id": transaction.ShopID,
|
||||
"transaction_type": transaction.TransactionType, "transaction_subtype": transaction.TransactionSubtype,
|
||||
"reference_type": transaction.ReferenceType, "reference_id": transaction.ReferenceID, "status": transaction.Status,
|
||||
},
|
||||
AfterData: map[string]any{"amount": transaction.Amount, "balance_before": transaction.BalanceBefore, "balance_after": transaction.BalanceAfter},
|
||||
}
|
||||
}
|
||||
|
||||
func reservationAuditAction(status int) (string, string, error) {
|
||||
switch status {
|
||||
case constants.AgentWalletReservationStatusFrozen:
|
||||
return constants.AuditActionAgentWalletOrderReserved, "代理主钱包已预占订单资金", nil
|
||||
case constants.AgentWalletReservationStatusReleased:
|
||||
return constants.AuditActionAgentWalletOrderReleased, "代理主钱包已释放订单预占", nil
|
||||
case constants.AgentWalletReservationStatusCompleted:
|
||||
return constants.AuditActionAgentWalletOrderCompleted, "代理主钱包已完成订单预占扣款", nil
|
||||
default:
|
||||
return "", "", errors.New(errors.CodeInvalidParam, "代理主钱包预占状态不受支持")
|
||||
}
|
||||
}
|
||||
|
||||
func reservationWalletState(wallet *model.AgentWallet, transaction *model.AgentWalletTransaction, event walletapp.ReservationEvent) (int64, int64, int64, int64) {
|
||||
afterBalance, afterFrozen := wallet.Balance, wallet.FrozenBalance
|
||||
switch event.Status {
|
||||
case constants.AgentWalletReservationStatusFrozen:
|
||||
return afterBalance, afterFrozen - event.Amount, afterBalance, afterFrozen
|
||||
case constants.AgentWalletReservationStatusReleased:
|
||||
return afterBalance, afterFrozen + event.Amount, afterBalance, afterFrozen
|
||||
default:
|
||||
return transaction.BalanceBefore, afterFrozen + event.Amount, transaction.BalanceAfter, afterFrozen
|
||||
}
|
||||
}
|
||||
|
||||
func reservationStatusBefore(status int) any {
|
||||
if status == constants.AgentWalletReservationStatusFrozen {
|
||||
return nil
|
||||
}
|
||||
return constants.AgentWalletReservationStatusFrozen
|
||||
}
|
||||
Reference in New Issue
Block a user