2ed5dfa6a1
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
1116 lines
36 KiB
Go
1116 lines
36 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"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 := appNormalizeLifecycleCardValues(param.CardValue, param.CardValues)
|
|
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), 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
|
|
}
|
|
return &model.AppCardLifecycleModel{
|
|
BatchNo: batch.BatchNo,
|
|
BatchType: batch.BatchType,
|
|
Operation: operation.Code,
|
|
Status: appLifecycleBatchStatus(batch.BatchStatus, batch.BatchAsyncStatus),
|
|
Quantity: batch.TotalCount,
|
|
SuccessCount: batch.SuccessCount,
|
|
FailCount: batch.FailCount,
|
|
FailReason: strings.TrimSpace(batch.FailReason),
|
|
Cards: []*model.AppCardModel{},
|
|
}, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
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, 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,
|
|
SourceBatchNo: batchNo,
|
|
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 {
|
|
values := make([]string, 0, len(cardValues)+1)
|
|
if strings.TrimSpace(cardValue) != "" {
|
|
values = append(values, cardValue)
|
|
}
|
|
values = append(values, cardValues...)
|
|
return appNormalizeRenewCardValues(values)
|
|
}
|
|
|
|
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
|
|
}
|