1173 lines
38 KiB
Go
1173 lines
38 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/batchmetadata"
|
||
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||
|
||
"github.com/gogf/gf/v2/database/gdb"
|
||
"github.com/gogf/gf/v2/encoding/gjson"
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
)
|
||
|
||
type appCardLifecycleOperation struct {
|
||
Code string
|
||
Name string
|
||
BatchType int
|
||
BatchPrefix string
|
||
TargetStatus int
|
||
}
|
||
|
||
type appCardLifecycleItem struct {
|
||
Card *appCardQueryRow
|
||
LockedCard *entity.TSimCardInfo
|
||
RowNo int
|
||
StatusBefore int
|
||
}
|
||
|
||
type appCardLifecycleExecutionRow struct {
|
||
DetailID int64
|
||
RowNo int
|
||
Iccid string
|
||
Msisdn string
|
||
Imsi string
|
||
Imei string
|
||
Authorized bool
|
||
ChanKey string
|
||
StatusBefore int
|
||
OrderNum string
|
||
UpstreamCode int
|
||
UpstreamMessage string
|
||
Reason string
|
||
Card *entity.TSimCardInfo
|
||
}
|
||
|
||
const (
|
||
appLifecycleRestartDelaySeconds = 300
|
||
appLifecycleRestartPhaseCreated = "created"
|
||
)
|
||
|
||
// Restart 兼容原重启接口。停机、复机和重启优先投递 worker;未启用 worker 时保留本地挡板降级。
|
||
func (s *sAppCard) Restart(ctx context.Context, param *model.AppCardRestartParam) (res *model.AppCardRestartModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRestartParam{}
|
||
}
|
||
lifecycle, err := s.LifecycleBatchSubmit(ctx, &model.AppCardLifecycleParam{
|
||
CardValue: param.CardValue,
|
||
Operation: "restart",
|
||
Remark: param.Remark,
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var card *model.AppCardModel
|
||
if lifecycle != nil && len(lifecycle.Cards) > 0 {
|
||
card = lifecycle.Cards[0]
|
||
}
|
||
return &model.AppCardRestartModel{
|
||
BatchNo: lifecycle.BatchNo,
|
||
BatchType: lifecycle.BatchType,
|
||
Status: lifecycle.Status,
|
||
Card: card,
|
||
}, nil
|
||
}
|
||
|
||
func (s *sAppCard) Stop(ctx context.Context, param *model.AppCardRestartParam) (res *model.AppCardLifecycleModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRestartParam{}
|
||
}
|
||
return s.LifecycleBatchSubmit(ctx, &model.AppCardLifecycleParam{
|
||
CardValue: param.CardValue,
|
||
Operation: "stop",
|
||
Remark: param.Remark,
|
||
})
|
||
}
|
||
|
||
func (s *sAppCard) Start(ctx context.Context, param *model.AppCardRestartParam) (res *model.AppCardLifecycleModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardRestartParam{}
|
||
}
|
||
return s.LifecycleBatchSubmit(ctx, &model.AppCardLifecycleParam{
|
||
CardValue: param.CardValue,
|
||
Operation: "start",
|
||
Remark: param.Remark,
|
||
})
|
||
}
|
||
|
||
// LifecycleBatchSubmit 提交卡片停机、复机和重启批次。
|
||
// 终端用户仅允许重启;停机和复机仍然只开放给企业用户。
|
||
func (s *sAppCard) LifecycleBatchSubmit(ctx context.Context, param *model.AppCardLifecycleParam) (res *model.AppCardLifecycleModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardLifecycleParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if s.isTerminalUser(ctx, user) && strings.TrimSpace(strings.ToLower(param.Operation)) != "restart" {
|
||
return nil, gerror.New("终端用户暂不能使用卡片停机和复机功能")
|
||
}
|
||
if user.DeptId <= 0 {
|
||
return nil, gerror.New("当前账号未绑定企业,不能操作卡片")
|
||
}
|
||
operation, err := appCardLifecycleOperationByCode(param.Operation)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if appCardLifecycleIsDataService(operation) && !appWorkerBatchAsyncEnabled(ctx) {
|
||
return nil, gerror.New("数据服务开停依赖异步执行服务,当前环境未启用")
|
||
}
|
||
if err = appEnsureFeatureAccess(ctx, "card:"+operation.Code); err != nil {
|
||
return nil, err
|
||
}
|
||
cardValues, mode, err := appNormalizeLifecycleCardValues(param.CardValue, param.CardValues)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(cardValues) == 0 {
|
||
return nil, gerror.New("请选择需要操作的卡")
|
||
}
|
||
if len(cardValues) > 100 {
|
||
return nil, gerror.New("单次最多支持100张卡")
|
||
}
|
||
|
||
now := gtime.Now()
|
||
batchNo, err := appNextBatchNo(ctx, operation.BatchPrefix)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
items := make([]*appCardLifecycleItem, 0, len(cardValues))
|
||
err = dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
seen := make(map[string]struct{}, len(cardValues))
|
||
for index, value := range cardValues {
|
||
item, err := s.lockLifecycleCard(ctx, tx, user, value, index+2, operation)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, exists := seen[item.Card.Iccid]; exists {
|
||
return gerror.Newf("批量操作卡重复:%s", item.Card.Iccid)
|
||
}
|
||
seen[item.Card.Iccid] = struct{}{}
|
||
items = append(items, item)
|
||
}
|
||
return s.insertPendingLifecycleBatch(ctx, tx, user, items, operation, batchNo, strings.TrimSpace(param.Remark), mode, now)
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if appWorkerBatchAsyncEnabled(ctx) && appLifecycleShouldPublishWorker(operation) {
|
||
if err = s.publishWorkerBatchExecute(ctx, batchNo, operation.BatchType); err != nil {
|
||
_ = s.markLifecycleBatchFailed(ctx, batchNo, err.Error())
|
||
return nil, err
|
||
}
|
||
} else if err = s.executeMockLifecycleBatch(ctx, batchNo); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
ownerType := appCardOwnerAgent
|
||
if s.isTerminalUser(ctx, user) {
|
||
ownerType = appCardOwnerTerminal
|
||
}
|
||
cards, err := s.loadLifecycleCardsForResponse(ctx, user, items, ownerType)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
status, err := s.loadLifecycleBatchStatus(ctx, batchNo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &model.AppCardLifecycleModel{
|
||
BatchNo: batchNo,
|
||
BatchType: operation.BatchType,
|
||
Operation: operation.Code,
|
||
Status: status,
|
||
Quantity: len(cards),
|
||
SuccessCount: 0,
|
||
FailCount: 0,
|
||
Cards: cards,
|
||
}, nil
|
||
}
|
||
|
||
func appLifecycleShouldPublishWorker(operation appCardLifecycleOperation) bool {
|
||
switch operation.BatchType {
|
||
case consts.BatchTypeStop, consts.BatchTypeStart, consts.BatchTypeRestart, consts.BatchTypeDataStart, consts.BatchTypeDataStop:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) LifecycleBatchStatus(ctx context.Context, param *model.AppCardLifecycleStatusParam) (res *model.AppCardLifecycleModel, err error) {
|
||
if param == nil {
|
||
param = &model.AppCardLifecycleStatusParam{}
|
||
}
|
||
user, err := appCurrentUser(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
batchNo := strings.TrimSpace(param.BatchNo)
|
||
if batchNo == "" {
|
||
return nil, gerror.New("批次号不能为空")
|
||
}
|
||
|
||
var batch *entity.BatchMain
|
||
columns := dao.BatchMain.Columns()
|
||
if err = dao.BatchMain.Ctx(ctx).
|
||
Fields(
|
||
columns.BatchNo,
|
||
columns.BatchType,
|
||
columns.BatchStatus,
|
||
columns.BatchAsyncStatus,
|
||
columns.TotalCount,
|
||
columns.SuccessCount,
|
||
columns.FailCount,
|
||
columns.FailReason,
|
||
columns.TenantId,
|
||
columns.CreatedBy,
|
||
).
|
||
Where(columns.BatchNo, batchNo).
|
||
Where(columns.TenantId, user.TenantId).
|
||
Where(columns.CreatedBy, user.UserId).
|
||
Limit(1).
|
||
Scan(&batch); err != nil {
|
||
return nil, gerror.Wrap(err, "查询卡片操作批次状态失败")
|
||
}
|
||
if batch == nil || batch.BatchNo == "" {
|
||
return nil, gerror.New("卡片操作批次不存在或无权查看")
|
||
}
|
||
|
||
operation, err := appCardLifecycleOperationByBatchType(batch.BatchType)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err = appEnsureFeatureAccess(ctx, "card:"+operation.Code); err != nil {
|
||
return nil, err
|
||
}
|
||
status := appLifecycleBatchStatus(batch.BatchStatus, batch.BatchAsyncStatus)
|
||
return &model.AppCardLifecycleModel{
|
||
BatchNo: batch.BatchNo,
|
||
BatchType: batch.BatchType,
|
||
Operation: operation.Code,
|
||
Status: status,
|
||
Quantity: batch.TotalCount,
|
||
SuccessCount: batch.SuccessCount,
|
||
FailCount: batch.FailCount,
|
||
FailReason: appLifecycleFailReasonForResponse(status, batch.FailReason),
|
||
Cards: []*model.AppCardModel{},
|
||
}, nil
|
||
}
|
||
|
||
func appLifecycleFailReasonForResponse(status string, reason string) string {
|
||
reason = strings.TrimSpace(reason)
|
||
if reason == "" {
|
||
return ""
|
||
}
|
||
lower := strings.ToLower(reason)
|
||
internalMarkers := []string{
|
||
"save data service detail",
|
||
"save lifecycle detail",
|
||
"update `",
|
||
"insert into",
|
||
"delete from",
|
||
"select ",
|
||
"error 10",
|
||
"sqlstate",
|
||
"column '",
|
||
"stack:",
|
||
}
|
||
for _, marker := range internalMarkers {
|
||
if strings.Contains(lower, marker) {
|
||
return "后台执行失败,请稍后重试;如持续失败,请联系管理员并提供批次号"
|
||
}
|
||
}
|
||
if len([]rune(reason)) > 160 {
|
||
return "后台执行失败,请稍后重试;如持续失败,请联系管理员并提供批次号"
|
||
}
|
||
if status == "failed" && (reason == "1 SIM cards failed" || reason == "1 SIM card failed") {
|
||
return "卡片操作执行失败,请稍后重试"
|
||
}
|
||
return reason
|
||
}
|
||
|
||
func (s *sAppCard) prepareLifecycleCard(ctx context.Context, user *entity.SysUser, cardValue string, rowNo int) (*appCardLifecycleItem, error) {
|
||
card, err := s.findVisibleCard(ctx, user, cardValue)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if card == nil {
|
||
return nil, gerror.Newf("卡%s不存在或不在当前企业可操作范围内", strings.TrimSpace(cardValue))
|
||
}
|
||
if err = s.ensureOperateCard(ctx, user, card, "操作"); err != nil {
|
||
return nil, err
|
||
}
|
||
return &appCardLifecycleItem{
|
||
Card: card,
|
||
RowNo: rowNo,
|
||
StatusBefore: card.BossCardStatus,
|
||
}, nil
|
||
}
|
||
|
||
func (s *sAppCard) lockLifecycleCard(ctx context.Context, tx gdb.TX, user *entity.SysUser, cardValue string, rowNo int, operation appCardLifecycleOperation) (*appCardLifecycleItem, error) {
|
||
card, err := s.findVisibleCard(ctx, user, cardValue)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if card == nil {
|
||
return nil, gerror.Newf("卡%s不存在或不在当前企业可操作范围内", strings.TrimSpace(cardValue))
|
||
}
|
||
if err = s.ensureOperateCard(ctx, user, card, operation.Name); err != nil {
|
||
return nil, err
|
||
}
|
||
channelInfo, err := s.resolveLifecycleCardChannelInfo(ctx, user.TenantId, card.Iccid, card.ChanKey)
|
||
if err != nil {
|
||
return nil, gerror.Wrapf(err, "解析卡%s通道失败", appFirstNotEmpty(card.Iccid, cardValue))
|
||
}
|
||
if !appCardChannelAvailable(channelInfo) {
|
||
return nil, gerror.Newf("此卡暂未收录,暂不能%s:%s", operation.Name, appFirstNotEmpty(card.Iccid, cardValue))
|
||
}
|
||
|
||
var locked *entity.TSimCardInfo
|
||
err = dao.TSimCardInfo.Ctx(ctx).TX(tx).
|
||
Where(dao.TSimCardInfo.Columns().Id, card.Id).
|
||
Where(dao.TSimCardInfo.Columns().TenantId, user.TenantId).
|
||
LockUpdate().
|
||
Limit(1).
|
||
Scan(&locked)
|
||
if err != nil {
|
||
return nil, gerror.Wrapf(err, "锁定卡%s失败", appFirstNotEmpty(card.Iccid, cardValue))
|
||
}
|
||
if locked == nil || locked.Id == 0 {
|
||
return nil, gerror.Newf("卡%s信息已变化,请刷新后重试", appFirstNotEmpty(card.Iccid, cardValue))
|
||
}
|
||
if locked.DeptId != card.DeptId || strings.TrimSpace(locked.Iccid) != strings.TrimSpace(card.Iccid) {
|
||
return nil, gerror.Newf("卡%s归属已变化,请刷新后重试", card.Iccid)
|
||
}
|
||
card.TSimCardInfo = *locked
|
||
card.ChanKey = channelInfo.ChanKey
|
||
card.ChanName = channelInfo.ChanName
|
||
card.ChanAlias = channelInfo.ChanAlias
|
||
card.TempKey = channelInfo.TempKey
|
||
if err = s.ensureLifecycleCardStatus(card, operation); err != nil {
|
||
return nil, err
|
||
}
|
||
statusBefore := locked.BossCardStatus
|
||
if appCardLifecycleIsDataService(operation) {
|
||
statusBefore = locked.DataServiceStatus
|
||
}
|
||
return &appCardLifecycleItem{
|
||
Card: card,
|
||
LockedCard: locked,
|
||
RowNo: rowNo,
|
||
StatusBefore: statusBefore,
|
||
}, nil
|
||
}
|
||
|
||
func (s *sAppCard) ensureLifecycleCardStatus(card *appCardQueryRow, operation appCardLifecycleOperation) error {
|
||
if card == nil {
|
||
return gerror.New("卡信息不存在")
|
||
}
|
||
cardLabel := appFirstNotEmpty(card.Iccid, card.Msisdn, card.Imsi, card.Imei)
|
||
switch operation.Code {
|
||
case "stop":
|
||
if card.BossCardStatus != consts.V3ApiSimBossCardStatusActivated {
|
||
return gerror.Newf("仅已激活状态的卡可以停机:%s", cardLabel)
|
||
}
|
||
case "start":
|
||
if card.BossCardStatus != consts.V3ApiSimBossCardStatusStopped {
|
||
return gerror.Newf("仅停机状态的卡可以复机:%s", cardLabel)
|
||
}
|
||
case "restart":
|
||
if card.BossCardStatus == consts.V3ApiSimBossCardStatusStopped {
|
||
return gerror.Newf("停机卡不能重启,请先复机:%s", cardLabel)
|
||
}
|
||
case "dataStart":
|
||
if card.DataServiceStatus == 1 {
|
||
return gerror.Newf("卡片数据服务已经开启:%s", cardLabel)
|
||
}
|
||
case "dataStop":
|
||
if card.DataServiceStatus == 0 {
|
||
return gerror.Newf("卡片数据服务已经关闭:%s", cardLabel)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) applyMockLifecycleCards(ctx context.Context, tx gdb.TX, user *entity.SysUser, items []*appCardLifecycleItem, operation appCardLifecycleOperation, now *gtime.Time) error {
|
||
if operation.Code == "restart" {
|
||
return nil
|
||
}
|
||
for _, item := range items {
|
||
result, err := dao.TSimCardInfo.Ctx(ctx).TX(tx).
|
||
Where(dao.TSimCardInfo.Columns().Id, item.LockedCard.Id).
|
||
Where(dao.TSimCardInfo.Columns().BossCardStatus, item.StatusBefore).
|
||
Data(do.TSimCardInfo{
|
||
BossCardStatus: operation.TargetStatus,
|
||
StatusSyncTime: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrapf(err, "更新卡%s状态失败", item.Card.Iccid)
|
||
}
|
||
affected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "读取卡状态更新数量失败")
|
||
}
|
||
if affected != 1 {
|
||
return gerror.Newf("卡%s状态已变化,请刷新后重试", item.Card.Iccid)
|
||
}
|
||
item.Card.BossCardStatus = operation.TargetStatus
|
||
item.Card.StatusSyncTime = now
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) insertPendingLifecycleBatch(ctx context.Context, tx gdb.TX, user *entity.SysUser, items []*appCardLifecycleItem, operation appCardLifecycleOperation, batchNo string, remark string, executionMode int, now *gtime.Time) error {
|
||
quantity := len(items)
|
||
_, err := dao.BatchMain.Ctx(ctx).TX(tx).Data(do.BatchMain{
|
||
BatchNo: batchNo,
|
||
BatchType: operation.BatchType,
|
||
BatchStatus: consts.BatchAsyncStatusPending,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusPending,
|
||
SourceType: consts.BatchSourceTypeBatchSelect,
|
||
SourceChannel: batchmetadata.MiniProgram,
|
||
ExecutionMode: batchmetadata.NormalizeMode(executionMode),
|
||
DeptId: user.DeptId,
|
||
TotalCount: quantity,
|
||
ValidCount: 0,
|
||
SuccessCount: 0,
|
||
FailCount: 0,
|
||
RunResult: consts.BatchRunResultPending,
|
||
BatchInfo: appLifecycleBatchInfo(operation, items, batchNo, remark, now),
|
||
Remark: gjson.New(map[string]any{"remark": remark}),
|
||
TenantId: user.TenantId,
|
||
CreatedDept: user.DeptId,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
}).Insert()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "写入待处理卡片操作批次主表失败")
|
||
}
|
||
|
||
details := make([]do.BatchDetail, 0, quantity)
|
||
for _, item := range items {
|
||
details = append(details, do.BatchDetail{
|
||
BatchNo: batchNo,
|
||
BatchType: operation.BatchType,
|
||
DetailStatus: consts.BatchDetailStatusPending,
|
||
BlankOutStatus: consts.BlankOutStatusNo,
|
||
RowNo: item.RowNo,
|
||
ItemId: item.Card.Iccid,
|
||
Snapshot: appLifecyclePendingDetailSnapshot(operation, item, batchNo),
|
||
TenantId: user.TenantId,
|
||
CreatedDept: user.DeptId,
|
||
CreatedBy: user.UserId,
|
||
CreatedAt: now,
|
||
UpdatedBy: user.UserId,
|
||
UpdatedAt: now,
|
||
})
|
||
}
|
||
if _, err = dao.BatchDetail.Ctx(ctx).TX(tx).Data(details).Insert(); err != nil {
|
||
return gerror.Wrap(err, "写入待处理卡片操作批次明细失败")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) executeMockLifecycleBatch(ctx context.Context, batchNo string) error {
|
||
claimed, err := s.claimMockLifecycleBatch(ctx, batchNo)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !claimed {
|
||
return nil
|
||
}
|
||
|
||
batch, operation, rows, err := s.loadMockLifecycleTask(ctx, batchNo)
|
||
if err != nil {
|
||
_ = s.markLifecycleBatchFailed(ctx, batchNo, err.Error())
|
||
return err
|
||
}
|
||
validationOK, failureReason, err := s.validateMockLifecycleRows(ctx, batch, operation, rows)
|
||
if err != nil {
|
||
_ = s.markLifecycleBatchFailed(ctx, batchNo, err.Error())
|
||
return err
|
||
}
|
||
if !validationOK {
|
||
if err = s.saveMockLifecycleRows(ctx, batch, operation, rows); err != nil {
|
||
return err
|
||
}
|
||
return s.finalizeMockLifecycleBatch(ctx, batch, operation, rows, validationOK, failureReason)
|
||
}
|
||
|
||
var upstream appSimUpstreamProvider
|
||
if operation.Code == "stop" || operation.Code == "start" {
|
||
upstream, err = newAppSimUpstreamProvider(ctx)
|
||
if err != nil {
|
||
for _, row := range rows {
|
||
if row != nil {
|
||
row.Reason = err.Error()
|
||
}
|
||
}
|
||
if saveErr := s.saveMockLifecycleRows(ctx, batch, operation, rows); saveErr != nil {
|
||
return saveErr
|
||
}
|
||
return s.finalizeMockLifecycleBatch(ctx, batch, operation, rows, validationOK, "OneLink挡板配置不可用")
|
||
}
|
||
}
|
||
|
||
for _, row := range rows {
|
||
if err = s.applyMockLifecycleRow(ctx, batch, operation, row, upstream); err != nil {
|
||
row.Reason = err.Error()
|
||
}
|
||
}
|
||
if err = s.saveMockLifecycleRows(ctx, batch, operation, rows); err != nil {
|
||
return err
|
||
}
|
||
return s.finalizeMockLifecycleBatch(ctx, batch, operation, rows, validationOK, "")
|
||
}
|
||
|
||
func (s *sAppCard) claimMockLifecycleBatch(ctx context.Context, batchNo string) (bool, error) {
|
||
claimed := false
|
||
err := dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
now := gtime.Now()
|
||
result, err := dao.BatchMain.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||
WhereIn(dao.BatchMain.Columns().BatchType, []int{consts.BatchTypeStart, consts.BatchTypeStop, consts.BatchTypeRestart, consts.BatchTypeDataStart, consts.BatchTypeDataStop}).
|
||
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusPending).
|
||
Data(do.BatchMain{
|
||
BatchStatus: consts.BatchAsyncStatusProcessing,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusProcessing,
|
||
RunResult: consts.BatchRunResultRunning,
|
||
StartTime: now,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "抢占卡片操作批次失败")
|
||
}
|
||
affected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if affected != 1 {
|
||
return nil
|
||
}
|
||
claimed = true
|
||
_, err = dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusPending).
|
||
Data(do.BatchDetail{
|
||
DetailStatus: consts.BatchDetailStatusRunning,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
return gerror.Wrap(err, "更新卡片操作批次明细为处理中失败")
|
||
})
|
||
return claimed, err
|
||
}
|
||
|
||
func (s *sAppCard) loadMockLifecycleTask(ctx context.Context, batchNo string) (*entity.BatchMain, appCardLifecycleOperation, []*appCardLifecycleExecutionRow, error) {
|
||
var batch *entity.BatchMain
|
||
if err := dao.BatchMain.Ctx(ctx).
|
||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||
Limit(1).
|
||
Scan(&batch); err != nil {
|
||
return nil, appCardLifecycleOperation{}, nil, gerror.Wrap(err, "读取卡片操作批次失败")
|
||
}
|
||
if batch == nil || batch.Id == 0 {
|
||
return nil, appCardLifecycleOperation{}, nil, gerror.New("卡片操作批次不存在")
|
||
}
|
||
operation, err := appCardLifecycleOperationByBatchType(batch.BatchType)
|
||
if err != nil {
|
||
return nil, appCardLifecycleOperation{}, nil, err
|
||
}
|
||
|
||
var details []*entity.BatchDetail
|
||
if err = dao.BatchDetail.Ctx(ctx).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||
Where(dao.BatchDetail.Columns().BatchType, batch.BatchType).
|
||
OrderAsc(dao.BatchDetail.Columns().RowNo).
|
||
Scan(&details); err != nil {
|
||
return nil, appCardLifecycleOperation{}, nil, gerror.Wrap(err, "读取卡片操作批次明细失败")
|
||
}
|
||
if len(details) == 0 {
|
||
return nil, appCardLifecycleOperation{}, nil, gerror.New("卡片操作批次没有明细")
|
||
}
|
||
|
||
rows := make([]*appCardLifecycleExecutionRow, 0, len(details))
|
||
for _, detail := range details {
|
||
row := &appCardLifecycleExecutionRow{
|
||
DetailID: detail.Id,
|
||
RowNo: detail.RowNo,
|
||
Iccid: strings.TrimSpace(detail.ItemId),
|
||
Reason: strings.TrimSpace(detail.ErrorReason),
|
||
Authorized: true,
|
||
}
|
||
if detail.Snapshot != nil {
|
||
if value := strings.TrimSpace(detail.Snapshot.Get("ICCID").String()); value != "" {
|
||
row.Iccid = value
|
||
}
|
||
row.Msisdn = strings.TrimSpace(detail.Snapshot.Get("MSISDN").String())
|
||
row.Imsi = strings.TrimSpace(detail.Snapshot.Get("IMSI").String())
|
||
row.Imei = strings.TrimSpace(detail.Snapshot.Get("IMEI").String())
|
||
row.Authorized = detail.Snapshot.Get("authorized").Bool()
|
||
}
|
||
rows = append(rows, row)
|
||
}
|
||
return batch, operation, rows, nil
|
||
}
|
||
|
||
func (s *sAppCard) validateMockLifecycleRows(ctx context.Context, batch *entity.BatchMain, operation appCardLifecycleOperation, rows []*appCardLifecycleExecutionRow) (bool, string, error) {
|
||
iccids := make([]string, 0, len(rows))
|
||
for _, row := range rows {
|
||
if row != nil && row.Iccid != "" {
|
||
iccids = append(iccids, row.Iccid)
|
||
}
|
||
}
|
||
cards := make(map[string]*entity.TSimCardInfo, len(iccids))
|
||
if len(iccids) > 0 {
|
||
var cardList []*entity.TSimCardInfo
|
||
if err := dao.TSimCardInfo.Ctx(ctx).
|
||
Where(dao.TSimCardInfo.Columns().TenantId, batch.TenantId).
|
||
WhereIn(dao.TSimCardInfo.Columns().Iccid, iccids).
|
||
Scan(&cardList); err != nil {
|
||
return false, "", err
|
||
}
|
||
for _, card := range cardList {
|
||
if card != nil {
|
||
cards[strings.TrimSpace(card.Iccid)] = card
|
||
}
|
||
}
|
||
}
|
||
|
||
hasFailure := false
|
||
for _, row := range rows {
|
||
if row == nil {
|
||
hasFailure = true
|
||
continue
|
||
}
|
||
card := cards[row.Iccid]
|
||
legacyChanKey := strings.TrimSpace(row.ChanKey)
|
||
if legacyChanKey == "" && card != nil {
|
||
legacyChanKey = strings.TrimSpace(card.ChanKey)
|
||
}
|
||
channelInfo, err := s.loadCardChannelInfo(ctx, batch.TenantId, row.Iccid, legacyChanKey)
|
||
if err != nil {
|
||
row.Reason = err.Error()
|
||
hasFailure = true
|
||
continue
|
||
}
|
||
switch {
|
||
case row.Reason != "":
|
||
case card == nil:
|
||
row.Reason = "ICCID不存在于正式卡记录"
|
||
case !row.Authorized:
|
||
row.Reason = "ICCID不在当前操作人的数据权限范围内"
|
||
case channelInfo == nil || strings.TrimSpace(channelInfo.ChanKey) == "":
|
||
row.Reason = "ICCID缺少通道标识chanKey"
|
||
case row.Msisdn != "" && row.Msisdn != strings.TrimSpace(card.Msisdn):
|
||
row.Reason = "MSISDN与正式卡记录不一致"
|
||
case row.Imsi != "" && row.Imsi != strings.TrimSpace(card.Imsi):
|
||
row.Reason = "IMSI与正式卡记录不一致"
|
||
default:
|
||
row.Card = card
|
||
row.Msisdn = strings.TrimSpace(card.Msisdn)
|
||
row.Imsi = strings.TrimSpace(card.Imsi)
|
||
row.Imei = strings.TrimSpace(card.Imei)
|
||
row.ChanKey = strings.TrimSpace(channelInfo.ChanKey)
|
||
row.StatusBefore = card.BossCardStatus
|
||
row.Reason = appLifecycleStatusError(operation, row, card)
|
||
}
|
||
if row.Reason != "" {
|
||
hasFailure = true
|
||
}
|
||
}
|
||
if !hasFailure {
|
||
return true, "", nil
|
||
}
|
||
for _, row := range rows {
|
||
if row != nil && row.Reason == "" {
|
||
row.Reason = "同批次存在校验失败卡,本卡未请求上游"
|
||
}
|
||
}
|
||
return false, "整批业务校验失败,未请求上游", nil
|
||
}
|
||
|
||
func appLifecycleStatusError(operation appCardLifecycleOperation, row *appCardLifecycleExecutionRow, card *entity.TSimCardInfo) string {
|
||
cardLabel := row.Iccid
|
||
if cardLabel == "" && card != nil {
|
||
cardLabel = appFirstNotEmpty(card.Iccid, card.Msisdn, card.Imsi, card.Imei)
|
||
}
|
||
switch operation.Code {
|
||
case "stop":
|
||
if card.BossCardStatus != consts.V3ApiSimBossCardStatusActivated {
|
||
return "仅已激活状态的卡可以自主停机:" + cardLabel
|
||
}
|
||
case "start":
|
||
if card.BossCardStatus != consts.V3ApiSimBossCardStatusStopped {
|
||
return "仅停机状态的卡可以自主复机:" + cardLabel
|
||
}
|
||
case "restart":
|
||
if card.BossCardStatus == consts.V3ApiSimBossCardStatusStopped {
|
||
return "停机卡不能重启,请先复机:" + cardLabel
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func (s *sAppCard) applyMockLifecycleRow(ctx context.Context, batch *entity.BatchMain, operation appCardLifecycleOperation, row *appCardLifecycleExecutionRow, upstream appSimUpstreamProvider) error {
|
||
if operation.Code == "restart" {
|
||
row.UpstreamCode = 0
|
||
row.UpstreamMessage = "重启操作本地模拟成功"
|
||
return nil
|
||
}
|
||
if upstream == nil {
|
||
return gerror.New("OneLink挡板客户端未初始化")
|
||
}
|
||
outcome, err := upstream.ChangeSimStatus(ctx, operation, row)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
row.OrderNum = outcome.OrderNum
|
||
row.UpstreamCode = outcome.Code
|
||
row.UpstreamMessage = outcome.Message
|
||
if !outcome.Success {
|
||
if strings.TrimSpace(row.UpstreamMessage) == "" {
|
||
row.UpstreamMessage = "OneLink挡板处理失败"
|
||
}
|
||
row.Reason = row.UpstreamMessage
|
||
return nil
|
||
}
|
||
now := gtime.Now()
|
||
result, err := dao.TSimCardInfo.Ctx(ctx).
|
||
Where(dao.TSimCardInfo.Columns().Id, row.Card.Id).
|
||
Where(dao.TSimCardInfo.Columns().TenantId, batch.TenantId).
|
||
Where(dao.TSimCardInfo.Columns().BossCardStatus, row.StatusBefore).
|
||
Data(do.TSimCardInfo{
|
||
BossCardStatus: operation.TargetStatus,
|
||
StatusSyncTime: now,
|
||
UpdatedBy: batch.CreatedBy,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "更新SIM卡本地状态失败")
|
||
}
|
||
affected, err := result.RowsAffected()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if affected != 1 {
|
||
return gerror.New("SIM卡本地状态已变化")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *sAppCard) saveMockLifecycleRows(ctx context.Context, batch *entity.BatchMain, operation appCardLifecycleOperation, rows []*appCardLifecycleExecutionRow) error {
|
||
now := gtime.Now()
|
||
return dao.BatchDetail.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
for _, row := range rows {
|
||
if row == nil {
|
||
continue
|
||
}
|
||
status := consts.BatchDetailStatusSuccess
|
||
if row.Reason != "" {
|
||
status = consts.BatchDetailStatusFailed
|
||
}
|
||
_, err := dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchDetail.Columns().Id, row.DetailID).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batch.BatchNo).
|
||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusRunning).
|
||
Data(do.BatchDetail{
|
||
DetailStatus: status,
|
||
ErrorReason: row.Reason,
|
||
Snapshot: appLifecycleExecutionSnapshot(operation, row, batch.BatchNo),
|
||
UpdatedBy: batch.CreatedBy,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
if err != nil {
|
||
return gerror.Wrap(err, "保存卡片操作批次明细结果失败")
|
||
}
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
func (s *sAppCard) finalizeMockLifecycleBatch(ctx context.Context, batch *entity.BatchMain, operation appCardLifecycleOperation, rows []*appCardLifecycleExecutionRow, validationOK bool, failureReason string) error {
|
||
successCount := 0
|
||
for _, row := range rows {
|
||
if row != nil && row.Reason == "" {
|
||
successCount++
|
||
}
|
||
}
|
||
failCount := len(rows) - successCount
|
||
validCount := 0
|
||
if validationOK {
|
||
validCount = len(rows)
|
||
}
|
||
batchStatus := consts.BatchAsyncStatusSuccess
|
||
runResult := consts.BatchRunResultSuccess
|
||
if failCount > 0 {
|
||
batchStatus = consts.BatchAsyncStatusFailed
|
||
runResult = consts.BatchRunResultFailed
|
||
if strings.TrimSpace(failureReason) == "" {
|
||
failureReason = "存在卡片处理失败"
|
||
}
|
||
}
|
||
now := gtime.Now()
|
||
_, err := dao.BatchMain.Ctx(ctx).
|
||
Where(dao.BatchMain.Columns().BatchNo, batch.BatchNo).
|
||
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusProcessing).
|
||
Data(do.BatchMain{
|
||
BatchStatus: batchStatus,
|
||
BatchAsyncStatus: batchStatus,
|
||
ValidCount: validCount,
|
||
SuccessCount: successCount,
|
||
FailCount: failCount,
|
||
RunResult: runResult,
|
||
FailReason: strings.TrimSpace(failureReason),
|
||
BatchInfo: appLifecycleFinalBatchInfo(batch.BatchInfo, operation, rows, validationOK, failureReason, now),
|
||
EndTime: now,
|
||
UpdatedBy: batch.CreatedBy,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
return gerror.Wrap(err, "汇总卡片操作批次失败")
|
||
}
|
||
|
||
func (s *sAppCard) markLifecycleBatchFailed(ctx context.Context, batchNo, reason string) error {
|
||
if strings.TrimSpace(reason) == "" {
|
||
reason = "后台执行失败"
|
||
}
|
||
now := gtime.Now()
|
||
return dao.BatchMain.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||
if _, err := dao.BatchDetail.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||
WhereIn(dao.BatchDetail.Columns().DetailStatus, []int{consts.BatchDetailStatusPending, consts.BatchDetailStatusRunning}).
|
||
Data(do.BatchDetail{
|
||
DetailStatus: consts.BatchDetailStatusFailed,
|
||
ErrorReason: reason,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update(); err != nil {
|
||
return err
|
||
}
|
||
_, err := dao.BatchMain.Ctx(ctx).TX(tx).
|
||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||
WhereIn(dao.BatchMain.Columns().BatchAsyncStatus, []int{consts.BatchAsyncStatusPending, consts.BatchAsyncStatusProcessing}).
|
||
Data(do.BatchMain{
|
||
BatchStatus: consts.BatchAsyncStatusFailed,
|
||
BatchAsyncStatus: consts.BatchAsyncStatusFailed,
|
||
RunResult: consts.BatchRunResultFailed,
|
||
FailReason: reason,
|
||
EndTime: now,
|
||
UpdatedAt: now,
|
||
}).
|
||
Update()
|
||
return err
|
||
})
|
||
}
|
||
|
||
func (s *sAppCard) loadLifecycleBatchStatus(ctx context.Context, batchNo string) (string, error) {
|
||
var batch *entity.BatchMain
|
||
if err := dao.BatchMain.Ctx(ctx).
|
||
Fields(dao.BatchMain.Columns().BatchStatus, dao.BatchMain.Columns().BatchAsyncStatus).
|
||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||
Limit(1).
|
||
Scan(&batch); err != nil {
|
||
return "", err
|
||
}
|
||
if batch == nil {
|
||
return "", gerror.New("卡片操作批次不存在")
|
||
}
|
||
return appLifecycleBatchStatus(batch.BatchStatus, batch.BatchAsyncStatus), nil
|
||
}
|
||
|
||
func appLifecycleBatchStatus(batchStatus int, batchAsyncStatus int) string {
|
||
status := batchAsyncStatus
|
||
if status == consts.BatchAsyncStatusCreated && batchStatus != consts.BatchAsyncStatusCreated {
|
||
status = batchStatus
|
||
}
|
||
switch status {
|
||
case consts.BatchAsyncStatusPending:
|
||
return "pending"
|
||
case consts.BatchAsyncStatusProcessing:
|
||
return "processing"
|
||
case consts.BatchAsyncStatusSuccess:
|
||
return "success"
|
||
case consts.BatchAsyncStatusFailed:
|
||
return "failed"
|
||
case consts.BatchAsyncStatusPartialSuccess:
|
||
return "partial_success"
|
||
default:
|
||
return "created"
|
||
}
|
||
}
|
||
|
||
func (s *sAppCard) loadLifecycleCardsForResponse(ctx context.Context, user *entity.SysUser, items []*appCardLifecycleItem, ownerType string) ([]*model.AppCardModel, error) {
|
||
cards := make([]*model.AppCardModel, 0, len(items))
|
||
for _, item := range items {
|
||
if item == nil || item.Card == nil {
|
||
continue
|
||
}
|
||
card, err := s.findVisibleCard(ctx, user, item.Card.Iccid)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if card == nil {
|
||
continue
|
||
}
|
||
cards = append(cards, s.cardRowToModel(ctx, card, user, ownerType, true))
|
||
}
|
||
return cards, nil
|
||
}
|
||
|
||
func appCardLifecycleOperationByCode(code string) (appCardLifecycleOperation, error) {
|
||
switch strings.TrimSpace(strings.ToLower(code)) {
|
||
case "stop", "suspend":
|
||
return appCardLifecycleOperation{
|
||
Code: "stop",
|
||
Name: "自主停机",
|
||
BatchType: consts.BatchTypeStop,
|
||
BatchPrefix: consts.BatchNoPrefixStop,
|
||
TargetStatus: consts.V3ApiSimBossCardStatusStopped,
|
||
}, nil
|
||
case "start", "resume":
|
||
return appCardLifecycleOperation{
|
||
Code: "start",
|
||
Name: "自主复机",
|
||
BatchType: consts.BatchTypeStart,
|
||
BatchPrefix: consts.BatchNoPrefixStart,
|
||
TargetStatus: consts.V3ApiSimBossCardStatusActivated,
|
||
}, nil
|
||
case "restart":
|
||
return appCardLifecycleOperation{
|
||
Code: "restart",
|
||
Name: "重启卡",
|
||
BatchType: consts.BatchTypeRestart,
|
||
BatchPrefix: consts.BatchNoPrefixRestart,
|
||
}, nil
|
||
case "datastart", "data-start":
|
||
return appCardLifecycleOperation{
|
||
Code: "dataStart",
|
||
Name: "开通数据服务",
|
||
BatchType: consts.BatchTypeDataStart,
|
||
BatchPrefix: consts.BatchNoPrefixDataStart,
|
||
TargetStatus: 1,
|
||
}, nil
|
||
case "datastop", "data-stop":
|
||
return appCardLifecycleOperation{
|
||
Code: "dataStop",
|
||
Name: "停用数据服务",
|
||
BatchType: consts.BatchTypeDataStop,
|
||
BatchPrefix: consts.BatchNoPrefixDataStop,
|
||
TargetStatus: 0,
|
||
}, nil
|
||
default:
|
||
return appCardLifecycleOperation{}, gerror.New("不支持的卡片操作")
|
||
}
|
||
}
|
||
|
||
func appCardLifecycleOperationByBatchType(batchType int) (appCardLifecycleOperation, error) {
|
||
switch batchType {
|
||
case consts.BatchTypeStop:
|
||
return appCardLifecycleOperationByCode("stop")
|
||
case consts.BatchTypeStart:
|
||
return appCardLifecycleOperationByCode("start")
|
||
case consts.BatchTypeRestart:
|
||
return appCardLifecycleOperationByCode("restart")
|
||
case consts.BatchTypeDataStart:
|
||
return appCardLifecycleOperationByCode("dataStart")
|
||
case consts.BatchTypeDataStop:
|
||
return appCardLifecycleOperationByCode("dataStop")
|
||
default:
|
||
return appCardLifecycleOperation{}, gerror.Newf("批次类型%d不是卡片操作批次", batchType)
|
||
}
|
||
}
|
||
|
||
func appCardLifecycleIsDataService(operation appCardLifecycleOperation) bool {
|
||
return operation.BatchType == consts.BatchTypeDataStart || operation.BatchType == consts.BatchTypeDataStop
|
||
}
|
||
|
||
func appLifecycleOperType(operation appCardLifecycleOperation) string {
|
||
switch operation.Code {
|
||
case "stop":
|
||
return consts.SimCardStatusOperTypeStop
|
||
case "start":
|
||
return consts.SimCardStatusOperTypeStart
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
func appNormalizeLifecycleCardValues(cardValue string, cardValues []string) ([]string, int, error) {
|
||
values := make([]string, 0, len(cardValues)+1)
|
||
if strings.TrimSpace(cardValue) != "" {
|
||
values = append(values, cardValue)
|
||
}
|
||
values = append(values, cardValues...)
|
||
normalized := appNormalizeRenewCardValues(values)
|
||
mode := batchmetadata.Bulk
|
||
if strings.TrimSpace(cardValue) != "" && len(cardValues) == 0 {
|
||
mode = batchmetadata.Single
|
||
if len(normalized) != 1 {
|
||
return nil, mode, gerror.New("单卡操作只能指定一张卡,请使用批量操作")
|
||
}
|
||
}
|
||
return normalized, mode, nil
|
||
}
|
||
|
||
func appLifecycleBatchInfo(operation appCardLifecycleOperation, items []*appCardLifecycleItem, batchNo string, remark string, now *gtime.Time) *gjson.Json {
|
||
info := map[string]any{
|
||
"source": "app",
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"targetBatchType": operation.BatchType,
|
||
"targetBatchName": operation.Name,
|
||
"operation": operation.Code,
|
||
"remark": strings.TrimSpace(remark),
|
||
"quantity": len(items),
|
||
"operateTime": now,
|
||
"cards": appLifecycleCardSnapshotList(operation, items),
|
||
}
|
||
if operType := appLifecycleOperType(operation); operType != "" {
|
||
info["operType"] = operType
|
||
}
|
||
if appCardLifecycleIsDataService(operation) {
|
||
info["apnName"] = consts.SimCardDataApnName
|
||
if operation.Code == "dataStart" {
|
||
info["operType"] = consts.SimCardDataOperTypeStart
|
||
} else {
|
||
info["operType"] = consts.SimCardDataOperTypeStop
|
||
}
|
||
}
|
||
if operation.Code == "restart" {
|
||
delete(info, "operType")
|
||
info["stopOperType"] = consts.SimCardStatusOperTypeStop
|
||
info["resumeOperType"] = consts.SimCardStatusOperTypeStart
|
||
info["resumeDelaySeconds"] = appLifecycleRestartDelaySeconds
|
||
info["restart"] = map[string]any{"phase": appLifecycleRestartPhaseCreated}
|
||
}
|
||
return gjson.New(info)
|
||
}
|
||
|
||
func appLifecycleFinalBatchInfo(batchInfo *gjson.Json, operation appCardLifecycleOperation, rows []*appCardLifecycleExecutionRow, validationOK bool, failureReason string, now *gtime.Time) *gjson.Json {
|
||
remark := ""
|
||
if batchInfo != nil {
|
||
remark = strings.TrimSpace(batchInfo.Get("remark").String())
|
||
}
|
||
successCount := 0
|
||
for _, row := range rows {
|
||
if row != nil && row.Reason == "" {
|
||
successCount++
|
||
}
|
||
}
|
||
return gjson.New(map[string]any{
|
||
"source": "app",
|
||
"onelinkMock": operation.Code != "restart",
|
||
"localMock": operation.Code == "restart",
|
||
"targetBatchType": operation.BatchType,
|
||
"targetBatchName": operation.Name,
|
||
"operation": operation.Code,
|
||
"remark": remark,
|
||
"quantity": len(rows),
|
||
"successCount": successCount,
|
||
"failCount": len(rows) - successCount,
|
||
"validationOK": validationOK,
|
||
"failureReason": strings.TrimSpace(failureReason),
|
||
"finishTime": now,
|
||
})
|
||
}
|
||
|
||
func appLifecyclePendingDetailSnapshot(operation appCardLifecycleOperation, item *appCardLifecycleItem, batchNo string) *gjson.Json {
|
||
card := item.Card
|
||
snapshot := map[string]any{
|
||
"ICCID": card.Iccid,
|
||
"MSISDN": card.Msisdn,
|
||
"IMSI": card.Imsi,
|
||
"IMEI": card.Imei,
|
||
"rowNo": item.RowNo,
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"operation": operation.Code,
|
||
"authorized": true,
|
||
"chanKey": card.ChanKey,
|
||
"statusBefore": item.StatusBefore,
|
||
"targetStatus": operation.TargetStatus,
|
||
}
|
||
if operType := appLifecycleOperType(operation); operType != "" {
|
||
snapshot["operType"] = operType
|
||
}
|
||
return gjson.New(snapshot)
|
||
}
|
||
|
||
func appLifecycleDetailSnapshot(operation appCardLifecycleOperation, item *appCardLifecycleItem, batchNo string) *gjson.Json {
|
||
card := item.Card
|
||
return gjson.New(map[string]any{
|
||
"ICCID": card.Iccid,
|
||
"MSISDN": card.Msisdn,
|
||
"IMSI": card.Imsi,
|
||
"IMEI": card.Imei,
|
||
"rowNo": item.RowNo,
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"operation": operation.Code,
|
||
"onelinkMock": operation.Code != "restart",
|
||
"localMock": operation.Code == "restart",
|
||
"authorized": true,
|
||
"chanKey": card.ChanKey,
|
||
"statusBefore": item.StatusBefore,
|
||
"targetStatus": operation.TargetStatus,
|
||
"upstreamCode": 0,
|
||
"upstreamMessage": "本地模拟成功",
|
||
})
|
||
}
|
||
|
||
func appLifecycleExecutionSnapshot(operation appCardLifecycleOperation, row *appCardLifecycleExecutionRow, batchNo string) *gjson.Json {
|
||
if row == nil {
|
||
return gjson.New(map[string]any{})
|
||
}
|
||
return gjson.New(map[string]any{
|
||
"ICCID": row.Iccid,
|
||
"MSISDN": row.Msisdn,
|
||
"IMSI": row.Imsi,
|
||
"IMEI": row.Imei,
|
||
"rowNo": row.RowNo,
|
||
"batchNo": strings.TrimSpace(batchNo),
|
||
"operation": operation.Code,
|
||
"onelinkMock": operation.Code != "restart",
|
||
"localMock": operation.Code == "restart",
|
||
"authorized": row.Authorized,
|
||
"chanKey": row.ChanKey,
|
||
"statusBefore": row.StatusBefore,
|
||
"targetStatus": operation.TargetStatus,
|
||
"orderNum": row.OrderNum,
|
||
"upstreamCode": row.UpstreamCode,
|
||
"upstreamMessage": row.UpstreamMessage,
|
||
"errorReason": row.Reason,
|
||
})
|
||
}
|
||
|
||
func appLifecycleCardSnapshotList(operation appCardLifecycleOperation, items []*appCardLifecycleItem) []map[string]any {
|
||
cards := make([]map[string]any, 0, len(items))
|
||
for _, item := range items {
|
||
cards = append(cards, map[string]any{
|
||
"cardId": item.Card.Id,
|
||
"iccid": item.Card.Iccid,
|
||
"msisdn": item.Card.Msisdn,
|
||
"operation": operation.Code,
|
||
"statusBefore": item.StatusBefore,
|
||
"targetStatus": operation.TargetStatus,
|
||
})
|
||
}
|
||
return cards
|
||
}
|