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

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

@@ -0,0 +1,189 @@
package audit
import (
"context"
"strconv"
"strings"
"github.com/bytedance/sonic"
"gorm.io/gorm"
approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// WriteApproval 将通用审批状态变化及其 Integration/Outbox 引用写入统一 Audit Event。
func (w *Writer) WriteApproval(ctx context.Context, tx *gorm.DB, change approvalapp.AuditChange) error {
if change.InstanceID == 0 || change.BusinessID == 0 || change.BusinessType == "" || change.SubmitterAccountID == 0 {
return errors.New(errors.CodeInvalidParam, "通用审批审计资源不完整")
}
resources, err := approvalResources(ctx, tx, change)
if err != nil {
return err
}
result := change.Result
if result == "" {
result = constants.AuditResultSuccess
}
return w.Append(ctx, tx, AppendInput{
EventID: change.EventID, ActionCode: change.ActionCode, Summary: change.Summary,
Actor: ActorInput{Kind: change.ActorKind, ID: change.ActorID, Name: change.ActorName}, Source: change.Source,
ScopeType: constants.AuditScopePlatform, Result: result, ErrorSummary: change.ErrorSummary,
CorrelationID: change.CorrelationID, ParentEventID: change.ParentEventID,
Metadata: map[string]any{"provider": change.Provider, "decision": change.Decision}, Resources: resources,
})
}
func approvalResources(ctx context.Context, tx *gorm.DB, change approvalapp.AuditChange) ([]ResourceInput, error) {
instanceID := strconv.FormatUint(uint64(change.InstanceID), 10)
resources := []ResourceInput{{
Type: constants.AuditResourceApprovalInstance, ID: &instanceID, Key: instanceID, DisplayName: "审批实例 " + instanceID,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleApprovalTarget,
IdentitySnapshot: map[string]any{
"id": change.InstanceID, "business_type": change.BusinessType, "business_id": change.BusinessID,
"submitter_account_id": change.SubmitterAccountID, "provider": change.Provider,
"external_ref": change.AfterExternalRef, "correlation_id": change.CorrelationID,
"status": statusValue(change.AfterStatus),
},
BeforeData: approvalState(change.BeforeStatus, change.BeforeExternalRef),
AfterData: approvalState(change.AfterStatus, change.AfterExternalRef),
}}
business, err := approvalBusinessResource(ctx, tx, change.BusinessType, change.BusinessID, change.InstanceID)
if err != nil {
return nil, err
}
resources = append(resources, business)
resources = append(resources, approvalSubmitterResource(change))
seenIntegrationIDs := make(map[string]struct{}, len(change.IntegrationIDs))
for _, integrationID := range change.IntegrationIDs {
integrationID = strings.TrimSpace(integrationID)
if integrationID == "" {
continue
}
if _, exists := seenIntegrationIDs[integrationID]; exists {
continue
}
seenIntegrationIDs[integrationID] = struct{}{}
resource, err := approvalIntegrationResource(ctx, tx, integrationID)
if err != nil {
return nil, err
}
resources = append(resources, resource)
}
if strings.TrimSpace(change.OutboxEventID) != "" {
resource, err := approvalOutboxResource(ctx, tx, change.OutboxEventID)
if err != nil {
return nil, err
}
resources = append(resources, resource)
}
return resources, nil
}
func approvalBusinessResource(ctx context.Context, tx *gorm.DB, businessType string, businessID, instanceID uint) (ResourceInput, error) {
id := strconv.FormatUint(uint64(businessID), 10)
switch businessType {
case constants.ApprovalBusinessTypeRefund:
var refund model.RefundRequest
if err := tx.WithContext(ctx).First(&refund, businessID).Error; err != nil {
return ResourceInput{}, errors.Wrap(errors.CodeDatabaseError, err, "查询审批关联退款单失败")
}
return ResourceInput{
Type: constants.AuditResourceRefund, ID: &id, Key: refund.RefundNo, DisplayName: refund.RefundNo,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleApprovalBusiness,
IdentitySnapshot: map[string]any{
"id": refund.ID, "refund_no": refund.RefundNo, "order_id": refund.OrderID, "order_no": refund.OrderNo,
"order_type": refund.OrderType, "asset_identifier": refund.AssetIdentifier, "shop_id": refund.ShopID,
"requested_refund_amount": refund.RequestedRefundAmount, "actual_received_amount": refund.ActualReceivedAmount,
"approval_instance_id": instanceID, "status": refund.Status,
},
}, nil
case constants.ApprovalBusinessTypeOfflineRecharge:
var recharge model.AgentRechargeRecord
if err := tx.WithContext(ctx).First(&recharge, businessID).Error; err != nil {
return ResourceInput{}, errors.Wrap(errors.CodeDatabaseError, err, "查询审批关联充值单失败")
}
return ResourceInput{
Type: constants.AuditResourceAgentRecharge, ID: &id, Key: recharge.RechargeNo, DisplayName: recharge.RechargeNo,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleApprovalBusiness,
IdentitySnapshot: map[string]any{
"id": recharge.ID, "recharge_no": recharge.RechargeNo, "user_id": recharge.UserID,
"shop_id": recharge.ShopID, "agent_wallet_id": recharge.AgentWalletID, "amount": recharge.Amount,
"payment_method": recharge.PaymentMethod, "payment_channel": recharge.PaymentChannel,
"approval_instance_id": instanceID, "status": recharge.Status,
},
}, nil
default:
return ResourceInput{}, errors.New(errors.CodeInvalidParam, "审批业务类型尚未注册审计资源")
}
}
func approvalSubmitterResource(change approvalapp.AuditChange) ResourceInput {
accountID := strconv.FormatUint(uint64(change.SubmitterAccountID), 10)
identity := map[string]any{"id": change.SubmitterAccountID}
var snapshot map[string]any
if sonic.Unmarshal(change.SubmitterSnapshot, &snapshot) == nil {
identity["username"] = snapshot["account_name"]
identity["user_type"] = snapshot["user_type"]
}
displayName, _ := identity["username"].(string)
if displayName == "" {
displayName = "账号 " + accountID
}
return ResourceInput{
Type: constants.AuditResourceAccount, ID: &accountID, Key: accountID, DisplayName: displayName,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleApprovalSubmitter,
IdentitySnapshot: identity,
}
}
func approvalIntegrationResource(ctx context.Context, tx *gorm.DB, integrationID string) (ResourceInput, error) {
var record model.IntegrationLog
if err := tx.WithContext(ctx).Where("integration_id = ?", integrationID).First(&record).Error; err != nil {
return ResourceInput{}, errors.Wrap(errors.CodeDatabaseError, err, "查询审批关联 Integration Log 失败")
}
id := strconv.FormatUint(uint64(record.ID), 10)
return ResourceInput{
Type: constants.AuditResourceIntegrationLog, ID: &id, Key: record.IntegrationID, DisplayName: record.IntegrationID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleApprovalIntegration,
IdentitySnapshot: map[string]any{
"integration_id": record.IntegrationID, "provider": record.Provider, "direction": record.Direction,
"operation": record.Operation, "external_id": record.ExternalID,
"resource_type": record.ResourceType, "resource_id": record.ResourceID, "resource_key": record.ResourceKey,
"correlation_id": record.CorrelationID,
},
}, nil
}
func approvalOutboxResource(ctx context.Context, tx *gorm.DB, eventID string) (ResourceInput, error) {
var event model.OutboxEvent
if err := tx.WithContext(ctx).Where("event_id = ?", eventID).First(&event).Error; err != nil {
return ResourceInput{}, errors.Wrap(errors.CodeDatabaseError, err, "查询审批关联 Outbox 事件失败")
}
id := strconv.FormatUint(uint64(event.ID), 10)
return ResourceInput{
Type: constants.AuditResourceOutboxEvent, ID: &id, Key: event.EventID, DisplayName: event.EventID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleApprovalOutbox,
IdentitySnapshot: map[string]any{
"event_id": event.EventID, "event_type": event.EventType, "aggregate_type": event.AggregateType,
"aggregate_id": event.AggregateID, "resource_type": event.ResourceType,
"resource_id": event.ResourceID, "business_key": event.BusinessKey,
},
}, nil
}
func approvalState(status *int, externalRef string) map[string]any {
if status == nil {
return nil
}
return map[string]any{"status": *status, "external_ref": externalRef}
}
func statusValue(status *int) any {
if status == nil {
return nil
}
return *status
}

View File

@@ -0,0 +1,46 @@
package audit
import (
"strconv"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// CommissionWithdrawalResource 构造佣金提现单审计资源,不记录收款账户信息。
func CommissionWithdrawalResource(withdrawal *model.CommissionWithdrawalRequest, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(withdrawal.ID), 10)
key := withdrawal.WithdrawalNo
if key == "" {
key = id
}
return ResourceInput{
Type: constants.AuditResourceCommissionWithdrawal, ID: optionalResourceID(withdrawal.ID),
Key: key, DisplayName: "佣金提现单 " + key, Relation: relation, Role: role,
IdentitySnapshot: map[string]any{
"id": withdrawal.ID, "withdrawal_no": withdrawal.WithdrawalNo,
"shop_id": withdrawal.ShopID, "applicant_id": withdrawal.ApplicantID,
"amount": withdrawal.Amount, "fee": withdrawal.Fee, "fee_rate": withdrawal.FeeRate,
"actual_amount": withdrawal.ActualAmount, "withdrawal_method": withdrawal.WithdrawalMethod,
"payment_type": withdrawal.PaymentType, "status": withdrawal.Status,
"processor_id": withdrawal.ProcessorID, "processed_at": withdrawal.ProcessedAt, "paid_at": withdrawal.PaidAt,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// AgentWalletResource 构造代理钱包审计资源及余额前后值。
func AgentWalletResource(wallet *model.AgentWallet, relation, role string, beforeData, afterData map[string]any) ResourceInput {
resource := agentWalletAuditResource(wallet, relation, role)
resource.BeforeData = beforeData
resource.AfterData = afterData
return resource
}
// AgentWalletTransactionResource 构造代理钱包流水审计资源。
func AgentWalletTransactionResource(transaction *model.AgentWalletTransaction, relation, role string) ResourceInput {
resource := agentWalletTransactionResource(transaction)
resource.Relation = relation
resource.Role = role
return resource
}

View File

@@ -0,0 +1,62 @@
package audit
import (
"context"
stderrors "errors"
"strconv"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
"github.com/break/junhong_cmp_fiber/pkg/constants"
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)
// RecordFailure 在业务回滚后使用独立短事务记录失败或拒绝事实。
func (w *Writer) RecordFailure(ctx context.Context, db *gorm.DB, input AppendInput, originalErr error) {
fillFailureInput(&input, originalErr)
if w == nil || db == nil {
recordFailureWriteError(ctx, input, pkgerrors.New(pkgerrors.CodeInvalidStatus, "统一审计失败记录接缝未配置"))
return
}
if err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return w.Append(ctx, tx, input)
}); err != nil {
recordFailureWriteError(ctx, input, err)
}
}
func fillFailureInput(input *AppendInput, originalErr error) {
var appErr *pkgerrors.AppError
if stderrors.As(originalErr, &appErr) && appErr != nil {
if input.Result == "" {
input.Result = constants.AuditResultDenied
if appErr.Code == pkgerrors.CodeDatabaseError || appErr.Code == pkgerrors.CodeInternalError {
input.Result = constants.AuditResultFailed
}
}
input.ErrorCode = strconv.Itoa(appErr.Code)
input.ErrorSummary = appErr.Message
return
}
if input.Result == "" {
input.Result = constants.AuditResultFailed
}
input.ErrorCode = strconv.Itoa(pkgerrors.CodeInternalError)
input.ErrorSummary = "业务操作失败"
}
func recordFailureWriteError(ctx context.Context, input AppendInput, err error) {
linkage := auditcontext.From(ctx)
resourceKey := ""
for _, resource := range input.Resources {
if resource.Relation == constants.AuditResourceRelationPrimary {
resourceKey = resource.Key
break
}
}
auditfailure.RecordSecondaryWriteFailure(
input.ActionCode, resourceKey, linkage.RequestID, linkage.CorrelationID, input.ErrorCode, err,
)
}

View File

@@ -0,0 +1,24 @@
package audit
import (
"strconv"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// NotificationResource 构造不包含通知正文的安全资源快照。
func NotificationResource(notification *model.Notification, relation, role string, before, after map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(notification.ID), 10)
return ResourceInput{
Type: constants.AuditResourceNotification, ID: &id, Key: notification.EventID + ":" + notification.RecipientKind + ":" + id,
DisplayName: notification.Type, Relation: relation, Role: role,
IdentitySnapshot: map[string]any{
"id": notification.ID, "event_id": notification.EventID,
"recipient_kind": notification.RecipientKind, "recipient_id": notification.RecipientID,
"category": notification.Category, "type": notification.Type, "severity": notification.Severity,
"ref_type": notification.RefType, "ref_id": notification.RefID, "ref_key": notification.RefKey,
},
BeforeData: before, AfterData: after, SubjectVisibility: constants.AuditSubjectInternalOnly,
}
}

View File

@@ -0,0 +1,218 @@
package audit
import (
"strconv"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// PackageSeriesResource 构造套餐系列审计资源。
func PackageSeriesResource(series *model.PackageSeries, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(series.ID), 10)
var resourceID *string
if series.ID > 0 {
resourceID = &id
}
return ResourceInput{
Type: constants.AuditResourcePackageSeries, ID: resourceID, Key: series.SeriesCode, DisplayName: series.SeriesName,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": series.ID, "series_code": series.SeriesCode, "series_name": series.SeriesName,
"status": series.Status, "enable_one_time_commission": series.EnableOneTimeCommission,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// PackageResource 构造套餐商品审计资源。
func PackageResource(pkg *model.Package, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(pkg.ID), 10)
var resourceID *string
if pkg.ID > 0 {
resourceID = &id
}
return ResourceInput{
Type: constants.AuditResourcePackage, ID: resourceID, Key: pkg.PackageCode, DisplayName: pkg.PackageName,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": pkg.ID, "package_code": pkg.PackageCode, "package_name": pkg.PackageName,
"series_id": pkg.SeriesID, "package_type": pkg.PackageType, "duration_months": pkg.DurationMonths,
"duration_days": pkg.DurationDays, "price_config_status": pkg.PriceConfigStatus,
"is_gift": pkg.IsGift, "status": pkg.Status, "shelf_status": pkg.ShelfStatus,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// ShopResource 构造套餐配置关联的店铺审计资源。
func ShopResource(shop *model.Shop, relation, role string) ResourceInput {
id := strconv.FormatUint(uint64(shop.ID), 10)
key := shop.ShopCode
if key == "" {
key = id
}
return ResourceInput{
Type: constants.AuditResourceShop, ID: &id, Key: key, DisplayName: shop.ShopName,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": shop.ID, "shop_code": shop.ShopCode, "shop_name": shop.ShopName,
"parent_id": shop.ParentID, "level": shop.Level,
},
}
}
// ShopSeriesAllocationResource 构造店铺系列授权审计资源。
func ShopSeriesAllocationResource(allocation *model.ShopSeriesAllocation, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(allocation.ID), 10)
var resourceID *string
if allocation.ID > 0 {
resourceID = &id
}
key := id
if allocation.ID == 0 {
key = "shop-series-" + strconv.FormatUint(uint64(allocation.ShopID), 10) + "-" + strconv.FormatUint(uint64(allocation.SeriesID), 10)
}
return ResourceInput{
Type: constants.AuditResourceShopSeriesAllocation, ID: resourceID, Key: key, DisplayName: "系列授权 " + key,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": allocation.ID, "shop_id": allocation.ShopID, "series_id": allocation.SeriesID,
"allocator_shop_id": allocation.AllocatorShopID, "status": allocation.Status,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// ShopPackageAllocationResource 构造店铺套餐授权审计资源。
func ShopPackageAllocationResource(allocation *model.ShopPackageAllocation, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(allocation.ID), 10)
return ResourceInput{
Type: constants.AuditResourceShopPackageAllocation, ID: &id, Key: id, DisplayName: "套餐授权 " + id,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": allocation.ID, "shop_id": allocation.ShopID, "package_id": allocation.PackageID,
"allocator_shop_id": allocation.AllocatorShopID, "series_allocation_id": allocation.SeriesAllocationID,
"status": allocation.Status, "shelf_status": allocation.ShelfStatus,
"retail_price_config_status": allocation.RetailPriceConfigStatus,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// ShopPackagePriceHistoryResource 构造套餐价格历史审计资源。
func ShopPackagePriceHistoryResource(history *model.ShopPackageAllocationPriceHistory) ResourceInput {
id := strconv.FormatUint(uint64(history.ID), 10)
return ResourceInput{
Type: constants.AuditResourceShopPackagePriceHistory, ID: &id, Key: id, DisplayName: "价格历史 " + id,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRolePackagePriceHistory,
IdentitySnapshot: map[string]any{
"id": history.ID, "allocation_id": history.AllocationID, "changed_by": history.ChangedBy,
"effective_from": history.EffectiveFrom,
},
AfterData: map[string]any{
"old_cost_price": history.OldCostPrice, "new_cost_price": history.NewCostPrice,
"change_reason": history.ChangeReason,
},
}
}
// PackageConfigBatchResource 构造套餐配置批次根资源。
func PackageConfigBatchResource(batchKey, operation string, shopID, seriesID uint) ResourceInput {
return ResourceInput{
Type: constants.AuditResourcePackageConfigBatch, Key: batchKey, DisplayName: batchKey,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRolePackageConfigBatch,
IdentitySnapshot: map[string]any{
"batch_key": batchKey, "operation": operation, "shop_id": shopID, "series_id": seriesID,
},
}
}
// PackageUsageResource 构造套餐权益生命周期审计资源。
func PackageUsageResource(usage *model.PackageUsage, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(usage.ID), 10)
return ResourceInput{
Type: constants.AuditResourcePackageUsage, ID: &id, Key: id, DisplayName: usage.PackageName,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": usage.ID, "order_id": usage.OrderID, "order_no": usage.OrderNo,
"refund_id": usage.RefundID, "refund_no": usage.RefundNo,
"package_id": usage.PackageID, "package_name": usage.PackageName, "usage_type": usage.UsageType,
"iot_card_id": usage.IotCardID, "device_id": usage.DeviceID,
"data_limit_mb": usage.DataLimitMB, "data_usage_mb": usage.DataUsageMB,
"activated_at": usage.ActivatedAt, "expires_at": usage.ExpiresAt, "status": usage.Status,
"pending_realname_activation": usage.PendingRealnameActivation,
"last_reset_at": usage.LastResetAt, "next_reset_at": usage.NextResetAt, "generation": usage.Generation,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// OrderResource 构造订单审计资源。
func OrderResource(order *model.Order, relation, role string) ResourceInput {
id := strconv.FormatUint(uint64(order.ID), 10)
var resourceID *string
if order.ID > 0 {
resourceID = &id
}
key := order.OrderNo
if key == "" {
key = id
}
return ResourceInput{
Type: constants.AuditResourceOrder, ID: resourceID, Key: key, DisplayName: key,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": order.ID, "order_no": order.OrderNo, "order_type": order.OrderType,
"buyer_type": order.BuyerType, "buyer_id": order.BuyerID,
"iot_card_id": order.IotCardID, "device_id": order.DeviceID,
"asset_identifier": order.AssetIdentifier, "total_amount": order.TotalAmount,
"actual_paid_amount": order.ActualPaidAmount, "payment_method": order.PaymentMethod,
"payment_status": order.PaymentStatus, "purchase_role": order.PurchaseRole,
"source": order.Source, "operator_account_id": order.OperatorAccountID,
"operator_account_type": order.OperatorAccountType, "operator_account_name": order.OperatorAccountName,
"seller_shop_id": order.SellerShopID, "expires_at": order.ExpiresAt,
},
}
}
// PaymentResource 构造支付记录审计资源。
func PaymentResource(payment *model.Payment, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(payment.ID), 10)
var resourceID *string
if payment.ID > 0 {
resourceID = &id
}
key := payment.PaymentNo
if key == "" {
key = id
}
return ResourceInput{
Type: constants.AuditResourcePayment, ID: resourceID, Key: key, DisplayName: key,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": payment.ID, "payment_no": payment.PaymentNo, "order_id": payment.OrderID,
"order_type": payment.OrderType, "payment_method": payment.PaymentMethod,
"amount": payment.Amount, "status": payment.Status,
"third_party_trade_no": payment.ThirdPartyTradeNo, "payment_config_id": payment.PaymentConfigID,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// RefundResource 构造退款单审计资源。
func RefundResource(refund *model.RefundRequest, relation, role string) ResourceInput {
id := strconv.FormatUint(uint64(refund.ID), 10)
var resourceID *string
if refund.ID > 0 {
resourceID = &id
}
key := refund.RefundNo
if key == "" {
key = id
}
return ResourceInput{
Type: constants.AuditResourceRefund, ID: resourceID, Key: key, DisplayName: key,
Relation: relation, Role: role, IdentitySnapshot: map[string]any{
"id": refund.ID, "refund_no": refund.RefundNo, "order_id": refund.OrderID,
"order_no": refund.OrderNo, "order_type": refund.OrderType,
"package_usage_id": refund.PackageUsageID, "asset_identifier": refund.AssetIdentifier,
"shop_id": refund.ShopID, "requested_refund_amount": refund.RequestedRefundAmount,
"actual_received_amount": refund.ActualReceivedAmount, "refund_reason": refund.RefundReason,
"approved_refund_amount": refund.ApprovedRefundAmount, "approval_instance_id": refund.ApprovalInstanceID,
"status": refund.Status, "commission_deducted": refund.CommissionDeducted, "asset_reset": refund.AssetReset,
},
}
}

View File

@@ -0,0 +1,84 @@
package audit
import (
"context"
"strconv"
"gorm.io/gorm"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// WriteAgentRechargePayment 将代理充值支付生命周期写入统一 Audit Event。
func (w *Writer) WriteAgentRechargePayment(ctx context.Context, tx *gorm.DB, change agentrecharge.PaymentAudit) error {
if change.Payment == nil || change.Payment.ID == 0 || change.Payment.PaymentNo == "" || change.Recharge == nil || change.Recharge.ID == 0 {
return errors.New(errors.CodeInvalidParam, "代理充值支付审计资源不完整")
}
payment := PaymentResource(change.Payment, constants.AuditResourceRelationPrimary, constants.AuditResourceRolePaymentTarget, change.BeforeData, change.AfterData)
rechargeID := strconv.FormatUint(uint64(change.Recharge.ID), 10)
rechargeRelation := constants.AuditResourceRelationReference
if len(change.RechargeBeforeData) > 0 || len(change.RechargeAfterData) > 0 {
rechargeRelation = constants.AuditResourceRelationAffected
}
rechargeStatus := any(change.Recharge.Status)
if status, ok := change.RechargeAfterData["status"]; ok {
rechargeStatus = status
}
recharge := ResourceInput{
Type: constants.AuditResourceAgentRecharge, ID: &rechargeID,
Key: change.Recharge.RechargeNo, DisplayName: change.Recharge.RechargeNo,
Relation: rechargeRelation, Role: constants.AuditResourceRolePaymentBusinessOrder,
IdentitySnapshot: map[string]any{
"id": change.Recharge.ID, "recharge_no": change.Recharge.RechargeNo,
"user_id": change.Recharge.UserID,
"shop_id": change.Recharge.ShopID, "agent_wallet_id": change.Recharge.AgentWalletID,
"amount": change.Recharge.Amount, "payment_method": change.Recharge.PaymentMethod,
"payment_channel": change.Recharge.PaymentChannel,
"approval_instance_id": change.Recharge.ApprovalInstanceID, "status": rechargeStatus,
},
BeforeData: change.RechargeBeforeData, AfterData: change.RechargeAfterData,
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: change.Summary,
}
resources := []ResourceInput{payment, recharge}
if change.Recharge.UserID > 0 {
var account model.Account
if err := tx.WithContext(ctx).Unscoped().First(&account, change.Recharge.UserID).Error; err != nil && err != gorm.ErrRecordNotFound {
return errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值提交人审计快照失败")
}
if account.ID > 0 {
accountID := strconv.FormatUint(uint64(account.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceAccount, ID: &accountID, Key: accountID, DisplayName: account.Username,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRechargeSubmitter,
IdentitySnapshot: accountIdentity(&account),
})
}
}
var shop model.Shop
if err := tx.WithContext(ctx).Unscoped().First(&shop, change.Recharge.ShopID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值店铺审计快照失败")
}
resources = append(resources, ShopResource(&shop, constants.AuditResourceRelationReference, constants.AuditResourceRoleRechargeShop))
var wallet model.AgentWallet
if err := tx.WithContext(ctx).First(&wallet, change.Recharge.AgentWalletID).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值钱包审计快照失败")
}
walletID := strconv.FormatUint(uint64(wallet.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceAgentWallet, ID: &walletID, Key: walletID, DisplayName: "代理主钱包 " + walletID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRechargeWallet,
IdentitySnapshot: map[string]any{
"id": wallet.ID, "shop_id": wallet.ShopID, "wallet_type": wallet.WalletType,
"currency": wallet.Currency, "status": wallet.Status,
},
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: change.Summary,
})
return w.Append(ctx, tx, AppendInput{
ActionCode: change.ActionCode, Summary: change.Summary,
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
CorrelationID: change.Payment.PaymentNo, Resources: resources,
})
}

View File

@@ -0,0 +1,73 @@
package audit
import (
"context"
"strconv"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
// PollingInput 描述轮询配置、规则或人工任务的审计事实。
type PollingInput struct {
EventID string
ActionCode string
Summary string
ResourceType string
ResourceID uint
ResourceKey string
DisplayName string
OperatorID uint
IdentitySnapshot map[string]any
BeforeData map[string]any
AfterData map[string]any
Metadata map[string]any
Cards []*model.IotCard
Result string
ErrorCode string
ErrorSummary string
}
// WritePolling 将轮询配置、规则或人工任务转换为统一 Audit Event。
func (w *Writer) WritePolling(ctx context.Context, tx *gorm.DB, input PollingInput) error {
if input.OperatorID == 0 || input.ResourceType == "" || input.ResourceKey == "" {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "轮询审计资源或操作者不完整")
}
resourceID := optionalResourceID(input.ResourceID)
resources := []ResourceInput{{
Type: input.ResourceType, ID: resourceID, Key: input.ResourceKey, DisplayName: input.DisplayName,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRolePollingTarget,
IdentitySnapshot: input.IdentitySnapshot, BeforeData: input.BeforeData, AfterData: input.AfterData,
SubjectVisibility: constants.AuditSubjectInternalOnly,
}}
for index, card := range input.Cards {
if card == nil || card.ID == 0 {
continue
}
resources = append(resources, ResourceInput{
Type: constants.AuditResourceIotCard, ID: optionalResourceID(card.ID),
Key: iotCardResourceKey(card), DisplayName: card.ICCID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRolePollingCard,
IdentitySnapshot: iotCardIdentity(card), SubjectVisibility: constants.AuditSubjectInternalOnly,
SortOrder: index + 1,
})
}
result := input.Result
if result == "" {
result = constants.AuditResultSuccess
}
return w.Append(ctx, tx, AppendInput{
EventID: input.EventID, ActionCode: input.ActionCode, Summary: input.Summary,
Actor: ActorInput{
Kind: constants.AuditActorAccount, ID: strconv.FormatUint(uint64(input.OperatorID), 10),
Name: middleware.GetUsernameFromContext(ctx),
},
Source: constants.AuditSourceAdminAPI, ScopeType: constants.AuditScopePlatform,
Result: result, ErrorCode: input.ErrorCode, ErrorSummary: input.ErrorSummary,
Metadata: input.Metadata, Resources: resources,
})
}

View File

@@ -0,0 +1,187 @@
package audit
import (
"context"
"strconv"
"gorm.io/gorm"
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// WriteAgentRecharge 将代理充值申请、终态和实际入账写入统一 Audit Event。
func (w *Writer) WriteAgentRecharge(ctx context.Context, tx *gorm.DB, change agentrecharge.RechargeAudit) error {
if change.Record == nil || change.Record.ID == 0 || change.Record.RechargeNo == "" {
return errors.New(errors.CodeInvalidParam, "代理充值审计资源不完整")
}
resources, err := agentRechargeResources(ctx, tx, change)
if err != nil {
return err
}
return w.Append(ctx, tx, AppendInput{
ActionCode: change.ActionCode, Summary: change.Summary,
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
CorrelationID: change.Record.RechargeNo, Resources: resources,
})
}
func agentRechargeResources(ctx context.Context, tx *gorm.DB, change agentrecharge.RechargeAudit) ([]ResourceInput, error) {
record := change.Record
id := strconv.FormatUint(uint64(record.ID), 10)
primary := ResourceInput{
Type: constants.AuditResourceAgentRecharge, ID: &id, Key: record.RechargeNo, DisplayName: record.RechargeNo,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleRechargeTarget,
IdentitySnapshot: map[string]any{
"id": record.ID, "recharge_no": record.RechargeNo, "user_id": record.UserID,
"shop_id": record.ShopID, "agent_wallet_id": record.AgentWalletID, "amount": record.Amount,
"payment_method": record.PaymentMethod, "payment_channel": record.PaymentChannel,
"payment_transaction_id": record.PaymentTransactionID, "approval_instance_id": record.ApprovalInstanceID,
"status": record.Status,
},
BeforeData: change.BeforeData, AfterData: change.AfterData,
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: change.Summary,
}
resources := []ResourceInput{primary}
var account model.Account
if record.UserID > 0 {
if err := tx.WithContext(ctx).Unscoped().First(&account, record.UserID).Error; err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值提交人审计快照失败")
}
if account.ID > 0 {
accountID := strconv.FormatUint(uint64(account.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceAccount, ID: &accountID, Key: accountID, DisplayName: account.Username,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRechargeSubmitter,
IdentitySnapshot: accountIdentity(&account),
})
}
}
var shop model.Shop
if err := tx.WithContext(ctx).Unscoped().First(&shop, record.ShopID).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值店铺审计快照失败")
}
resources = append(resources, ShopResource(&shop, constants.AuditResourceRelationReference, constants.AuditResourceRoleRechargeShop))
if change.Payment != nil {
resources = append(resources, PaymentResource(change.Payment, constants.AuditResourceRelationReference, constants.AuditResourceRolePaymentTarget, nil, nil))
}
if change.Approval != nil {
approvalID := strconv.FormatUint(uint64(change.Approval.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceApprovalInstance, ID: &approvalID, Key: approvalID, DisplayName: "审批实例 " + approvalID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRechargeApproval,
IdentitySnapshot: map[string]any{
"id": change.Approval.ID, "business_type": change.Approval.BusinessType,
"business_id": change.Approval.BusinessID, "submitter_account_id": change.Approval.SubmitterAccountID,
"provider": change.Approval.Provider, "external_ref": change.Approval.ExternalRef,
"correlation_id": change.Approval.CorrelationID, "status": change.Approval.Status,
},
})
}
if change.Wallet != nil {
walletID := strconv.FormatUint(uint64(change.Wallet.ID), 10)
relation := constants.AuditResourceRelationReference
if change.Transaction != nil {
relation = constants.AuditResourceRelationAffected
}
wallet := ResourceInput{
Type: constants.AuditResourceAgentWallet, ID: &walletID, Key: walletID, DisplayName: "代理主钱包 " + walletID,
Relation: relation, Role: constants.AuditResourceRoleRechargeWallet,
IdentitySnapshot: map[string]any{
"id": change.Wallet.ID, "shop_id": change.Wallet.ShopID, "wallet_type": change.Wallet.WalletType,
"currency": change.Wallet.Currency, "status": change.Wallet.Status,
},
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: change.Summary,
}
if change.Transaction != nil {
wallet.BeforeData = map[string]any{"balance": change.Transaction.BalanceBefore}
wallet.AfterData = map[string]any{"balance": change.Transaction.BalanceAfter}
}
resources = append(resources, wallet)
}
if change.Transaction != nil {
transactionID := strconv.FormatUint(uint64(change.Transaction.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceAgentWalletTransaction, ID: &transactionID, Key: transactionID, DisplayName: "代理钱包流水 " + transactionID,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleRechargeWalletTransaction,
IdentitySnapshot: map[string]any{
"id": change.Transaction.ID, "agent_wallet_id": change.Transaction.AgentWalletID,
"shop_id": change.Transaction.ShopID, "transaction_type": change.Transaction.TransactionType,
"transaction_subtype": change.Transaction.TransactionSubtype,
"reference_type": change.Transaction.ReferenceType, "reference_id": change.Transaction.ReferenceID,
"status": change.Transaction.Status,
},
AfterData: map[string]any{
"amount": change.Transaction.Amount, "balance_before": change.Transaction.BalanceBefore,
"balance_after": change.Transaction.BalanceAfter,
},
})
}
return resources, nil
}
// AssetRechargeReferences 构造个人资产充值关联的提交人、钱包和资产资源。
func AssetRechargeReferences(ctx context.Context, tx *gorm.DB, recharge *model.RechargeOrder) ([]ResourceInput, error) {
if recharge == nil || recharge.ID == 0 {
return nil, errors.New(errors.CodeInvalidParam, "资产充值审计资源不完整")
}
resources := make([]ResourceInput, 0, 3)
var customer model.PersonalCustomer
if err := tx.WithContext(ctx).Unscoped().First(&customer, recharge.UserID).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产充值提交人审计快照失败")
}
customerID := strconv.FormatUint(uint64(customer.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourcePersonalCustomer, ID: &customerID, Key: customerID, DisplayName: customer.Nickname,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRechargeSubmitter,
IdentitySnapshot: map[string]any{
"id": customer.ID, "nickname": customer.Nickname, "wx_open_id": customer.WxOpenID,
"wx_union_id": customer.WxUnionID, "status": customer.Status,
},
})
var wallet model.AssetWallet
if err := tx.WithContext(ctx).First(&wallet, recharge.AssetWalletID).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产充值钱包审计快照失败")
}
walletID := strconv.FormatUint(uint64(wallet.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceAssetWallet, ID: &walletID, Key: walletID, DisplayName: "资产钱包 " + walletID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRechargeWallet,
IdentitySnapshot: map[string]any{
"id": wallet.ID, "resource_type": wallet.ResourceType, "resource_id": wallet.ResourceID,
"currency": wallet.Currency, "shop_id_tag": wallet.ShopIDTag, "enterprise_id_tag": wallet.EnterpriseIDTag,
},
})
switch recharge.ResourceType {
case constants.AssetWalletResourceTypeIotCard:
var card model.IotCard
if err := tx.WithContext(ctx).Unscoped().First(&card, recharge.ResourceID).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产充值卡审计快照失败")
}
cardID := strconv.FormatUint(uint64(card.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceIotCard, ID: &cardID, Key: IotCardResourceKey(&card), DisplayName: card.ICCID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleOrderAsset,
IdentitySnapshot: IotCardIdentitySnapshot(&card), SubjectVisibility: constants.AuditSubjectResult,
SubjectSummary: "资产充值状态已更新",
})
case constants.AssetWalletResourceTypeDevice:
var device model.Device
if err := tx.WithContext(ctx).Unscoped().First(&device, recharge.ResourceID).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产充值设备审计快照失败")
}
deviceID := strconv.FormatUint(uint64(device.ID), 10)
resources = append(resources, ResourceInput{
Type: constants.AuditResourceDevice, ID: &deviceID, Key: DeviceResourceKey(&device), DisplayName: device.VirtualNo,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleOrderAsset,
IdentitySnapshot: DeviceIdentitySnapshot(&device), SubjectVisibility: constants.AuditSubjectResult,
SubjectSummary: "资产充值状态已更新",
})
}
return resources, nil
}

View File

@@ -0,0 +1,128 @@
package audit
import (
"context"
"strconv"
"gorm.io/gorm"
refundapprovalapp "github.com/break/junhong_cmp_fiber/internal/application/refundapproval"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)
// WriteRefundApplication 将退款申请、审批和关联业务资源写入同一事务。
func (w *Writer) WriteRefundApplication(ctx context.Context, tx *gorm.DB, input refundapprovalapp.ApplicationAudit) error {
if input.Refund == nil || input.Order == nil || input.Approval == nil || input.Submitter == nil {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "退款申请审计资源不完整")
}
primary := RefundResource(input.Refund, constants.AuditResourceRelationPrimary, constants.AuditResourceRoleRefundTarget)
primary.AfterData = refundStateData(input.Refund)
primary.SubjectVisibility = constants.AuditSubjectResult
primary.SubjectSummary = "退款申请已提交"
resources := []ResourceInput{
primary,
OrderResource(input.Order, constants.AuditResourceRelationReference, constants.AuditResourceRoleRefundOrder),
ApprovalInstanceResource(input.Approval, constants.AuditResourceRelationAffected, constants.AuditResourceRoleRefundApproval, nil, map[string]any{"status": input.Approval.Status}),
AccountResource(input.Submitter, constants.AuditResourceRelationReference, constants.AuditResourceRoleRefundSubmitter),
}
for index := 1; index < len(resources); index++ {
resources[index].SubjectVisibility = constants.AuditSubjectInternalOnly
}
asset, err := RefundAssetResource(ctx, tx, input.Order, "退款申请已提交")
if err != nil {
return err
}
if asset != nil {
resources = append(resources, *asset)
}
return w.Append(ctx, tx, AppendInput{
EventID: "refund:" + strconv.FormatUint(uint64(input.Refund.ID), 10) + ":created",
ActionCode: constants.AuditActionRefundCreated, Summary: "提交退款申请",
Actor: ActorInput{Kind: constants.AuditActorAccount, ID: strconv.FormatUint(uint64(input.Submitter.ID), 10), Name: input.Submitter.Username},
Source: constants.AuditSourceAdminAPI, ScopeType: constants.AuditScopePlatform,
Result: constants.AuditResultSuccess, CorrelationID: input.Refund.RefundNo,
Metadata: map[string]any{"requested_refund_amount": input.Refund.RequestedRefundAmount},
Resources: resources,
})
}
// ApprovalInstanceResource 构造审批实例审计资源。
func ApprovalInstanceResource(instance *model.ApprovalInstance, relation, role string, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(instance.ID), 10)
return ResourceInput{
Type: constants.AuditResourceApprovalInstance, ID: &id, Key: id, DisplayName: "审批实例 " + id,
Relation: relation, Role: role,
IdentitySnapshot: map[string]any{
"id": instance.ID, "business_type": instance.BusinessType, "business_id": instance.BusinessID,
"provider": instance.Provider, "external_ref": instance.ExternalRef, "status": instance.Status,
},
BeforeData: beforeData, AfterData: afterData,
}
}
// AccountResource 构造退款链路中的后台账号资源。
func AccountResource(account *model.Account, relation, role string) ResourceInput {
id := strconv.FormatUint(uint64(account.ID), 10)
return ResourceInput{
Type: constants.AuditResourceAccount, ID: &id, Key: id, DisplayName: account.Username,
Relation: relation, Role: role, IdentitySnapshot: accountIdentity(account),
}
}
// RefundAssetResource 构造退款订单实际关联的卡或设备资源。
func RefundAssetResource(ctx context.Context, tx *gorm.DB, order *model.Order, subjectSummary string) (*ResourceInput, error) {
if order.IotCardID != nil {
var card model.IotCard
if err := tx.WithContext(ctx).First(&card, *order.IotCardID).Error; err != nil {
return nil, pkgerrors.Wrap(pkgerrors.CodeDatabaseError, err, "查询退款关联卡审计快照失败")
}
id := strconv.FormatUint(uint64(card.ID), 10)
return &ResourceInput{
Type: constants.AuditResourceIotCard, ID: &id, Key: IotCardResourceKey(&card), DisplayName: card.ICCID,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRefundAsset,
IdentitySnapshot: IotCardIdentitySnapshot(&card), SubjectVisibility: constants.AuditSubjectResult,
SubjectSummary: subjectSummary,
}, nil
}
if order.DeviceID != nil {
var device model.Device
if err := tx.WithContext(ctx).First(&device, *order.DeviceID).Error; err != nil {
return nil, pkgerrors.Wrap(pkgerrors.CodeDatabaseError, err, "查询退款关联设备审计快照失败")
}
id := strconv.FormatUint(uint64(device.ID), 10)
return &ResourceInput{
Type: constants.AuditResourceDevice, ID: &id, Key: DeviceResourceKey(&device), DisplayName: device.VirtualNo,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRoleRefundAsset,
IdentitySnapshot: DeviceIdentitySnapshot(&device), SubjectVisibility: constants.AuditSubjectResult,
SubjectSummary: subjectSummary,
}, nil
}
return nil, nil
}
// CommissionRecordResource 构造退款失效的佣金记录资源。
func CommissionRecordResource(record *model.CommissionRecord, beforeData, afterData map[string]any) ResourceInput {
id := strconv.FormatUint(uint64(record.ID), 10)
return ResourceInput{
Type: constants.AuditResourceCommissionRecord, ID: &id, Key: id, DisplayName: "佣金记录 " + id,
Relation: constants.AuditResourceRelationAffected, Role: constants.AuditResourceRoleRefundCommission,
IdentitySnapshot: map[string]any{
"id": record.ID, "shop_id": record.ShopID, "order_id": record.OrderID,
"iot_card_id": record.IotCardID, "device_id": record.DeviceID,
"commission_source": record.CommissionSource, "amount": record.Amount,
"status": record.Status, "released_at": record.ReleasedAt,
},
BeforeData: beforeData, AfterData: afterData,
}
}
func refundStateData(refund *model.RefundRequest) map[string]any {
return map[string]any{
"status": refund.Status, "approved_refund_amount": refund.ApprovedRefundAmount,
"approval_instance_id": refund.ApprovalInstanceID, "processor_id": refund.ProcessorID,
"processed_at": refund.ProcessedAt, "commission_deducted": refund.CommissionDeducted,
"asset_reset": refund.AssetReset, "reject_reason": refund.RejectReason, "remark": refund.Remark,
}
}

View File

@@ -17,6 +17,13 @@ type ActionDefinition struct {
AllowedVisibility []string
SubjectFields []string
SensitiveRead bool
AllowedOrigins []ActionOrigin
}
// ActionOrigin 定义动作允许的操作者与入口组合。
type ActionOrigin struct {
Actor string
Source string
}
// ResourceDefinition 是受控审计资源的快照契约。
@@ -70,6 +77,76 @@ func NewRegistry() *Registry {
personalPhoneBound := personalAction(constants.AuditActionPersonalCustomerPhoneBound, "绑定个人手机号", []string{"phone"})
personalPhoneChanged := personalAction(constants.AuditActionPersonalCustomerPhoneChanged, "更换个人手机号", []string{"phone"})
personalWechatIdentityUpdated := personalAction(constants.AuditActionPersonalCustomerWechatIdentityUpdated, "同步个人微信主体", []string{"app_id", "app_type"})
personalAssetBound := personalAction(constants.AuditActionPersonalCustomerAssetBound, "绑定个人客户资产", []string{"asset_type", "asset_id"})
personalAssetBound.Category = constants.AuditCategoryAsset
personalAssetUnbound := customerAssetAdminAction(constants.AuditActionPersonalCustomerAssetUnbound, "解除个人客户资产绑定")
personalAssetBindingMigrated := customerAssetAdminAction(constants.AuditActionPersonalCustomerAssetBindingMigrated, "迁移个人客户资产绑定")
iotCardCreated := iotCardAction(constants.AuditActionIotCardCreated, "创建 IoT 卡", constants.AuditActorSystemTask, constants.AuditSourceWorker)
iotCardDeleted := iotCardAction(constants.AuditActionIotCardDeleted, "删除 IoT 卡", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardDeleted.Risk = constants.AuditRiskHigh
iotCardBatchDeleted := ActionDefinition{
Code: constants.AuditActionIotCardBatchDeleted, Name: "批量删除 IoT 卡",
Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceIotCardBatch, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
iotCardAllocationBatch := iotCardBatchAction(constants.AuditActionIotCardAllocationBatch, "批量分配 IoT 卡")
iotCardAllocated := iotCardAction(constants.AuditActionIotCardAllocated, "分配 IoT 卡", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardRecallBatch := iotCardBatchAction(constants.AuditActionIotCardRecallBatch, "批量回收 IoT 卡")
iotCardRecalled := iotCardAction(constants.AuditActionIotCardRecalled, "回收 IoT 卡", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardSeriesBindingBatch := iotCardBatchAction(constants.AuditActionIotCardSeriesBindingBatch, "批量设置 IoT 卡系列绑定")
iotCardSeriesBound := iotCardAction(constants.AuditActionIotCardSeriesBound, "设置 IoT 卡系列绑定", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardSpeedTierSet := iotCardAction(constants.AuditActionIotCardSpeedTierSet, "设置 IoT 卡固定限速档位", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardRealnamePolicyBatchUpdated := iotCardBatchAction(constants.AuditActionIotCardRealnamePolicyBatchUpdated, "批量更新 IoT 卡实名策略")
iotCardRealnamePolicyUpdated := iotCardAction(constants.AuditActionIotCardRealnamePolicyUpdated, "更新 IoT 卡实名策略", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardRealnameStatusUpdated := iotCardAction(constants.AuditActionIotCardRealnameStatusUpdated, "人工更新 IoT 卡实名状态", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardRealnameCallbackSynced := iotCardAction(constants.AuditActionIotCardRealnameCallbackSynced, "运营商回调同步 IoT 卡实名状态", constants.AuditActorExternalSystem, constants.AuditSourceCallback)
iotCardManualRefreshed := iotCardAction(constants.AuditActionIotCardManualRefreshed, "人工刷新 IoT 卡", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardPersonalRefreshed := iotCardAction(constants.AuditActionIotCardPersonalRefreshed, "个人客户刷新 IoT 卡", constants.AuditActorPersonalCustomer, constants.AuditSourcePersonalAPI)
iotCardManualStopped := iotCardAction(constants.AuditActionIotCardManualStopped, "人工停用 IoT 卡网络", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardManualStarted := iotCardAction(constants.AuditActionIotCardManualStarted, "人工恢复 IoT 卡网络", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardAutoStopped := iotCardAction(constants.AuditActionIotCardAutoStopped, "自动停用 IoT 卡网络", constants.AuditActorSystemTask, constants.AuditSourceWorker)
iotCardAutoStarted := iotCardAction(constants.AuditActionIotCardAutoStarted, "自动恢复 IoT 卡网络", constants.AuditActorSystemTask, constants.AuditSourceWorker)
iotCardOpenAPIStarted := iotCardAction(constants.AuditActionIotCardOpenAPIStarted, "OpenAPI 恢复 IoT 卡网络", constants.AuditActorOpenAPI, constants.AuditSourceOpenAPI)
iotCardAutoStopReasonUpdated := iotCardAction(constants.AuditActionIotCardAutoStopReasonUpdated, "自动更新 IoT 卡停机原因", constants.AuditActorSystemTask, constants.AuditSourceWorker)
deviceCreated := deviceAction(constants.AuditActionDeviceCreated, "导入创建设备", constants.AuditActorSystemTask, constants.AuditSourceWorker)
deviceDeleted := deviceAction(constants.AuditActionDeviceDeleted, "删除设备", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceDeleted.Risk = constants.AuditRiskHigh
deviceAllocationBatch := deviceMultiOriginBatchAction(constants.AuditActionDeviceAllocationBatch, "批量分配设备")
deviceAllocated := deviceMultiOriginAction(constants.AuditActionDeviceAllocated, "分配设备")
deviceRecallBatch := deviceMultiOriginBatchAction(constants.AuditActionDeviceRecallBatch, "批量回收设备")
deviceRecalled := deviceMultiOriginAction(constants.AuditActionDeviceRecalled, "回收设备")
deviceSeriesBindingBatch := deviceMultiOriginBatchAction(constants.AuditActionDeviceSeriesBindingBatch, "批量设置设备系列绑定")
deviceSeriesBound := deviceMultiOriginAction(constants.AuditActionDeviceSeriesBound, "设置设备系列绑定")
deviceRealnamePolicyBatchUpdated := deviceAccountBatchAction(constants.AuditActionDeviceRealnamePolicyBatchUpdated, "批量更新设备实名策略")
deviceRealnamePolicyUpdated := deviceAction(constants.AuditActionDeviceRealnamePolicyUpdated, "更新设备实名策略", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceStopped := deviceAction(constants.AuditActionDeviceStopped, "停用设备绑定卡网络", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceStarted := deviceAction(constants.AuditActionDeviceStarted, "恢复设备绑定卡网络", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceWiFiSet := deviceExternalAction(constants.AuditActionDeviceWiFiSet, "设置设备 Wi-Fi", false)
deviceSwitchModeSet := deviceAction(constants.AuditActionDeviceSwitchModeSet, "设置设备切卡模式", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceRebooted := deviceExternalAction(constants.AuditActionDeviceRebooted, "重启设备", true)
deviceReset := deviceExternalAction(constants.AuditActionDeviceReset, "恢复设备出厂设置", true)
deviceCardBound := deviceAction(constants.AuditActionDeviceCardBound, "设备绑定 IoT 卡", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceCardUnbound := deviceAction(constants.AuditActionDeviceCardUnbound, "设备解绑 IoT 卡", constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceCurrentCardSwitched := deviceExternalAction(constants.AuditActionDeviceCurrentCardSwitched, "切换设备当前卡", true)
cardExchangeCreated := cardExchangeAction(constants.AuditActionCardExchangeCreated, "创建卡换货单", constants.AuditRiskNormal, false)
cardExchangeShippingInfoSubmitted := cardExchangeAction(constants.AuditActionCardExchangeShippingInfoSubmitted, "提交卡换货收货信息", constants.AuditRiskHigh, true)
cardExchangeShipped := cardExchangeAction(constants.AuditActionCardExchangeShipped, "卡换货发货", constants.AuditRiskNormal, false)
cardExchangeCompleted := cardExchangeAction(constants.AuditActionCardExchangeCompleted, "完成卡换货", constants.AuditRiskHigh, false)
cardExchangeCancelled := cardExchangeAction(constants.AuditActionCardExchangeCancelled, "取消卡换货", constants.AuditRiskNormal, false)
cardExchangeRenewed := cardExchangeAction(constants.AuditActionCardExchangeRenewed, "换出旧卡转新", constants.AuditRiskHigh, false)
cardExchangeRenewed.DefaultVisibility = constants.AuditSubjectInternalOnly
cardExchangeRenewed.AllowedVisibility = []string{constants.AuditSubjectInternalOnly}
deviceExchangeCreated := cardExchangeAction(constants.AuditActionDeviceExchangeCreated, "创建设备换货单", constants.AuditRiskNormal, false)
deviceExchangeShippingInfoSubmitted := cardExchangeAction(constants.AuditActionDeviceExchangeShippingInfoSubmitted, "提交设备换货收货信息", constants.AuditRiskHigh, true)
deviceExchangeShipped := cardExchangeAction(constants.AuditActionDeviceExchangeShipped, "设备换货发货", constants.AuditRiskNormal, false)
deviceExchangeCompleted := cardExchangeAction(constants.AuditActionDeviceExchangeCompleted, "完成设备换货", constants.AuditRiskHigh, false)
deviceExchangeCancelled := cardExchangeAction(constants.AuditActionDeviceExchangeCancelled, "取消设备换货", constants.AuditRiskNormal, false)
deviceExchangeRenewed := cardExchangeAction(constants.AuditActionDeviceExchangeRenewed, "换出旧设备转新", constants.AuditRiskHigh, false)
deviceExchangeRenewed.DefaultVisibility = constants.AuditSubjectInternalOnly
deviceExchangeRenewed.AllowedVisibility = []string{constants.AuditSubjectInternalOnly}
systemConfigUpdated := ActionDefinition{
Code: constants.AuditActionSystemConfigUpdated, Name: "更新受控系统配置",
Category: constants.AuditCategoryConfiguration, Risk: constants.AuditRiskHigh,
@@ -78,6 +155,19 @@ func NewRegistry() *Registry {
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
paymentConfigCreated := connectionConfigAction(constants.AuditActionPaymentConfigCreated, "创建支付连接配置", constants.AuditResourcePaymentConfig, constants.AuditRiskHigh)
paymentConfigUpdated := connectionConfigAction(constants.AuditActionPaymentConfigUpdated, "更新支付连接配置", constants.AuditResourcePaymentConfig, constants.AuditRiskHigh)
paymentConfigDeleted := connectionConfigAction(constants.AuditActionPaymentConfigDeleted, "删除支付连接配置", constants.AuditResourcePaymentConfig, constants.AuditRiskHigh)
paymentConfigActivated := connectionConfigAction(constants.AuditActionPaymentConfigActivated, "激活支付连接配置", constants.AuditResourcePaymentConfig, constants.AuditRiskHigh)
paymentConfigDeactivated := connectionConfigAction(constants.AuditActionPaymentConfigDeactivated, "停用支付连接配置", constants.AuditResourcePaymentConfig, constants.AuditRiskHigh)
carrierCreated := connectionConfigAction(constants.AuditActionCarrierCreated, "创建运营商配置", constants.AuditResourceCarrier, constants.AuditRiskNormal)
carrierUpdated := connectionConfigAction(constants.AuditActionCarrierUpdated, "更新运营商配置", constants.AuditResourceCarrier, constants.AuditRiskNormal)
carrierDeleted := connectionConfigAction(constants.AuditActionCarrierDeleted, "删除运营商配置", constants.AuditResourceCarrier, constants.AuditRiskHigh)
carrierStatusUpdated := connectionConfigAction(constants.AuditActionCarrierStatusUpdated, "更新运营商配置状态", constants.AuditResourceCarrier, constants.AuditRiskHigh)
wecomApplicationSaved := connectionConfigAction(constants.AuditActionWeComApplicationSaved, "保存企业微信应用配置", constants.AuditResourceWeComApplication, constants.AuditRiskHigh)
wecomDefaultCreatorSaved := connectionConfigAction(constants.AuditActionWeComDefaultCreatorSaved, "保存企业微信默认审批发起人", constants.AuditResourceWeComApplication, constants.AuditRiskHigh)
wecomMembersSynced := connectionConfigAction(constants.AuditActionWeComMembersSynced, "同步企业微信应用可见成员", constants.AuditResourceWeComApplication, constants.AuditRiskNormal)
wecomApprovalSceneSaved := connectionConfigAction(constants.AuditActionWeComApprovalSceneSaved, "保存企业微信审批场景配置", constants.AuditResourceWeComApprovalScene, constants.AuditRiskHigh)
outboxReplayed := outboxRecoveryAction(
constants.AuditActionOutboxReplayed,
"人工重放 Outbox 事件",
@@ -97,6 +187,37 @@ func NewRegistry() *Registry {
constants.AuditResourceDevice,
)
deviceBatchItem.AllowedVisibility = []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult}
iotCardImportTaskCreated := taskAction(constants.AuditActionIotCardImportTaskCreated, "创建 IoT 卡导入任务", constants.AuditResourceIotCardImportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
iotCardImportTaskCompleted := taskAction(constants.AuditActionIotCardImportTaskCompleted, "完成 IoT 卡导入任务", constants.AuditResourceIotCardImportTask, constants.AuditActorSystemTask, constants.AuditSourceWorker)
deviceImportTaskCreated := taskAction(constants.AuditActionDeviceImportTaskCreated, "创建设备导入任务", constants.AuditResourceDeviceImportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
deviceImportTaskCompleted := taskAction(constants.AuditActionDeviceImportTaskCompleted, "完成设备导入任务", constants.AuditResourceDeviceImportTask, constants.AuditActorSystemTask, constants.AuditSourceWorker)
assetPackageBatchOrderTaskCreated := taskAction(constants.AuditActionAssetPackageBatchOrderTaskCreated, "创建资产套餐批量订购任务", constants.AuditResourceAssetPackageBatchOrderTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
assetPackageBatchOrderTaskCompleted := taskAction(constants.AuditActionAssetPackageBatchOrderTaskCompleted, "完成资产套餐批量订购任务", constants.AuditResourceAssetPackageBatchOrderTask, constants.AuditActorSystemTask, constants.AuditSourceWorker)
orderPackageInvalidateTaskCreated := taskAction(constants.AuditActionOrderPackageInvalidateTaskCreated, "创建订单套餐批量失效任务", constants.AuditResourceOrderPackageInvalidateTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
orderPackageInvalidateTaskCompleted := taskAction(constants.AuditActionOrderPackageInvalidateTaskCompleted, "完成订单套餐批量失效任务", constants.AuditResourceOrderPackageInvalidateTask, constants.AuditActorSystemTask, constants.AuditSourceWorker)
orderPackageInvalidateItem := taskAction(constants.AuditActionOrderPackageInvalidateItem, "失效订单套餐权益", constants.AuditResourceOrder, constants.AuditActorSystemTask, constants.AuditSourceWorker)
exportTaskCreated := taskAction(constants.AuditActionExportTaskCreated, "创建业务导出任务", constants.AuditResourceExportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
exportTaskCancelled := taskAction(constants.AuditActionExportTaskCancelled, "取消业务导出任务", constants.AuditResourceExportTask, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
notificationDelivered := notificationAction(constants.AuditActionNotificationDelivered, "生成站内通知", constants.AuditResourceNotification, constants.AuditActorSystemTask, constants.AuditSourceWorker)
notificationRead := notificationAction(constants.AuditActionNotificationRead, "标记通知已读", constants.AuditResourceNotification, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
notificationRead.AllowedOrigins = []ActionOrigin{{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI}}
notificationReadAll := notificationAction(constants.AuditActionNotificationReadAll, "批量标记通知已读", constants.AuditResourceNotificationReadBatch, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
notificationReadAll.AllowedOrigins = []ActionOrigin{{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI}}
notificationCleanup := notificationAction(constants.AuditActionNotificationCleanup, "清理过期通知", constants.AuditResourceNotificationCleanupBatch, constants.AuditActorSystemTask, constants.AuditSourceWorker)
notificationCleanupItem := notificationAction(constants.AuditActionNotificationCleanupItem, "清理单条过期通知", constants.AuditResourceNotification, constants.AuditActorSystemTask, constants.AuditSourceWorker)
pollingConfigCreated := pollingAction(constants.AuditActionPollingConfigCreated, "创建轮询配置", constants.AuditResourcePollingConfig, constants.AuditRiskHigh)
pollingConfigUpdated := pollingAction(constants.AuditActionPollingConfigUpdated, "更新轮询配置", constants.AuditResourcePollingConfig, constants.AuditRiskHigh)
pollingConfigDeleted := pollingAction(constants.AuditActionPollingConfigDeleted, "删除轮询配置", constants.AuditResourcePollingConfig, constants.AuditRiskHigh)
pollingConfigStatusUpdated := pollingAction(constants.AuditActionPollingConfigStatusUpdated, "更新轮询配置状态", constants.AuditResourcePollingConfig, constants.AuditRiskHigh)
pollingConcurrencyUpdated := pollingAction(constants.AuditActionPollingConcurrencyUpdated, "更新轮询并发配置", constants.AuditResourcePollingConcurrencyConfig, constants.AuditRiskNormal)
pollingConcurrencyReset := pollingAction(constants.AuditActionPollingConcurrencyReset, "重置轮询并发计数", constants.AuditResourcePollingConcurrencyConfig, constants.AuditRiskNormal)
pollingAlertRuleCreated := pollingAction(constants.AuditActionPollingAlertRuleCreated, "创建轮询告警规则", constants.AuditResourcePollingAlertRule, constants.AuditRiskNormal)
pollingAlertRuleUpdated := pollingAction(constants.AuditActionPollingAlertRuleUpdated, "更新轮询告警规则", constants.AuditResourcePollingAlertRule, constants.AuditRiskNormal)
pollingAlertRuleDeleted := pollingAction(constants.AuditActionPollingAlertRuleDeleted, "删除轮询告警规则", constants.AuditResourcePollingAlertRule, constants.AuditRiskNormal)
pollingManualTriggerSingle := pollingAction(constants.AuditActionPollingManualTriggerSingle, "单卡手动触发", constants.AuditResourcePollingManualTrigger, constants.AuditRiskNormal)
pollingManualTriggerBatch := pollingAction(constants.AuditActionPollingManualTriggerBatch, "批量手动触发", constants.AuditResourcePollingManualTrigger, constants.AuditRiskNormal)
pollingManualTriggerByCondition := pollingAction(constants.AuditActionPollingManualTriggerByCondition, "条件筛选触发", constants.AuditResourcePollingManualTrigger, constants.AuditRiskNormal)
pollingManualCancelled := pollingAction(constants.AuditActionPollingManualCancelled, "取消手动触发任务", constants.AuditResourcePollingManualTrigger, constants.AuditRiskNormal)
wecomCredentialsRead := ActionDefinition{
Code: constants.AuditActionWeComCredentialsRead, Name: "读取企业微信应用明文凭据",
Category: constants.AuditCategorySecurity, Risk: constants.AuditRiskHigh,
@@ -115,9 +236,121 @@ func NewRegistry() *Registry {
permissionCreated := accessAction(constants.AuditActionPermissionCreated, "创建权限", constants.AuditResourcePermission)
permissionUpdated := accessAction(constants.AuditActionPermissionUpdated, "更新权限", constants.AuditResourcePermission)
permissionDeleted := accessAction(constants.AuditActionPermissionDeleted, "删除权限", constants.AuditResourcePermission)
packageSeriesCreated := packageConfigAction(constants.AuditActionPackageSeriesCreated, "创建套餐系列", constants.AuditResourcePackageSeries, constants.AuditRiskNormal)
packageSeriesUpdated := packageConfigAction(constants.AuditActionPackageSeriesUpdated, "更新套餐系列", constants.AuditResourcePackageSeries, constants.AuditRiskNormal)
packageSeriesDeleted := packageConfigAction(constants.AuditActionPackageSeriesDeleted, "删除套餐系列", constants.AuditResourcePackageSeries, constants.AuditRiskHigh)
packageSeriesStatusUpdated := packageConfigAction(constants.AuditActionPackageSeriesStatusUpdated, "更新套餐系列状态", constants.AuditResourcePackageSeries, constants.AuditRiskNormal)
packageCreated := packageConfigAction(constants.AuditActionPackageCreated, "创建套餐商品", constants.AuditResourcePackage, constants.AuditRiskNormal)
packageUpdated := packageConfigAction(constants.AuditActionPackageUpdated, "更新套餐商品", constants.AuditResourcePackage, constants.AuditRiskNormal)
packageDeleted := packageConfigAction(constants.AuditActionPackageDeleted, "删除套餐商品", constants.AuditResourcePackage, constants.AuditRiskHigh)
packageStatusUpdated := packageConfigAction(constants.AuditActionPackageStatusUpdated, "更新套餐商品状态", constants.AuditResourcePackage, constants.AuditRiskNormal)
packageShelfStatusUpdated := packageConfigAction(constants.AuditActionPackageShelfStatusUpdated, "更新套餐上架状态", constants.AuditResourcePackage, constants.AuditRiskNormal)
shopPackageShelfStatusUpdated := packageConfigAction(constants.AuditActionShopPackageShelfStatusUpdated, "更新店铺套餐上架状态", constants.AuditResourceShopPackageAllocation, constants.AuditRiskNormal)
packageRetailPriceUpdated := packageConfigAction(constants.AuditActionPackageRetailPriceUpdated, "更新店铺套餐零售价", constants.AuditResourceShopPackageAllocation, constants.AuditRiskNormal)
shopSeriesGrantCreated := packageConfigAction(constants.AuditActionShopSeriesGrantCreated, "创建店铺套餐系列授权", constants.AuditResourceShopSeriesAllocation, constants.AuditRiskNormal)
shopSeriesGrantUpdated := packageConfigAction(constants.AuditActionShopSeriesGrantUpdated, "更新店铺套餐系列授权", constants.AuditResourceShopSeriesAllocation, constants.AuditRiskNormal)
shopSeriesGrantPackagesManaged := packageConfigAction(constants.AuditActionShopSeriesGrantPackagesManaged, "管理店铺系列套餐授权", constants.AuditResourceShopSeriesAllocation, constants.AuditRiskNormal)
shopSeriesGrantDeleted := packageConfigAction(constants.AuditActionShopSeriesGrantDeleted, "删除店铺套餐系列授权", constants.AuditResourceShopSeriesAllocation, constants.AuditRiskHigh)
shopPackageBatchAllocated := packageConfigAction(constants.AuditActionShopPackageBatchAllocated, "批量分配店铺套餐", constants.AuditResourcePackageConfigBatch, constants.AuditRiskNormal)
shopPackageAllocated := packageConfigAction(constants.AuditActionShopPackageAllocated, "分配店铺套餐", constants.AuditResourceShopPackageAllocation, constants.AuditRiskNormal)
shopPackageExpiryBaseUpdated := packageConfigAction(constants.AuditActionShopPackageExpiryBaseUpdated, "更新店铺套餐生效条件", constants.AuditResourceShopPackageAllocation, constants.AuditRiskNormal)
shopPackageBatchPricingUpdated := packageConfigAction(constants.AuditActionShopPackageBatchPricingUpdated, "批量更新店铺套餐成本价", constants.AuditResourcePackageConfigBatch, constants.AuditRiskNormal)
shopPackagePricingItemUpdated := packageConfigAction(constants.AuditActionShopPackagePricingItemUpdated, "更新店铺套餐成本价", constants.AuditResourceShopPackageAllocation, constants.AuditRiskNormal)
packageUsageActivated := packageUsageAction(constants.AuditActionPackageUsageActivated, "激活套餐权益")
packageUsageExpired := packageUsageAction(constants.AuditActionPackageUsageExpired, "套餐权益到期")
packageUsageTrafficDeducted := packageUsageAction(constants.AuditActionPackageUsageTrafficDeducted, "扣减套餐权益流量")
packageUsageTrafficReset := packageUsageAction(constants.AuditActionPackageUsageTrafficReset, "重置套餐权益流量")
packageUsageRefundInvalidated := packageUsageAction(constants.AuditActionPackageUsageRefundInvalidated, "退款失效套餐权益")
packageUsageAssetInvalidated := packageUsageAction(constants.AuditActionPackageUsageAssetInvalidated, "资产失效套餐权益")
orderCreated := orderAction(constants.AuditActionOrderCreated, "创建订单")
orderCancelled := orderAction(constants.AuditActionOrderCancelled, "取消订单")
orderWalletPaid := orderAction(constants.AuditActionOrderWalletPaid, "钱包支付订单")
orderExpiredClosed := orderAction(constants.AuditActionOrderExpiredClosed, "关闭过期订单")
orderOnlinePaid := orderAction(constants.AuditActionOrderOnlinePaid, "第三方支付订单")
orderOnlinePaid.AllowedOrigins = append(orderOnlinePaid.AllowedOrigins, ActionOrigin{Actor: constants.AuditActorExternalSystem, Source: constants.AuditSourceCallback})
agentWalletOrderDebited := agentWalletOrderAction(constants.AuditActionAgentWalletOrderDebited, "代理主钱包订单扣款")
agentWalletOrderReserved := agentWalletOrderAction(constants.AuditActionAgentWalletOrderReserved, "代理主钱包订单资金预占")
agentWalletOrderReleased := agentWalletOrderAction(constants.AuditActionAgentWalletOrderReleased, "释放代理主钱包订单预占")
agentWalletOrderCompleted := agentWalletOrderAction(constants.AuditActionAgentWalletOrderCompleted, "完成代理主钱包订单预占扣款")
agentWalletBalanceAdjusted := agentWalletAction(constants.AuditActionAgentWalletBalanceAdjusted, "人工调整代理主钱包余额")
agentWalletCreditChanged := agentWalletAction(constants.AuditActionAgentWalletCreditChanged, "调整代理主钱包信用额度")
paymentCreated := paymentAction(constants.AuditActionPaymentCreated, "创建支付记录", false)
paymentConfirmed := paymentAction(constants.AuditActionPaymentConfirmed, "确认支付成功", true)
paymentFailed := paymentAction(constants.AuditActionPaymentFailed, "关闭失败支付记录", false)
agentRechargeCreated := rechargeAction(constants.AuditActionAgentRechargeCreated, "创建代理充值申请", constants.AuditResourceAgentRecharge)
agentRechargeCredited := rechargeAction(constants.AuditActionAgentRechargeCredited, "代理充值资金入账", constants.AuditResourceAgentRecharge)
agentRechargeClosed := rechargeAction(constants.AuditActionAgentRechargeClosed, "关闭代理充值申请", constants.AuditResourceAgentRecharge)
assetRechargeAutoPurchased := rechargeAction(constants.AuditActionAssetRechargeAutoPurchased, "充值后自动购包", constants.AuditResourceRechargeOrder)
refundCreated := refundAction(constants.AuditActionRefundCreated, "提交退款申请", false)
refundApproved := refundAction(constants.AuditActionRefundApproved, "通过退款审批", true)
refundRejected := refundAction(constants.AuditActionRefundRejected, "拒绝退款审批", true)
refundReturned := refundAction(constants.AuditActionRefundReturned, "退回退款申请", false)
refundResubmitted := refundAction(constants.AuditActionRefundResubmitted, "重新提交退款申请", false)
refundCommissionInvalidated := refundSystemAction(constants.AuditActionRefundCommissionInvalidated, "退款失效佣金")
refundAssetProcessed := refundSystemAction(constants.AuditActionRefundAssetProcessed, "完成退款资产后处理")
approvalRequested := approvalAction(constants.AuditActionApprovalRequested, "提交通用审批申请", []ActionOrigin{
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
})
approvalSubmissionSynced := approvalAction(constants.AuditActionApprovalSubmissionSynced, "同步审批提交结果", []ActionOrigin{
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
})
approvalSubmissionRecovered := approvalAction(constants.AuditActionApprovalSubmissionRecovered, "恢复审批提交结果", []ActionOrigin{
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
{Actor: constants.AuditActorExternalSystem, Source: constants.AuditSourceCallback},
})
approvalDecisionSynced := approvalAction(constants.AuditActionApprovalDecisionSynced, "同步审批权威终态", []ActionOrigin{
{Actor: constants.AuditActorExternalSystem, Source: constants.AuditSourceCallback},
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
})
approvalDecisionSynced.Risk = constants.AuditRiskHigh
commissionCalculated := ActionDefinition{
Code: constants.AuditActionCommissionCalculated, Name: "计算订单佣金",
Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceOrder, AllowedActor: constants.AuditActorSystemTask,
Source: constants.AuditSourceWorker, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
commissionCredited := ActionDefinition{
Code: constants.AuditActionCommissionCredited, Name: "佣金入账",
Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceCommissionRecord, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
AllowedOrigins: []ActionOrigin{
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
},
}
commissionInvalidated := ActionDefinition{
Code: constants.AuditActionCommissionInvalidated, Name: "失效待审佣金",
Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceCommissionRecord, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
withdrawalRequested := commissionWithdrawalAction(constants.AuditActionCommissionWithdrawalRequested, "提交佣金提现申请")
withdrawalApproved := commissionWithdrawalAction(constants.AuditActionCommissionWithdrawalApproved, "通过佣金提现申请")
withdrawalRejected := commissionWithdrawalAction(constants.AuditActionCommissionWithdrawalRejected, "驳回佣金提现申请")
return &Registry{
actionsByOperation: map[string]ActionDefinition{
constants.AuditOperationSystemConfigUpdate: systemConfigUpdated,
constants.AuditOperationPaymentConfigCreate: paymentConfigCreated,
constants.AuditOperationPaymentConfigUpdate: paymentConfigUpdated,
constants.AuditOperationPaymentConfigDelete: paymentConfigDeleted,
constants.AuditOperationPaymentConfigActivate: paymentConfigActivated,
constants.AuditOperationPaymentConfigDeactivate: paymentConfigDeactivated,
constants.AuditOperationCarrierCreate: carrierCreated,
constants.AuditOperationCarrierUpdate: carrierUpdated,
constants.AuditOperationCarrierDelete: carrierDeleted,
constants.AuditOperationCarrierStatusUpdate: carrierStatusUpdated,
constants.AuditOperationWeComApplicationSave: wecomApplicationSaved,
constants.AuditOperationWeComDefaultCreatorSave: wecomDefaultCreatorSaved,
constants.AuditOperationWeComMembersSync: wecomMembersSynced,
constants.AuditOperationWeComApprovalSceneSave: wecomApprovalSceneSaved,
constants.AuditOperationOutboxReplay: outboxReplayed,
constants.AuditOperationOutboxReleaseExpiredLease: outboxExpiredLeaseReleased,
},
@@ -155,11 +388,109 @@ func NewRegistry() *Registry {
constants.AuditActionPersonalCustomerPhoneBound: personalPhoneBound,
constants.AuditActionPersonalCustomerPhoneChanged: personalPhoneChanged,
constants.AuditActionPersonalCustomerWechatIdentityUpdated: personalWechatIdentityUpdated,
constants.AuditActionPersonalCustomerAssetBound: personalAssetBound,
constants.AuditActionPersonalCustomerAssetUnbound: personalAssetUnbound,
constants.AuditActionPersonalCustomerAssetBindingMigrated: personalAssetBindingMigrated,
constants.AuditActionIotCardCreated: iotCardCreated,
constants.AuditActionIotCardDeleted: iotCardDeleted,
constants.AuditActionIotCardBatchDeleted: iotCardBatchDeleted,
constants.AuditActionIotCardAllocationBatch: iotCardAllocationBatch,
constants.AuditActionIotCardAllocated: iotCardAllocated,
constants.AuditActionIotCardRecallBatch: iotCardRecallBatch,
constants.AuditActionIotCardRecalled: iotCardRecalled,
constants.AuditActionIotCardSeriesBindingBatch: iotCardSeriesBindingBatch,
constants.AuditActionIotCardSeriesBound: iotCardSeriesBound,
constants.AuditActionIotCardSpeedTierSet: iotCardSpeedTierSet,
constants.AuditActionIotCardRealnamePolicyBatchUpdated: iotCardRealnamePolicyBatchUpdated,
constants.AuditActionIotCardRealnamePolicyUpdated: iotCardRealnamePolicyUpdated,
constants.AuditActionIotCardRealnameStatusUpdated: iotCardRealnameStatusUpdated,
constants.AuditActionIotCardRealnameCallbackSynced: iotCardRealnameCallbackSynced,
constants.AuditActionIotCardManualRefreshed: iotCardManualRefreshed,
constants.AuditActionIotCardPersonalRefreshed: iotCardPersonalRefreshed,
constants.AuditActionIotCardManualStopped: iotCardManualStopped,
constants.AuditActionIotCardManualStarted: iotCardManualStarted,
constants.AuditActionIotCardAutoStopped: iotCardAutoStopped,
constants.AuditActionIotCardAutoStarted: iotCardAutoStarted,
constants.AuditActionIotCardOpenAPIStarted: iotCardOpenAPIStarted,
constants.AuditActionIotCardAutoStopReasonUpdated: iotCardAutoStopReasonUpdated,
constants.AuditActionDeviceCreated: deviceCreated,
constants.AuditActionDeviceDeleted: deviceDeleted,
constants.AuditActionDeviceAllocationBatch: deviceAllocationBatch,
constants.AuditActionDeviceAllocated: deviceAllocated,
constants.AuditActionDeviceRecallBatch: deviceRecallBatch,
constants.AuditActionDeviceRecalled: deviceRecalled,
constants.AuditActionDeviceSeriesBindingBatch: deviceSeriesBindingBatch,
constants.AuditActionDeviceSeriesBound: deviceSeriesBound,
constants.AuditActionDeviceRealnamePolicyBatchUpdated: deviceRealnamePolicyBatchUpdated,
constants.AuditActionDeviceRealnamePolicyUpdated: deviceRealnamePolicyUpdated,
constants.AuditActionDeviceStopped: deviceStopped,
constants.AuditActionDeviceStarted: deviceStarted,
constants.AuditActionDeviceWiFiSet: deviceWiFiSet,
constants.AuditActionDeviceSwitchModeSet: deviceSwitchModeSet,
constants.AuditActionDeviceRebooted: deviceRebooted,
constants.AuditActionDeviceReset: deviceReset,
constants.AuditActionDeviceCardBound: deviceCardBound,
constants.AuditActionDeviceCardUnbound: deviceCardUnbound,
constants.AuditActionDeviceCurrentCardSwitched: deviceCurrentCardSwitched,
constants.AuditActionCardExchangeCreated: cardExchangeCreated,
constants.AuditActionCardExchangeShippingInfoSubmitted: cardExchangeShippingInfoSubmitted,
constants.AuditActionCardExchangeShipped: cardExchangeShipped,
constants.AuditActionCardExchangeCompleted: cardExchangeCompleted,
constants.AuditActionCardExchangeCancelled: cardExchangeCancelled,
constants.AuditActionCardExchangeRenewed: cardExchangeRenewed,
constants.AuditActionDeviceExchangeCreated: deviceExchangeCreated,
constants.AuditActionDeviceExchangeShippingInfoSubmitted: deviceExchangeShippingInfoSubmitted,
constants.AuditActionDeviceExchangeShipped: deviceExchangeShipped,
constants.AuditActionDeviceExchangeCompleted: deviceExchangeCompleted,
constants.AuditActionDeviceExchangeCancelled: deviceExchangeCancelled,
constants.AuditActionDeviceExchangeRenewed: deviceExchangeRenewed,
constants.AuditActionSystemConfigUpdated: systemConfigUpdated,
constants.AuditActionPaymentConfigCreated: paymentConfigCreated,
constants.AuditActionPaymentConfigUpdated: paymentConfigUpdated,
constants.AuditActionPaymentConfigDeleted: paymentConfigDeleted,
constants.AuditActionPaymentConfigActivated: paymentConfigActivated,
constants.AuditActionPaymentConfigDeactivated: paymentConfigDeactivated,
constants.AuditActionCarrierCreated: carrierCreated,
constants.AuditActionCarrierUpdated: carrierUpdated,
constants.AuditActionCarrierDeleted: carrierDeleted,
constants.AuditActionCarrierStatusUpdated: carrierStatusUpdated,
constants.AuditActionWeComApplicationSaved: wecomApplicationSaved,
constants.AuditActionWeComDefaultCreatorSaved: wecomDefaultCreatorSaved,
constants.AuditActionWeComMembersSynced: wecomMembersSynced,
constants.AuditActionWeComApprovalSceneSaved: wecomApprovalSceneSaved,
constants.AuditActionOutboxReplayed: outboxReplayed,
constants.AuditActionOutboxExpiredLeaseReleased: outboxExpiredLeaseReleased,
constants.AuditActionDeviceBatchAllocationCompleted: deviceBatchCompleted,
constants.AuditActionDeviceBatchAllocationItem: deviceBatchItem,
constants.AuditActionIotCardImportTaskCreated: iotCardImportTaskCreated,
constants.AuditActionIotCardImportTaskCompleted: iotCardImportTaskCompleted,
constants.AuditActionDeviceImportTaskCreated: deviceImportTaskCreated,
constants.AuditActionDeviceImportTaskCompleted: deviceImportTaskCompleted,
constants.AuditActionAssetPackageBatchOrderTaskCreated: assetPackageBatchOrderTaskCreated,
constants.AuditActionAssetPackageBatchOrderTaskCompleted: assetPackageBatchOrderTaskCompleted,
constants.AuditActionOrderPackageInvalidateTaskCreated: orderPackageInvalidateTaskCreated,
constants.AuditActionOrderPackageInvalidateTaskCompleted: orderPackageInvalidateTaskCompleted,
constants.AuditActionOrderPackageInvalidateItem: orderPackageInvalidateItem,
constants.AuditActionExportTaskCreated: exportTaskCreated,
constants.AuditActionExportTaskCancelled: exportTaskCancelled,
constants.AuditActionNotificationDelivered: notificationDelivered,
constants.AuditActionNotificationRead: notificationRead,
constants.AuditActionNotificationReadAll: notificationReadAll,
constants.AuditActionNotificationCleanup: notificationCleanup,
constants.AuditActionNotificationCleanupItem: notificationCleanupItem,
constants.AuditActionPollingConfigCreated: pollingConfigCreated,
constants.AuditActionPollingConfigUpdated: pollingConfigUpdated,
constants.AuditActionPollingConfigDeleted: pollingConfigDeleted,
constants.AuditActionPollingConfigStatusUpdated: pollingConfigStatusUpdated,
constants.AuditActionPollingConcurrencyUpdated: pollingConcurrencyUpdated,
constants.AuditActionPollingConcurrencyReset: pollingConcurrencyReset,
constants.AuditActionPollingAlertRuleCreated: pollingAlertRuleCreated,
constants.AuditActionPollingAlertRuleUpdated: pollingAlertRuleUpdated,
constants.AuditActionPollingAlertRuleDeleted: pollingAlertRuleDeleted,
constants.AuditActionPollingManualTriggerSingle: pollingManualTriggerSingle,
constants.AuditActionPollingManualTriggerBatch: pollingManualTriggerBatch,
constants.AuditActionPollingManualTriggerByCondition: pollingManualTriggerByCondition,
constants.AuditActionPollingManualCancelled: pollingManualCancelled,
constants.AuditActionWeComCredentialsRead: wecomCredentialsRead,
constants.AuditActionRoleCreated: roleCreated,
constants.AuditActionRoleUpdated: roleUpdated,
@@ -172,6 +503,67 @@ func NewRegistry() *Registry {
constants.AuditActionPermissionCreated: permissionCreated,
constants.AuditActionPermissionUpdated: permissionUpdated,
constants.AuditActionPermissionDeleted: permissionDeleted,
constants.AuditActionPackageSeriesCreated: packageSeriesCreated,
constants.AuditActionPackageSeriesUpdated: packageSeriesUpdated,
constants.AuditActionPackageSeriesDeleted: packageSeriesDeleted,
constants.AuditActionPackageSeriesStatusUpdated: packageSeriesStatusUpdated,
constants.AuditActionPackageCreated: packageCreated,
constants.AuditActionPackageUpdated: packageUpdated,
constants.AuditActionPackageDeleted: packageDeleted,
constants.AuditActionPackageStatusUpdated: packageStatusUpdated,
constants.AuditActionPackageShelfStatusUpdated: packageShelfStatusUpdated,
constants.AuditActionShopPackageShelfStatusUpdated: shopPackageShelfStatusUpdated,
constants.AuditActionPackageRetailPriceUpdated: packageRetailPriceUpdated,
constants.AuditActionShopSeriesGrantCreated: shopSeriesGrantCreated,
constants.AuditActionShopSeriesGrantUpdated: shopSeriesGrantUpdated,
constants.AuditActionShopSeriesGrantPackagesManaged: shopSeriesGrantPackagesManaged,
constants.AuditActionShopSeriesGrantDeleted: shopSeriesGrantDeleted,
constants.AuditActionShopPackageBatchAllocated: shopPackageBatchAllocated,
constants.AuditActionShopPackageAllocated: shopPackageAllocated,
constants.AuditActionShopPackageExpiryBaseUpdated: shopPackageExpiryBaseUpdated,
constants.AuditActionShopPackageBatchPricingUpdated: shopPackageBatchPricingUpdated,
constants.AuditActionShopPackagePricingItemUpdated: shopPackagePricingItemUpdated,
constants.AuditActionPackageUsageActivated: packageUsageActivated,
constants.AuditActionPackageUsageExpired: packageUsageExpired,
constants.AuditActionPackageUsageTrafficDeducted: packageUsageTrafficDeducted,
constants.AuditActionPackageUsageTrafficReset: packageUsageTrafficReset,
constants.AuditActionPackageUsageRefundInvalidated: packageUsageRefundInvalidated,
constants.AuditActionPackageUsageAssetInvalidated: packageUsageAssetInvalidated,
constants.AuditActionOrderCreated: orderCreated,
constants.AuditActionOrderCancelled: orderCancelled,
constants.AuditActionOrderWalletPaid: orderWalletPaid,
constants.AuditActionOrderExpiredClosed: orderExpiredClosed,
constants.AuditActionOrderOnlinePaid: orderOnlinePaid,
constants.AuditActionAgentWalletOrderDebited: agentWalletOrderDebited,
constants.AuditActionAgentWalletOrderReserved: agentWalletOrderReserved,
constants.AuditActionAgentWalletOrderReleased: agentWalletOrderReleased,
constants.AuditActionAgentWalletOrderCompleted: agentWalletOrderCompleted,
constants.AuditActionAgentWalletBalanceAdjusted: agentWalletBalanceAdjusted,
constants.AuditActionAgentWalletCreditChanged: agentWalletCreditChanged,
constants.AuditActionPaymentCreated: paymentCreated,
constants.AuditActionPaymentConfirmed: paymentConfirmed,
constants.AuditActionPaymentFailed: paymentFailed,
constants.AuditActionAgentRechargeCreated: agentRechargeCreated,
constants.AuditActionAgentRechargeCredited: agentRechargeCredited,
constants.AuditActionAgentRechargeClosed: agentRechargeClosed,
constants.AuditActionAssetRechargeAutoPurchased: assetRechargeAutoPurchased,
constants.AuditActionRefundCreated: refundCreated,
constants.AuditActionRefundApproved: refundApproved,
constants.AuditActionRefundRejected: refundRejected,
constants.AuditActionRefundReturned: refundReturned,
constants.AuditActionRefundResubmitted: refundResubmitted,
constants.AuditActionRefundCommissionInvalidated: refundCommissionInvalidated,
constants.AuditActionRefundAssetProcessed: refundAssetProcessed,
constants.AuditActionApprovalRequested: approvalRequested,
constants.AuditActionApprovalSubmissionSynced: approvalSubmissionSynced,
constants.AuditActionApprovalSubmissionRecovered: approvalSubmissionRecovered,
constants.AuditActionApprovalDecisionSynced: approvalDecisionSynced,
constants.AuditActionCommissionCalculated: commissionCalculated,
constants.AuditActionCommissionCredited: commissionCredited,
constants.AuditActionCommissionInvalidated: commissionInvalidated,
constants.AuditActionCommissionWithdrawalRequested: withdrawalRequested,
constants.AuditActionCommissionWithdrawalApproved: withdrawalApproved,
constants.AuditActionCommissionWithdrawalRejected: withdrawalRejected,
},
resources: map[string]ResourceDefinition{
constants.AuditResourceAccount: {
@@ -190,6 +582,18 @@ func NewRegistry() *Registry {
Type: constants.AuditResourceSystemConfig, Name: "受控系统配置",
IdentityFields: []string{"config_key", "module"},
},
constants.AuditResourcePaymentConfig: {
Type: constants.AuditResourcePaymentConfig, Name: "支付连接配置",
IdentityFields: []string{"id", "name", "provider_type", "is_active", "credentials_configured"},
},
constants.AuditResourceCarrier: {
Type: constants.AuditResourceCarrier, Name: "运营商配置",
IdentityFields: []string{"id", "carrier_code", "carrier_name", "carrier_type", "status"},
},
constants.AuditResourceWeComApprovalScene: {
Type: constants.AuditResourceWeComApprovalScene, Name: "企业微信审批场景配置",
IdentityFields: []string{"id", "business_type", "application_id", "template_id", "template_name", "status"},
},
constants.AuditResourceOutboxEvent: {
Type: constants.AuditResourceOutboxEvent, Name: "Outbox 事件",
IdentityFields: []string{
@@ -197,13 +601,68 @@ func NewRegistry() *Registry {
"resource_type", "resource_id", "business_key",
},
},
constants.AuditResourceIntegrationLog: {
Type: constants.AuditResourceIntegrationLog, Name: "外部集成日志",
IdentityFields: []string{
"integration_id", "provider", "direction", "operation", "result", "external_id",
"resource_type", "resource_id", "resource_key", "correlation_id",
},
},
constants.AuditResourceDeviceBatchTask: {
Type: constants.AuditResourceDeviceBatchTask, Name: "设备批量分配任务",
IdentityFields: []string{"task_no", "operation_type"},
},
constants.AuditResourceIotCardImportTask: {
Type: constants.AuditResourceIotCardImportTask, Name: "IoT 卡导入任务",
IdentityFields: []string{"id", "task_no", "file_name", "carrier_id", "carrier_name", "batch_no", "card_category", "realname_policy"},
},
constants.AuditResourceDeviceImportTask: {
Type: constants.AuditResourceDeviceImportTask, Name: "设备导入任务",
IdentityFields: []string{"id", "task_no", "file_name", "operation_type", "target_id", "batch_no", "realname_policy"},
},
constants.AuditResourceAssetPackageBatchOrderTask: {
Type: constants.AuditResourceAssetPackageBatchOrderTask, Name: "资产套餐批量订购任务",
IdentityFields: []string{"id", "task_no", "file_name", "package_id", "package_code", "package_name", "payment_method"},
},
constants.AuditResourceOrderPackageInvalidateTask: {
Type: constants.AuditResourceOrderPackageInvalidateTask, Name: "订单套餐批量失效任务",
IdentityFields: []string{"id", "task_no", "file_name"},
},
constants.AuditResourceExportTask: {
Type: constants.AuditResourceExportTask, Name: "业务导出任务",
IdentityFields: []string{"id", "task_no", "scene", "format", "creator_user_id", "creator_user_type", "creator_shop_id", "creator_enterprise_id", "scope_shop_ids"},
},
constants.AuditResourceNotification: {
Type: constants.AuditResourceNotification, Name: "站内通知",
IdentityFields: []string{"id", "event_id", "recipient_kind", "recipient_id", "category", "type", "severity", "ref_type", "ref_id", "ref_key"},
},
constants.AuditResourceNotificationReadBatch: {
Type: constants.AuditResourceNotificationReadBatch, Name: "通知批量已读",
IdentityFields: []string{"recipient_kind", "recipient_id", "category", "updated_count"},
},
constants.AuditResourceNotificationCleanupBatch: {
Type: constants.AuditResourceNotificationCleanupBatch, Name: "通知清理批次",
IdentityFields: []string{"category", "cutoff", "deleted_count", "first_id", "last_id"},
},
constants.AuditResourcePollingConfig: {
Type: constants.AuditResourcePollingConfig, Name: "轮询配置",
IdentityFields: []string{"id", "config_name", "card_condition", "card_category", "carrier_id", "priority", "status"},
},
constants.AuditResourcePollingConcurrencyConfig: {
Type: constants.AuditResourcePollingConcurrencyConfig, Name: "轮询并发配置",
IdentityFields: []string{"id", "task_type", "max_concurrency"},
},
constants.AuditResourcePollingAlertRule: {
Type: constants.AuditResourcePollingAlertRule, Name: "轮询告警规则",
IdentityFields: []string{"id", "rule_name", "task_type", "metric_type", "operator", "threshold", "alert_level", "status"},
},
constants.AuditResourcePollingManualTrigger: {
Type: constants.AuditResourcePollingManualTrigger, Name: "手动轮询任务",
IdentityFields: []string{"id", "task_type", "trigger_type", "total_count", "status", "triggered_by"},
},
constants.AuditResourceDevice: {
Type: constants.AuditResourceDevice, Name: "设备",
IdentityFields: []string{"id", "virtual_no", "imei", "sn", "generation"},
IdentityFields: []string{"id", "virtual_no", "imei", "sn", "device_name", "device_model", "device_type", "manufacturer", "shop_id", "series_id", "generation"},
},
constants.AuditResourceIotCard: {
Type: constants.AuditResourceIotCard, Name: "IoT卡",
@@ -215,11 +674,11 @@ func NewRegistry() *Registry {
},
constants.AuditResourceOrder: {
Type: constants.AuditResourceOrder, Name: "订单",
IdentityFields: []string{"id", "order_no", "buyer_type", "buyer_id", "asset_identifier", "total_amount", "payment_method", "payment_status"},
IdentityFields: []string{"id", "order_no", "order_type", "buyer_type", "buyer_id", "iot_card_id", "device_id", "asset_identifier", "total_amount", "actual_paid_amount", "payment_method", "payment_status", "purchase_role", "source", "operator_account_id", "operator_account_type", "operator_account_name", "seller_shop_id", "expires_at"},
},
constants.AuditResourceRefund: {
Type: constants.AuditResourceRefund, Name: "退款单",
IdentityFields: []string{"id", "refund_no", "order_id", "order_no", "asset_identifier", "shop_id", "requested_refund_amount", "status"},
IdentityFields: []string{"id", "refund_no", "order_id", "order_no", "order_type", "package_usage_id", "asset_identifier", "shop_id", "requested_refund_amount", "actual_received_amount", "refund_reason", "approved_refund_amount", "approval_instance_id", "status", "commission_deducted", "asset_reset"},
},
constants.AuditResourceEnterprise: {
Type: constants.AuditResourceEnterprise, Name: "企业",
@@ -233,21 +692,81 @@ func NewRegistry() *Registry {
Type: constants.AuditResourceAssetAllocationRecord, Name: "资产分配记录",
IdentityFields: []string{"id", "allocation_no", "asset_type", "asset_id", "asset_identifier", "from_owner_type", "from_owner_id", "to_owner_type", "to_owner_id"},
},
constants.AuditResourcePackageSeries: {
Type: constants.AuditResourcePackageSeries, Name: "套餐系列",
IdentityFields: []string{"id", "series_code", "series_name", "status", "enable_one_time_commission"},
},
constants.AuditResourcePackage: {
Type: constants.AuditResourcePackage, Name: "套餐商品",
IdentityFields: []string{"id", "package_code", "package_name", "series_id", "package_type", "duration_months", "duration_days", "price_config_status", "is_gift", "status", "shelf_status"},
},
constants.AuditResourceShopSeriesAllocation: {
Type: constants.AuditResourceShopSeriesAllocation, Name: "店铺套餐系列授权",
IdentityFields: []string{"id", "shop_id", "series_id", "allocator_shop_id", "status"},
},
constants.AuditResourceShopPackageAllocation: {
Type: constants.AuditResourceShopPackageAllocation, Name: "店铺套餐授权",
IdentityFields: []string{"id", "shop_id", "package_id", "allocator_shop_id", "series_allocation_id", "status", "shelf_status", "retail_price_config_status"},
},
constants.AuditResourceShopPackagePriceHistory: {
Type: constants.AuditResourceShopPackagePriceHistory, Name: "店铺套餐价格历史",
IdentityFields: []string{"id", "allocation_id", "changed_by", "effective_from"},
},
constants.AuditResourcePackageConfigBatch: {
Type: constants.AuditResourcePackageConfigBatch, Name: "套餐配置批次",
IdentityFields: []string{"batch_key", "operation", "shop_id", "series_id"},
},
constants.AuditResourceExchangeOrder: {
Type: constants.AuditResourceExchangeOrder, Name: "换货单",
IdentityFields: []string{"id", "exchange_no", "old_asset_type", "old_asset_id", "new_asset_type", "new_asset_id", "shop_id", "status"},
IdentityFields: []string{"id", "exchange_no", "flow_type", "old_asset_type", "old_asset_id", "old_asset_identifier", "new_asset_type", "new_asset_id", "new_asset_identifier", "shop_id", "status"},
},
constants.AuditResourceAgentRecharge: {
Type: constants.AuditResourceAgentRecharge, Name: "代理充值单",
IdentityFields: []string{"id", "recharge_no", "shop_id", "agent_wallet_id", "approval_instance_id", "status"},
IdentityFields: []string{"id", "recharge_no", "user_id", "shop_id", "agent_wallet_id", "amount", "payment_method", "payment_channel", "payment_transaction_id", "approval_instance_id", "status"},
},
constants.AuditResourceRechargeOrder: {
Type: constants.AuditResourceRechargeOrder, Name: "资产充值单",
IdentityFields: []string{"id", "recharge_order_no", "user_id", "asset_wallet_id", "resource_type", "resource_id", "amount", "status"},
},
constants.AuditResourceAssetWallet: {
Type: constants.AuditResourceAssetWallet, Name: "资产钱包",
IdentityFields: []string{"id", "resource_type", "resource_id", "currency"},
IdentityFields: []string{"id", "resource_type", "resource_id", "currency", "shop_id_tag", "enterprise_id_tag"},
},
constants.AuditResourceAssetWalletTransaction: {
Type: constants.AuditResourceAssetWalletTransaction, Name: "资产钱包流水",
IdentityFields: []string{"id", "asset_wallet_id", "resource_type", "resource_id", "transaction_type", "reference_type", "reference_no", "status"},
},
constants.AuditResourceAgentWallet: {
Type: constants.AuditResourceAgentWallet, Name: "代理主钱包",
IdentityFields: []string{"id", "shop_id", "wallet_type", "currency", "status", "credit_enabled", "credit_limit"},
},
constants.AuditResourceAgentWalletTransaction: {
Type: constants.AuditResourceAgentWalletTransaction, Name: "代理主钱包流水",
IdentityFields: []string{"id", "agent_wallet_id", "shop_id", "transaction_type", "transaction_subtype", "reference_type", "reference_id", "status"},
},
constants.AuditResourceAgentWalletReservation: {
Type: constants.AuditResourceAgentWalletReservation, Name: "代理主钱包预占",
IdentityFields: []string{"id", "agent_wallet_id", "shop_id", "amount", "status", "reference_type", "reference_id"},
},
constants.AuditResourcePayment: {
Type: constants.AuditResourcePayment, Name: "支付记录",
IdentityFields: []string{"id", "payment_no", "order_id", "order_type", "payment_method", "amount", "status", "third_party_trade_no", "payment_config_id"},
},
constants.AuditResourcePackageUsage: {
Type: constants.AuditResourcePackageUsage, Name: "套餐权益",
IdentityFields: []string{"id", "order_id", "order_no", "refund_id", "refund_no", "package_id", "package_name", "usage_type", "iot_card_id", "device_id", "data_limit_mb", "data_usage_mb", "activated_at", "expires_at", "status", "pending_realname_activation", "last_reset_at", "next_reset_at", "generation"},
},
constants.AuditResourceApprovalInstance: {
Type: constants.AuditResourceApprovalInstance, Name: "审批实例",
IdentityFields: []string{"id", "business_type", "business_id", "provider", "external_ref", "status"},
IdentityFields: []string{"id", "business_type", "business_id", "submitter_account_id", "provider", "external_ref", "correlation_id", "status"},
},
constants.AuditResourceCommissionRecord: {
Type: constants.AuditResourceCommissionRecord, Name: "佣金记录",
IdentityFields: []string{"id", "shop_id", "order_id", "iot_card_id", "device_id", "commission_source", "amount", "status", "released_at"},
},
constants.AuditResourceCommissionWithdrawal: {
Type: constants.AuditResourceCommissionWithdrawal, Name: "佣金提现单",
IdentityFields: []string{"id", "withdrawal_no", "shop_id", "applicant_id", "amount", "fee", "fee_rate", "actual_amount", "withdrawal_method", "payment_type", "status", "processor_id", "processed_at", "paid_at"},
},
constants.AuditResourceWeComApplication: {
Type: constants.AuditResourceWeComApplication, Name: "企业微信应用配置",
@@ -277,6 +796,22 @@ func NewRegistry() *Registry {
Type: constants.AuditResourcePersonalCustomerOpenID, Name: "个人客户微信主体",
IdentityFields: []string{"id", "customer_id", "app_id", "open_id", "union_id", "app_type"},
},
constants.AuditResourcePersonalCustomerDevice: {
Type: constants.AuditResourcePersonalCustomerDevice, Name: "个人客户设备号绑定",
IdentityFields: []string{"id", "customer_id", "virtual_no", "bind_at", "last_used_at", "status"},
},
constants.AuditResourcePersonalCustomerICCID: {
Type: constants.AuditResourcePersonalCustomerICCID, Name: "个人客户 ICCID 绑定",
IdentityFields: []string{"id", "customer_id", "iccid", "iccid_19", "bind_at", "last_used_at", "status"},
},
constants.AuditResourceIotCardBatch: {
Type: constants.AuditResourceIotCardBatch, Name: "IoT 卡批量操作",
IdentityFields: []string{"request_id", "card_count"},
},
constants.AuditResourceDeviceBatch: {
Type: constants.AuditResourceDeviceBatch, Name: "设备批量操作",
IdentityFields: []string{"request_id", "correlation_id", "device_count", "operation_type"},
},
},
}
}
@@ -352,6 +887,233 @@ func personalAction(code, name string, subjectFields []string) ActionDefinition
}
}
func customerAssetAdminAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourcePersonalCustomer, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
}
}
func packageConfigAction(code, name, primaryResource, risk string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryConfiguration, Risk: risk,
PrimaryResource: primaryResource, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
func packageUsageAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourcePackageUsage, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
AllowedOrigins: []ActionOrigin{
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
},
}
}
func orderAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceOrder, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult, constants.AuditSubjectDetail},
AllowedOrigins: []ActionOrigin{
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI},
{Actor: constants.AuditActorOpenAPI, Source: constants.AuditSourceOpenAPI},
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
},
}
}
func agentWalletOrderAction(code, name string) ActionDefinition {
action := orderAction(code, name)
action.Risk = constants.AuditRiskHigh
return action
}
func agentWalletAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceAgentWallet, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
}
}
func paymentAction(code, name string, confirmation bool) ActionDefinition {
origins := []ActionOrigin{
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI},
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
}
if confirmation {
origins = append(origins, ActionOrigin{Actor: constants.AuditActorExternalSystem, Source: constants.AuditSourceCallback})
}
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourcePayment, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
AllowedOrigins: origins,
}
}
func rechargeAction(code, name, primaryResource string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: primaryResource, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult, constants.AuditSubjectDetail},
AllowedOrigins: []ActionOrigin{
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI},
{Actor: constants.AuditActorExternalSystem, Source: constants.AuditSourceCallback},
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
},
}
}
func refundAction(code, name string, allowWorker bool) ActionDefinition {
origins := []ActionOrigin{{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI}}
if allowWorker {
origins = append(origins, ActionOrigin{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker})
}
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceRefund, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
AllowedOrigins: origins,
}
}
func refundSystemAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceRefund, AllowedActor: constants.AuditActorSystemTask,
Source: constants.AuditSourceWorker, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
}
}
func approvalAction(code, name string, origins []ActionOrigin) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceApprovalInstance, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly}, AllowedOrigins: origins,
}
}
func commissionWithdrawalAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
PrimaryResource: constants.AuditResourceCommissionWithdrawal,
AllowedActor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI,
RequireTransaction: true, DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult, constants.AuditSubjectDetail},
SubjectFields: []string{"amount", "fee", "actual_amount", "withdrawal_method", "payment_type", "status"},
}
}
func iotCardAction(code, name, actorKind, source string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceIotCard, AllowedActor: actorKind, Source: source,
RequireTransaction: true, DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
}
}
func deviceAction(code, name, actorKind, source string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceDevice, AllowedActor: actorKind, Source: source,
RequireTransaction: true, DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
}
}
func deviceMultiOriginAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceDevice, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
AllowedOrigins: []ActionOrigin{
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
},
}
}
func deviceExternalAction(code, name string, allowOpenAPI bool) ActionDefinition {
action := deviceAction(code, name, constants.AuditActorAccount, constants.AuditSourceAdminAPI)
action.AllowedOrigins = []ActionOrigin{{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI}}
if allowOpenAPI {
action.AllowedOrigins = append(action.AllowedOrigins, ActionOrigin{Actor: constants.AuditActorOpenAPI, Source: constants.AuditSourceOpenAPI})
}
return action
}
func cardExchangeAction(code, name, risk string, personal bool) ActionDefinition {
action := ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: risk,
PrimaryResource: constants.AuditResourceExchangeOrder, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectResult,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
}
if personal {
action.AllowedOrigins = []ActionOrigin{{Actor: constants.AuditActorPersonalCustomer, Source: constants.AuditSourcePersonalAPI}}
}
return action
}
func deviceMultiOriginBatchAction(code, name string) ActionDefinition {
action := deviceMultiOriginAction(code, name)
action.PrimaryResource = constants.AuditResourceDeviceBatch
action.DefaultVisibility = constants.AuditSubjectInternalOnly
action.AllowedVisibility = []string{constants.AuditSubjectInternalOnly}
return action
}
func deviceAccountBatchAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceDeviceBatch, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
func iotCardBatchAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: constants.AuditRiskNormal,
PrimaryResource: constants.AuditResourceIotCardBatch, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
func accountLifecycleAction(code, name, risk string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryIdentity, Risk: risk,
@@ -372,6 +1134,34 @@ func deviceBatchAction(code, name, primaryResource string) ActionDefinition {
}
}
func taskAction(code, name, primaryResource, actor, source string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskNormal,
PrimaryResource: primaryResource, AllowedActor: actor, Source: source, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
func notificationAction(code, name, primaryResource, actor, source string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskLow,
PrimaryResource: primaryResource, AllowedActor: actor, Source: source, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
func pollingAction(code, name, primaryResource, risk string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryAsset, Risk: risk,
PrimaryResource: primaryResource, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
func outboxRecoveryAction(code, name string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryReliability, Risk: constants.AuditRiskHigh,
@@ -382,6 +1172,16 @@ func outboxRecoveryAction(code, name string) ActionDefinition {
}
}
func connectionConfigAction(code, name, resourceType, risk string) ActionDefinition {
return ActionDefinition{
Code: code, Name: name, Category: constants.AuditCategoryConfiguration, Risk: risk,
PrimaryResource: resourceType, AllowedActor: constants.AuditActorAccount,
Source: constants.AuditSourceAdminAPI, RequireTransaction: true,
DefaultVisibility: constants.AuditSubjectInternalOnly,
AllowedVisibility: []string{constants.AuditSubjectInternalOnly},
}
}
// Action 返回已注册动作定义。
func (r *Registry) Action(code string) (ActionDefinition, bool) {
if r == nil {

View File

@@ -0,0 +1,91 @@
package audit
import (
"context"
"crypto/sha256"
"fmt"
"strconv"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/pkg/constants"
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)
// TaskInput 描述导入、批购、失效或导出任务的安全审计事实。
type TaskInput struct {
EventID string
ActionCode string
Summary string
TaskID uint
TaskNo string
DisplayName string
Actor ActorInput
Source string
ScopeType string
ScopeID string
ScopeName string
Result string
ErrorCode string
ErrorSummary string
CorrelationID string
ParentEventID string
BatchTotal int
SuccessCount int
FailCount int
IdentitySnapshot map[string]any
BeforeData map[string]any
AfterData map[string]any
Metadata map[string]any
}
// WriteTask 将任务状态与批量统计写入对应的注册任务资源。
func (w *Writer) WriteTask(ctx context.Context, tx *gorm.DB, input TaskInput) error {
if w == nil || w.registry == nil {
return pkgerrors.New(pkgerrors.CodeInvalidStatus, "任务统一审计 Writer 未正确配置")
}
action, ok := w.registry.Action(input.ActionCode)
if !ok {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "任务审计动作未注册")
}
if input.TaskNo == "" {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "任务审计缺少稳定任务编号")
}
var taskID *string
if input.TaskID != 0 {
value := strconv.FormatUint(uint64(input.TaskID), 10)
taskID = &value
}
displayName := input.DisplayName
if displayName == "" {
displayName = input.TaskNo
}
return w.Append(ctx, tx, AppendInput{
EventID: input.EventID, ActionCode: input.ActionCode, Summary: input.Summary,
Actor: input.Actor, Source: input.Source,
ScopeType: input.ScopeType, ScopeID: input.ScopeID, ScopeName: input.ScopeName,
Result: input.Result, ErrorCode: input.ErrorCode, ErrorSummary: input.ErrorSummary,
CorrelationID: input.CorrelationID, ParentEventID: input.ParentEventID,
BatchTotal: input.BatchTotal, SuccessCount: input.SuccessCount, FailCount: input.FailCount,
Metadata: input.Metadata,
Resources: []ResourceInput{{
Type: action.PrimaryResource, ID: taskID, Key: input.TaskNo, DisplayName: displayName,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleBatchTask,
IdentitySnapshot: input.IdentitySnapshot, BeforeData: input.BeforeData, AfterData: input.AfterData,
SubjectVisibility: constants.AuditSubjectInternalOnly,
}},
})
}
// TaskEventID 返回可重试任务阶段的稳定审计事件 ID。
func TaskEventID(resourceType string, taskID uint, phase string) string {
if taskID == 0 || phase == "" {
return ""
}
value := fmt.Sprintf("task:%s:%d:%s", resourceType, taskID, phase)
if len(value) <= 64 {
return value
}
digest := sha256.Sum256([]byte(value))
return fmt.Sprintf("task:%x", digest[:16])
}

View 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
}

View File

@@ -167,7 +167,7 @@ func (w *Writer) WriteAccessChange(ctx context.Context, tx *gorm.DB, change acce
}
func accessResources(change accessauditapp.ChangeAudit, primaryResource string) ([]ResourceInput, error) {
resources := make([]ResourceInput, 0, 2+len(change.Accounts)+len(change.Cards)+len(change.CardAuthorizations)+len(change.Devices)+len(change.DeviceBindings)+len(change.DeviceAuthorizations)+len(change.PersonalPhones)+len(change.PersonalOpenIDs)+len(change.Roles)+len(change.Permissions))
resources := make([]ResourceInput, 0, 2+len(change.Accounts)+len(change.Cards)+len(change.CardAuthorizations)+len(change.Devices)+len(change.DeviceBindings)+len(change.DeviceAuthorizations)+len(change.PersonalPhones)+len(change.PersonalOpenIDs)+len(change.PersonalDevices)+len(change.PersonalICCIDs)+len(change.Roles)+len(change.Permissions))
switch primaryResource {
case constants.AuditResourceAccount:
if change.Account == nil || (change.Account.ID == 0 && change.Account.Username == "") {
@@ -392,6 +392,46 @@ func accessResources(change accessauditapp.ChangeAudit, primaryResource string)
SubjectVisibility: constants.AuditSubjectInternalOnly, SortOrder: index + 1,
})
}
for index, item := range change.PersonalDevices {
if item.Binding == nil || item.Binding.ID == 0 {
return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "个人客户设备绑定审计资源不完整")
}
relation := item.Relation
if relation == "" {
relation = constants.AuditResourceRelationAffected
}
role := item.Role
if role == "" {
role = constants.AuditResourceRolePersonalCustomerAssetBinding
}
resources = append(resources, ResourceInput{
Type: constants.AuditResourcePersonalCustomerDevice, ID: optionalResourceID(item.Binding.ID),
Key: strconv.FormatUint(uint64(item.Binding.ID), 10), DisplayName: item.Binding.VirtualNo,
Relation: relation, Role: role, IdentitySnapshot: personalCustomerDeviceIdentity(item.Binding),
BeforeData: item.BeforeData, AfterData: item.AfterData,
SubjectVisibility: constants.AuditSubjectInternalOnly, SortOrder: index + 1,
})
}
for index, item := range change.PersonalICCIDs {
if item.Binding == nil || item.Binding.ID == 0 {
return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "个人客户 ICCID 绑定审计资源不完整")
}
relation := item.Relation
if relation == "" {
relation = constants.AuditResourceRelationAffected
}
role := item.Role
if role == "" {
role = constants.AuditResourceRolePersonalCustomerAssetBinding
}
resources = append(resources, ResourceInput{
Type: constants.AuditResourcePersonalCustomerICCID, ID: optionalResourceID(item.Binding.ID),
Key: strconv.FormatUint(uint64(item.Binding.ID), 10), DisplayName: item.Binding.ICCID,
Relation: relation, Role: role, IdentitySnapshot: personalCustomerICCIDIdentity(item.Binding),
BeforeData: item.BeforeData, AfterData: item.AfterData,
SubjectVisibility: constants.AuditSubjectInternalOnly, SortOrder: index + 1,
})
}
if primaryResource == constants.AuditResourceRole {
if change.Role == nil || (change.Role.ID == 0 && change.Role.RoleName == "") {
return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "角色审计资源不完整")
@@ -482,6 +522,16 @@ func iotCardIdentity(card *model.IotCard) map[string]any {
}
}
// IotCardIdentitySnapshot 返回统一 Registry 允许的 IoT 卡身份快照。
func IotCardIdentitySnapshot(card *model.IotCard) map[string]any {
return iotCardIdentity(card)
}
// IotCardResourceKey 返回 IoT 卡审计使用的稳定资源 Key。
func IotCardResourceKey(card *model.IotCard) string {
return iotCardResourceKey(card)
}
func deviceResourceKey(device *model.Device) string {
if device.ID != 0 {
return strconv.FormatUint(uint64(device.ID), 10)
@@ -492,10 +542,22 @@ func deviceResourceKey(device *model.Device) string {
func deviceIdentity(device *model.Device) map[string]any {
return map[string]any{
"id": device.ID, "virtual_no": device.VirtualNo, "imei": device.IMEI,
"sn": device.SN, "generation": device.Generation,
"sn": device.SN, "device_name": device.DeviceName, "device_model": device.DeviceModel,
"device_type": device.DeviceType, "manufacturer": device.Manufacturer,
"shop_id": device.ShopID, "series_id": device.SeriesID, "generation": device.Generation,
}
}
// DeviceIdentitySnapshot 返回统一 Registry 允许的设备身份快照。
func DeviceIdentitySnapshot(device *model.Device) map[string]any {
return deviceIdentity(device)
}
// DeviceResourceKey 返回设备审计使用的稳定资源 Key。
func DeviceResourceKey(device *model.Device) string {
return deviceResourceKey(device)
}
func deviceSimBindingIdentity(binding *model.DeviceSimBinding) map[string]any {
return map[string]any{
"id": binding.ID, "device_id": binding.DeviceID, "slot_position": binding.SlotPosition,
@@ -541,6 +603,21 @@ func personalCustomerOpenIDIdentity(openID *model.PersonalCustomerOpenID) map[st
}
}
func personalCustomerDeviceIdentity(binding *model.PersonalCustomerDevice) map[string]any {
return map[string]any{
"id": binding.ID, "customer_id": binding.CustomerID, "virtual_no": binding.VirtualNo,
"bind_at": binding.BindAt, "last_used_at": binding.LastUsedAt, "status": binding.Status,
}
}
func personalCustomerICCIDIdentity(binding *model.PersonalCustomerICCID) map[string]any {
return map[string]any{
"id": binding.ID, "customer_id": binding.CustomerID, "iccid": binding.ICCID,
"iccid_19": binding.ICCID19, "bind_at": binding.BindAt,
"last_used_at": binding.LastUsedAt, "status": binding.Status,
}
}
func roleResource(role *model.Role, beforeData, afterData map[string]any) ResourceInput {
return ResourceInput{
Type: constants.AuditResourceRole, ID: optionalResourceID(role.ID), Key: roleResourceKey(role), DisplayName: role.RoleName,
@@ -706,6 +783,14 @@ func (w *Writer) WriteConfigChange(ctx context.Context, tx *gorm.DB, change syst
if result == "" {
result = constants.AuditResultSuccess
}
displayName := change.DisplayName
if displayName == "" {
displayName = change.ConfigKey
}
identity := change.Identity
if identity == nil {
identity = map[string]any{"config_key": change.ConfigKey, "module": change.Module}
}
return w.Append(ctx, tx, AppendInput{
ActionCode: action.Code, Summary: change.Description,
Actor: ActorInput{
@@ -719,9 +804,9 @@ func (w *Writer) WriteConfigChange(ctx context.Context, tx *gorm.DB, change syst
ErrorCode: change.ErrorCode, ErrorSummary: change.ErrorSummary,
RequestID: change.RequestID, CorrelationID: change.CorrelationID,
Resources: []ResourceInput{{
Type: action.PrimaryResource, Key: change.ConfigKey, DisplayName: change.ConfigKey,
Type: action.PrimaryResource, ID: change.ResourceID, Key: change.ConfigKey, DisplayName: displayName,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleConfig,
IdentitySnapshot: map[string]any{"config_key": change.ConfigKey, "module": change.Module},
IdentitySnapshot: identity,
BeforeData: change.BeforeData, AfterData: change.AfterData,
SubjectVisibility: action.DefaultVisibility,
}},
@@ -795,7 +880,7 @@ func (w *Writer) Append(ctx context.Context, tx *gorm.DB, input AppendInput) err
if !ok {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "审计动作未注册")
}
if input.Actor.Kind != action.AllowedActor || input.Actor.ID == "" || input.Source != action.Source {
if !actionAllowsOrigin(action, input.Actor.Kind, input.Source) || input.Actor.ID == "" {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "审计操作者或入口不符合动作注册规则")
}
if !validResult(input.Result) || len(input.Resources) == 0 {
@@ -853,6 +938,18 @@ func (w *Writer) Append(ctx context.Context, tx *gorm.DB, input AppendInput) err
return nil
}
func actionAllowsOrigin(action ActionDefinition, actor, source string) bool {
if action.AllowedActor == actor && action.Source == source {
return true
}
for _, origin := range action.AllowedOrigins {
if origin.Actor == actor && origin.Source == source {
return true
}
}
return false
}
func fillFromContext(ctx context.Context, input AppendInput) AppendInput {
value := auditcontext.From(ctx)
if input.Actor.Kind == "" {