This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
stderrors "errors"
|
||||
|
||||
"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/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
@@ -15,44 +16,145 @@ var deactivatableAssetStatuses = []int{constants.AssetStatusInStock, constants.A
|
||||
|
||||
// LifecycleService 资产生命周期服务
|
||||
type LifecycleService struct {
|
||||
db *gorm.DB
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
db *gorm.DB
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
assetAuditService assetAuditSvc.OperationLogger
|
||||
}
|
||||
|
||||
// NewLifecycleService 创建资产生命周期服务
|
||||
func NewLifecycleService(db *gorm.DB, iotCardStore *postgres.IotCardStore, deviceStore *postgres.DeviceStore) *LifecycleService {
|
||||
func NewLifecycleService(
|
||||
db *gorm.DB,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
deviceStore *postgres.DeviceStore,
|
||||
assetAuditService assetAuditSvc.OperationLogger,
|
||||
) *LifecycleService {
|
||||
return &LifecycleService{
|
||||
db: db,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
db: db,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
assetAuditService: assetAuditService,
|
||||
}
|
||||
}
|
||||
|
||||
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 卡
|
||||
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) {
|
||||
return errors.New(errors.CodeIotCardNotFound)
|
||||
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
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
|
||||
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,
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
|
||||
beforeData := map[string]any{
|
||||
"asset_status": card.AssetStatus,
|
||||
"iccid": card.ICCID,
|
||||
"virtual_no": card.VirtualNo,
|
||||
}
|
||||
|
||||
if !canDeactivateAsset(card.AssetStatus) {
|
||||
return errors.New(errors.CodeForbidden, "当前状态不允许停用")
|
||||
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,
|
||||
})
|
||||
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 {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "停用IoT卡失败")
|
||||
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 {
|
||||
return errors.New(errors.CodeConflict, "状态已变更,请刷新后重试")
|
||||
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,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,25 +163,103 @@ 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) {
|
||||
return errors.New(errors.CodeNotFound, "设备不存在")
|
||||
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
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
|
||||
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,
|
||||
})
|
||||
return appErr
|
||||
}
|
||||
|
||||
beforeData := map[string]any{
|
||||
"asset_status": device.AssetStatus,
|
||||
"virtual_no": device.VirtualNo,
|
||||
}
|
||||
|
||||
if !canDeactivateAsset(device.AssetStatus) {
|
||||
return errors.New(errors.CodeForbidden, "当前状态不允许停用")
|
||||
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,
|
||||
})
|
||||
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 {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "停用设备失败")
|
||||
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 {
|
||||
return errors.New(errors.CodeConflict, "状态已变更,请刷新后重试")
|
||||
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,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
269
internal/service/asset_audit/builder.go
Normal file
269
internal/service/asset_audit/builder.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package asset_audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
// OperatorInfo 审计操作者信息。
|
||||
type OperatorInfo struct {
|
||||
ID *uint
|
||||
Type string
|
||||
Name string
|
||||
}
|
||||
|
||||
// BuildLogParams 构建资产审计日志参数。
|
||||
type BuildLogParams struct {
|
||||
Operator OperatorInfo
|
||||
|
||||
AssetType string
|
||||
AssetID uint
|
||||
AssetIdentifier string
|
||||
|
||||
OperationType string
|
||||
OperationDesc string
|
||||
|
||||
BeforeData map[string]any
|
||||
AfterData map[string]any
|
||||
|
||||
ResultStatus string
|
||||
ErrorCode string
|
||||
ErrorMsg string
|
||||
|
||||
BatchTotal int
|
||||
SuccessCount int
|
||||
FailCount int
|
||||
}
|
||||
|
||||
// BuildLog 统一构建资产审计日志。
|
||||
func BuildLog(ctx context.Context, p BuildLogParams) *model.AssetOperationLog {
|
||||
resultStatus := strings.TrimSpace(p.ResultStatus)
|
||||
if resultStatus == "" {
|
||||
resultStatus = constants.AssetAuditResultSuccess
|
||||
}
|
||||
|
||||
operationDesc := strings.TrimSpace(p.OperationDesc)
|
||||
if operationDesc == "" {
|
||||
operationDesc = p.OperationType
|
||||
}
|
||||
|
||||
log := &model.AssetOperationLog{
|
||||
OperatorID: p.Operator.ID,
|
||||
OperatorType: strings.TrimSpace(p.Operator.Type),
|
||||
OperatorName: strings.TrimSpace(p.Operator.Name),
|
||||
AssetType: NormalizeAssetType(p.AssetType),
|
||||
AssetID: p.AssetID,
|
||||
AssetIdentifier: strings.TrimSpace(p.AssetIdentifier),
|
||||
OperationType: strings.TrimSpace(p.OperationType),
|
||||
OperationDesc: operationDesc,
|
||||
BeforeData: sanitizeAndTrimJSONB(p.BeforeData),
|
||||
AfterData: sanitizeAndTrimJSONB(p.AfterData),
|
||||
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||
IPAddress: middleware.GetIPFromContext(ctx),
|
||||
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||
RequestPath: middleware.GetRequestPathFromContext(ctx),
|
||||
RequestMethod: middleware.GetRequestMethodFromContext(ctx),
|
||||
ResultStatus: resultStatus,
|
||||
BatchTotal: nonNegative(p.BatchTotal),
|
||||
SuccessCount: nonNegative(p.SuccessCount),
|
||||
FailCount: nonNegative(p.FailCount),
|
||||
}
|
||||
|
||||
if log.OperatorType == "" {
|
||||
log.OperatorType = constants.AssetAuditOperatorTypeSystem
|
||||
}
|
||||
if log.OperatorName == "" {
|
||||
log.OperatorName = "系统任务"
|
||||
}
|
||||
|
||||
if code := strings.TrimSpace(p.ErrorCode); code != "" {
|
||||
log.ErrorCode = strPtr(code)
|
||||
}
|
||||
if msg := strings.TrimSpace(p.ErrorMsg); msg != "" {
|
||||
msg = limitString(msg, 1000)
|
||||
log.ErrorMsg = strPtr(msg)
|
||||
}
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
// OperatorFromContext 从 context 解析操作者信息。
|
||||
func OperatorFromContext(ctx context.Context) OperatorInfo {
|
||||
uid := middleware.GetUserIDFromContext(ctx)
|
||||
ut := middleware.GetUserTypeFromContext(ctx)
|
||||
|
||||
if uid == 0 {
|
||||
return SystemOperator("系统任务")
|
||||
}
|
||||
|
||||
op := OperatorInfo{ID: uintPtr(uid)}
|
||||
switch ut {
|
||||
case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
|
||||
op.Type = constants.AssetAuditOperatorTypeAdmin
|
||||
op.Name = fmt.Sprintf("后台用户#%d", uid)
|
||||
case constants.UserTypeAgent:
|
||||
op.Type = constants.AssetAuditOperatorTypeAgent
|
||||
op.Name = fmt.Sprintf("代理用户#%d", uid)
|
||||
case constants.UserTypeEnterprise:
|
||||
op.Type = constants.AssetAuditOperatorTypeEnterprise
|
||||
op.Name = fmt.Sprintf("企业用户#%d", uid)
|
||||
case constants.UserTypePersonalCustomer:
|
||||
op.Type = constants.AssetAuditOperatorTypePersonal
|
||||
op.Name = fmt.Sprintf("个人客户#%d", uid)
|
||||
default:
|
||||
op.Type = constants.AssetAuditOperatorTypeAdmin
|
||||
op.Name = fmt.Sprintf("用户#%d", uid)
|
||||
}
|
||||
return op
|
||||
}
|
||||
|
||||
// SystemOperator 构建系统操作者信息。
|
||||
func SystemOperator(name string) OperatorInfo {
|
||||
n := strings.TrimSpace(name)
|
||||
if n == "" {
|
||||
n = "系统任务"
|
||||
}
|
||||
return OperatorInfo{
|
||||
ID: nil,
|
||||
Type: constants.AssetAuditOperatorTypeSystem,
|
||||
Name: n,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildErrorInfo 从统一错误提取审计错误码与错误摘要。
|
||||
func BuildErrorInfo(err error) (string, string) {
|
||||
if err == nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
var appErr *apperrors.AppError
|
||||
if strings.TrimSpace(err.Error()) == "" {
|
||||
return "", "未知错误"
|
||||
}
|
||||
|
||||
if stderrors.As(err, &appErr) && appErr != nil {
|
||||
return strconv.Itoa(appErr.Code), appErr.Message
|
||||
}
|
||||
|
||||
return "", err.Error()
|
||||
}
|
||||
|
||||
// NormalizeAssetType 统一资产类型值。
|
||||
func NormalizeAssetType(assetType string) string {
|
||||
switch strings.TrimSpace(assetType) {
|
||||
case "card":
|
||||
return constants.AssetTypeIotCard
|
||||
case "device":
|
||||
return constants.AssetTypeDevice
|
||||
default:
|
||||
return strings.TrimSpace(assetType)
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeAndTrimJSONB(data map[string]any) model.JSONB {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sanitized := sanitizeMap(data)
|
||||
raw, err := sonic.Marshal(sanitized)
|
||||
if err != nil {
|
||||
return model.JSONB{"_marshal_error": "序列化失败"}
|
||||
}
|
||||
if len(raw) <= constants.AssetAuditDataMaxBytes {
|
||||
return model.JSONB(sanitized)
|
||||
}
|
||||
|
||||
preview := string(raw)
|
||||
if len(preview) > constants.AssetAuditPreviewMaxBytes {
|
||||
preview = preview[:constants.AssetAuditPreviewMaxBytes]
|
||||
}
|
||||
|
||||
return model.JSONB{
|
||||
"_truncated": true,
|
||||
"_original_bytes": len(raw),
|
||||
"_preview": preview,
|
||||
"_message": "数据超过大小限制,已裁剪",
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeMap(source map[string]any) map[string]any {
|
||||
out := make(map[string]any, len(source))
|
||||
for k, v := range source {
|
||||
if isSensitiveKey(k) {
|
||||
out[k] = "******"
|
||||
continue
|
||||
}
|
||||
out[k] = sanitizeValue(v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sanitizeValue(v any) any {
|
||||
switch vv := v.(type) {
|
||||
case map[string]any:
|
||||
return sanitizeMap(vv)
|
||||
case model.JSONB:
|
||||
return sanitizeMap(map[string]any(vv))
|
||||
case []map[string]any:
|
||||
arr := make([]any, 0, len(vv))
|
||||
for _, item := range vv {
|
||||
arr = append(arr, sanitizeMap(item))
|
||||
}
|
||||
return arr
|
||||
case []any:
|
||||
arr := make([]any, 0, len(vv))
|
||||
for _, item := range vv {
|
||||
arr = append(arr, sanitizeValue(item))
|
||||
}
|
||||
return arr
|
||||
case string:
|
||||
return limitString(vv, 2000)
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func isSensitiveKey(key string) bool {
|
||||
lower := strings.ToLower(strings.TrimSpace(key))
|
||||
for _, kw := range []string{"password", "passwd", "pwd", "secret", "token", "wifi_password", "wifipwd"} {
|
||||
if strings.Contains(lower, kw) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nonNegative(v int) int {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func limitString(value string, max int) string {
|
||||
if max <= 0 || len(value) <= max {
|
||||
return value
|
||||
}
|
||||
return value[:max]
|
||||
}
|
||||
|
||||
func strPtr(v string) *string {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
return nil
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
func uintPtr(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
149
internal/service/asset_audit/service.go
Normal file
149
internal/service/asset_audit/service.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Package asset_audit 提供资产操作审计日志服务。
|
||||
package asset_audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AssetOperationLogStore 资产操作日志存储接口。
|
||||
type AssetOperationLogStore interface {
|
||||
Create(ctx context.Context, log *model.AssetOperationLog) error
|
||||
ListByAssetPaged(
|
||||
ctx context.Context,
|
||||
assetType string,
|
||||
assetID uint,
|
||||
page int,
|
||||
pageSize int,
|
||||
operationType string,
|
||||
resultStatus string,
|
||||
) ([]*model.AssetOperationLog, int64, error)
|
||||
}
|
||||
|
||||
// OperationLogger 资产审计记录接口。
|
||||
type OperationLogger interface {
|
||||
LogOperation(ctx context.Context, log *model.AssetOperationLog)
|
||||
}
|
||||
|
||||
// Service 资产审计服务。
|
||||
type Service struct {
|
||||
store AssetOperationLogStore
|
||||
}
|
||||
|
||||
// ListByAssetParams 按资产查询日志参数。
|
||||
type ListByAssetParams struct {
|
||||
AssetType string
|
||||
AssetID uint
|
||||
Page int
|
||||
PageSize int
|
||||
OperationType string
|
||||
ResultStatus string
|
||||
}
|
||||
|
||||
// NewService 创建资产审计服务实例。
|
||||
func NewService(store AssetOperationLogStore) *Service {
|
||||
return &Service{store: store}
|
||||
}
|
||||
|
||||
// LogOperation 记录资产操作日志(异步写入,不阻塞主流程)。
|
||||
func (s *Service) LogOperation(ctx context.Context, log *model.AssetOperationLog) {
|
||||
if s == nil || s.store == nil || log == nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := s.store.Create(context.Background(), log); err != nil {
|
||||
logger.GetAppLogger().Error("写入资产操作日志失败",
|
||||
zap.String("asset_type", log.AssetType),
|
||||
zap.Uint("asset_id", log.AssetID),
|
||||
zap.String("operation_type", log.OperationType),
|
||||
zap.String("result_status", log.ResultStatus),
|
||||
zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// ListByAsset 按资产分页查询操作日志。
|
||||
func (s *Service) ListByAsset(ctx context.Context, params ListByAssetParams) (*dto.AssetOperationLogListResponse, error) {
|
||||
if s == nil || s.store == nil {
|
||||
return &dto.AssetOperationLogListResponse{
|
||||
Total: 0,
|
||||
Page: 1,
|
||||
PageSize: constants.DefaultPageSize,
|
||||
Items: []*dto.AssetOperationLogItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
page := params.Page
|
||||
pageSize := params.PageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = constants.DefaultPageSize
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
logs, total, err := s.store.ListByAssetPaged(
|
||||
ctx,
|
||||
NormalizeAssetType(params.AssetType),
|
||||
params.AssetID,
|
||||
page,
|
||||
pageSize,
|
||||
params.OperationType,
|
||||
params.ResultStatus,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]*dto.AssetOperationLogItem, 0, len(logs))
|
||||
for _, item := range logs {
|
||||
items = append(items, toAssetOperationLogItem(item))
|
||||
}
|
||||
|
||||
return &dto.AssetOperationLogListResponse{
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toAssetOperationLogItem(log *model.AssetOperationLog) *dto.AssetOperationLogItem {
|
||||
if log == nil {
|
||||
return nil
|
||||
}
|
||||
return &dto.AssetOperationLogItem{
|
||||
ID: log.ID,
|
||||
CreatedAt: log.CreatedAt,
|
||||
OperatorID: log.OperatorID,
|
||||
OperatorType: log.OperatorType,
|
||||
OperatorName: log.OperatorName,
|
||||
AssetType: log.AssetType,
|
||||
AssetID: log.AssetID,
|
||||
AssetIdentifier: log.AssetIdentifier,
|
||||
OperationType: log.OperationType,
|
||||
OperationDesc: log.OperationDesc,
|
||||
BeforeData: map[string]any(log.BeforeData),
|
||||
AfterData: map[string]any(log.AfterData),
|
||||
RequestID: log.RequestID,
|
||||
IPAddress: log.IPAddress,
|
||||
UserAgent: log.UserAgent,
|
||||
RequestPath: log.RequestPath,
|
||||
RequestMethod: log.RequestMethod,
|
||||
ResultStatus: log.ResultStatus,
|
||||
ErrorCode: log.ErrorCode,
|
||||
ErrorMsg: log.ErrorMsg,
|
||||
BatchTotal: log.BatchTotal,
|
||||
SuccessCount: log.SuccessCount,
|
||||
FailCount: log.FailCount,
|
||||
}
|
||||
}
|
||||
88
internal/service/device/audit.go
Normal file
88
internal/service/device/audit.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"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/pkg/constants"
|
||||
)
|
||||
|
||||
// AssetAuditService 资产审计服务接口。
|
||||
type AssetAuditService interface {
|
||||
LogOperation(ctx context.Context, log *model.AssetOperationLog)
|
||||
}
|
||||
|
||||
func (s *Service) logDeviceAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAuditService == nil {
|
||||
return
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
if p.AssetType == "" {
|
||||
p.AssetType = constants.AssetTypeDevice
|
||||
}
|
||||
s.assetAuditService.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
func (s *Service) logDeviceOperation(
|
||||
ctx context.Context,
|
||||
operationType string,
|
||||
operationDesc string,
|
||||
resultStatus string,
|
||||
device *model.Device,
|
||||
beforeData map[string]any,
|
||||
afterData map[string]any,
|
||||
batchTotal int,
|
||||
successCount int,
|
||||
failCount int,
|
||||
err error,
|
||||
) {
|
||||
if strings.TrimSpace(operationDesc) == "" {
|
||||
operationDesc = operationType
|
||||
}
|
||||
if strings.TrimSpace(resultStatus) == "" {
|
||||
if err != nil {
|
||||
resultStatus = constants.AssetAuditResultFailed
|
||||
} else {
|
||||
resultStatus = constants.AssetAuditResultSuccess
|
||||
}
|
||||
}
|
||||
|
||||
params := assetAuditSvc.BuildLogParams{
|
||||
OperationType: operationType,
|
||||
OperationDesc: operationDesc,
|
||||
ResultStatus: resultStatus,
|
||||
BeforeData: beforeData,
|
||||
AfterData: afterData,
|
||||
BatchTotal: batchTotal,
|
||||
SuccessCount: successCount,
|
||||
FailCount: failCount,
|
||||
}
|
||||
if device != nil {
|
||||
params.AssetID = device.ID
|
||||
params.AssetIdentifier = device.VirtualNo
|
||||
}
|
||||
if err != nil {
|
||||
params.ErrorCode, params.ErrorMsg = assetAuditSvc.BuildErrorInfo(err)
|
||||
}
|
||||
|
||||
s.logDeviceAudit(ctx, params)
|
||||
}
|
||||
|
||||
func deviceSnapshot(device *model.Device) map[string]any {
|
||||
if device == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": device.ID,
|
||||
"virtual_no": device.VirtualNo,
|
||||
"shop_id": device.ShopID,
|
||||
"status": device.Status,
|
||||
"series_id": device.SeriesID,
|
||||
"enable_polling": device.EnablePolling,
|
||||
"realname_policy": device.RealnamePolicy,
|
||||
}
|
||||
}
|
||||
@@ -77,37 +77,195 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
|
||||
device, err := s.deviceStore.GetByID(ctx, deviceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "设备不存在")
|
||||
appErr := errors.New(errors.CodeNotFound, "设备不存在")
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"device_id": deviceID,
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"device_id": deviceID,
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.SlotPosition > device.MaxSimSlots {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "插槽位置超出设备最大插槽数")
|
||||
appErr := errors.New(errors.CodeInvalidParam, "插槽位置超出设备最大插槽数")
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡被拒绝",
|
||||
constants.AssetAuditResultDenied,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
existingBinding, err := s.deviceSimBindingStore.GetByDeviceAndSlot(ctx, device.ID, req.SlotPosition)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
if existingBinding != nil {
|
||||
return nil, errors.New(errors.CodeConflict, "该插槽已有绑定的卡")
|
||||
appErr := errors.New(errors.CodeConflict, "该插槽已有绑定的卡")
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡被拒绝",
|
||||
constants.AssetAuditResultDenied,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
card, err := s.iotCardStore.GetByID(ctx, req.IotCardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeIotCardNotFound)
|
||||
appErr := errors.New(errors.CodeIotCardNotFound)
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
activeBinding, err := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
if activeBinding != nil {
|
||||
return nil, errors.New(errors.CodeIotCardBoundToDevice, "该卡已绑定到其他设备")
|
||||
appErr := errors.New(errors.CodeIotCardBoundToDevice, "该卡已绑定到其他设备")
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡被拒绝",
|
||||
constants.AssetAuditResultDenied,
|
||||
device,
|
||||
map[string]any{
|
||||
"device": deviceSnapshot(device),
|
||||
"card": map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"status": card.Status,
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"iot_card_id": req.IotCardID,
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
binding := &model.DeviceSimBinding{
|
||||
@@ -118,6 +276,28 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
|
||||
}
|
||||
|
||||
if err := s.deviceSimBindingStore.Create(ctx, binding); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{
|
||||
"device": deviceSnapshot(device),
|
||||
"card": map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"status": card.Status,
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"slot_position": req.SlotPosition,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -130,6 +310,32 @@ func (s *Service) BindCard(ctx context.Context, deviceID uint, req *dto.BindCard
|
||||
)
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceBindCard,
|
||||
"设备绑卡",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{
|
||||
"device": deviceSnapshot(device),
|
||||
"card": map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"status": card.Status,
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"binding_id": binding.ID,
|
||||
"slot_position": req.SlotPosition,
|
||||
"iot_card_id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
},
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
|
||||
return &dto.BindCardToDeviceResponse{
|
||||
BindingID: binding.ID,
|
||||
Message: "绑定成功",
|
||||
@@ -140,20 +346,97 @@ func (s *Service) UnbindCard(ctx context.Context, deviceID uint, cardID uint) (*
|
||||
device, err := s.deviceStore.GetByID(ctx, deviceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "设备不存在")
|
||||
appErr := errors.New(errors.CodeNotFound, "设备不存在")
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceUnbindCard,
|
||||
"设备解绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"device_id": deviceID,
|
||||
"iot_card_id": cardID,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceUnbindCard,
|
||||
"设备解绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"device_id": deviceID,
|
||||
"iot_card_id": cardID,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
binding, err := s.deviceSimBindingStore.GetByDeviceAndCard(ctx, device.ID, cardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "该卡未绑定到此设备")
|
||||
appErr := errors.New(errors.CodeNotFound, "该卡未绑定到此设备")
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceUnbindCard,
|
||||
"设备解绑卡被拒绝",
|
||||
constants.AssetAuditResultDenied,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"iot_card_id": cardID},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
appErr,
|
||||
)
|
||||
return nil, appErr
|
||||
}
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceUnbindCard,
|
||||
"设备解绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"iot_card_id": cardID},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.deviceSimBindingStore.Unbind(ctx, binding.ID); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceUnbindCard,
|
||||
"设备解绑卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{
|
||||
"device": deviceSnapshot(device),
|
||||
"binding_id": binding.ID,
|
||||
"iot_card_id": binding.IotCardID,
|
||||
},
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -166,6 +449,27 @@ func (s *Service) UnbindCard(ctx context.Context, deviceID uint, cardID uint) (*
|
||||
)
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceUnbindCard,
|
||||
"设备解绑卡",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{
|
||||
"device": deviceSnapshot(device),
|
||||
"binding_id": binding.ID,
|
||||
"iot_card_id": binding.IotCardID,
|
||||
},
|
||||
map[string]any{
|
||||
"iot_card_id": cardID,
|
||||
"unbind": true,
|
||||
},
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
|
||||
return &dto.UnbindCardFromDeviceResponse{
|
||||
Message: "解绑成功",
|
||||
}, nil
|
||||
|
||||
@@ -4,26 +4,28 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// getDeviceIMEI 通过标识符获取设备并验证 IMEI 存在
|
||||
// getGatewayDevice 通过标识符获取设备并验证 IMEI 存在
|
||||
// 提供统一的设备查找 + IMEI 校验逻辑,供所有 Gateway 代理方法复用
|
||||
func (s *Service) getDeviceIMEI(ctx context.Context, identifier string) (string, error) {
|
||||
func (s *Service) getGatewayDevice(ctx context.Context, identifier string) (*model.Device, string, error) {
|
||||
device, err := s.deviceStore.GetByIdentifier(ctx, identifier)
|
||||
if err != nil {
|
||||
return "", errors.New(errors.CodeNotFound, "设备不存在或无权限访问")
|
||||
return nil, "", errors.New(errors.CodeNotFound, "设备不存在或无权限访问")
|
||||
}
|
||||
if device.IMEI == "" {
|
||||
return "", errors.New(errors.CodeInvalidParam, "该设备未配置 IMEI,无法调用网关接口")
|
||||
return nil, "", errors.New(errors.CodeInvalidParam, "该设备未配置 IMEI,无法调用网关接口")
|
||||
}
|
||||
return device.IMEI, nil
|
||||
return device, device.IMEI, nil
|
||||
}
|
||||
|
||||
// GatewayGetDeviceInfo 通过标识符查询设备网关信息
|
||||
func (s *Service) GatewayGetDeviceInfo(ctx context.Context, identifier string) (*gateway.DeviceInfoResp, error) {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
_, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -34,7 +36,7 @@ func (s *Service) GatewayGetDeviceInfo(ctx context.Context, identifier string) (
|
||||
|
||||
// GatewayGetSlotInfo 通过标识符查询设备卡槽信息
|
||||
func (s *Service) GatewayGetSlotInfo(ctx context.Context, identifier string) (*gateway.SlotInfoResp, error) {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
_, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -45,73 +47,356 @@ func (s *Service) GatewayGetSlotInfo(ctx context.Context, identifier string) (*g
|
||||
|
||||
// GatewaySetSpeedLimit 通过标识符设置设备限速
|
||||
func (s *Service) GatewaySetSpeedLimit(ctx context.Context, identifier string, req *dto.SetSpeedLimitRequest) error {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
device, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSpeedLimit,
|
||||
"设备限速失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"identifier": identifier,
|
||||
"speed_limit": req.SpeedLimit,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return s.gatewayClient.SetSpeedLimit(ctx, &gateway.SpeedLimitReq{
|
||||
if err = s.gatewayClient.SetSpeedLimit(ctx, &gateway.SpeedLimitReq{
|
||||
DeviceID: imei,
|
||||
SpeedLimit: req.SpeedLimit,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSpeedLimit,
|
||||
"设备限速失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"speed_limit": req.SpeedLimit},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSpeedLimit,
|
||||
"设备限速",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"speed_limit": req.SpeedLimit},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GatewaySetWiFi 通过标识符设置设备 WiFi
|
||||
func (s *Service) GatewaySetWiFi(ctx context.Context, identifier string, req *dto.SetWiFiRequest) error {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
device, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSetWiFi,
|
||||
"设备设置WiFi失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"identifier": identifier,
|
||||
"card_no": req.CardNo,
|
||||
"ssid": req.SSID,
|
||||
"enabled": req.Enabled,
|
||||
"password": req.Password,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return s.gatewayClient.SetWiFi(ctx, &gateway.WiFiReq{
|
||||
if err = s.gatewayClient.SetWiFi(ctx, &gateway.WiFiReq{
|
||||
CardNo: req.CardNo,
|
||||
DeviceID: imei,
|
||||
SSID: req.SSID,
|
||||
Password: req.Password,
|
||||
Enabled: req.Enabled,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSetWiFi,
|
||||
"设备设置WiFi失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"card_no": req.CardNo,
|
||||
"ssid": req.SSID,
|
||||
"enabled": req.Enabled,
|
||||
"password": req.Password,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSetWiFi,
|
||||
"设备设置WiFi",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{
|
||||
"card_no": req.CardNo,
|
||||
"ssid": req.SSID,
|
||||
"enabled": req.Enabled,
|
||||
"password": req.Password,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GatewaySwitchCard 通过标识符切换设备使用的卡
|
||||
func (s *Service) GatewaySwitchCard(ctx context.Context, identifier string, req *dto.SwitchCardRequest) error {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
device, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSwitchCard,
|
||||
"设备切卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"identifier": identifier,
|
||||
"target_iccid": req.TargetICCID,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return s.gatewayClient.SwitchCard(ctx, &gateway.SwitchCardReq{
|
||||
if err = s.gatewayClient.SwitchCard(ctx, &gateway.SwitchCardReq{
|
||||
CardNo: imei,
|
||||
ICCID: req.TargetICCID,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSwitchCard,
|
||||
"设备切卡失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"target_iccid": req.TargetICCID},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSwitchCard,
|
||||
"设备切卡",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"target_iccid": req.TargetICCID},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GatewayRebootDevice 通过标识符重启设备
|
||||
func (s *Service) GatewayRebootDevice(ctx context.Context, identifier string) error {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
device, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceReboot,
|
||||
"设备重启失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{"identifier": identifier},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return s.gatewayClient.RebootDevice(ctx, &gateway.DeviceOperationReq{
|
||||
if err = s.gatewayClient.RebootDevice(ctx, &gateway.DeviceOperationReq{
|
||||
DeviceID: imei,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceReboot,
|
||||
"设备重启失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceReboot,
|
||||
"设备重启",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GatewayResetDevice 通过标识符恢复设备出厂设置
|
||||
func (s *Service) GatewayResetDevice(ctx context.Context, identifier string) error {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
device, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceReset,
|
||||
"设备恢复出厂失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{"identifier": identifier},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return s.gatewayClient.ResetDevice(ctx, &gateway.DeviceOperationReq{
|
||||
if err = s.gatewayClient.ResetDevice(ctx, &gateway.DeviceOperationReq{
|
||||
DeviceID: imei,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceReset,
|
||||
"设备恢复出厂失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceReset,
|
||||
"设备恢复出厂",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GatewaySwitchMode 通过标识符设置设备切卡模式
|
||||
func (s *Service) GatewaySwitchMode(ctx context.Context, identifier string, req *dto.SetSwitchModeRequest) error {
|
||||
imei, err := s.getDeviceIMEI(ctx, identifier)
|
||||
device, imei, err := s.getGatewayDevice(ctx, identifier)
|
||||
if err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSwitchMode,
|
||||
"设备切卡模式切换失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
nil,
|
||||
nil,
|
||||
map[string]any{
|
||||
"identifier": identifier,
|
||||
"switch_mode": req.SwitchMode,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return s.gatewayClient.SwitchMode(ctx, &gateway.SwitchModeReq{
|
||||
if err = s.gatewayClient.SwitchMode(ctx, &gateway.SwitchModeReq{
|
||||
CardNo: imei,
|
||||
SwitchMode: req.SwitchMode,
|
||||
})
|
||||
}); err != nil {
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSwitchMode,
|
||||
"设备切卡模式切换失败",
|
||||
constants.AssetAuditResultFailed,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"switch_mode": req.SwitchMode},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logDeviceOperation(
|
||||
ctx,
|
||||
constants.AssetAuditOpDeviceSwitchMode,
|
||||
"设备切卡模式切换",
|
||||
constants.AssetAuditResultSuccess,
|
||||
device,
|
||||
map[string]any{"device": deviceSnapshot(device)},
|
||||
map[string]any{"switch_mode": req.SwitchMode},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
56
internal/service/device_import/audit.go
Normal file
56
internal/service/device_import/audit.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package device_import
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// AssetAuditService 资产审计服务接口。
|
||||
type AssetAuditService interface {
|
||||
LogOperation(ctx context.Context, log *model.AssetOperationLog)
|
||||
}
|
||||
|
||||
func (s *Service) logDeviceImportAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAudit == nil {
|
||||
return
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
if p.OperationType == "" {
|
||||
p.OperationType = constants.AssetAuditOpDeviceImportTaskCreate
|
||||
}
|
||||
if p.AssetType == "" {
|
||||
p.AssetType = constants.AssetTypeDevice
|
||||
}
|
||||
s.assetAudit.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
func newDeviceImportAuditParams(
|
||||
taskID uint,
|
||||
taskNo string,
|
||||
req *dto.ImportDeviceRequest,
|
||||
resultStatus string,
|
||||
err error,
|
||||
) assetAuditSvc.BuildLogParams {
|
||||
afterData := map[string]any{}
|
||||
if req != nil {
|
||||
afterData["batch_no"] = req.BatchNo
|
||||
afterData["file_key"] = req.FileKey
|
||||
afterData["realname_policy"] = req.RealnamePolicy
|
||||
}
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
return assetAuditSvc.BuildLogParams{
|
||||
AssetID: taskID,
|
||||
AssetIdentifier: taskNo,
|
||||
OperationDesc: "创建设备导入任务",
|
||||
ResultStatus: resultStatus,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: afterData,
|
||||
}
|
||||
}
|
||||
@@ -20,24 +20,33 @@ type Service struct {
|
||||
db *gorm.DB
|
||||
importTaskStore *postgres.DeviceImportTaskStore
|
||||
queueClient *queue.Client
|
||||
assetAudit AssetAuditService
|
||||
}
|
||||
|
||||
type DeviceImportPayload struct {
|
||||
TaskID uint `json:"task_id"`
|
||||
}
|
||||
|
||||
func New(db *gorm.DB, importTaskStore *postgres.DeviceImportTaskStore, queueClient *queue.Client) *Service {
|
||||
func New(
|
||||
db *gorm.DB,
|
||||
importTaskStore *postgres.DeviceImportTaskStore,
|
||||
queueClient *queue.Client,
|
||||
assetAudit AssetAuditService,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
importTaskStore: importTaskStore,
|
||||
queueClient: queueClient,
|
||||
assetAudit: assetAudit,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportDeviceRequest) (*dto.ImportDeviceResponse, error) {
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
appErr := errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
s.logDeviceImportAudit(ctx, newDeviceImportAuditParams(0, "", req, constants.AssetAuditResultDenied, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
taskNo := s.importTaskStore.GenerateTaskNo(ctx)
|
||||
@@ -55,16 +64,22 @@ func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportDeviceReq
|
||||
task.Updater = userID
|
||||
|
||||
if err := s.importTaskStore.Create(ctx, task); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "创建导入任务失败")
|
||||
appErr := errors.Wrap(errors.CodeInternalError, err, "创建导入任务失败")
|
||||
s.logDeviceImportAudit(ctx, newDeviceImportAuditParams(0, taskNo, req, constants.AssetAuditResultFailed, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
payload := DeviceImportPayload{TaskID: task.ID}
|
||||
err := s.queueClient.EnqueueTask(ctx, constants.TaskTypeDeviceImport, payload)
|
||||
if err != nil {
|
||||
s.importTaskStore.UpdateStatus(ctx, task.ID, model.ImportTaskStatusFailed, "任务入队失败: "+err.Error())
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "任务入队失败")
|
||||
appErr := errors.Wrap(errors.CodeInternalError, err, "任务入队失败")
|
||||
s.logDeviceImportAudit(ctx, newDeviceImportAuditParams(task.ID, taskNo, req, constants.AssetAuditResultFailed, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
s.logDeviceImportAudit(ctx, newDeviceImportAuditParams(task.ID, taskNo, req, constants.AssetAuditResultSuccess, nil))
|
||||
|
||||
return &dto.ImportDeviceResponse{
|
||||
TaskID: task.ID,
|
||||
TaskNo: taskNo,
|
||||
|
||||
58
internal/service/iot_card/audit.go
Normal file
58
internal/service/iot_card/audit.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package iot_card
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// AssetAuditService 资产审计服务接口。
|
||||
type AssetAuditService interface {
|
||||
LogOperation(ctx context.Context, log *model.AssetOperationLog)
|
||||
}
|
||||
|
||||
func (s *Service) logCardAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAuditService == nil {
|
||||
return
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
if p.AssetType == "" {
|
||||
p.AssetType = constants.AssetTypeIotCard
|
||||
}
|
||||
s.assetAuditService.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
func (s *StopResumeService) logCardAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAuditService == nil {
|
||||
return
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
if p.AssetType == "" {
|
||||
p.AssetType = constants.AssetTypeIotCard
|
||||
}
|
||||
s.assetAuditService.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
func cardSnapshot(card *model.IotCard) map[string]any {
|
||||
if card == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"shop_id": card.ShopID,
|
||||
"status": card.Status,
|
||||
"series_id": card.SeriesID,
|
||||
"enable_polling": card.EnablePolling,
|
||||
"realname_policy": card.RealnamePolicy,
|
||||
"real_name_status": card.RealNameStatus,
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -64,6 +65,7 @@ type Service struct {
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
redis *redis.Client
|
||||
assetIdentifierStore *postgres.AssetIdentifierStore
|
||||
assetAuditService AssetAuditService
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -76,6 +78,7 @@ func New(
|
||||
packageSeriesStore *postgres.PackageSeriesStore,
|
||||
gatewayClient *gateway.Client,
|
||||
logger *zap.Logger,
|
||||
assetAuditService AssetAuditService,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
@@ -87,6 +90,7 @@ func New(
|
||||
packageSeriesStore: packageSeriesStore,
|
||||
gatewayClient: gatewayClient,
|
||||
logger: logger,
|
||||
assetAuditService: assetAuditService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,11 +345,37 @@ func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]str
|
||||
|
||||
func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandaloneCardsRequest, operatorID uint, operatorShopID *uint) (*dto.AllocateStandaloneCardsResponse, error) {
|
||||
if err := s.validateDirectSubordinate(ctx, operatorShopID, req.ToShopID); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardAllocate,
|
||||
OperationDesc: "单卡分配被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: map[string]any{
|
||||
"to_shop_id": req.ToShopID,
|
||||
"selection_type": req.SelectionType,
|
||||
"requested_count": len(req.ICCIDs),
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cards, err := s.getCardsForAllocation(ctx, req, operatorShopID)
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardAllocate,
|
||||
OperationDesc: "单卡分配执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: map[string]any{
|
||||
"to_shop_id": req.ToShopID,
|
||||
"selection_type": req.SelectionType,
|
||||
"requested_count": len(req.ICCIDs),
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -401,6 +431,19 @@ func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandalone
|
||||
}
|
||||
|
||||
if len(cardIDs) == 0 {
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardAllocate,
|
||||
OperationDesc: "单卡分配被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorMsg: "无可分配卡",
|
||||
BatchTotal: len(cards),
|
||||
FailCount: len(failedItems),
|
||||
AfterData: map[string]any{
|
||||
"to_shop_id": req.ToShopID,
|
||||
"failed_items": failedItems,
|
||||
"selection_type": req.SelectionType,
|
||||
},
|
||||
})
|
||||
return &dto.AllocateStandaloneCardsResponse{
|
||||
TotalCount: len(cards),
|
||||
SuccessCount: 0,
|
||||
@@ -426,6 +469,21 @@ func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandalone
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardAllocate,
|
||||
OperationDesc: "单卡分配执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(cards),
|
||||
SuccessCount: len(cardIDs),
|
||||
FailCount: len(failedItems),
|
||||
AfterData: map[string]any{
|
||||
"to_shop_id": req.ToShopID,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -436,6 +494,32 @@ func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandalone
|
||||
}
|
||||
}
|
||||
|
||||
beforeData := make([]map[string]any, 0, len(cards))
|
||||
for _, card := range cards {
|
||||
beforeData = append(beforeData, map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"shop_id": card.ShopID,
|
||||
"status": card.Status,
|
||||
})
|
||||
}
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardAllocate,
|
||||
OperationDesc: "单卡分配",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BatchTotal: len(cards),
|
||||
SuccessCount: len(cardIDs),
|
||||
FailCount: len(failedItems),
|
||||
BeforeData: map[string]any{
|
||||
"cards": beforeData,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"to_shop_id": req.ToShopID,
|
||||
"new_status": newStatus,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
|
||||
return &dto.AllocateStandaloneCardsResponse{
|
||||
TotalCount: len(cards),
|
||||
SuccessCount: len(cardIDs),
|
||||
@@ -449,6 +533,18 @@ func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCard
|
||||
// 1. 查询卡列表
|
||||
cards, err := s.getCardsForRecall(ctx, req)
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRecall,
|
||||
OperationDesc: "单卡回收执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: map[string]any{
|
||||
"selection_type": req.SelectionType,
|
||||
"requested_count": len(req.ICCIDs),
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -535,6 +631,18 @@ func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCard
|
||||
}
|
||||
|
||||
if len(cardIDs) == 0 {
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRecall,
|
||||
OperationDesc: "单卡回收被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorMsg: "无可回收卡",
|
||||
BatchTotal: len(cards),
|
||||
FailCount: len(failedItems),
|
||||
AfterData: map[string]any{
|
||||
"failed_items": failedItems,
|
||||
"selection_type": req.SelectionType,
|
||||
},
|
||||
})
|
||||
return &dto.RecallStandaloneCardsResponse{
|
||||
TotalCount: len(cards),
|
||||
SuccessCount: 0,
|
||||
@@ -570,6 +678,20 @@ func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCard
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRecall,
|
||||
OperationDesc: "单卡回收执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(cards),
|
||||
SuccessCount: len(cardIDs),
|
||||
FailCount: len(failedItems),
|
||||
AfterData: map[string]any{
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -580,6 +702,32 @@ func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCard
|
||||
}
|
||||
}
|
||||
|
||||
beforeData := make([]map[string]any, 0, len(successCards))
|
||||
for _, card := range successCards {
|
||||
beforeData = append(beforeData, map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"shop_id": card.ShopID,
|
||||
"status": card.Status,
|
||||
})
|
||||
}
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRecall,
|
||||
OperationDesc: "单卡回收",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BatchTotal: len(cards),
|
||||
SuccessCount: len(cardIDs),
|
||||
FailCount: len(failedItems),
|
||||
BeforeData: map[string]any{
|
||||
"cards": beforeData,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"to_shop_id": newShopID,
|
||||
"new_status": newStatus,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
|
||||
return &dto.RecallStandaloneCardsResponse{
|
||||
TotalCount: len(cards),
|
||||
SuccessCount: len(cardIDs),
|
||||
@@ -750,10 +898,35 @@ func (s *Service) buildRecallRecords(successCards []*model.IotCard, toShopID *ui
|
||||
func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCardSeriesBindngRequest, operatorShopID *uint) (*dto.BatchSetCardSeriesBindngResponse, error) {
|
||||
cards, err := s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs)
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cards) == 0 {
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorMsg: "卡不存在",
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
FailCount: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
})
|
||||
return &dto.BatchSetCardSeriesBindngResponse{
|
||||
SuccessCount: 0,
|
||||
FailCount: len(req.ICCIDs),
|
||||
@@ -772,12 +945,53 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
packageSeries, err = s.packageSeriesStore.GetByID(ctx, req.SeriesID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在或已禁用")
|
||||
denyErr := errors.New(errors.CodeNotFound, "套餐系列不存在或已禁用")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
})
|
||||
return nil, denyErr
|
||||
}
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
if packageSeries.Status != 1 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "套餐系列不存在或已禁用")
|
||||
denyErr := errors.New(errors.CodeInvalidParam, "套餐系列不存在或已禁用")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"iccids": req.ICCIDs,
|
||||
},
|
||||
})
|
||||
return nil, denyErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,10 +1050,67 @@ func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCa
|
||||
seriesIDPtr = &req.SeriesID
|
||||
}
|
||||
if err := s.iotCardStore.BatchUpdateSeriesID(ctx, successCardIDs, seriesIDPtr); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: "卡系列绑定执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
SuccessCount: len(successCardIDs),
|
||||
FailCount: len(failedItems),
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"success_ids": successCardIDs,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resultStatus := constants.AssetAuditResultSuccess
|
||||
operationDesc := "批量设置卡系列绑定"
|
||||
errorMsg := ""
|
||||
if len(successCardIDs) == 0 && len(failedItems) > 0 {
|
||||
resultStatus = constants.AssetAuditResultDenied
|
||||
operationDesc = "卡系列绑定被拒绝"
|
||||
errorMsg = "无可操作卡"
|
||||
}
|
||||
beforeCards := make([]map[string]any, 0, len(successCardIDs))
|
||||
successSet := make(map[uint]struct{}, len(successCardIDs))
|
||||
for _, id := range successCardIDs {
|
||||
successSet[id] = struct{}{}
|
||||
}
|
||||
for _, card := range cards {
|
||||
if _, ok := successSet[card.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
beforeCards = append(beforeCards, map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"series_id": card.SeriesID,
|
||||
})
|
||||
}
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardSeriesBinding,
|
||||
OperationDesc: operationDesc,
|
||||
ResultStatus: resultStatus,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(req.ICCIDs),
|
||||
SuccessCount: len(successCardIDs),
|
||||
FailCount: len(failedItems),
|
||||
BeforeData: map[string]any{
|
||||
"cards": beforeCards,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"series_id": req.SeriesID,
|
||||
"success_ids": successCardIDs,
|
||||
"failed_items": failedItems,
|
||||
},
|
||||
})
|
||||
|
||||
return &dto.BatchSetCardSeriesBindngResponse{
|
||||
SuccessCount: len(successCardIDs),
|
||||
FailCount: len(failedItems),
|
||||
@@ -1052,19 +1323,74 @@ func (s *Service) UpdatePollingStatus(ctx context.Context, cardID uint, enablePo
|
||||
card, err := s.iotCardStore.GetByID(ctx, cardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
denyErr := errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "更新卡轮询状态被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AssetIdentifier: "",
|
||||
AfterData: map[string]any{
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "更新卡轮询状态执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AfterData: map[string]any{
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否需要更新
|
||||
if card.EnablePolling == enablePolling {
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "更新卡轮询状态",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"enable_polling": card.EnablePolling,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
return nil // 状态未变化
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
card.EnablePolling = enablePolling
|
||||
if err := s.iotCardStore.Update(ctx, card); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "更新卡轮询状态执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"enable_polling": !enablePolling,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1082,6 +1408,20 @@ func (s *Service) UpdatePollingStatus(ctx context.Context, cardID uint, enablePo
|
||||
}
|
||||
}
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "更新卡轮询状态",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"enable_polling": !enablePolling,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1093,6 +1433,21 @@ func (s *Service) BatchUpdatePollingStatus(ctx context.Context, cardIDs []uint,
|
||||
|
||||
// 批量更新数据库
|
||||
if err := s.iotCardStore.BatchUpdatePollingStatus(ctx, cardIDs, enablePolling); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "批量更新卡轮询状态执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(cardIDs),
|
||||
FailCount: len(cardIDs),
|
||||
AfterData: map[string]any{
|
||||
"card_ids": cardIDs,
|
||||
"enable_polling": enablePolling,
|
||||
"trigger_source": "batch",
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1112,6 +1467,19 @@ func (s *Service) BatchUpdatePollingStatus(ctx context.Context, cardIDs []uint,
|
||||
}
|
||||
}
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardPollingStatus,
|
||||
OperationDesc: "批量更新卡轮询状态",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BatchTotal: len(cardIDs),
|
||||
SuccessCount: len(cardIDs),
|
||||
AfterData: map[string]any{
|
||||
"card_ids": cardIDs,
|
||||
"enable_polling": enablePolling,
|
||||
"trigger_source": "batch",
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1120,12 +1488,42 @@ func (s *Service) DeleteCard(ctx context.Context, cardID uint) error {
|
||||
card, err := s.iotCardStore.GetByID(ctx, cardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
denyErr := errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardDelete,
|
||||
OperationDesc: "删除卡被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardDelete,
|
||||
OperationDesc: "删除卡执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.iotCardStore.Delete(ctx, cardID); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardDelete,
|
||||
OperationDesc: "删除卡执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1139,6 +1537,18 @@ func (s *Service) DeleteCard(ctx context.Context, cardID uint) error {
|
||||
s.pollingCallback.OnCardDeleted(ctx, cardID)
|
||||
}
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardDelete,
|
||||
OperationDesc: "删除卡",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
AfterData: map[string]any{
|
||||
"deleted": true,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1147,9 +1557,39 @@ func (s *Service) BatchDeleteCards(ctx context.Context, cardIDs []uint) error {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
cards, queryErr := s.iotCardStore.GetByIDs(ctx, cardIDs)
|
||||
if queryErr != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(queryErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardBatchDelete,
|
||||
OperationDesc: "批量删除卡执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(cardIDs),
|
||||
FailCount: len(cardIDs),
|
||||
AfterData: map[string]any{
|
||||
"card_ids": cardIDs,
|
||||
},
|
||||
})
|
||||
return queryErr
|
||||
}
|
||||
|
||||
// 批量软删除
|
||||
if err := s.iotCardStore.BatchDelete(ctx, cardIDs); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardBatchDelete,
|
||||
OperationDesc: "批量删除卡执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BatchTotal: len(cardIDs),
|
||||
FailCount: len(cardIDs),
|
||||
AfterData: map[string]any{
|
||||
"card_ids": cardIDs,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1161,6 +1601,24 @@ func (s *Service) BatchDeleteCards(ctx context.Context, cardIDs []uint) error {
|
||||
s.pollingCallback.OnCardDeleted(ctx, cardID)
|
||||
}
|
||||
}
|
||||
beforeCards := make([]map[string]any, 0, len(cards))
|
||||
for _, card := range cards {
|
||||
beforeCards = append(beforeCards, cardSnapshot(card))
|
||||
}
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardBatchDelete,
|
||||
OperationDesc: "批量删除卡",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BatchTotal: len(cardIDs),
|
||||
SuccessCount: len(cardIDs),
|
||||
BeforeData: map[string]any{
|
||||
"cards": beforeCards,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"card_ids": cardIDs,
|
||||
"deleted": true,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1171,19 +1629,75 @@ func (s *Service) UpdateRealnamePolicy(ctx context.Context, cardID uint, realnam
|
||||
card, err := s.iotCardStore.GetByID(ctx, cardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
denyErr := errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnamePolicy,
|
||||
OperationDesc: "更新卡实名认证策略被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AfterData: map[string]any{
|
||||
"realname_policy": realnamePolicy,
|
||||
},
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
|
||||
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnamePolicy,
|
||||
OperationDesc: "更新卡实名认证策略执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AfterData: map[string]any{
|
||||
"realname_policy": realnamePolicy,
|
||||
},
|
||||
})
|
||||
return wrapErr
|
||||
}
|
||||
|
||||
// 幂等检查
|
||||
if card.RealnamePolicy == realnamePolicy {
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnamePolicy,
|
||||
OperationDesc: "更新卡实名认证策略",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"realname_policy": card.RealnamePolicy,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"realname_policy": realnamePolicy,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
if err := s.iotCardStore.UpdateRealnamePolicy(ctx, cardID, realnamePolicy); err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新实名认证策略失败")
|
||||
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "更新实名认证策略失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnamePolicy,
|
||||
OperationDesc: "更新卡实名认证策略执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"realname_policy": card.RealnamePolicy,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"realname_policy": realnamePolicy,
|
||||
},
|
||||
})
|
||||
return wrapErr
|
||||
}
|
||||
|
||||
s.logger.Info("更新卡实名认证策略",
|
||||
@@ -1191,6 +1705,20 @@ func (s *Service) UpdateRealnamePolicy(ctx context.Context, cardID uint, realnam
|
||||
zap.String("realname_policy", realnamePolicy),
|
||||
)
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnamePolicy,
|
||||
OperationDesc: "更新卡实名认证策略",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"realname_policy": card.RealnamePolicy,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"realname_policy": realnamePolicy,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1199,15 +1727,54 @@ func (s *Service) UpdateRealnamePolicy(ctx context.Context, cardID uint, realnam
|
||||
func (s *Service) ManualUpdateRealnameStatus(ctx context.Context, cardID uint, realNameStatus int) (*model.IotCard, error) {
|
||||
if realNameStatus != constants.RealNameStatusNotVerified &&
|
||||
realNameStatus != constants.RealNameStatusVerified {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "无效的实名状态")
|
||||
denyErr := errors.New(errors.CodeInvalidParam, "无效的实名状态")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnameStatus,
|
||||
OperationDesc: "手动更新卡实名状态被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": realNameStatus,
|
||||
},
|
||||
})
|
||||
return nil, denyErr
|
||||
}
|
||||
|
||||
card, err := s.iotCardStore.GetByID(ctx, cardID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
denyErr := errors.New(errors.CodeNotFound, "IoT卡不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnameStatus,
|
||||
OperationDesc: "手动更新卡实名状态被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": realNameStatus,
|
||||
},
|
||||
})
|
||||
return nil, denyErr
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
|
||||
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnameStatus,
|
||||
OperationDesc: "手动更新卡实名状态执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": realNameStatus,
|
||||
},
|
||||
})
|
||||
return nil, wrapErr
|
||||
}
|
||||
|
||||
oldStatus := card.RealNameStatus
|
||||
@@ -1227,12 +1794,46 @@ func (s *Service) ManualUpdateRealnameStatus(ctx context.Context, cardID uint, r
|
||||
}
|
||||
|
||||
if err := s.iotCardStore.UpdateFields(ctx, cardID, fields); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "更新卡实名状态失败")
|
||||
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "更新卡实名状态失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnameStatus,
|
||||
OperationDesc: "手动更新卡实名状态执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"real_name_status": oldStatus,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": realNameStatus,
|
||||
},
|
||||
})
|
||||
return nil, wrapErr
|
||||
}
|
||||
|
||||
freshCard, err := s.iotCardStore.GetByID(ctx, cardID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询更新后的IoT卡失败")
|
||||
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "查询更新后的IoT卡失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnameStatus,
|
||||
OperationDesc: "手动更新卡实名状态执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"real_name_status": oldStatus,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": realNameStatus,
|
||||
},
|
||||
})
|
||||
return nil, wrapErr
|
||||
}
|
||||
|
||||
if statusChanged {
|
||||
@@ -1249,6 +1850,22 @@ func (s *Service) ManualUpdateRealnameStatus(ctx context.Context, cardID uint, r
|
||||
zap.Int("new_status", realNameStatus),
|
||||
zap.Uint("operator_id", middleware.GetUserIDFromContext(ctx)))
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardRealnameStatus,
|
||||
OperationDesc: "手动更新卡实名状态",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: freshCard.ID,
|
||||
AssetIdentifier: freshCard.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"real_name_status": oldStatus,
|
||||
"first_realname_at": card.FirstRealnameAt,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"real_name_status": freshCard.RealNameStatus,
|
||||
"first_realname_at": freshCard.FirstRealnameAt,
|
||||
},
|
||||
})
|
||||
|
||||
return freshCard, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
"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/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
@@ -36,6 +37,7 @@ type StopResumeService struct {
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
gatewayClient *gateway.Client
|
||||
logger *zap.Logger
|
||||
assetAuditService AssetAuditService
|
||||
|
||||
maxRetries int
|
||||
retryInterval time.Duration
|
||||
@@ -49,6 +51,7 @@ func NewStopResumeService(
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore,
|
||||
gatewayClient *gateway.Client,
|
||||
logger *zap.Logger,
|
||||
assetAuditService AssetAuditService,
|
||||
) *StopResumeService {
|
||||
return &StopResumeService{
|
||||
redis: redis,
|
||||
@@ -57,6 +60,7 @@ func NewStopResumeService(
|
||||
deviceSimBindingStore: deviceSimBindingStore,
|
||||
gatewayClient: gatewayClient,
|
||||
logger: logger,
|
||||
assetAuditService: assetAuditService,
|
||||
maxRetries: 3,
|
||||
retryInterval: 2 * time.Second,
|
||||
}
|
||||
@@ -363,6 +367,16 @@ func (s *StopResumeService) ResumeCardIfStopped(ctx context.Context, carrierType
|
||||
func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) error {
|
||||
card, err := s.iotCardStore.GetByID(ctx, cardID)
|
||||
if err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: assetAuditSvc.SystemOperator("系统任务"),
|
||||
OperationType: constants.AssetAuditOpCardAutoStart,
|
||||
OperationDesc: "自动复机执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: cardID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -407,6 +421,21 @@ func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) e
|
||||
zap.Uint("card_id", cardID),
|
||||
zap.String("iccid", card.ICCID),
|
||||
zap.Error(err))
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: assetAuditSvc.SystemOperator("系统任务"),
|
||||
OperationType: constants.AssetAuditOpCardAutoStart,
|
||||
OperationDesc: "自动复机执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -420,6 +449,25 @@ func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) e
|
||||
zap.Uint("card_id", cardID),
|
||||
zap.String("iccid", card.ICCID),
|
||||
zap.Error(err))
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: assetAuditSvc.SystemOperator("系统任务"),
|
||||
OperationType: constants.AssetAuditOpCardAutoStart,
|
||||
OperationDesc: "自动复机执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOffline,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOnline,
|
||||
"stop_reason": "",
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -429,13 +477,52 @@ func (s *StopResumeService) resumeSingleCard(ctx context.Context, cardID uint) e
|
||||
zap.Uint("card_id", cardID),
|
||||
zap.String("iccid", card.ICCID))
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: assetAuditSvc.SystemOperator("系统任务"),
|
||||
OperationType: constants.AssetAuditOpCardAutoStart,
|
||||
OperationDesc: "自动复机",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOffline,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOnline,
|
||||
"stop_reason": "",
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopCardWithRetry 调用运营商停机接口(带重试机制),并更新 DB 停机原因
|
||||
func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.IotCard, stopReason string) error {
|
||||
operator := assetAuditSvc.SystemOperator("系统任务")
|
||||
operationType := constants.AssetAuditOpCardAutoStop
|
||||
operationDesc := "自动停卡"
|
||||
if stopReason == constants.StopReasonManual {
|
||||
operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
operationType = constants.AssetAuditOpCardManualStop
|
||||
operationDesc = "手动停卡"
|
||||
}
|
||||
|
||||
if s.gatewayClient == nil {
|
||||
return errors.New(errors.CodeInternalError, "Gateway 未配置,停复机操作不可用")
|
||||
failErr := errors.New(errors.CodeInternalError, "Gateway 未配置,停复机操作不可用")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(failErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: operator,
|
||||
OperationType: operationType,
|
||||
OperationDesc: operationDesc + "执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return failErr
|
||||
}
|
||||
|
||||
s.logger.Info("调用网关停机",
|
||||
@@ -470,9 +557,44 @@ func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.I
|
||||
zap.Uint("card_id", card.ID),
|
||||
zap.String("iccid", card.ICCID),
|
||||
zap.Error(updateErr))
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(updateErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: operator,
|
||||
OperationType: operationType,
|
||||
OperationDesc: operationDesc + "执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOffline,
|
||||
"stop_reason": stopReason,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
s.invalidatePollingCache(ctx, card.ID)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: operator,
|
||||
OperationType: operationType,
|
||||
OperationDesc: operationDesc,
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOffline,
|
||||
"stop_reason": stopReason,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -483,6 +605,26 @@ func (s *StopResumeService) stopCardWithRetry(ctx context.Context, card *model.I
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(lastErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
Operator: operator,
|
||||
OperationType: operationType,
|
||||
OperationDesc: operationDesc + "执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOffline,
|
||||
"stop_reason": stopReason,
|
||||
},
|
||||
})
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
@@ -529,11 +671,33 @@ func (s *StopResumeService) resumeCardWithRetry(ctx context.Context, card *model
|
||||
func (s *StopResumeService) ManualStopCard(ctx context.Context, iccid string) error {
|
||||
card, err := s.iotCardStore.GetByICCID(ctx, iccid)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeNotFound, "卡不存在")
|
||||
denyErr := errors.New(errors.CodeNotFound, "卡不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStop,
|
||||
OperationDesc: "手动停卡被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetIdentifier: iccid,
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
|
||||
if card.RealNameStatus != constants.RealNameStatusVerified {
|
||||
return errors.New(errors.CodeForbidden, "卡未实名,无法操作")
|
||||
denyErr := errors.New(errors.CodeForbidden, "卡未实名,无法操作")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStop,
|
||||
OperationDesc: "手动停卡被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
|
||||
// 检查绑定设备是否在复机保护期
|
||||
@@ -542,10 +706,37 @@ func (s *StopResumeService) ManualStopCard(ctx context.Context, iccid string) er
|
||||
if bindErr == nil && binding != nil {
|
||||
exists, _ := s.redis.Exists(ctx, constants.RedisDeviceProtectKey(binding.DeviceID, "start")).Result()
|
||||
if exists > 0 {
|
||||
return errors.New(errors.CodeForbidden, "设备复机保护期内,禁止停机")
|
||||
denyErr := errors.New(errors.CodeForbidden, "设备复机保护期内,禁止停机")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStop,
|
||||
OperationDesc: "手动停卡被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
AfterData: map[string]any{
|
||||
"device_id": binding.DeviceID,
|
||||
},
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
} else if bindErr != nil && !stderrors.Is(bindErr, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
|
||||
wrapErr := errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStop,
|
||||
OperationDesc: "手动停卡执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return wrapErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,11 +751,33 @@ func (s *StopResumeService) ManualStopCard(ctx context.Context, iccid string) er
|
||||
func (s *StopResumeService) ManualStartCard(ctx context.Context, iccid string) error {
|
||||
card, err := s.iotCardStore.GetByICCID(ctx, iccid)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeNotFound, "卡不存在")
|
||||
denyErr := errors.New(errors.CodeNotFound, "卡不存在")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetIdentifier: iccid,
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
|
||||
if card.RealNameStatus != constants.RealNameStatusVerified {
|
||||
return errors.New(errors.CodeForbidden, "卡未实名,无法操作")
|
||||
denyErr := errors.New(errors.CodeForbidden, "卡未实名,无法操作")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
|
||||
// 检查绑定设备是否在停机保护期
|
||||
@@ -573,15 +786,54 @@ func (s *StopResumeService) ManualStartCard(ctx context.Context, iccid string) e
|
||||
if bindErr == nil && binding != nil {
|
||||
exists, _ := s.redis.Exists(ctx, constants.RedisDeviceProtectKey(binding.DeviceID, "stop")).Result()
|
||||
if exists > 0 {
|
||||
return errors.New(errors.CodeForbidden, "设备停机保护期内,禁止复机")
|
||||
denyErr := errors.New(errors.CodeForbidden, "设备停机保护期内,禁止复机")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(denyErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
AfterData: map[string]any{
|
||||
"device_id": binding.DeviceID,
|
||||
},
|
||||
})
|
||||
return denyErr
|
||||
}
|
||||
} else if bindErr != nil && !stderrors.Is(bindErr, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
|
||||
wrapErr := errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定关系失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return wrapErr
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.resumeCardWithRetry(ctx, card); err != nil {
|
||||
return errors.Wrap(errors.CodeGatewayError, err, "调用运营商复机失败,请稍后重试")
|
||||
wrapErr := errors.Wrap(errors.CodeGatewayError, err, "调用运营商复机失败,请稍后重试")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: cardSnapshot(card),
|
||||
})
|
||||
return wrapErr
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@@ -590,11 +842,46 @@ func (s *StopResumeService) ManualStartCard(ctx context.Context, iccid string) e
|
||||
"resumed_at": now,
|
||||
"stop_reason": "",
|
||||
}); err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡状态失败")
|
||||
wrapErr := errors.Wrap(errors.CodeDatabaseError, err, "更新卡状态失败")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(wrapErr)
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机执行失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOnline,
|
||||
"stop_reason": "",
|
||||
},
|
||||
})
|
||||
return wrapErr
|
||||
}
|
||||
|
||||
s.invalidatePollingCache(ctx, card.ID)
|
||||
|
||||
s.logCardAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
OperationType: constants.AssetAuditOpCardManualStart,
|
||||
OperationDesc: "手动复机",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
AssetID: card.ID,
|
||||
AssetIdentifier: card.ICCID,
|
||||
BeforeData: map[string]any{
|
||||
"network_status": card.NetworkStatus,
|
||||
"stop_reason": card.StopReason,
|
||||
},
|
||||
AfterData: map[string]any{
|
||||
"network_status": constants.NetworkStatusOnline,
|
||||
"stop_reason": "",
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
58
internal/service/iot_card_import/audit.go
Normal file
58
internal/service/iot_card_import/audit.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package iot_card_import
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// AssetAuditService 资产审计服务接口。
|
||||
type AssetAuditService interface {
|
||||
LogOperation(ctx context.Context, log *model.AssetOperationLog)
|
||||
}
|
||||
|
||||
func (s *Service) logIotCardImportAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAudit == nil {
|
||||
return
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
if p.OperationType == "" {
|
||||
p.OperationType = constants.AssetAuditOpIotCardImportTaskCreate
|
||||
}
|
||||
if p.AssetType == "" {
|
||||
p.AssetType = constants.AssetTypeIotCard
|
||||
}
|
||||
s.assetAudit.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
func newIotCardImportAuditParams(
|
||||
taskID uint,
|
||||
taskNo string,
|
||||
req *dto.ImportIotCardRequest,
|
||||
resultStatus string,
|
||||
err error,
|
||||
) assetAuditSvc.BuildLogParams {
|
||||
afterData := map[string]any{}
|
||||
if req != nil {
|
||||
afterData["carrier_id"] = req.CarrierID
|
||||
afterData["batch_no"] = req.BatchNo
|
||||
afterData["file_key"] = req.FileKey
|
||||
afterData["card_category"] = req.CardCategory
|
||||
afterData["realname_policy"] = req.RealnamePolicy
|
||||
}
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
return assetAuditSvc.BuildLogParams{
|
||||
AssetID: taskID,
|
||||
AssetIdentifier: taskNo,
|
||||
OperationDesc: "创建IoT卡导入任务",
|
||||
ResultStatus: resultStatus,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: afterData,
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ type Service struct {
|
||||
importTaskStore *postgres.IotCardImportTaskStore
|
||||
carrierStore carrierGetter
|
||||
queueClient *queue.Client
|
||||
assetAudit AssetAuditService
|
||||
}
|
||||
|
||||
type carrierGetter interface {
|
||||
@@ -43,12 +44,18 @@ func (s *CarrierStore) GetByID(ctx context.Context, id uint) (*model.Carrier, er
|
||||
return &carrier, nil
|
||||
}
|
||||
|
||||
func New(db *gorm.DB, importTaskStore *postgres.IotCardImportTaskStore, queueClient *queue.Client) *Service {
|
||||
func New(
|
||||
db *gorm.DB,
|
||||
importTaskStore *postgres.IotCardImportTaskStore,
|
||||
queueClient *queue.Client,
|
||||
assetAudit AssetAuditService,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
importTaskStore: importTaskStore,
|
||||
carrierStore: NewCarrierStore(db),
|
||||
queueClient: queueClient,
|
||||
assetAudit: assetAudit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +66,16 @@ type IotCardImportPayload struct {
|
||||
func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportIotCardRequest) (*dto.ImportIotCardResponse, error) {
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
appErr := errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(0, "", req, constants.AssetAuditResultDenied, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
carrier, err := s.carrierStore.GetByID(ctx, req.CarrierID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "运营商不存在")
|
||||
appErr := errors.New(errors.CodeInvalidParam, "运营商不存在")
|
||||
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(0, "", req, constants.AssetAuditResultDenied, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
taskNo := s.importTaskStore.GenerateTaskNo(ctx)
|
||||
@@ -91,16 +102,22 @@ func (s *Service) CreateImportTask(ctx context.Context, req *dto.ImportIotCardRe
|
||||
task.Updater = userID
|
||||
|
||||
if err := s.importTaskStore.Create(ctx, task); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "创建导入任务失败")
|
||||
appErr := errors.Wrap(errors.CodeInternalError, err, "创建导入任务失败")
|
||||
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(0, taskNo, req, constants.AssetAuditResultFailed, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
payload := IotCardImportPayload{TaskID: task.ID}
|
||||
err = s.queueClient.EnqueueTask(ctx, constants.TaskTypeIotCardImport, payload)
|
||||
if err != nil {
|
||||
s.importTaskStore.UpdateStatus(ctx, task.ID, model.ImportTaskStatusFailed, "任务入队失败: "+err.Error())
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "任务入队失败")
|
||||
appErr := errors.Wrap(errors.CodeInternalError, err, "任务入队失败")
|
||||
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(task.ID, taskNo, req, constants.AssetAuditResultFailed, appErr))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
s.logIotCardImportAudit(ctx, newIotCardImportAuditParams(task.ID, taskNo, req, constants.AssetAuditResultSuccess, nil))
|
||||
|
||||
return &dto.ImportIotCardResponse{
|
||||
TaskID: task.ID,
|
||||
TaskNo: taskNo,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/polling"
|
||||
assetAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_audit"
|
||||
iotCardSvc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -21,6 +22,7 @@ type AssetPollingService struct {
|
||||
iotCardService *iotCardSvc.Service
|
||||
queueMgr *polling.PollingQueueManager
|
||||
logger *zap.Logger
|
||||
assetAuditService assetAuditSvc.OperationLogger
|
||||
}
|
||||
|
||||
// NewAssetPollingService 创建资产轮询管控服务
|
||||
@@ -30,6 +32,7 @@ func NewAssetPollingService(
|
||||
iotCardService *iotCardSvc.Service,
|
||||
queueMgr *polling.PollingQueueManager,
|
||||
logger *zap.Logger,
|
||||
assetAuditService assetAuditSvc.OperationLogger,
|
||||
) *AssetPollingService {
|
||||
return &AssetPollingService{
|
||||
deviceStore: deviceStore,
|
||||
@@ -37,9 +40,23 @@ func NewAssetPollingService(
|
||||
iotCardService: iotCardService,
|
||||
queueMgr: queueMgr,
|
||||
logger: logger,
|
||||
assetAuditService: assetAuditService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AssetPollingService) logAssetPollingAudit(ctx context.Context, p assetAuditSvc.BuildLogParams) {
|
||||
if s == nil || s.assetAuditService == nil {
|
||||
return
|
||||
}
|
||||
if p.OperationType == "" {
|
||||
p.OperationType = constants.AssetAuditOpAssetPollingStatus
|
||||
}
|
||||
if p.Operator.Type == "" {
|
||||
p.Operator = assetAuditSvc.OperatorFromContext(ctx)
|
||||
}
|
||||
s.assetAuditService.LogOperation(ctx, assetAuditSvc.BuildLog(ctx, p))
|
||||
}
|
||||
|
||||
// UpdatePollingStatus 更新资产轮询状态
|
||||
// assetType: "card" 或 "device"
|
||||
// assetID: 资产ID
|
||||
@@ -47,12 +64,87 @@ func NewAssetPollingService(
|
||||
func (s *AssetPollingService) UpdatePollingStatus(ctx context.Context, assetType string, assetID uint, enablePolling bool) error {
|
||||
switch assetType {
|
||||
case constants.AssetTypeIotCard:
|
||||
beforeData := map[string]any{
|
||||
"asset_type": constants.AssetTypeIotCard,
|
||||
"asset_id": assetID,
|
||||
"enable_polling": "unknown",
|
||||
"source_service": "asset_polling",
|
||||
}
|
||||
afterData := map[string]any{
|
||||
"asset_type": constants.AssetTypeIotCard,
|
||||
"asset_id": assetID,
|
||||
"enable_polling": enablePolling,
|
||||
}
|
||||
// S2 修复:委托给 IotCardService,确保 DB 写入 + PollingCallback 回调一并触发
|
||||
return s.iotCardService.UpdatePollingStatus(ctx, assetID, enablePolling)
|
||||
if err := s.iotCardService.UpdatePollingStatus(ctx, assetID, enablePolling); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logAssetPollingAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: assetID,
|
||||
OperationDesc: "统一入口更新轮询状态失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
AfterData: afterData,
|
||||
})
|
||||
return err
|
||||
}
|
||||
s.logAssetPollingAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeIotCard,
|
||||
AssetID: assetID,
|
||||
OperationDesc: "统一入口更新轮询状态",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BeforeData: beforeData,
|
||||
AfterData: afterData,
|
||||
})
|
||||
return nil
|
||||
|
||||
case constants.AssetTypeDevice:
|
||||
device, getErr := s.deviceStore.GetByID(ctx, assetID)
|
||||
if getErr != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(getErr)
|
||||
s.logAssetPollingAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: assetID,
|
||||
OperationDesc: "统一入口更新轮询状态失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: map[string]any{
|
||||
"asset_type": constants.AssetTypeDevice,
|
||||
"asset_id": assetID,
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
return getErr
|
||||
}
|
||||
beforeData := map[string]any{
|
||||
"asset_type": constants.AssetTypeDevice,
|
||||
"asset_id": device.ID,
|
||||
"asset_identifier": device.VirtualNo,
|
||||
"enable_polling": device.EnablePolling,
|
||||
}
|
||||
afterData := map[string]any{
|
||||
"asset_type": constants.AssetTypeDevice,
|
||||
"asset_id": device.ID,
|
||||
"asset_identifier": device.VirtualNo,
|
||||
"enable_polling": enablePolling,
|
||||
}
|
||||
// 1. 更新设备的 enable_polling 字段
|
||||
if err := s.deviceStore.UpdatePollingStatus(ctx, assetID, enablePolling); err != nil {
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logAssetPollingAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.VirtualNo,
|
||||
OperationDesc: "统一入口更新轮询状态失败",
|
||||
ResultStatus: constants.AssetAuditResultFailed,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
BeforeData: beforeData,
|
||||
AfterData: afterData,
|
||||
})
|
||||
return err
|
||||
}
|
||||
bindings, err := s.deviceBindingStore.ListByDeviceID(ctx, assetID)
|
||||
@@ -81,9 +173,33 @@ func (s *AssetPollingService) UpdatePollingStatus(ctx context.Context, assetType
|
||||
}
|
||||
}
|
||||
}
|
||||
s.logAssetPollingAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: constants.AssetTypeDevice,
|
||||
AssetID: device.ID,
|
||||
AssetIdentifier: device.VirtualNo,
|
||||
OperationDesc: "统一入口更新轮询状态",
|
||||
ResultStatus: constants.AssetAuditResultSuccess,
|
||||
BeforeData: beforeData,
|
||||
AfterData: afterData,
|
||||
})
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidParam, "资产类型无效,支持 card 或 device")
|
||||
err := errors.New(errors.CodeInvalidParam, "资产类型无效,支持 card 或 device")
|
||||
errorCode, errorMsg := assetAuditSvc.BuildErrorInfo(err)
|
||||
s.logAssetPollingAudit(ctx, assetAuditSvc.BuildLogParams{
|
||||
AssetType: assetType,
|
||||
AssetID: assetID,
|
||||
OperationDesc: "统一入口更新轮询状态被拒绝",
|
||||
ResultStatus: constants.AssetAuditResultDenied,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMsg: errorMsg,
|
||||
AfterData: map[string]any{
|
||||
"asset_type": assetType,
|
||||
"asset_id": assetID,
|
||||
"enable_polling": enablePolling,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user