收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
54
internal/service/package_series/audit.go
Normal file
54
internal/service/package_series/audit.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package package_series
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/errors"
|
||||
)
|
||||
|
||||
// SetAccessAudit 注入套餐系列统一审计 Writer。
|
||||
func (s *Service) SetAccessAudit(db *gorm.DB, writer *audit.Writer) {
|
||||
s.db = db
|
||||
s.auditWriter = writer
|
||||
}
|
||||
|
||||
func (s *Service) appendAudit(ctx context.Context, tx *gorm.DB, actionCode, summary string, series *model.PackageSeries, beforeData, afterData map[string]any) error {
|
||||
if s.auditWriter == nil || s.db == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "套餐系列统一审计接缝未配置")
|
||||
}
|
||||
return s.auditWriter.Append(ctx, tx, audit.AppendInput{
|
||||
ActionCode: actionCode, Summary: summary, ScopeType: constants.AuditScopePlatform,
|
||||
Result: constants.AuditResultSuccess,
|
||||
Resources: []audit.ResourceInput{audit.PackageSeriesResource(
|
||||
series, constants.AuditResourceRelationPrimary, constants.AuditResourceRolePackageSeriesTarget, beforeData, afterData,
|
||||
)},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) recordAuditFailure(ctx context.Context, actionCode, summary string, series *model.PackageSeries, beforeData map[string]any, businessErr error) {
|
||||
if series == nil {
|
||||
return
|
||||
}
|
||||
s.auditWriter.RecordFailure(ctx, s.db, audit.AppendInput{
|
||||
ActionCode: actionCode, Summary: summary, ScopeType: constants.AuditScopePlatform,
|
||||
Resources: []audit.ResourceInput{audit.PackageSeriesResource(
|
||||
series, constants.AuditResourceRelationPrimary, constants.AuditResourceRolePackageSeriesTarget, beforeData, nil,
|
||||
)},
|
||||
}, businessErr)
|
||||
}
|
||||
|
||||
func packageSeriesData(series *model.PackageSeries) map[string]any {
|
||||
if series == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"series_name": series.SeriesName, "description": series.Description, "status": series.Status,
|
||||
"enable_one_time_commission": series.EnableOneTimeCommission,
|
||||
"one_time_commission_config": series.OneTimeCommissionConfigJSON,
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"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"
|
||||
@@ -18,9 +19,11 @@ import (
|
||||
|
||||
// Service 套餐系列业务服务
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||
packageStore *postgres.PackageStore
|
||||
auditWriter *audit.Writer
|
||||
}
|
||||
|
||||
// New 创建套餐系列服务实例
|
||||
@@ -32,7 +35,7 @@ func New(packageSeriesStore *postgres.PackageSeriesStore, shopSeriesAllocationSt
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreatePackageSeriesRequest) (*dto.PackageSeriesResponse, error) {
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreatePackageSeriesRequest) (_ *dto.PackageSeriesResponse, retErr error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
@@ -50,6 +53,13 @@ func (s *Service) Create(ctx context.Context, req *dto.CreatePackageSeriesReques
|
||||
Status: constants.StatusEnabled,
|
||||
OneTimeCommissionConfigJSON: "{}",
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
failedSeries := *series
|
||||
failedSeries.ID = 0
|
||||
s.recordAuditFailure(ctx, constants.AuditActionPackageSeriesCreated, "创建套餐系列失败 "+series.SeriesCode, &failedSeries, nil, retErr)
|
||||
}
|
||||
}()
|
||||
series.Creator = currentUserID
|
||||
|
||||
if req.EnableOneTimeCommission != nil {
|
||||
@@ -69,8 +79,13 @@ func (s *Service) Create(ctx context.Context, req *dto.CreatePackageSeriesReques
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.packageSeriesStore.Create(ctx, series); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "创建套餐系列失败")
|
||||
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := postgres.NewPackageSeriesStore(tx).Create(ctx, series); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建套餐系列失败")
|
||||
}
|
||||
return s.appendAudit(ctx, tx, constants.AuditActionPackageSeriesCreated, "创建套餐系列 "+series.SeriesCode, series, nil, packageSeriesData(series))
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toResponse(series), nil
|
||||
@@ -87,7 +102,7 @@ func (s *Service) Get(ctx context.Context, id uint) (*dto.PackageSeriesResponse,
|
||||
return s.toResponse(series), nil
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageSeriesRequest) (*dto.PackageSeriesResponse, error) {
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageSeriesRequest) (_ *dto.PackageSeriesResponse, retErr error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
@@ -100,6 +115,12 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageSer
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
|
||||
}
|
||||
before := *series
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
s.recordAuditFailure(ctx, constants.AuditActionPackageSeriesUpdated, "更新套餐系列失败 "+before.SeriesCode, &before, packageSeriesData(&before), retErr)
|
||||
}
|
||||
}()
|
||||
|
||||
if req.SeriesName != nil {
|
||||
series.SeriesName = *req.SeriesName
|
||||
@@ -128,21 +149,31 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageSer
|
||||
}
|
||||
series.Updater = currentUserID
|
||||
|
||||
if err := s.packageSeriesStore.Update(ctx, series); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "更新套餐系列失败")
|
||||
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := postgres.NewPackageSeriesStore(tx).Update(ctx, series); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "更新套餐系列失败")
|
||||
}
|
||||
return s.appendAudit(ctx, tx, constants.AuditActionPackageSeriesUpdated, "更新套餐系列 "+series.SeriesCode, series, packageSeriesData(&before), packageSeriesData(series))
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toResponse(series), nil
|
||||
}
|
||||
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
_, err := s.packageSeriesStore.GetByID(ctx, id)
|
||||
func (s *Service) Delete(ctx context.Context, id uint) (retErr error) {
|
||||
series, err := s.packageSeriesStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "套餐系列不存在")
|
||||
}
|
||||
return errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
s.recordAuditFailure(ctx, constants.AuditActionPackageSeriesDeleted, "删除套餐系列失败 "+series.SeriesCode, series, packageSeriesData(series), retErr)
|
||||
}
|
||||
}()
|
||||
|
||||
count, err := s.packageStore.CountBySeriesID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -152,11 +183,12 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
return errors.New(errors.CodeInvalidParam, fmt.Sprintf("该系列下有 %d 个关联套餐,请先处理后再删除", count))
|
||||
}
|
||||
|
||||
if err := s.packageSeriesStore.Delete(ctx, id); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "删除套餐系列失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := postgres.NewPackageSeriesStore(tx).Delete(ctx, id); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "删除套餐系列失败")
|
||||
}
|
||||
return s.appendAudit(ctx, tx, constants.AuditActionPackageSeriesDeleted, "删除套餐系列 "+series.SeriesCode, series, packageSeriesData(series), map[string]any{"deleted": true})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, req *dto.PackageSeriesListRequest) ([]*dto.PackageSeriesResponse, int64, error) {
|
||||
@@ -223,7 +255,7 @@ func (s *Service) List(ctx context.Context, req *dto.PackageSeriesListRequest) (
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) (retErr error) {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
@@ -236,15 +268,22 @@ func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
}
|
||||
return errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
|
||||
}
|
||||
before := *series
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
s.recordAuditFailure(ctx, constants.AuditActionPackageSeriesStatusUpdated, "更新套餐系列状态失败 "+before.SeriesCode, &before, map[string]any{"status": before.Status}, retErr)
|
||||
}
|
||||
}()
|
||||
|
||||
series.Status = status
|
||||
series.Updater = currentUserID
|
||||
|
||||
if err := s.packageSeriesStore.Update(ctx, series); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "更新套餐系列状态失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := postgres.NewPackageSeriesStore(tx).Update(ctx, series); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "更新套餐系列状态失败")
|
||||
}
|
||||
return s.appendAudit(ctx, tx, constants.AuditActionPackageSeriesStatusUpdated, "更新套餐系列状态 "+series.SeriesCode, series, map[string]any{"status": before.Status}, map[string]any{"status": series.Status})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) toResponse(series *model.PackageSeries) *dto.PackageSeriesResponse {
|
||||
|
||||
Reference in New Issue
Block a user