收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
200
internal/service/customer_binding/audit.go
Normal file
200
internal/service/customer_binding/audit.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package customer_binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
accessauditapp "github.com/break/junhong_cmp_fiber/internal/application/accessaudit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
)
|
||||
|
||||
// SetAccessAudit 注入个人客户资产关系的统一审计接缝。
|
||||
func (s *Service) SetAccessAudit(writer accessauditapp.Writer) {
|
||||
s.accessAudit = writer
|
||||
}
|
||||
|
||||
func (s *Service) writeBindingAudit(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
actionCode, summary string,
|
||||
customerID uint,
|
||||
personalDevices []accessauditapp.PersonalCustomerDeviceChange,
|
||||
personalICCIDs []accessauditapp.PersonalCustomerICCIDChange,
|
||||
cards []accessauditapp.IotCardChange,
|
||||
devices []accessauditapp.DeviceChange,
|
||||
) error {
|
||||
if s.accessAudit == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "个人客户资产审计接缝未配置")
|
||||
}
|
||||
var customer model.PersonalCustomer
|
||||
if err := tx.WithContext(ctx).First(&customer, customerID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询个人客户审计快照失败")
|
||||
}
|
||||
|
||||
value := auditcontext.From(ctx)
|
||||
operatorID := customerID
|
||||
actorKind := constants.AuditActorPersonalCustomer
|
||||
actorName := customer.Nickname
|
||||
source := constants.AuditSourcePersonalAPI
|
||||
scopeType := constants.AuditScopePersonalCustomer
|
||||
visibility := constants.AuditSubjectDetail
|
||||
subjectData := map[string]any(nil)
|
||||
if actionCode == constants.AuditActionPersonalCustomerAssetBound {
|
||||
assetType, assetID := bindingAssetReference(cards, devices)
|
||||
subjectData = map[string]any{"asset_type": assetType, "asset_id": assetID}
|
||||
} else {
|
||||
operatorID = middleware.GetUserIDFromContext(ctx)
|
||||
if parsed, err := strconv.ParseUint(value.ActorID, 10, 64); err == nil && parsed > 0 {
|
||||
operatorID = uint(parsed)
|
||||
}
|
||||
actorKind = value.ActorKind
|
||||
actorName = value.ActorName
|
||||
source = value.Source
|
||||
scopeType = constants.AuditScopePlatform
|
||||
visibility = constants.AuditSubjectResult
|
||||
}
|
||||
if operatorID == 0 {
|
||||
return errors.New(errors.CodeInvalidStatus, "个人客户资产审计操作者不完整")
|
||||
}
|
||||
|
||||
return s.accessAudit.WriteAccessChange(ctx, tx, accessauditapp.ChangeAudit{
|
||||
ActionCode: actionCode, Summary: summary, Result: constants.AuditResultSuccess,
|
||||
OperatorID: operatorID, ActorKind: actorKind, ActorName: actorName, Source: source, ScopeType: scopeType,
|
||||
PersonalCustomer: &customer, PersonalDevices: personalDevices, PersonalICCIDs: personalICCIDs,
|
||||
Cards: cards, Devices: devices,
|
||||
SubjectVisibility: visibility, SubjectSummary: summary, SubjectData: subjectData,
|
||||
})
|
||||
}
|
||||
|
||||
func bindingAssetReference(cards []accessauditapp.IotCardChange, devices []accessauditapp.DeviceChange) (string, uint) {
|
||||
if len(cards) > 0 && cards[0].Card != nil {
|
||||
return constants.AuditResourceIotCard, cards[0].Card.ID
|
||||
}
|
||||
if len(devices) > 0 && devices[0].Device != nil {
|
||||
return constants.AuditResourceDevice, devices[0].Device.ID
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
func cardAuditChange(card *model.IotCard, relation, role string, beforeData, afterData map[string]any) accessauditapp.IotCardChange {
|
||||
return accessauditapp.IotCardChange{
|
||||
Card: card, Relation: relation, Role: role, BeforeData: beforeData, AfterData: afterData,
|
||||
SubjectSummary: "个人客户资产关系已更新",
|
||||
}
|
||||
}
|
||||
|
||||
func deviceAuditChange(device *model.Device, relation, role string, beforeData, afterData map[string]any) accessauditapp.DeviceChange {
|
||||
return accessauditapp.DeviceChange{
|
||||
Device: device, Relation: relation, Role: role, BeforeData: beforeData, AfterData: afterData,
|
||||
SubjectSummary: "个人客户资产关系已更新",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) loadAuditAssets(ctx context.Context, tx *gorm.DB, oldType string, oldID uint, newType string, newID uint) ([]accessauditapp.IotCardChange, []accessauditapp.DeviceChange, error) {
|
||||
cards := make([]accessauditapp.IotCardChange, 0, 2)
|
||||
devices := make([]accessauditapp.DeviceChange, 0, 2)
|
||||
appendAsset := func(assetType string, assetID uint, role string) error {
|
||||
switch normalizeAssetType(assetType) {
|
||||
case assetTypeIotCard:
|
||||
card, err := s.readCard(ctx, tx, assetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cards = append(cards, cardAuditChange(card, constants.AuditResourceRelationAffected, role, nil, nil))
|
||||
case assetTypeDevice:
|
||||
device, err := s.readDevice(ctx, tx, assetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
devices = append(devices, deviceAuditChange(device, constants.AuditResourceRelationAffected, role, nil, nil))
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidParam, "无效的资产类型")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := appendAsset(oldType, oldID, constants.AuditResourceRolePersonalCustomerOldAsset); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := appendAsset(newType, newID, constants.AuditResourceRolePersonalCustomerNewAsset); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cards, devices, nil
|
||||
}
|
||||
|
||||
func (s *Service) writeMigrationAudit(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
customerID uint,
|
||||
oldType string,
|
||||
oldID uint,
|
||||
newType string,
|
||||
newID uint,
|
||||
personalDevices []accessauditapp.PersonalCustomerDeviceChange,
|
||||
personalICCIDs []accessauditapp.PersonalCustomerICCIDChange,
|
||||
) error {
|
||||
cards, devices, err := s.loadAuditAssets(ctx, tx, oldType, oldID, newType, newID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.writeBindingAudit(
|
||||
ctx, tx, constants.AuditActionPersonalCustomerAssetBindingMigrated, "换货迁移个人客户资产绑定",
|
||||
customerID, personalDevices, personalICCIDs, cards, devices,
|
||||
)
|
||||
}
|
||||
|
||||
// UnbindByVirtualNo 按现有换货重置语义删除设备号绑定,并在同一事务记录实际删除关系。
|
||||
func (s *Service) UnbindByVirtualNo(ctx context.Context, tx *gorm.DB, assetType string, assetID uint, virtualNo string) error {
|
||||
if s.accessAudit == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "个人客户资产审计接缝未配置")
|
||||
}
|
||||
if tx == nil {
|
||||
tx = s.db
|
||||
}
|
||||
records, err := s.makePCD(tx).GetByDeviceNo(ctx, virtualNo)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询个人客户资产绑定失败")
|
||||
}
|
||||
if err := tx.WithContext(ctx).Where("virtual_no = ?", virtualNo).Delete(&model.PersonalCustomerDevice{}).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "清理个人客户绑定失败")
|
||||
}
|
||||
for _, record := range records {
|
||||
if record == nil {
|
||||
continue
|
||||
}
|
||||
cards := []accessauditapp.IotCardChange(nil)
|
||||
devices := []accessauditapp.DeviceChange(nil)
|
||||
switch normalizeAssetType(assetType) {
|
||||
case assetTypeIotCard:
|
||||
card, loadErr := s.readCard(ctx, tx, assetID)
|
||||
if loadErr != nil {
|
||||
return loadErr
|
||||
}
|
||||
cards = append(cards, cardAuditChange(card, constants.AuditResourceRelationAffected, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil))
|
||||
case assetTypeDevice:
|
||||
device, loadErr := s.readDevice(ctx, tx, assetID)
|
||||
if loadErr != nil {
|
||||
return loadErr
|
||||
}
|
||||
devices = append(devices, deviceAuditChange(device, constants.AuditResourceRelationAffected, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil))
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidParam, "无效的资产类型")
|
||||
}
|
||||
if err := s.writeBindingAudit(
|
||||
ctx, tx, constants.AuditActionPersonalCustomerAssetUnbound, "解除个人客户资产绑定", record.CustomerID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: record, Role: constants.AuditResourceRolePersonalCustomerAssetBinding,
|
||||
BeforeData: map[string]any{"virtual_no": record.VirtualNo, "status": record.Status},
|
||||
AfterData: map[string]any{"deleted": true},
|
||||
}}, nil, cards, devices,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user