Files
break fcfa347005
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m38s
修复
2026-08-12 10:42:35 +08:00

203 lines
7.6 KiB
Go

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}
cards = nil
devices = nil
} 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
}