收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -14,10 +14,14 @@ import (
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"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/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/asynctask"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
@@ -41,11 +45,16 @@ type AssetPackageBatchOrderHandler struct {
|
||||
orderCreator AssetPackageBatchOrderCreator
|
||||
storageService *storage.Service
|
||||
logger *zap.Logger
|
||||
auditWriter *audit.Writer
|
||||
}
|
||||
|
||||
// NewAssetPackageBatchOrderHandler 创建资产套餐批量订购任务处理器。
|
||||
func NewAssetPackageBatchOrderHandler(taskStore *postgres.AssetPackageBatchOrderTaskStore, shopStore *postgres.ShopStore, orderCreator AssetPackageBatchOrderCreator, storageService *storage.Service, logger *zap.Logger) *AssetPackageBatchOrderHandler {
|
||||
return &AssetPackageBatchOrderHandler{taskStore: taskStore, shopStore: shopStore, orderCreator: orderCreator, storageService: storageService, logger: logger}
|
||||
func NewAssetPackageBatchOrderHandler(taskStore *postgres.AssetPackageBatchOrderTaskStore, shopStore *postgres.ShopStore, orderCreator AssetPackageBatchOrderCreator, storageService *storage.Service, logger *zap.Logger, auditWriters ...*audit.Writer) *AssetPackageBatchOrderHandler {
|
||||
handler := &AssetPackageBatchOrderHandler{taskStore: taskStore, shopStore: shopStore, orderCreator: orderCreator, storageService: storageService, logger: logger}
|
||||
if len(auditWriters) > 0 {
|
||||
handler.auditWriter = auditWriters[0]
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// Handle 处理资产套餐批量订购任务。
|
||||
@@ -60,6 +69,12 @@ func (h *AssetPackageBatchOrderHandler) Handle(ctx context.Context, taskMessage
|
||||
h.logger.Error("查询资产套餐批量订购任务失败", zap.Uint("task_id", payload.TaskID), zap.Error(err))
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
rootEventID := audit.TaskEventID(constants.AuditResourceAssetPackageBatchOrderTask, taskRecord.ID, "completed")
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeAssetPackageBatchOrder,
|
||||
ActorName: "资产套餐批量订购任务", Source: constants.AuditSourceWorker,
|
||||
CorrelationID: taskRecord.TaskNo, ParentEventID: rootEventID,
|
||||
})
|
||||
claimed, err := h.taskStore.Claim(ctx, taskRecord.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -71,17 +86,68 @@ func (h *AssetPackageBatchOrderHandler) Handle(ctx context.Context, taskMessage
|
||||
rows, err := h.downloadAndParse(ctx, taskRecord.StorageKey)
|
||||
if err != nil {
|
||||
h.logger.Error("下载或解析批量订购CSV失败", zap.Uint("task_id", taskRecord.ID), zap.Error(err))
|
||||
_ = h.taskStore.MarkFailed(ctx, taskRecord.ID, err.Error())
|
||||
if finishErr := h.finishBatchOrderTask(ctx, taskRecord, nil, 0, 1, asynctask.StatusFailed, err.Error()); finishErr != nil {
|
||||
h.resetBatchOrderTaskForRetry(ctx, taskRecord.ID)
|
||||
return finishErr
|
||||
}
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
items, successCount, failCount := h.processRows(ctx, taskRecord, rows)
|
||||
if err := h.taskStore.Complete(ctx, taskRecord.ID, items, successCount, failCount); err != nil {
|
||||
if err := h.finishBatchOrderTask(ctx, taskRecord, items, successCount, failCount, asynctask.StatusCompleted, ""); err != nil {
|
||||
h.resetBatchOrderTaskForRetry(ctx, taskRecord.ID)
|
||||
return err
|
||||
}
|
||||
h.logger.Info("资产套餐批量订购任务完成", zap.Uint("task_id", taskRecord.ID), zap.Int("success", successCount), zap.Int("fail", failCount))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *AssetPackageBatchOrderHandler) resetBatchOrderTaskForRetry(ctx context.Context, taskID uint) {
|
||||
_ = h.taskStore.DB().WithContext(ctx).Model(&model.AssetPackageBatchOrderTask{}).
|
||||
Where("id = ? AND status = ?", taskID, asynctask.StatusProcessing).
|
||||
Updates(map[string]any{"status": asynctask.StatusPending, "started_at": nil}).Error
|
||||
}
|
||||
|
||||
func (h *AssetPackageBatchOrderHandler) finishBatchOrderTask(ctx context.Context, taskRecord *model.AssetPackageBatchOrderTask, items model.AssetPackageBatchOrderResultItems, successCount, failCount, status int, errorMessage string) error {
|
||||
if h.auditWriter == nil {
|
||||
return apperrors.New(apperrors.CodeInvalidStatus, "资产套餐批量订购统一审计接缝未配置")
|
||||
}
|
||||
return h.taskStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
txStore := h.taskStore.WithTx(tx)
|
||||
if status == asynctask.StatusFailed {
|
||||
if err := txStore.MarkFailed(ctx, taskRecord.ID, errorMessage); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := txStore.Complete(ctx, taskRecord.ID, items, successCount, failCount); err != nil {
|
||||
return err
|
||||
}
|
||||
rootID := audit.TaskEventID(constants.AuditResourceAssetPackageBatchOrderTask, taskRecord.ID, "completed")
|
||||
var childCount int64
|
||||
if err := tx.WithContext(ctx).Model(&model.AuditEvent{}).
|
||||
Where("parent_event_id = ? AND action_code = ?", rootID, constants.AuditActionOrderCreated).
|
||||
Count(&childCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
result := batchAuditResult(int(childCount), failCount)
|
||||
return h.auditWriter.WriteTask(ctx, tx, audit.TaskInput{
|
||||
EventID: rootID, ActionCode: constants.AuditActionAssetPackageBatchOrderTaskCompleted,
|
||||
Summary: "完成资产套餐批量订购任务", TaskID: taskRecord.ID, TaskNo: taskRecord.TaskNo,
|
||||
Result: result, CorrelationID: taskRecord.TaskNo,
|
||||
ParentEventID: audit.TaskEventID(constants.AuditResourceAssetPackageBatchOrderTask, taskRecord.ID, "created"),
|
||||
BatchTotal: len(items), SuccessCount: int(childCount), FailCount: failCount,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": taskRecord.ID, "task_no": taskRecord.TaskNo, "file_name": taskRecord.FileName,
|
||||
"package_id": taskRecord.PackageID, "package_code": taskRecord.PackageCode,
|
||||
"package_name": taskRecord.PackageName, "payment_method": taskRecord.PaymentMethod,
|
||||
},
|
||||
BeforeData: map[string]any{"status": asynctask.StatusProcessing},
|
||||
AfterData: map[string]any{
|
||||
"status": status, "total_count": len(items), "success_count": successCount, "fail_count": failCount,
|
||||
},
|
||||
Metadata: map[string]any{"task_success_count": successCount, "task_fail_count": failCount},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type assetPackageBatchOrderRow struct {
|
||||
Line int
|
||||
Identifier string
|
||||
@@ -150,6 +216,12 @@ func (h *AssetPackageBatchOrderHandler) processRows(ctx context.Context, taskRec
|
||||
Username: taskRecord.CreatorName, ShopID: taskRecord.CreatorShopID,
|
||||
SubordinateShopIDs: subordinateShopIDs,
|
||||
})
|
||||
workerCtx = auditcontext.With(workerCtx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeAssetPackageBatchOrder,
|
||||
ActorName: "资产套餐批量订购任务", Source: constants.AuditSourceWorker,
|
||||
CorrelationID: taskRecord.TaskNo,
|
||||
ParentEventID: audit.TaskEventID(constants.AuditResourceAssetPackageBatchOrderTask, taskRecord.ID, "completed"),
|
||||
})
|
||||
buyerType, buyerID := "", uint(0)
|
||||
if taskRecord.CreatorUserType == constants.UserTypeAgent {
|
||||
buyerType, buyerID = model.BuyerTypeAgent, taskRecord.CreatorShopID
|
||||
|
||||
Reference in New Issue
Block a user