This commit is contained in:
@@ -3,10 +3,14 @@ package asset
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"strconv"
|
||||
|
||||
infraAudit "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditfailure"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
@@ -14,255 +18,154 @@ import (
|
||||
|
||||
var deactivatableAssetStatuses = []int{constants.AssetStatusInStock, constants.AssetStatusSold}
|
||||
|
||||
// LifecycleService 资产生命周期服务
|
||||
// LifecycleService 资产生命周期服务。
|
||||
type LifecycleService struct {
|
||||
db *gorm.DB
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
assetAuditService assetAuditSvc.OperationLogger
|
||||
db *gorm.DB
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
auditWriter *infraAudit.Writer
|
||||
}
|
||||
|
||||
// NewLifecycleService 创建资产生命周期服务
|
||||
func NewLifecycleService(
|
||||
db *gorm.DB,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
deviceStore *postgres.DeviceStore,
|
||||
assetAuditService assetAuditSvc.OperationLogger,
|
||||
) *LifecycleService {
|
||||
return &LifecycleService{
|
||||
db: db,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
assetAuditService: assetAuditService,
|
||||
}
|
||||
// NewLifecycleService 创建资产生命周期服务。
|
||||
func NewLifecycleService(db *gorm.DB, iotCardStore *postgres.IotCardStore, deviceStore *postgres.DeviceStore, auditWriter *infraAudit.Writer) *LifecycleService {
|
||||
return &LifecycleService{db: db, iotCardStore: iotCardStore, deviceStore: deviceStore, auditWriter: auditWriter}
|
||||
}
|
||||
|
||||
func (s *LifecycleService) logLifecycleAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAuditService == nil {
|
||||
return
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
if p.OperationType == "" {
|
||||
p.OperationType = constants.AssetAuditOpAssetDeactivate
|
||||
}
|
||||
s.assetAuditService.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
// DeactivateIotCard 手动停用 IoT 卡
|
||||
// DeactivateIotCard 手动停用 IoT 卡。
|
||||
func (s *LifecycleService) DeactivateIotCard(ctx context.Context, id uint) error {
|
||||
card, err := s.iotCardStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
appErr := errors.New(errors.CodeIotCardNotFound)
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: id,
|
||||
OperationDesc: "统一入口停用IoT卡失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: id,
|
||||
OperationDesc: "统一入口停用IoT卡失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
})
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
appErr = errors.New(errors.CodeIotCardNotFound)
|
||||
}
|
||||
result := constants.AuditResultFailed
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
result = constants.AuditResultDenied
|
||||
}
|
||||
s.recordLifecycleFailure(ctx, constants.AuditActionIotCardDeactivated, "停用 IoT 卡失败", result, cardResourceStub(id), nil, appErr)
|
||||
return appErr
|
||||
}
|
||||
|
||||
beforeData := map[string]any{
|
||||
"asset_status": card.AssetStatus,
|
||||
"iccid": card.ICCID,
|
||||
"virtual_no": card.VirtualNo,
|
||||
}
|
||||
|
||||
beforeData := map[string]any{"asset_status": card.AssetStatus}
|
||||
if !canDeactivateAsset(card.AssetStatus) {
|
||||
appErr := errors.New(errors.CodeForbidden, "当前状态不允许停用")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
OperationDesc: "统一入口停用IoT卡被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
})
|
||||
s.recordLifecycleFailure(ctx, constants.AuditActionIotCardDeactivated, "停用 IoT 卡被拒绝", constants.AuditResultDenied, card, beforeData, appErr)
|
||||
return appErr
|
||||
}
|
||||
|
||||
result := s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id = ? AND asset_status IN ?", id, deactivatableAssetStatuses).
|
||||
Update("asset_status", constants.AssetStatusDeactivated)
|
||||
if result.Error != nil {
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, result.Error, "停用IoT卡失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
OperationDesc: "统一入口停用IoT卡失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
AfterData: map[string]any{
|
||||
"asset_status": constants.AssetStatusDeactivated,
|
||||
"iccid": card.ICCID,
|
||||
"virtual_no": card.VirtualNo,
|
||||
},
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
appErr := errors.New(errors.CodeConflict, "状态已变更,请刷新后重试")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
OperationDesc: "统一入口停用IoT卡失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
OperationDesc: "统一入口停用IoT卡",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BeforeData: beforeData,
|
||||
AfterData: map[string]any{
|
||||
"asset_status": constants.AssetStatusDeactivated,
|
||||
"iccid": card.ICCID,
|
||||
"virtual_no": card.VirtualNo,
|
||||
},
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.IotCard{}).Where("id = ? AND asset_status IN ?", id, deactivatableAssetStatuses).Update("asset_status", constants.AssetStatusDeactivated)
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "停用IoT卡失败")
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeConflict, "状态已变更,请刷新后重试")
|
||||
}
|
||||
return s.appendCardDeactivationAudit(ctx, tx, card, beforeData, map[string]any{"asset_status": constants.AssetStatusDeactivated}, constants.AuditResultSuccess, nil)
|
||||
})
|
||||
|
||||
return nil
|
||||
if err != nil {
|
||||
s.recordLifecycleFailure(ctx, constants.AuditActionIotCardDeactivated, "停用 IoT 卡失败", constants.AuditResultFailed, card, beforeData, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DeactivateDevice 手动停用设备
|
||||
// DeactivateDevice 手动停用设备。
|
||||
func (s *LifecycleService) DeactivateDevice(ctx context.Context, id uint) error {
|
||||
device, err := s.deviceStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
appErr := errors.New(errors.CodeNotFound, "设备不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: id,
|
||||
OperationDesc: "统一入口停用设备失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: id,
|
||||
OperationDesc: "统一入口停用设备失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
})
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
appErr = errors.New(errors.CodeNotFound, "设备不存在")
|
||||
}
|
||||
result := constants.AuditResultFailed
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
result = constants.AuditResultDenied
|
||||
}
|
||||
s.recordLifecycleFailure(ctx, constants.AuditActionDeviceDeactivated, "停用设备失败", result, deviceResourceStub(id), nil, appErr)
|
||||
return appErr
|
||||
}
|
||||
|
||||
beforeData := map[string]any{
|
||||
"asset_status": device.AssetStatus,
|
||||
"virtual_no": device.VirtualNo,
|
||||
}
|
||||
|
||||
beforeData := map[string]any{"asset_status": device.AssetStatus}
|
||||
if !canDeactivateAsset(device.AssetStatus) {
|
||||
appErr := errors.New(errors.CodeForbidden, "当前状态不允许停用")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.VirtualNo,
|
||||
OperationDesc: "统一入口停用设备被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
})
|
||||
s.recordLifecycleFailure(ctx, constants.AuditActionDeviceDeactivated, "停用设备被拒绝", constants.AuditResultDenied, device, beforeData, appErr)
|
||||
return appErr
|
||||
}
|
||||
|
||||
result := s.db.WithContext(ctx).Model(&model.Device{}).
|
||||
Where("id = ? AND asset_status IN ?", id, deactivatableAssetStatuses).
|
||||
Update("asset_status", constants.AssetStatusDeactivated)
|
||||
if result.Error != nil {
|
||||
appErr := errors.Wrap(errors.CodeDatabaseError, result.Error, "停用设备失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.VirtualNo,
|
||||
OperationDesc: "统一入口停用设备失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
AfterData: map[string]any{
|
||||
"asset_status": constants.AssetStatusDeactivated,
|
||||
"virtual_no": device.VirtualNo,
|
||||
},
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
appErr := errors.New(errors.CodeConflict, "状态已变更,请刷新后重试")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(appErr)
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.VirtualNo,
|
||||
OperationDesc: "统一入口停用设备失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
|
||||
s.logLifecycleAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.VirtualNo,
|
||||
OperationDesc: "统一入口停用设备",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BeforeData: beforeData,
|
||||
AfterData: map[string]any{
|
||||
"asset_status": constants.AssetStatusDeactivated,
|
||||
"virtual_no": device.VirtualNo,
|
||||
},
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.Device{}).Where("id = ? AND asset_status IN ?", id, deactivatableAssetStatuses).Update("asset_status", constants.AssetStatusDeactivated)
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "停用设备失败")
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeConflict, "状态已变更,请刷新后重试")
|
||||
}
|
||||
return s.appendDeviceDeactivationAudit(ctx, tx, device, beforeData, map[string]any{"asset_status": constants.AssetStatusDeactivated}, constants.AuditResultSuccess, nil)
|
||||
})
|
||||
|
||||
return nil
|
||||
if err != nil {
|
||||
s.recordLifecycleFailure(ctx, constants.AuditActionDeviceDeactivated, "停用设备失败", constants.AuditResultFailed, device, beforeData, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *LifecycleService) appendCardDeactivationAudit(ctx context.Context, tx *gorm.DB, card *model.IotCard, beforeData, afterData map[string]any, result string, businessErr error) error {
|
||||
if s.auditWriter == nil || card == nil || card.ID == 0 {
|
||||
return errors.New(errors.CodeInvalidStatus, "IoT 卡资产统一审计接缝未配置或资源不完整")
|
||||
}
|
||||
id := strconv.FormatUint(uint64(card.ID), 10)
|
||||
errorCode, errorSummary := assetAuditSvc.BuildErrorInfo(businessErr)
|
||||
return s.auditWriter.Append(ctx, tx, infraAudit.AppendInput{
|
||||
ActionCode: constants.AuditActionIotCardDeactivated, Summary: "停用 IoT 卡资产", Result: result,
|
||||
ErrorCode: errorCode, ErrorSummary: errorSummary, ScopeType: constants.AuditScopePlatform,
|
||||
Resources: []infraAudit.ResourceInput{{
|
||||
Type: constants.AuditResourceIotCard, ID: &id, Key: infraAudit.IotCardResourceKey(card), DisplayName: card.ICCID,
|
||||
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleIotCardTarget,
|
||||
IdentitySnapshot: infraAudit.IotCardIdentitySnapshot(card), BeforeData: beforeData, AfterData: afterData,
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "IoT 卡资产已停用",
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LifecycleService) appendDeviceDeactivationAudit(ctx context.Context, tx *gorm.DB, device *model.Device, beforeData, afterData map[string]any, result string, businessErr error) error {
|
||||
if s.auditWriter == nil || device == nil || device.ID == 0 {
|
||||
return errors.New(errors.CodeInvalidStatus, "设备资产统一审计接缝未配置或资源不完整")
|
||||
}
|
||||
id := strconv.FormatUint(uint64(device.ID), 10)
|
||||
errorCode, errorSummary := assetAuditSvc.BuildErrorInfo(businessErr)
|
||||
return s.auditWriter.Append(ctx, tx, infraAudit.AppendInput{
|
||||
ActionCode: constants.AuditActionDeviceDeactivated, Summary: "停用设备资产", Result: result,
|
||||
ErrorCode: errorCode, ErrorSummary: errorSummary, ScopeType: constants.AuditScopePlatform,
|
||||
Resources: []infraAudit.ResourceInput{{
|
||||
Type: constants.AuditResourceDevice, ID: &id, Key: infraAudit.DeviceResourceKey(device), DisplayName: device.VirtualNo,
|
||||
Relation: constants.AuditResourceRelationPrimary, Role: constants.AuditResourceRoleDeviceTarget,
|
||||
IdentitySnapshot: infraAudit.DeviceIdentitySnapshot(device), BeforeData: beforeData, AfterData: afterData,
|
||||
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "设备资产已停用",
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LifecycleService) recordLifecycleFailure(ctx context.Context, actionCode, summary, result string, resource any, beforeData map[string]any, businessErr error) {
|
||||
if s == nil || s.db == nil || s.auditWriter == nil {
|
||||
return
|
||||
}
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
switch value := resource.(type) {
|
||||
case *model.IotCard:
|
||||
return s.appendCardDeactivationAudit(ctx, tx, value, beforeData, nil, result, businessErr)
|
||||
case *model.Device:
|
||||
return s.appendDeviceDeactivationAudit(ctx, tx, value, beforeData, nil, result, businessErr)
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidStatus, "资产审计资源类型无效")
|
||||
}
|
||||
})
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
linkage := auditcontext.From(ctx)
|
||||
errorCode, _ := assetAuditSvc.BuildErrorInfo(businessErr)
|
||||
auditfailure.RecordSecondaryWriteFailure(actionCode, summary, linkage.RequestID, linkage.CorrelationID, errorCode, err)
|
||||
}
|
||||
|
||||
func cardResourceStub(id uint) *model.IotCard { return &model.IotCard{Model: gorm.Model{ID: id}} }
|
||||
func deviceResourceStub(id uint) *model.Device { return &model.Device{Model: gorm.Model{ID: id}} }
|
||||
|
||||
func canDeactivateAsset(assetStatus int) bool {
|
||||
return assetStatus == constants.AssetStatusInStock || assetStatus == constants.AssetStatusSold
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user