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

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

@@ -2,11 +2,15 @@ package iot_card_import
import (
"context"
"strconv"
"gorm.io/gorm"
infraAudit "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
// AssetAuditService 资产审计服务接口。
@@ -14,46 +18,44 @@ type AssetAuditService interface {
LogOperation(ctx context.Context, log *model.AssetOperationLog)
}
func (s *Service) logIotCardImportAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
if s == nil || s.assetAudit == nil {
return
}
if p.Operator.Type == "" {
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
}
if p.OperationType == "" {
p.OperationType = constants.AssetAuditOpIotCardImportTaskCreate
}
if p.AssetType == "" {
p.AssetType = constants.AssetTypeIotCard
}
p.BeforeData, p.AfterData = assetAuditSvc.WrapOperationContent(p.BeforeData, p.AfterData, nil)
s.assetAudit.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
func (s *Service) writeImportTaskAudit(ctx context.Context, tx *gorm.DB, task *model.IotCardImportTask, before, after map[string]any, result, phase, errorCode, errorSummary string) error {
return s.auditWriter.WriteTask(ctx, tx, infraAudit.TaskInput{
EventID: infraAudit.TaskEventID(constants.AuditResourceIotCardImportTask, task.ID, phase),
ActionCode: constants.AuditActionIotCardImportTaskCreated, Summary: "创建 IoT 卡导入任务",
TaskID: task.ID, TaskNo: task.TaskNo,
Actor: infraAudit.ActorInput{
Kind: constants.AuditActorAccount, ID: strconv.FormatUint(uint64(middleware.GetUserIDFromContext(ctx)), 10),
Name: middleware.GetUsernameFromContext(ctx),
},
Source: constants.AuditSourceAdminAPI, ScopeType: constants.AuditScopePlatform,
Result: result, ErrorCode: errorCode, ErrorSummary: errorSummary,
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: before, AfterData: after,
})
}
func newIotCardImportAuditParams(
taskID uint,
taskNo string,
req *dto.ImportIotCardRequest,
resultStatus string,
err error,
) assetAuditSvc.BuildLogParams {
afterData := map[string]any{}
if req != nil {
afterData["carrier_id"] = req.CarrierID
afterData["batch_no"] = req.BatchNo
afterData["file_key"] = req.FileKey
afterData["card_category"] = req.CardCategory
afterData["realname_policy"] = req.RealnamePolicy
func (s *Service) recordImportTaskAudit(ctx context.Context, task *model.IotCardImportTask, before, after map[string]any, result, phase string, errorCode int, summary string) {
if s == nil || s.db == nil || s.auditWriter == nil || task == nil || task.TaskNo == "" {
return
}
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
return assetAuditSvc.BuildLogParams{
AssetID: taskID,
AssetIdentifier: taskNo,
OperationDesc: "创建IoT卡导入任务",
ResultStatus: resultStatus,
ErrorCode: errorCode,
ErrorMsg: errorMsg,
AfterData: afterData,
code := strconv.Itoa(errorCode)
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
return s.writeImportTaskAudit(ctx, tx, task, before, after, result, phase, code, summary)
}); err != nil {
auditfailure.RecordSecondaryWriteFailure(constants.AuditActionIotCardImportTaskCreated, task.TaskNo, "", task.TaskNo, code, err)
}
}
func importTaskState(task *model.IotCardImportTask) map[string]any {
if task == nil {
return nil
}
return map[string]any{
"status": task.Status, "total_count": task.TotalCount, "success_count": task.SuccessCount,
"skip_count": task.SkipCount, "fail_count": task.FailCount,
}
}

View File

@@ -3,8 +3,10 @@ package iot_card_import
import (
"context"
"path/filepath"
"strconv"
"time"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store"
@@ -23,6 +25,7 @@ type Service struct {
carrierStore carrierGetter
queueClient *queue.Client
assetAudit AssetAuditService
auditWriter *audit.Writer
}
type carrierGetter interface {
@@ -50,14 +53,19 @@ func New(
importTaskStore *postgres.IotCardImportTaskStore,
queueClient *queue.Client,
assetAudit AssetAuditService,
auditWriters ...*audit.Writer,
) *Service {
return &Service{
service := &Service{
db: db,
importTaskStore: importTaskStore,
carrierStore: NewCarrierStore(db),
queueClient: queueClient,
assetAudit: assetAudit,
}
if len(auditWriters) > 0 {
service.auditWriter = auditWriters[0]
}
return service
}
type IotCardImportPayload struct {
@@ -67,16 +75,12 @@ type IotCardImportPayload struct {
func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportIotCardRequest) (*dto.ImportIotCardResponse, error) {
userID := middleware.GetUserIDFromContext(ctx)
if userID == 0 {
appErr := errors.New(errors.CodeUnauthorized, "未授权访问")
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(0, "", req, constants.AssetAuditResultDenied, appErr))
return nil, appErr
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
carrier, err := s.carrierStore.GetByID(ctx, req.CarrierID)
if err != nil {
appErr := errors.New(errors.CodeInvalidParam, "运营商不存在")
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(0, "", req, constants.AssetAuditResultDenied, appErr))
return nil, appErr
return nil, errors.New(errors.CodeInvalidParam, "运营商不存在")
}
taskNo := s.importTaskStore.GenerateTaskNo(ctx)
@@ -103,9 +107,17 @@ func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportIotCardRe
task.Creator = userID
task.Updater = userID
if err := s.importTaskStore.Create(ctx, task); err != nil {
if s.auditWriter == nil {
return nil, errors.New(errors.CodeInvalidStatus, "IoT 卡导入任务统一审计接缝未配置")
}
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.WithContext(ctx).Create(task).Error; err != nil {
return err
}
return s.writeImportTaskAudit(ctx, tx, task, nil, importTaskState(task), constants.AuditResultSuccess, "created", "", "")
}); err != nil {
appErr := errors.Wrap(errors.CodeInternalError, err, "创建导入任务失败")
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(0, taskNo, req, constants.AssetAuditResultFailed, appErr))
s.recordImportTaskAudit(ctx, task, nil, importTaskState(task), constants.AuditResultFailed, "create_failed", errors.CodeDatabaseError, "创建 IoT 卡导入任务失败")
return nil, appErr
}
@@ -117,14 +129,23 @@ func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportIotCardRe
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeIotCardImport)),
)
if err != nil {
s.importTaskStore.UpdateStatus(ctx, task.ID, model.ImportTaskStatusFailed, "任务入队失败: "+err.Error())
secondaryErr := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
before := importTaskState(task)
if updateErr := tx.WithContext(ctx).Model(&model.IotCardImportTask{}).Where("id = ?", task.ID).Updates(map[string]any{
"status": model.ImportTaskStatusFailed, "error_message": "任务入队失败", "completed_at": time.Now(), "updated_at": time.Now(),
}).Error; updateErr != nil {
return updateErr
}
task.Status, task.ErrorMessage = model.ImportTaskStatusFailed, "任务入队失败"
return s.writeImportTaskAudit(ctx, tx, task, before, importTaskState(task), constants.AuditResultFailed, "enqueue_failed", strconv.Itoa(errors.CodeTaskQueueError), "IoT 卡导入任务入队失败")
})
if secondaryErr != nil {
s.recordImportTaskAudit(ctx, task, nil, importTaskState(task), constants.AuditResultFailed, "enqueue_audit_failed", errors.CodeTaskQueueError, "IoT 卡导入任务入队失败")
}
appErr := errors.Wrap(errors.CodeInternalError, err, "任务入队失败")
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(task.ID, taskNo, req, constants.AssetAuditResultFailed, appErr))
return nil, appErr
}
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(task.ID, taskNo, req, constants.AssetAuditResultSuccess, nil))
return &dto.ImportIotCardResponse{
TaskID: task.ID,
TaskNo: taskNo,