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

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

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"
@@ -14,9 +15,12 @@ import (
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
"github.com/break/junhong_cmp_fiber/pkg/constants"
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/storage"
"github.com/break/junhong_cmp_fiber/pkg/utils"
"github.com/break/junhong_cmp_fiber/pkg/validator"
@@ -41,14 +45,15 @@ type PollingCallback interface {
}
type IotCardImportHandler struct {
db *gorm.DB
redis *redis.Client
importTaskStore *postgres.IotCardImportTaskStore
iotCardStore *postgres.IotCardStore
db *gorm.DB
redis *redis.Client
importTaskStore *postgres.IotCardImportTaskStore
iotCardStore *postgres.IotCardStore
assetWalletStore *postgres.AssetWalletStore
storageService *storage.Service
pollingCallback PollingCallback
logger *zap.Logger
storageService *storage.Service
pollingCallback PollingCallback
auditWriter *audit.Writer
logger *zap.Logger
}
func NewIotCardImportHandler(
@@ -59,6 +64,7 @@ func NewIotCardImportHandler(
assetWalletStore *postgres.AssetWalletStore,
storageSvc *storage.Service,
pollingCallback PollingCallback,
auditWriter *audit.Writer,
logger *zap.Logger,
) *IotCardImportHandler {
return &IotCardImportHandler{
@@ -69,6 +75,7 @@ func NewIotCardImportHandler(
assetWalletStore: assetWalletStore,
storageService: storageSvc,
pollingCallback: pollingCallback,
auditWriter: auditWriter,
logger: logger,
}
}
@@ -91,6 +98,12 @@ func (h *IotCardImportHandler) HandleIotCardImport(ctx context.Context, task *as
)
return asynq.SkipRetry
}
ctx = auditcontext.With(ctx, auditcontext.Context{
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeIotCardImport,
ActorName: "IoT 卡导入任务", Source: constants.AuditSourceWorker,
CorrelationID: importTask.TaskNo,
ParentEventID: audit.TaskEventID(constants.AuditResourceIotCardImportTask, importTask.ID, "completed"),
})
switch importTask.Status {
case model.ImportTaskStatusPending:
@@ -125,7 +138,9 @@ func (h *IotCardImportHandler) HandleIotCardImport(ctx context.Context, task *as
zap.Uint("task_id", importTask.ID),
zap.Error(err),
)
h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusFailed, err.Error())
if finishErr := h.finishImportTask(ctx, importTask, 0, 0, 1, model.ImportTaskStatusFailed, err.Error()); finishErr != nil {
return finishErr
}
return asynq.SkipRetry
}
@@ -138,12 +153,12 @@ func (h *IotCardImportHandler) HandleIotCardImport(ctx context.Context, task *as
result.failedItems = append(parseFailures, result.failedItems...)
result.failCount += len(parseFailures)
h.importTaskStore.UpdateResult(ctx, importTask.ID, result.successCount, result.skipCount, result.failCount, result.skippedItems, result.failedItems)
status, errorMessage := model.ImportTaskStatusCompleted, ""
if result.failCount > 0 && result.successCount == 0 {
h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusFailed, "所有导入均失败")
} else {
h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusCompleted, "")
status, errorMessage = model.ImportTaskStatusFailed, "所有导入均失败"
}
if err := h.finishImportTask(ctx, importTask, result.successCount, result.skipCount, result.failCount, status, errorMessage, result.skippedItems, result.failedItems); err != nil {
return err
}
h.logger.Info("IoT 卡导入任务完成",
@@ -434,7 +449,10 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
})
}
}
return tx.CreateInBatches(&identifiers, 500).Error
if err := tx.CreateInBatches(&identifiers, 500).Error; err != nil {
return err
}
return h.appendCardCreateAudits(ctx, tx, iotCards)
})
if txErr != nil {
@@ -463,6 +481,91 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
}
}
func (h *IotCardImportHandler) appendCardCreateAudits(ctx context.Context, tx *gorm.DB, cards []*model.IotCard) error {
if h.auditWriter == nil {
return pkgerrors.New(pkgerrors.CodeInvalidStatus, "IoT 卡导入统一审计接缝未配置")
}
for _, card := range cards {
if card == nil || card.ID == 0 {
return pkgerrors.New(pkgerrors.CodeInvalidStatus, "IoT 卡导入审计资源不完整")
}
resourceID := strconv.FormatUint(uint64(card.ID), 10)
if err := h.auditWriter.Append(ctx, tx, audit.AppendInput{
EventID: audit.TaskEventID(constants.AuditResourceIotCardImportTask, card.ID, "item"),
ActionCode: constants.AuditActionIotCardCreated, Summary: "导入创建 IoT 卡",
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
Resources: []audit.ResourceInput{{
Type: constants.AuditResourceIotCard, ID: &resourceID,
Key: audit.IotCardResourceKey(card), DisplayName: card.ICCID,
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleIotCardTarget,
IdentitySnapshot: audit.IotCardIdentitySnapshot(card), AfterData: map[string]any{"created": true},
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "IoT 卡已导入",
}},
}); err != nil {
return err
}
}
return nil
}
func (h *IotCardImportHandler) finishImportTask(ctx context.Context, task *model.IotCardImportTask, successCount, skipCount, failCount, status int, errorMessage string, items ...model.ImportResultItems) error {
if h.auditWriter == nil {
return pkgerrors.New(pkgerrors.CodeInvalidStatus, "IoT 卡导入任务统一审计接缝未配置")
}
return h.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
now := time.Now()
updates := map[string]any{
"status": status, "success_count": successCount, "skip_count": skipCount, "fail_count": failCount,
"error_message": errorMessage, "completed_at": now, "updated_at": now,
}
if len(items) > 0 {
updates["skipped_items"] = items[0]
}
if len(items) > 1 {
updates["failed_items"] = items[1]
}
if err := tx.WithContext(ctx).Model(&model.IotCardImportTask{}).Where("id = ?", task.ID).Updates(updates).Error; err != nil {
return err
}
rootID := audit.TaskEventID(constants.AuditResourceIotCardImportTask, task.ID, "completed")
var childCount int64
if err := tx.WithContext(ctx).Model(&model.AuditEvent{}).
Where("parent_event_id = ? AND action_code = ?", rootID, constants.AuditActionIotCardCreated).
Count(&childCount).Error; err != nil {
return err
}
result := batchAuditResult(int(childCount), failCount)
return h.auditWriter.WriteTask(ctx, tx, audit.TaskInput{
EventID: rootID, ActionCode: constants.AuditActionIotCardImportTaskCompleted,
Summary: "完成 IoT 卡导入任务", TaskID: task.ID, TaskNo: task.TaskNo,
Result: result, CorrelationID: task.TaskNo,
ParentEventID: audit.TaskEventID(constants.AuditResourceIotCardImportTask, task.ID, "created"),
BatchTotal: task.TotalCount, SuccessCount: int(childCount), FailCount: failCount,
IdentitySnapshot: map[string]any{
"id": task.ID, "task_no": task.TaskNo, "file_name": task.FileName,
"carrier_id": task.CarrierID, "carrier_name": task.CarrierName, "batch_no": task.BatchNo,
"card_category": task.CardCategory, "realname_policy": task.RealnamePolicy,
},
BeforeData: map[string]any{"status": model.ImportTaskStatusProcessing},
AfterData: map[string]any{
"status": status, "total_count": task.TotalCount, "success_count": successCount,
"skip_count": skipCount, "fail_count": failCount,
},
Metadata: map[string]any{"skip_count": skipCount},
})
})
}
func batchAuditResult(successCount, failCount int) string {
if successCount > 0 && failCount > 0 {
return constants.AuditResultPartial
}
if successCount == 0 && failCount > 0 {
return constants.AuditResultFailed
}
return constants.AuditResultSuccess
}
// batchCreateWallets 批量为 IoT 卡创建资产钱包
func (h *IotCardImportHandler) batchCreateWallets(ctx context.Context, cards []*model.IotCard) {
if h.assetWalletStore == nil {