收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -2,6 +2,8 @@ package export_task
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
@@ -10,10 +12,12 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/exporter"
|
||||
"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"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
@@ -28,6 +32,7 @@ type Service struct {
|
||||
queueClient *queue.Client
|
||||
storageSvc *storage.Service
|
||||
sceneRegistry *exporter.Registry
|
||||
auditWriter *audit.Writer
|
||||
}
|
||||
|
||||
type dispatchPayload struct {
|
||||
@@ -35,14 +40,18 @@ type dispatchPayload struct {
|
||||
}
|
||||
|
||||
// New 创建导出任务服务。
|
||||
func New(db *gorm.DB, taskStore *postgres.ExportTaskStore, queueClient *queue.Client, storageSvc *storage.Service) *Service {
|
||||
return &Service{
|
||||
func New(db *gorm.DB, taskStore *postgres.ExportTaskStore, queueClient *queue.Client, storageSvc *storage.Service, auditWriters ...*audit.Writer) *Service {
|
||||
service := &Service{
|
||||
db: db,
|
||||
taskStore: taskStore,
|
||||
queueClient: queueClient,
|
||||
storageSvc: storageSvc,
|
||||
sceneRegistry: exporter.NewDefaultRegistry(db),
|
||||
}
|
||||
if len(auditWriters) > 0 {
|
||||
service.auditWriter = auditWriters[0]
|
||||
}
|
||||
return service
|
||||
}
|
||||
|
||||
// CreateTask 创建导出任务并入队 dispatch。
|
||||
@@ -118,7 +127,16 @@ func (s *Service) CreateTask(ctx context.Context, req *dto.CreateExportTaskReque
|
||||
task.Creator = userID
|
||||
task.Updater = userID
|
||||
|
||||
if err := s.taskStore.Create(ctx, task); err != nil {
|
||||
if s.auditWriter == nil {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "导出任务统一审计接缝未配置")
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.taskStore.WithTx(tx).Create(ctx, task); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.writeTaskAudit(ctx, tx, constants.AuditActionExportTaskCreated, "创建业务导出任务", task, nil, exportTaskState(task), constants.AuditResultSuccess, "created", "", "")
|
||||
}); err != nil {
|
||||
s.recordTaskAudit(ctx, constants.AuditActionExportTaskCreated, "创建业务导出任务失败", task, nil, exportTaskState(task), constants.AuditResultFailed, "create_failed", errors.CodeDatabaseError)
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建导出任务失败")
|
||||
}
|
||||
|
||||
@@ -130,7 +148,18 @@ func (s *Service) CreateTask(ctx context.Context, req *dto.CreateExportTaskReque
|
||||
asynq.Timeout(constants.ExportDispatchTaskTimeout),
|
||||
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeExportDispatch)),
|
||||
); err != nil {
|
||||
_ = s.taskStore.MarkFailed(ctx, task.ID, userID, "导出任务入队失败")
|
||||
secondaryErr := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.taskStore.WithTx(tx).MarkFailed(ctx, task.ID, userID, "导出任务入队失败"); err != nil {
|
||||
return err
|
||||
}
|
||||
before := exportTaskState(task)
|
||||
task.Status = constants.ExportTaskStatusFailed
|
||||
task.ErrorMessage = "导出任务入队失败"
|
||||
return s.writeTaskAudit(ctx, tx, constants.AuditActionExportTaskCreated, "导出任务入队失败", task, before, exportTaskState(task), constants.AuditResultFailed, "enqueue_failed", strconv.Itoa(errors.CodeTaskQueueError), "导出任务入队失败")
|
||||
})
|
||||
if secondaryErr != nil {
|
||||
auditfailure.RecordSecondaryWriteFailure(constants.AuditActionExportTaskCreated, task.TaskNo, "", task.TaskNo, strconv.Itoa(errors.CodeTaskQueueError), secondaryErr)
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeTaskQueueError, err, "导出任务入队失败")
|
||||
}
|
||||
|
||||
@@ -233,45 +262,68 @@ func (s *Service) CancelTask(ctx context.Context, id uint) (*dto.CancelExportTas
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询导出任务失败")
|
||||
}
|
||||
|
||||
if s.auditWriter == nil {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "导出任务统一审计接缝未配置")
|
||||
}
|
||||
message := "取消请求已提交"
|
||||
switch task.Status {
|
||||
case constants.ExportTaskStatusPending:
|
||||
ok, err := s.taskStore.CancelPendingTask(ctx, id, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "取消导出任务失败")
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
}
|
||||
message = "任务已取消"
|
||||
case constants.ExportTaskStatusProcessing:
|
||||
if !task.CancelRequested {
|
||||
ok, err := s.taskStore.SetCancelRequested(ctx, id, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "提交取消请求失败")
|
||||
before := exportTaskState(task)
|
||||
changed := false
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
txStore := s.taskStore.WithTx(tx)
|
||||
switch task.Status {
|
||||
case constants.ExportTaskStatusPending:
|
||||
ok, updateErr := txStore.CancelPendingTask(ctx, id, userID)
|
||||
if updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
return errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
}
|
||||
} else {
|
||||
message = "取消请求已提交,请稍后刷新状态"
|
||||
task.Status, task.CancelRequested, task.Progress = constants.ExportTaskStatusCancelled, true, 100
|
||||
message, changed = "任务已取消", true
|
||||
case constants.ExportTaskStatusProcessing:
|
||||
if task.CancelRequested {
|
||||
message = "取消请求已提交,请稍后刷新状态"
|
||||
return nil
|
||||
}
|
||||
ok, updateErr := txStore.SetCancelRequested(ctx, id, userID)
|
||||
if updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if !ok {
|
||||
return errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
}
|
||||
task.CancelRequested, changed = true, true
|
||||
case constants.ExportTaskStatusCompleted, constants.ExportTaskStatusFailed, constants.ExportTaskStatusCancelled:
|
||||
return errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
}
|
||||
case constants.ExportTaskStatusCompleted, constants.ExportTaskStatusFailed, constants.ExportTaskStatusCancelled:
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
default:
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "当前状态不支持取消")
|
||||
}
|
||||
|
||||
latestTask, err := s.taskStore.GetByID(ctx, id)
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
return s.writeTaskAudit(ctx, tx, constants.AuditActionExportTaskCancelled, message, task, before, exportTaskState(task), constants.AuditResultSuccess, "cancelled", "", "")
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询最新任务状态失败")
|
||||
result := constants.AuditResultFailed
|
||||
errorCode := errors.CodeDatabaseError
|
||||
var appErr *errors.AppError
|
||||
if stderrors.As(err, &appErr) && appErr.Code == errors.CodeInvalidStatus {
|
||||
result = constants.AuditResultDenied
|
||||
errorCode = appErr.Code
|
||||
}
|
||||
s.recordTaskAudit(ctx, constants.AuditActionExportTaskCancelled, "取消业务导出任务失败", task, before, exportTaskState(task), result, "", errorCode)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "取消导出任务失败")
|
||||
}
|
||||
|
||||
return &dto.CancelExportTaskResponse{
|
||||
TaskID: latestTask.ID,
|
||||
Status: latestTask.Status,
|
||||
StatusName: constants.GetExportTaskStatusName(latestTask.Status),
|
||||
CancelRequested: latestTask.CancelRequested,
|
||||
TaskID: task.ID,
|
||||
Status: task.Status,
|
||||
StatusName: constants.GetExportTaskStatusName(task.Status),
|
||||
CancelRequested: task.CancelRequested,
|
||||
Message: message,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user