收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
47
internal/service/asset_package_batch_order/audit.go
Normal file
47
internal/service/asset_package_batch_order/audit.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package asset_package_batch_order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"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/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
)
|
||||
|
||||
func (s *Service) writeBatchOrderTaskAudit(ctx context.Context, tx *gorm.DB, task *model.AssetPackageBatchOrderTask, before, after map[string]any, result, phase, errorCode, errorSummary string) error {
|
||||
scopeType, scopeID := constants.AuditScopePlatform, ""
|
||||
if task.CreatorShopID != 0 {
|
||||
scopeType, scopeID = constants.AuditScopeShop, strconv.FormatUint(uint64(task.CreatorShopID), 10)
|
||||
}
|
||||
return s.auditWriter.WriteTask(ctx, tx, audit.TaskInput{
|
||||
EventID: audit.TaskEventID(constants.AuditResourceAssetPackageBatchOrderTask, task.ID, phase),
|
||||
ActionCode: constants.AuditActionAssetPackageBatchOrderTaskCreated,
|
||||
Summary: "创建资产套餐批量订购任务", TaskID: task.ID, TaskNo: task.TaskNo,
|
||||
Actor: audit.ActorInput{
|
||||
Kind: constants.AuditActorAccount, ID: strconv.FormatUint(uint64(middleware.GetUserIDFromContext(ctx)), 10),
|
||||
Name: middleware.GetUsernameFromContext(ctx),
|
||||
},
|
||||
Source: constants.AuditSourceAdminAPI, ScopeType: scopeType, ScopeID: scopeID,
|
||||
Result: result, ErrorCode: errorCode, ErrorSummary: errorSummary,
|
||||
IdentitySnapshot: map[string]any{
|
||||
"id": task.ID, "task_no": task.TaskNo, "file_name": task.FileName,
|
||||
"package_id": task.PackageID, "package_code": task.PackageCode,
|
||||
"package_name": task.PackageName, "payment_method": task.PaymentMethod,
|
||||
},
|
||||
BeforeData: before, AfterData: after,
|
||||
})
|
||||
}
|
||||
|
||||
func batchOrderTaskState(task *model.AssetPackageBatchOrderTask) map[string]any {
|
||||
if task == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"status": task.Status, "total_count": task.TotalCount,
|
||||
"success_count": task.SuccessCount, "fail_count": task.FailCount,
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,20 @@ package asset_package_batch_order
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"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"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/asynctask"
|
||||
"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"
|
||||
@@ -26,11 +29,16 @@ type Service struct {
|
||||
taskStore *postgres.AssetPackageBatchOrderTaskStore
|
||||
packageStore *postgres.PackageStore
|
||||
queueClient *queue.Client
|
||||
auditWriter *audit.Writer
|
||||
}
|
||||
|
||||
// New 创建资产套餐批量订购任务服务。
|
||||
func New(taskStore *postgres.AssetPackageBatchOrderTaskStore, packageStore *postgres.PackageStore, queueClient *queue.Client) *Service {
|
||||
return &Service{taskStore: taskStore, packageStore: packageStore, queueClient: queueClient}
|
||||
func New(taskStore *postgres.AssetPackageBatchOrderTaskStore, packageStore *postgres.PackageStore, queueClient *queue.Client, auditWriters ...*audit.Writer) *Service {
|
||||
service := &Service{taskStore: taskStore, packageStore: packageStore, queueClient: queueClient}
|
||||
if len(auditWriters) > 0 {
|
||||
service.auditWriter = auditWriters[0]
|
||||
}
|
||||
return service
|
||||
}
|
||||
|
||||
// TaskPayload 批量订购 Worker 结构化载荷。
|
||||
@@ -69,7 +77,15 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAssetPackageBatchOr
|
||||
CreatorUserType: middleware.GetUserTypeFromContext(ctx), CreatorShopID: middleware.GetShopIDFromContext(ctx),
|
||||
CreatorName: middleware.GetUsernameFromContext(ctx),
|
||||
}
|
||||
if err := s.taskStore.Create(ctx, task); err != nil {
|
||||
if s.auditWriter == nil {
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "资产套餐批量订购统一审计接缝未配置")
|
||||
}
|
||||
if err := s.taskStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.taskStore.WithTx(tx).Create(ctx, task); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.writeBatchOrderTaskAudit(ctx, tx, task, nil, batchOrderTaskState(task), constants.AuditResultSuccess, "created", "", "")
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建批量订购任务失败")
|
||||
}
|
||||
var enqueueErr error
|
||||
@@ -82,10 +98,19 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateAssetPackageBatchOr
|
||||
}
|
||||
if enqueueErr != nil {
|
||||
message := "批量订购任务入队失败"
|
||||
_ = s.taskStore.MarkFailed(ctx, task.ID, message)
|
||||
task.Status, task.ErrorMessage = asynctask.StatusFailed, message
|
||||
now := time.Now()
|
||||
task.CompletedAt = &now
|
||||
secondaryErr := s.taskStore.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
before := batchOrderTaskState(task)
|
||||
if err := s.taskStore.WithTx(tx).MarkFailed(ctx, task.ID, message); err != nil {
|
||||
return err
|
||||
}
|
||||
task.Status, task.ErrorMessage = asynctask.StatusFailed, message
|
||||
now := time.Now()
|
||||
task.CompletedAt = &now
|
||||
return s.writeBatchOrderTaskAudit(ctx, tx, task, before, batchOrderTaskState(task), constants.AuditResultFailed, "enqueue_failed", strconv.Itoa(errors.CodeTaskQueueError), message)
|
||||
})
|
||||
if secondaryErr != nil {
|
||||
auditfailure.RecordSecondaryWriteFailure(constants.AuditActionAssetPackageBatchOrderTaskCreated, task.TaskNo, "", task.TaskNo, strconv.Itoa(errors.CodeTaskQueueError), secondaryErr)
|
||||
}
|
||||
}
|
||||
return toResponse(task), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user