收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"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/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -67,8 +68,9 @@ type Service struct {
|
||||
makePCI func(*gorm.DB) pciOps
|
||||
markAsSold func(ctx context.Context, tx *gorm.DB, assetType string, assetID uint) error
|
||||
// readCard/readDevice 用于 Bind/Migrate 内部,接收事务 db 以保持读写在同一事务内
|
||||
readCard func(ctx context.Context, db *gorm.DB, id uint) (*model.IotCard, error)
|
||||
readDevice func(ctx context.Context, db *gorm.DB, id uint) (*model.Device, error)
|
||||
readCard func(ctx context.Context, db *gorm.DB, id uint) (*model.IotCard, error)
|
||||
readDevice func(ctx context.Context, db *gorm.DB, id uint) (*model.Device, error)
|
||||
accessAudit accessauditapp.Writer
|
||||
}
|
||||
|
||||
// New 创建客户绑定服务实例
|
||||
@@ -216,6 +218,9 @@ func collectActiveCardCustomerIDs(target map[uint]struct{}, records []*model.Per
|
||||
// 有虚拟号的 IoT 卡 / 设备 → tb_personal_customer_device
|
||||
// 无虚拟号的 IoT 卡 → tb_personal_customer_iccid(Issue 02)
|
||||
func (s *Service) Bind(ctx context.Context, tx *gorm.DB, customerID uint, assetType string, assetID uint) error {
|
||||
if s.accessAudit == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "个人客户资产审计接缝未配置")
|
||||
}
|
||||
if tx == nil {
|
||||
tx = s.db
|
||||
}
|
||||
@@ -229,10 +234,10 @@ func (s *Service) Bind(ctx context.Context, tx *gorm.DB, customerID uint, assetT
|
||||
return err
|
||||
}
|
||||
if card.VirtualNo != "" {
|
||||
return s.bindViaPCD(ctx, pcd, tx, customerID, card.VirtualNo, assetTypeIotCard, assetID)
|
||||
return s.bindViaPCD(ctx, pcd, tx, customerID, card.VirtualNo, assetTypeIotCard, assetID, card, nil)
|
||||
}
|
||||
// 无虚拟号路径(Issue 02)
|
||||
return s.bindViaPCI(ctx, pci, tx, customerID, card.ICCID, assetTypeIotCard, assetID)
|
||||
return s.bindViaPCI(ctx, pci, tx, customerID, card.ICCID, assetTypeIotCard, assetID, card)
|
||||
|
||||
case assetTypeDevice:
|
||||
device, err := s.readDevice(ctx, tx, assetID)
|
||||
@@ -243,14 +248,14 @@ func (s *Service) Bind(ctx context.Context, tx *gorm.DB, customerID uint, assetT
|
||||
if key == "" {
|
||||
key = device.IMEI
|
||||
}
|
||||
return s.bindViaPCD(ctx, pcd, tx, customerID, key, assetType, assetID)
|
||||
return s.bindViaPCD(ctx, pcd, tx, customerID, key, assetType, assetID, nil, device)
|
||||
}
|
||||
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
// bindViaPCD 通过 tb_personal_customer_device 创建绑定
|
||||
func (s *Service) bindViaPCD(ctx context.Context, pcd pcdOps, tx *gorm.DB, customerID uint, virtualNo string, assetType string, assetID uint) error {
|
||||
func (s *Service) bindViaPCD(ctx context.Context, pcd pcdOps, tx *gorm.DB, customerID uint, virtualNo string, assetType string, assetID uint, card *model.IotCard, device *model.Device) error {
|
||||
count, err := pcd.CountByVirtualNo(ctx, virtualNo)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询资产绑定数量失败")
|
||||
@@ -262,8 +267,9 @@ func (s *Service) bindViaPCD(ctx context.Context, pcd pcdOps, tx *gorm.DB, custo
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询客户资产绑定关系失败")
|
||||
}
|
||||
|
||||
var record *model.PersonalCustomerDevice
|
||||
if !exists {
|
||||
record := &model.PersonalCustomerDevice{
|
||||
record = &model.PersonalCustomerDevice{
|
||||
CustomerID: customerID,
|
||||
VirtualNo: virtualNo,
|
||||
Status: 1,
|
||||
@@ -274,14 +280,30 @@ func (s *Service) bindViaPCD(ctx context.Context, pcd pcdOps, tx *gorm.DB, custo
|
||||
}
|
||||
|
||||
if firstEverBind {
|
||||
return s.markAsSold(ctx, tx, assetType, assetID)
|
||||
if err := s.markAsSold(ctx, tx, assetType, assetID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
cards := []accessauditapp.IotCardChange(nil)
|
||||
devices := []accessauditapp.DeviceChange(nil)
|
||||
if card != nil {
|
||||
cards = append(cards, cardAuditChange(card, constants.AuditResourceRelationReference, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil))
|
||||
}
|
||||
if device != nil {
|
||||
devices = append(devices, deviceAuditChange(device, constants.AuditResourceRelationReference, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil))
|
||||
}
|
||||
return s.writeBindingAudit(ctx, tx, constants.AuditActionPersonalCustomerAssetBound, "绑定个人客户资产", customerID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: record, Role: constants.AuditResourceRolePersonalCustomerAssetBinding,
|
||||
AfterData: map[string]any{"virtual_no": record.VirtualNo, "status": record.Status},
|
||||
}}, nil, cards, devices)
|
||||
}
|
||||
|
||||
// bindViaPCI 通过 tb_personal_customer_iccid 创建绑定(无虚拟号卡专用,Issue 02)
|
||||
func (s *Service) bindViaPCI(ctx context.Context, pci pciOps, tx *gorm.DB, customerID uint, iccid string, assetType string, assetID uint) error {
|
||||
func (s *Service) bindViaPCI(ctx context.Context, pci pciOps, tx *gorm.DB, customerID uint, iccid string, assetType string, assetID uint, card *model.IotCard) error {
|
||||
count, err := pci.CountByICCID(ctx, iccid)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询 ICCID 绑定数量失败")
|
||||
@@ -293,12 +315,13 @@ func (s *Service) bindViaPCI(ctx context.Context, pci pciOps, tx *gorm.DB, custo
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询客户 ICCID 绑定关系失败")
|
||||
}
|
||||
|
||||
var record *model.PersonalCustomerICCID
|
||||
if !exists {
|
||||
iccid19 := iccid
|
||||
if len(iccid) == 20 {
|
||||
iccid19 = iccid[:19]
|
||||
}
|
||||
record := &model.PersonalCustomerICCID{
|
||||
record = &model.PersonalCustomerICCID{
|
||||
CustomerID: customerID,
|
||||
ICCID: iccid,
|
||||
ICCID19: iccid19,
|
||||
@@ -310,15 +333,28 @@ func (s *Service) bindViaPCI(ctx context.Context, pci pciOps, tx *gorm.DB, custo
|
||||
}
|
||||
|
||||
if firstEverBind {
|
||||
return s.markAsSold(ctx, tx, assetType, assetID)
|
||||
if err := s.markAsSold(ctx, tx, assetType, assetID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return s.writeBindingAudit(ctx, tx, constants.AuditActionPersonalCustomerAssetBound, "绑定个人客户资产", customerID,
|
||||
nil, []accessauditapp.PersonalCustomerICCIDChange{{
|
||||
Binding: record, Role: constants.AuditResourceRolePersonalCustomerAssetBinding,
|
||||
AfterData: map[string]any{"iccid": record.ICCID, "status": record.Status},
|
||||
}}, []accessauditapp.IotCardChange{
|
||||
cardAuditChange(card, constants.AuditResourceRelationReference, constants.AuditResourceRolePersonalCustomerBoundAsset, nil, nil),
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// Migrate 将旧资产的所有有效客户绑定迁移到新资产(换货专用)
|
||||
// 无绑定时静默跳过;按旧/新资产虚拟号有无路由到 pcd 或 pci
|
||||
func (s *Service) Migrate(ctx context.Context, tx *gorm.DB, oldAssetType string, oldAssetID uint, newAssetType string, newAssetID uint) error {
|
||||
if s.accessAudit == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "个人客户资产审计接缝未配置")
|
||||
}
|
||||
if tx == nil {
|
||||
tx = s.db
|
||||
}
|
||||
@@ -332,9 +368,9 @@ func (s *Service) Migrate(ctx context.Context, tx *gorm.DB, oldAssetType string,
|
||||
return err
|
||||
}
|
||||
if oldCard.VirtualNo != "" {
|
||||
return s.migrateFromPCD(ctx, tx, pcd, pci, oldCard.VirtualNo, newAssetType, newAssetID)
|
||||
return s.migrateFromPCD(ctx, tx, pcd, pci, oldCard.VirtualNo, oldAssetType, oldAssetID, newAssetType, newAssetID)
|
||||
}
|
||||
return s.migrateFromPCI(ctx, tx, pcd, pci, oldCard.ICCID, newAssetType, newAssetID)
|
||||
return s.migrateFromPCI(ctx, tx, pcd, pci, oldCard.ICCID, oldAssetType, oldAssetID, newAssetType, newAssetID)
|
||||
|
||||
case assetTypeDevice:
|
||||
oldDevice, err := s.readDevice(ctx, tx, oldAssetID)
|
||||
@@ -345,14 +381,14 @@ func (s *Service) Migrate(ctx context.Context, tx *gorm.DB, oldAssetType string,
|
||||
if key == "" {
|
||||
key = oldDevice.IMEI
|
||||
}
|
||||
return s.migrateFromPCD(ctx, tx, pcd, pci, key, newAssetType, newAssetID)
|
||||
return s.migrateFromPCD(ctx, tx, pcd, pci, key, oldAssetType, oldAssetID, newAssetType, newAssetID)
|
||||
}
|
||||
|
||||
return errors.New(errors.CodeInvalidParam)
|
||||
}
|
||||
|
||||
// migrateFromPCD 将 tb_personal_customer_device 中 oldKey 的所有有效绑定迁移到新资产
|
||||
func (s *Service) migrateFromPCD(ctx context.Context, db *gorm.DB, pcd pcdOps, pci pciOps, oldKey string, newAssetType string, newAssetID uint) error {
|
||||
func (s *Service) migrateFromPCD(ctx context.Context, db *gorm.DB, pcd pcdOps, pci pciOps, oldKey string, oldAssetType string, oldAssetID uint, newAssetType string, newAssetID uint) error {
|
||||
records, err := pcd.GetByDeviceNo(ctx, oldKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询旧资产绑定记录失败")
|
||||
@@ -381,6 +417,16 @@ func (s *Service) migrateFromPCD(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
if err := pcd.UpdateVirtualNo(ctx, rec.ID, newCard.VirtualNo); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "迁移客户绑定关系失败")
|
||||
}
|
||||
after := *rec
|
||||
after.VirtualNo = newCard.VirtualNo
|
||||
if err := s.writeMigrationAudit(ctx, db, rec.CustomerID, oldAssetType, oldAssetID, newAssetType, newAssetID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: &after, Role: constants.AuditResourceRolePersonalCustomerAssetBinding,
|
||||
BeforeData: map[string]any{"virtual_no": rec.VirtualNo, "status": rec.Status},
|
||||
AfterData: map[string]any{"virtual_no": after.VirtualNo, "status": after.Status},
|
||||
}}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 新卡无虚拟号:禁用旧 pcd + 创建新 pci
|
||||
@@ -401,6 +447,17 @@ func (s *Service) migrateFromPCD(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
if err := pci.Create(ctx, newPCI); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建新客户绑定关系失败")
|
||||
}
|
||||
if err := s.writeMigrationAudit(ctx, db, rec.CustomerID, oldAssetType, oldAssetID, newAssetType, newAssetID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: rec, Role: constants.AuditResourceRolePersonalCustomerOldAssetBinding,
|
||||
BeforeData: map[string]any{"virtual_no": rec.VirtualNo, "status": rec.Status},
|
||||
AfterData: map[string]any{"virtual_no": rec.VirtualNo, "status": 0},
|
||||
}}, []accessauditapp.PersonalCustomerICCIDChange{{
|
||||
Binding: newPCI, Role: constants.AuditResourceRolePersonalCustomerNewAssetBinding,
|
||||
AfterData: map[string]any{"iccid": newPCI.ICCID, "status": newPCI.Status},
|
||||
}}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,6 +474,16 @@ func (s *Service) migrateFromPCD(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
if err := pcd.UpdateVirtualNo(ctx, rec.ID, newKey); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "迁移设备客户绑定关系失败")
|
||||
}
|
||||
after := *rec
|
||||
after.VirtualNo = newKey
|
||||
if err := s.writeMigrationAudit(ctx, db, rec.CustomerID, oldAssetType, oldAssetID, newAssetType, newAssetID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: &after, Role: constants.AuditResourceRolePersonalCustomerAssetBinding,
|
||||
BeforeData: map[string]any{"virtual_no": rec.VirtualNo, "status": rec.Status},
|
||||
AfterData: map[string]any{"virtual_no": after.VirtualNo, "status": after.Status},
|
||||
}}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -427,7 +494,7 @@ func (s *Service) migrateFromPCD(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
}
|
||||
|
||||
// migrateFromPCI 将 tb_personal_customer_iccid 中 oldICCID 的所有有效绑定迁移到新资产
|
||||
func (s *Service) migrateFromPCI(ctx context.Context, db *gorm.DB, pcd pcdOps, pci pciOps, oldICCID string, newAssetType string, newAssetID uint) error {
|
||||
func (s *Service) migrateFromPCI(ctx context.Context, db *gorm.DB, pcd pcdOps, pci pciOps, oldICCID string, oldAssetType string, oldAssetID uint, newAssetType string, newAssetID uint) error {
|
||||
records, err := pci.GetByICCID(ctx, oldICCID)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "查询旧 ICCID 绑定记录失败")
|
||||
@@ -463,6 +530,17 @@ func (s *Service) migrateFromPCI(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
if err := pcd.Create(ctx, newPCD); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建新客户绑定关系失败")
|
||||
}
|
||||
if err := s.writeMigrationAudit(ctx, db, rec.CustomerID, oldAssetType, oldAssetID, newAssetType, newAssetID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: newPCD, Role: constants.AuditResourceRolePersonalCustomerNewAssetBinding,
|
||||
AfterData: map[string]any{"virtual_no": newPCD.VirtualNo, "status": newPCD.Status},
|
||||
}}, []accessauditapp.PersonalCustomerICCIDChange{{
|
||||
Binding: rec, Role: constants.AuditResourceRolePersonalCustomerOldAssetBinding,
|
||||
BeforeData: map[string]any{"iccid": rec.ICCID, "status": rec.Status},
|
||||
AfterData: map[string]any{"iccid": rec.ICCID, "status": 0},
|
||||
}}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 新卡也无虚拟号:禁用旧 pci + 创建新 pci(新 ICCID)
|
||||
@@ -483,6 +561,20 @@ func (s *Service) migrateFromPCI(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
if err := pci.Create(ctx, newPCI); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建新 ICCID 绑定关系失败")
|
||||
}
|
||||
if err := s.writeMigrationAudit(ctx, db, rec.CustomerID, oldAssetType, oldAssetID, newAssetType, newAssetID,
|
||||
nil, []accessauditapp.PersonalCustomerICCIDChange{
|
||||
{
|
||||
Binding: rec, Role: constants.AuditResourceRolePersonalCustomerOldAssetBinding,
|
||||
BeforeData: map[string]any{"iccid": rec.ICCID, "status": rec.Status},
|
||||
AfterData: map[string]any{"iccid": rec.ICCID, "status": 0},
|
||||
},
|
||||
{
|
||||
Binding: newPCI, Role: constants.AuditResourceRolePersonalCustomerNewAssetBinding,
|
||||
AfterData: map[string]any{"iccid": newPCI.ICCID, "status": newPCI.Status},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +599,17 @@ func (s *Service) migrateFromPCI(ctx context.Context, db *gorm.DB, pcd pcdOps, p
|
||||
if err := pcd.Create(ctx, newPCD); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建新客户绑定关系失败")
|
||||
}
|
||||
if err := s.writeMigrationAudit(ctx, db, rec.CustomerID, oldAssetType, oldAssetID, newAssetType, newAssetID,
|
||||
[]accessauditapp.PersonalCustomerDeviceChange{{
|
||||
Binding: newPCD, Role: constants.AuditResourceRolePersonalCustomerNewAssetBinding,
|
||||
AfterData: map[string]any{"virtual_no": newPCD.VirtualNo, "status": newPCD.Status},
|
||||
}}, []accessauditapp.PersonalCustomerICCIDChange{{
|
||||
Binding: rec, Role: constants.AuditResourceRolePersonalCustomerOldAssetBinding,
|
||||
BeforeData: map[string]any{"iccid": rec.ICCID, "status": rec.Status},
|
||||
AfterData: map[string]any{"iccid": rec.ICCID, "status": 0},
|
||||
}}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user