chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。 - Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件) - 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、 MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位; 私网 IP → 文档地址段;库名 xiujie_admin → iotlcm - 真实手机号/邮箱 → 合法 11 位编造号 / example.com - 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、 私有依赖 go get 行、明文 registry 登录口令 - 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名 - Dockerfile 改用公共基础镜像 - 拼错的 RADEME.md → README.md,README 重写 - 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用 - 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type BatchExecutor interface {
|
||||
Execute(ctx context.Context, batchNo string) error
|
||||
MarkFailed(ctx context.Context, batchNo, reason string) error
|
||||
}
|
||||
|
||||
func Dispatch(executor BatchExecutor, batchNo string) {
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
reason := fmt.Sprintf("后台执行异常: %v", recovered)
|
||||
g.Log().Errorf(ctx, "SIM lifecycle batch panic, batchNo=%s, reason=%s", batchNo, reason)
|
||||
if err := executor.MarkFailed(ctx, batchNo, reason); err != nil {
|
||||
g.Log().Errorf(ctx, "mark SIM lifecycle batch failed after panic, batchNo=%s, error=%+v", batchNo, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err := executor.Execute(ctx, batchNo); err != nil {
|
||||
g.Log().Errorf(ctx, "execute SIM lifecycle batch failed, batchNo=%s, error=%+v", batchNo, err)
|
||||
if markErr := executor.MarkFailed(ctx, batchNo, err.Error()); markErr != nil {
|
||||
g.Log().Errorf(ctx, "mark SIM lifecycle batch failed, batchNo=%s, error=%+v", batchNo, markErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type executorRepository interface {
|
||||
ClaimBatch(ctx context.Context, batchNo string) (bool, error)
|
||||
LoadTask(ctx context.Context, batchNo string) (*Task, error)
|
||||
LoadCards(ctx context.Context, task *Task) (map[string]*entity.TSimCardInfo, error)
|
||||
SaveValidationFailure(ctx context.Context, task *Task) error
|
||||
SaveOutcome(ctx context.Context, task *Task, row *Row, card *entity.TSimCardInfo, outcome Outcome) error
|
||||
SaveDetailFailure(ctx context.Context, task *Task, row *Row) error
|
||||
FinalizeBatch(ctx context.Context, task *Task) error
|
||||
MarkBatchFailed(ctx context.Context, batchNo, reason string) error
|
||||
}
|
||||
|
||||
type statusChanger interface {
|
||||
ChangeSimStatus(ctx context.Context, input *model.SimCardStatusChangeInput) ([]model.SimBaseRes[model.SimCardStatusChangeItem], error)
|
||||
}
|
||||
|
||||
type Executor struct {
|
||||
repo executorRepository
|
||||
upstream statusChanger
|
||||
}
|
||||
|
||||
func NewExecutor() *Executor {
|
||||
return &Executor{repo: NewRepository(), upstream: service.UpstreamChan()}
|
||||
}
|
||||
|
||||
func newExecutor(repo executorRepository, upstream statusChanger) *Executor {
|
||||
return &Executor{repo: repo, upstream: upstream}
|
||||
}
|
||||
|
||||
func (e *Executor) Execute(ctx context.Context, batchNo string) error {
|
||||
batchNo = strings.TrimSpace(batchNo)
|
||||
if batchNo == "" {
|
||||
return gerror.New("批次号不能为空")
|
||||
}
|
||||
claimed, err := e.repo.ClaimBatch(ctx, batchNo)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "抢占停复机批次失败")
|
||||
}
|
||||
if !claimed {
|
||||
return nil
|
||||
}
|
||||
task, err := e.repo.LoadTask(ctx, batchNo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cards, err := e.repo.LoadCards(ctx, task)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "读取SIM卡业务数据失败")
|
||||
}
|
||||
if !validateTask(task, cards) {
|
||||
if err = e.repo.SaveValidationFailure(ctx, task); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.repo.FinalizeBatch(ctx, task)
|
||||
}
|
||||
|
||||
groups := groupRowsByChannel(task.Rows)
|
||||
channelKeys := make([]string, 0, len(groups))
|
||||
for chanKey := range groups {
|
||||
channelKeys = append(channelKeys, chanKey)
|
||||
}
|
||||
sort.Strings(channelKeys)
|
||||
for _, chanKey := range channelKeys {
|
||||
rows := groups[chanKey]
|
||||
outcomes := e.changeGroup(ctx, task, rows)
|
||||
for i, row := range rows {
|
||||
outcome := outcomes[i]
|
||||
card := cards[row.Iccid]
|
||||
if err = e.repo.SaveOutcome(ctx, task, row, card, outcome); err == nil {
|
||||
continue
|
||||
}
|
||||
row.OrderNum = outcome.OrderNum
|
||||
row.UpstreamCode = outcome.Code
|
||||
row.UpstreamReason = outcome.Message
|
||||
row.Reason = "上游已返回,但本地状态落库失败: " + err.Error()
|
||||
if saveErr := e.repo.SaveDetailFailure(ctx, task, row); saveErr != nil {
|
||||
return gerror.Wrap(saveErr, "记录本地状态落库失败结果失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
return e.repo.FinalizeBatch(ctx, task)
|
||||
}
|
||||
|
||||
func (e *Executor) MarkFailed(ctx context.Context, batchNo, reason string) error {
|
||||
return e.repo.MarkBatchFailed(ctx, strings.TrimSpace(batchNo), strings.TrimSpace(reason))
|
||||
}
|
||||
|
||||
func validateTask(task *Task, cards map[string]*entity.TSimCardInfo) bool {
|
||||
hasFailure := false
|
||||
for _, row := range task.Rows {
|
||||
if row == nil {
|
||||
hasFailure = true
|
||||
continue
|
||||
}
|
||||
if row.Reason != "" {
|
||||
hasFailure = true
|
||||
continue
|
||||
}
|
||||
card := cards[row.Iccid]
|
||||
if card == nil {
|
||||
row.AddErrorReason("ICCID不存在于正式卡记录")
|
||||
} else if !row.Authorized {
|
||||
row.AddErrorReason("ICCID不在当前操作人的数据权限范围内")
|
||||
} else if strings.TrimSpace(card.ChanKey) == "" {
|
||||
row.AddErrorReason("ICCID缺少通道标识chanKey")
|
||||
} else if row.Msisdn != "" && row.Msisdn != strings.TrimSpace(card.Msisdn) {
|
||||
row.AddErrorReason("MSISDN与正式卡记录不一致")
|
||||
} else if row.Imsi != "" && row.Imsi != strings.TrimSpace(card.Imsi) {
|
||||
row.AddErrorReason("IMSI与正式卡记录不一致")
|
||||
} else {
|
||||
validateCardStatus(task.Operation, row, card)
|
||||
}
|
||||
if row.Reason != "" {
|
||||
hasFailure = true
|
||||
continue
|
||||
}
|
||||
row.Msisdn = strings.TrimSpace(card.Msisdn)
|
||||
row.Imsi = strings.TrimSpace(card.Imsi)
|
||||
row.ChanKey = strings.TrimSpace(card.ChanKey)
|
||||
row.StatusBefore = card.BossCardStatus
|
||||
}
|
||||
if hasFailure {
|
||||
for _, row := range task.Rows {
|
||||
if row != nil && row.Reason == "" {
|
||||
row.AddErrorReason("同批次存在校验失败卡,本卡未请求上游")
|
||||
}
|
||||
}
|
||||
task.FailureReason = "整批业务校验失败,未请求上游"
|
||||
return false
|
||||
}
|
||||
task.ValidationOK = true
|
||||
return true
|
||||
}
|
||||
|
||||
func validateCardStatus(operation Operation, row *Row, card *entity.TSimCardInfo) {
|
||||
// TODO: 正常系统开发完成后恢复卡状态限制
|
||||
// 注释原因:开发环境经常出现卡状态不在预期可操作范围内的情况,先放行
|
||||
_ = operation
|
||||
_ = row
|
||||
_ = card
|
||||
// switch operation {
|
||||
// case OperationSuspend:
|
||||
// if card.BossCardStatus != consts.V3ApiSimBossCardStatusActivated {
|
||||
// row.AddErrorReason("仅已激活状态的卡可以自主停机")
|
||||
// }
|
||||
// case OperationResume:
|
||||
// if card.BossCardStatus != consts.V3ApiSimBossCardStatusStopped {
|
||||
// row.AddErrorReason("仅停机状态的卡可以自主复机")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func groupRowsByChannel(rows []*Row) map[string][]*Row {
|
||||
groups := make(map[string][]*Row)
|
||||
for _, row := range rows {
|
||||
groups[row.ChanKey] = append(groups[row.ChanKey], row)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func (e *Executor) changeGroup(ctx context.Context, task *Task, rows []*Row) []Outcome {
|
||||
simKeys := make([]*model.SimKey, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
simKeys = append(simKeys, &model.SimKey{
|
||||
ChanKey: row.ChanKey,
|
||||
ICCID: row.Iccid,
|
||||
MSISDN: row.Msisdn,
|
||||
IMSI: row.Imsi,
|
||||
})
|
||||
}
|
||||
response, err := e.upstream.ChangeSimStatus(ctx, &model.SimCardStatusChangeInput{
|
||||
SimKeys: simKeys,
|
||||
ReqData: model.SimCardStatusChangeInputItem{OperType: task.Config.OperType},
|
||||
})
|
||||
if err != nil {
|
||||
outcomes := make([]Outcome, len(rows))
|
||||
for i := range outcomes {
|
||||
outcomes[i] = Outcome{Message: "上游请求失败: " + err.Error()}
|
||||
}
|
||||
return outcomes
|
||||
}
|
||||
return mapOutcomes(rows, response)
|
||||
}
|
||||
|
||||
func mapOutcomes(rows []*Row, response []model.SimBaseRes[model.SimCardStatusChangeItem]) []Outcome {
|
||||
outcomes := make([]Outcome, len(rows))
|
||||
used := make([]bool, len(response))
|
||||
for rowIndex, row := range rows {
|
||||
matched := -1
|
||||
for responseIndex, item := range response {
|
||||
if !used[responseIndex] && sameSim(row, item.SimKey) {
|
||||
matched = responseIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched < 0 && rowIndex < len(response) && !used[rowIndex] && response[rowIndex].SimKey == nil {
|
||||
matched = rowIndex
|
||||
}
|
||||
if matched < 0 {
|
||||
outcomes[rowIndex] = Outcome{Message: "上游未返回该卡处理结果"}
|
||||
continue
|
||||
}
|
||||
used[matched] = true
|
||||
item := response[matched]
|
||||
message := strings.TrimSpace(item.Message)
|
||||
if item.Code != 0 && message == "" {
|
||||
message = "上游处理失败"
|
||||
}
|
||||
outcomes[rowIndex] = Outcome{
|
||||
Success: item.Code == 0,
|
||||
Code: item.Code,
|
||||
Message: message,
|
||||
OrderNum: strings.TrimSpace(item.ResData.OrderNum),
|
||||
}
|
||||
}
|
||||
return outcomes
|
||||
}
|
||||
|
||||
func sameSim(row *Row, key *model.SimKey) bool {
|
||||
if row == nil || key == nil {
|
||||
return false
|
||||
}
|
||||
if value := strings.TrimSpace(key.ICCID); value != "" {
|
||||
return value == row.Iccid
|
||||
}
|
||||
if value := strings.TrimSpace(key.MSISDN); value != "" {
|
||||
return value == row.Msisdn
|
||||
}
|
||||
if value := strings.TrimSpace(key.IMSI); value != "" {
|
||||
return value == row.Imsi
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type Operation int
|
||||
|
||||
const (
|
||||
OperationSuspend Operation = iota + 1
|
||||
OperationResume
|
||||
OperationRestart
|
||||
)
|
||||
|
||||
type OperationConfig struct {
|
||||
Operation Operation
|
||||
Code string
|
||||
Name string
|
||||
BatchType int
|
||||
BatchPrefix string
|
||||
OperType string
|
||||
TargetStatus int
|
||||
TaskName string
|
||||
ExportScene int
|
||||
ImportDir string
|
||||
ResultDir string
|
||||
}
|
||||
|
||||
func (operation Operation) Config() (OperationConfig, error) {
|
||||
switch operation {
|
||||
case OperationSuspend:
|
||||
return OperationConfig{
|
||||
Operation: operation,
|
||||
Code: "stop",
|
||||
Name: "自主停机",
|
||||
BatchType: consts.BatchTypeStop,
|
||||
BatchPrefix: consts.BatchNoPrefixStop,
|
||||
OperType: consts.SimCardStatusOperTypeStop,
|
||||
TargetStatus: consts.V3ApiSimBossCardStatusStopped,
|
||||
TaskName: consts.ResourceBatchDownloadTaskSimCardStop,
|
||||
ExportScene: consts.ResourceExportSceneStop,
|
||||
ImportDir: consts.SimCardStopSubDirImportExcel,
|
||||
ResultDir: consts.SimCardStopSubDirResultExcel,
|
||||
}, nil
|
||||
case OperationResume:
|
||||
return OperationConfig{
|
||||
Operation: operation,
|
||||
Code: "start",
|
||||
Name: "自主复机",
|
||||
BatchType: consts.BatchTypeStart,
|
||||
BatchPrefix: consts.BatchNoPrefixStart,
|
||||
OperType: consts.SimCardStatusOperTypeStart,
|
||||
TargetStatus: consts.V3ApiSimBossCardStatusActivated,
|
||||
TaskName: consts.ResourceBatchDownloadTaskSimCardStart,
|
||||
ExportScene: consts.ResourceExportSceneStart,
|
||||
ImportDir: consts.SimCardStartSubDirImportExcel,
|
||||
ResultDir: consts.SimCardStartSubDirResultExcel,
|
||||
}, nil
|
||||
case OperationRestart:
|
||||
return OperationConfig{
|
||||
Operation: operation,
|
||||
Code: "restart",
|
||||
Name: "自主重启",
|
||||
BatchType: consts.BatchTypeRestart,
|
||||
BatchPrefix: consts.BatchNoPrefixRestart,
|
||||
TaskName: consts.ResourceBatchDownloadTaskSimCardRestart,
|
||||
ExportScene: consts.ResourceExportSceneRestart,
|
||||
ImportDir: consts.SimCardRestartSubDirImportExcel,
|
||||
ResultDir: consts.SimCardRestartSubDirResultExcel,
|
||||
}, nil
|
||||
default:
|
||||
return OperationConfig{}, gerror.New("不支持的SIM卡状态操作")
|
||||
}
|
||||
}
|
||||
|
||||
func OperationByBatchType(batchType int) (Operation, error) {
|
||||
switch batchType {
|
||||
case consts.BatchTypeStop:
|
||||
return OperationSuspend, nil
|
||||
case consts.BatchTypeStart:
|
||||
return OperationResume, nil
|
||||
default:
|
||||
return 0, gerror.Newf("批次类型%d不是停复机执行批次", batchType)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/excel"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/text"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func ParseExcelFileRows(ctx context.Context, filePath string) ([]batchimport.Row, error) {
|
||||
parsed, err := excel.ParseFile(ctx, filePath, excel.ParseOptions{RequiredHeaders: []string{"ICCID"}, MaxDataRows: consts.SimCardBatchMaxRows})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(parsed))
|
||||
for _, item := range parsed {
|
||||
rows = append(rows, &Row{
|
||||
RowNo: item.RowNo,
|
||||
Iccid: excel.Field(item, "ICCID"),
|
||||
Msisdn: excel.Field(item, "MSISDN"),
|
||||
Imsi: excel.Field(item, "IMSI"),
|
||||
})
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func ParseInputRows(ctx context.Context, input string) ([]batchimport.Row, error) {
|
||||
lines, err := text.ParseWhitespaceFields(ctx, input)
|
||||
if err != nil {
|
||||
if errors.Is(err, text.ErrEmptyInput) {
|
||||
return nil, gerror.New("批量输入内容不能为空")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
row := &Row{RowNo: line.RowNo}
|
||||
switch len(line.Fields) {
|
||||
case 1:
|
||||
row.Iccid = line.Fields[0]
|
||||
case 3:
|
||||
row.Iccid = line.Fields[0]
|
||||
row.Msisdn = line.Fields[1]
|
||||
row.Imsi = line.Fields[2]
|
||||
default:
|
||||
row.AddErrorReason("格式错误,应为 ICCID 或 ICCID MSISDN IMSI")
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func validateBasicRows(rows []batchimport.Row) error {
|
||||
firstByIccid := make(map[string]*Row, len(rows))
|
||||
issues := make([]string, 0)
|
||||
for _, item := range rows {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return gerror.New("停复机批次行类型错误")
|
||||
}
|
||||
row.Iccid = strings.TrimSpace(row.Iccid)
|
||||
row.Msisdn = strings.TrimSpace(row.Msisdn)
|
||||
row.Imsi = strings.TrimSpace(row.Imsi)
|
||||
if row.Reason != "" {
|
||||
issues = append(issues, fmt.Sprintf("第%d行:%s", row.RowNo, row.Reason))
|
||||
continue
|
||||
}
|
||||
if row.Iccid == "" {
|
||||
row.AddErrorReason("ICCID不能为空")
|
||||
issues = append(issues, fmt.Sprintf("第%d行:ICCID不能为空", row.RowNo))
|
||||
continue
|
||||
}
|
||||
if first, exists := firstByIccid[row.Iccid]; exists {
|
||||
row.AddErrorReason(fmt.Sprintf("ICCID与第%d行重复", first.RowNo))
|
||||
first.AddErrorReason("ICCID重复")
|
||||
issues = append(issues, fmt.Sprintf("第%d行:ICCID与第%d行重复", row.RowNo, first.RowNo))
|
||||
continue
|
||||
}
|
||||
firstByIccid[row.Iccid] = row
|
||||
}
|
||||
if len(issues) > 0 {
|
||||
return gerror.New("停复机批次基础格式校验失败:" + strings.Join(issues, ";"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
"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/frame/g"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
config OperationConfig
|
||||
}
|
||||
|
||||
func NewProcessor(operation Operation) (*Processor, error) {
|
||||
config, err := operation.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Processor{config: config}, nil
|
||||
}
|
||||
|
||||
func (p *Processor) Config() batchimport.ProcessorConfig {
|
||||
return batchimport.ProcessorConfig{
|
||||
Code: "sim_card_" + p.config.Code,
|
||||
BatchType: p.config.BatchType,
|
||||
BatchNoPrefix: p.config.BatchPrefix,
|
||||
TaskName: p.config.TaskName,
|
||||
ExportScene: p.config.ExportScene,
|
||||
ImportDir: p.config.ImportDir,
|
||||
ResultDir: p.config.ResultDir,
|
||||
MaxFileSize: consts.SimCardLifecycleMaxFileSize,
|
||||
MaxRows: consts.SimCardBatchMaxRows,
|
||||
TemplateHeaders: []string{"ICCID", "MSISDN", "IMSI"},
|
||||
RequiredHeaders: []string{"ICCID"},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) {
|
||||
return ParseExcelFileRows(ctx, filePath)
|
||||
}
|
||||
|
||||
func (p *Processor) ValidateRows(_ context.Context, rows []batchimport.Row) error {
|
||||
return validateBasicRows(rows)
|
||||
}
|
||||
|
||||
func (p *Processor) Execute(context.Context, gdb.TX, *batchimport.SubmitRequest, []batchimport.Row) error {
|
||||
return gerror.New("停复机批次只能由异步执行器执行")
|
||||
}
|
||||
|
||||
func (p *Processor) BuildBatchInfo(_ context.Context, req batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadID int64) *gjson.Json {
|
||||
info := g.Map{
|
||||
"operation": p.config.Code,
|
||||
}
|
||||
if p.config.OperType != "" {
|
||||
info["operType"] = p.config.OperType
|
||||
}
|
||||
if p.config.Operation == OperationRestart {
|
||||
info["stopOperType"] = consts.SimCardStatusOperTypeStop
|
||||
info["resumeOperType"] = consts.SimCardStatusOperTypeStart
|
||||
info["resumeDelaySeconds"] = 300
|
||||
info["restart"] = g.Map{"phase": "created"}
|
||||
}
|
||||
if options, ok := req.BizParams.(*SubmitOptions); ok && options != nil {
|
||||
if remark := strings.TrimSpace(options.Remark); remark != "" {
|
||||
info["remark"] = remark
|
||||
}
|
||||
}
|
||||
if result != nil && result.ImportFileOssId > 0 {
|
||||
info["importFileOssId"] = result.ImportFileOssId
|
||||
} else if importOss != nil && importOss.OssId > 0 {
|
||||
info["importFileOssId"] = importOss.OssId
|
||||
}
|
||||
if resultOss != nil {
|
||||
info["resultFileOssId"] = resultOss.OssId
|
||||
}
|
||||
if downloadID > 0 {
|
||||
info["resultDownloadId"] = downloadID
|
||||
}
|
||||
return gjson.New(info)
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchmetadata"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type Repository struct{}
|
||||
|
||||
func NewRepository() *Repository {
|
||||
return &Repository{}
|
||||
}
|
||||
|
||||
func (r *Repository) ClaimBatch(ctx context.Context, batchNo string) (bool, error) {
|
||||
claimed := false
|
||||
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
result, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
|
||||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||||
WhereIn(dao.BatchMain.Columns().BatchType, []int{consts.BatchTypeStart, consts.BatchTypeStop}).
|
||||
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusPending).
|
||||
Data(do.BatchMain{
|
||||
BatchAsyncStatus: consts.BatchAsyncStatusProcessing,
|
||||
RunResult: consts.BatchRunResultRunning,
|
||||
StartTime: gtime.Now(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected != 1 {
|
||||
return nil
|
||||
}
|
||||
claimed = true
|
||||
_, err = tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusPending).
|
||||
Data(do.BatchDetail{DetailStatus: consts.BatchDetailStatusRunning}).
|
||||
Update()
|
||||
return err
|
||||
})
|
||||
return claimed, err
|
||||
}
|
||||
|
||||
func (r *Repository) LoadTask(ctx context.Context, batchNo string) (*Task, error) {
|
||||
batch := &entity.BatchMain{}
|
||||
if err := dao.BatchMain.Ctx(ctx).
|
||||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||||
Scan(batch); err != nil {
|
||||
return nil, gerror.Wrap(err, "读取停复机批次失败")
|
||||
}
|
||||
if batch.Id == 0 {
|
||||
return nil, gerror.New("停复机批次不存在")
|
||||
}
|
||||
operation, err := OperationByBatchType(batch.BatchType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, err := operation.Config()
|
||||
if err != nil {
|
||||
return 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, gerror.Wrap(err, "读取停复机批次明细失败")
|
||||
}
|
||||
if len(details) == 0 {
|
||||
return nil, gerror.New("停复机批次没有明细")
|
||||
}
|
||||
|
||||
task := &Task{
|
||||
Batch: batch,
|
||||
Operation: operation,
|
||||
Config: config,
|
||||
Rows: make([]*Row, 0, len(details)),
|
||||
Operator: batchimport.OperatorContext{TenantId: batch.TenantId, DeptId: batch.CreatedDept, UserId: batch.CreatedBy},
|
||||
StartTime: batch.StartTime,
|
||||
}
|
||||
if batch.BatchInfo != nil {
|
||||
task.Remark = strings.TrimSpace(batch.BatchInfo.Get("remark").String())
|
||||
}
|
||||
task.ImportOssID = sourceOssIDFromBatch(batch)
|
||||
for _, detail := range details {
|
||||
row := &Row{
|
||||
DetailID: detail.Id,
|
||||
RowNo: detail.RowNo,
|
||||
Iccid: strings.TrimSpace(detail.ItemId),
|
||||
Reason: strings.TrimSpace(detail.ErrorReason),
|
||||
}
|
||||
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.Authorized = detail.Snapshot.Get("authorized").Bool()
|
||||
}
|
||||
task.Rows = append(task.Rows, row)
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (r *Repository) LoadCards(ctx context.Context, task *Task) (map[string]*entity.TSimCardInfo, error) {
|
||||
result := make(map[string]*entity.TSimCardInfo, len(task.Rows))
|
||||
iccids := make([]string, 0, len(task.Rows))
|
||||
for _, row := range task.Rows {
|
||||
if row != nil && row.Iccid != "" {
|
||||
iccids = append(iccids, row.Iccid)
|
||||
}
|
||||
}
|
||||
if len(iccids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
var cards []*entity.TSimCardInfo
|
||||
err := dao.TSimCardInfo.Ctx(ctx).
|
||||
Where(dao.TSimCardInfo.Columns().TenantId, task.Batch.TenantId).
|
||||
WhereIn(dao.TSimCardInfo.Columns().Iccid, iccids).
|
||||
Scan(&cards)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, card := range cards {
|
||||
if card != nil {
|
||||
result[strings.TrimSpace(card.Iccid)] = card
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) SaveValidationFailure(ctx context.Context, task *Task) error {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
for _, row := range task.Rows {
|
||||
if row == nil {
|
||||
continue
|
||||
}
|
||||
if _, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().Id, row.DetailID).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
|
||||
Data(do.BatchDetail{
|
||||
DetailStatus: consts.BatchDetailStatusFailed,
|
||||
ErrorReason: row.Reason,
|
||||
Snapshot: row.Snapshot(),
|
||||
UpdatedBy: task.Operator.UserId,
|
||||
}).Update(); err != nil {
|
||||
return gerror.Wrap(err, "保存整批校验失败明细失败")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) SaveOutcome(ctx context.Context, task *Task, row *Row, card *entity.TSimCardInfo, outcome Outcome) error {
|
||||
row.OrderNum = outcome.OrderNum
|
||||
row.UpstreamCode = outcome.Code
|
||||
row.UpstreamReason = outcome.Message
|
||||
if outcome.Success {
|
||||
row.Reason = ""
|
||||
} else if strings.TrimSpace(outcome.Message) != "" {
|
||||
row.Reason = outcome.Message
|
||||
} else {
|
||||
row.Reason = "上游处理失败"
|
||||
}
|
||||
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if outcome.Success {
|
||||
result, err := tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
|
||||
Where(dao.TSimCardInfo.Columns().Id, card.Id).
|
||||
Where(dao.TSimCardInfo.Columns().TenantId, task.Batch.TenantId).
|
||||
Where(dao.TSimCardInfo.Columns().BossCardStatus, row.StatusBefore).
|
||||
Data(do.TSimCardInfo{
|
||||
BossCardStatus: task.Config.TargetStatus,
|
||||
StatusSyncTime: gtime.Now(),
|
||||
UpdatedBy: task.Operator.UserId,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected != 1 {
|
||||
return gerror.New("SIM卡本地状态已变化")
|
||||
}
|
||||
}
|
||||
|
||||
detailStatus := consts.BatchDetailStatusFailed
|
||||
if outcome.Success {
|
||||
detailStatus = consts.BatchDetailStatusSuccess
|
||||
}
|
||||
result, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().Id, row.DetailID).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
|
||||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusRunning).
|
||||
Data(do.BatchDetail{
|
||||
DetailStatus: detailStatus,
|
||||
ErrorReason: row.Reason,
|
||||
Snapshot: row.Snapshot(),
|
||||
UpdatedBy: task.Operator.UserId,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected != 1 {
|
||||
return gerror.New("批次明细状态已变化")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) SaveDetailFailure(ctx context.Context, task *Task, row *Row) error {
|
||||
_, err := dao.BatchDetail.Ctx(ctx).
|
||||
Where(dao.BatchDetail.Columns().Id, row.DetailID).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
|
||||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusRunning).
|
||||
Data(do.BatchDetail{
|
||||
DetailStatus: consts.BatchDetailStatusFailed,
|
||||
ErrorReason: row.Reason,
|
||||
Snapshot: row.Snapshot(),
|
||||
UpdatedBy: task.Operator.UserId,
|
||||
}).Update()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Repository) FinalizeBatch(ctx context.Context, task *Task) error {
|
||||
processor, err := NewProcessor(task.Operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows := make([]batchimport.Row, 0, len(task.Rows))
|
||||
successCount := 0
|
||||
for _, row := range task.Rows {
|
||||
rows = append(rows, row)
|
||||
if row != nil && row.Reason == "" {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
failCount := len(task.Rows) - successCount
|
||||
validCount := 0
|
||||
if task.ValidationOK {
|
||||
validCount = len(task.Rows)
|
||||
}
|
||||
importStatus := consts.ImportStatusSuccess
|
||||
batchStatus := consts.BatchAsyncStatusSuccess
|
||||
runResult := consts.BatchRunResultSuccess
|
||||
if failCount > 0 {
|
||||
importStatus = consts.ImportStatusFailed
|
||||
batchStatus = consts.BatchAsyncStatusFailed
|
||||
runResult = consts.BatchRunResultFailed
|
||||
if task.FailureReason == "" {
|
||||
task.FailureReason = fmt.Sprintf("%d张卡处理失败", failCount)
|
||||
}
|
||||
}
|
||||
|
||||
result := &batchimport.Result{
|
||||
ImportStatus: importStatus,
|
||||
BatchNo: task.Batch.BatchNo,
|
||||
BatchType: task.Batch.BatchType,
|
||||
FailReason: task.FailureReason,
|
||||
ImportFileOssId: task.ImportOssID,
|
||||
TotalRows: len(task.Rows),
|
||||
ValidRows: validCount,
|
||||
ErrorRows: failCount,
|
||||
InsertedRows: successCount,
|
||||
}
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Channel(task.Batch.SourceChannel),
|
||||
ExecutionMode: int(task.Batch.ExecutionMode),
|
||||
BizParams: &SubmitOptions{Remark: task.Remark},
|
||||
SourceType: task.Batch.SourceType,
|
||||
Operator: task.Operator,
|
||||
BatchNo: task.Batch.BatchNo,
|
||||
BatchType: task.Batch.BatchType,
|
||||
}
|
||||
importOss, err := r.loadImportOss(ctx, task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endTime := gtime.Now()
|
||||
backgroundCtx := contexts.Set(ctx, &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: task.Operator.UserId,
|
||||
DeptId: task.Operator.DeptId,
|
||||
TenantId: task.Operator.TenantId,
|
||||
}}})
|
||||
resultOss, downloadID, err := batchimport.SaveAsyncResultFiles(backgroundCtx, req, processor, result, rows, importOss, task.StartTime, endTime)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "生成停复机结果Excel失败")
|
||||
}
|
||||
batchInfo := processor.BuildBatchInfo(backgroundCtx, req, result, importOss, resultOss, downloadID)
|
||||
_, err = dao.BatchMain.Ctx(ctx).
|
||||
Where(dao.BatchMain.Columns().BatchNo, task.Batch.BatchNo).
|
||||
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusProcessing).
|
||||
Data(do.BatchMain{
|
||||
BatchAsyncStatus: batchStatus,
|
||||
ValidCount: validCount,
|
||||
SuccessCount: successCount,
|
||||
FailCount: failCount,
|
||||
RunResult: runResult,
|
||||
FailReason: task.FailureReason,
|
||||
ResultOssId: lifecycleOssIDValue(resultOss.OssId),
|
||||
BatchInfo: batchInfo,
|
||||
EndTime: endTime,
|
||||
UpdatedBy: task.Operator.UserId,
|
||||
}).Update()
|
||||
return gerror.Wrap(err, "汇总停复机批次失败")
|
||||
}
|
||||
|
||||
func (r *Repository) loadImportOss(ctx context.Context, task *Task) (*model.SysOssSaveContentModel, error) {
|
||||
if task.ImportOssID == 0 {
|
||||
return &model.SysOssSaveContentModel{SysOss: &entity.SysOss{}}, nil
|
||||
}
|
||||
oss := &entity.SysOss{}
|
||||
if err := dao.SysOss.Ctx(ctx).
|
||||
Where(dao.SysOss.Columns().OssId, task.ImportOssID).
|
||||
Where(dao.SysOss.Columns().TenantId, task.Batch.TenantId).
|
||||
Scan(oss); err != nil {
|
||||
return nil, gerror.Wrap(err, "读取原始导入文件失败")
|
||||
}
|
||||
return &model.SysOssSaveContentModel{SysOss: oss}, nil
|
||||
}
|
||||
|
||||
func sourceOssIDFromBatch(batch *entity.BatchMain) int64 {
|
||||
if batch == nil {
|
||||
return 0
|
||||
}
|
||||
if batch.SourceOssId > 0 {
|
||||
return batch.SourceOssId
|
||||
}
|
||||
if batch.BatchInfo == nil {
|
||||
return 0
|
||||
}
|
||||
for _, path := range []string{"importFileOssId", "source.importFileOssId", "source.inputFileOssId"} {
|
||||
if ossID := batch.BatchInfo.Get(path).Int64(); ossID > 0 {
|
||||
return ossID
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func lifecycleOssIDValue(ossID int64) any {
|
||||
if ossID <= 0 {
|
||||
return nil
|
||||
}
|
||||
return ossID
|
||||
}
|
||||
|
||||
func (r *Repository) MarkBatchFailed(ctx context.Context, batchNo, reason string) error {
|
||||
if batchNo == "" {
|
||||
return gerror.New("批次号不能为空")
|
||||
}
|
||||
if reason == "" {
|
||||
reason = "后台执行失败"
|
||||
}
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
_, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||||
WhereIn(dao.BatchDetail.Columns().DetailStatus, []int{consts.BatchDetailStatusPending, consts.BatchDetailStatusRunning}).
|
||||
Data(do.BatchDetail{
|
||||
DetailStatus: consts.BatchDetailStatusFailed,
|
||||
ErrorReason: reason,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
successCount, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusSuccess).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
failCount, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
|
||||
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
|
||||
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusFailed).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Ctx(ctx).Model(dao.BatchMain.Table()).
|
||||
Where(dao.BatchMain.Columns().BatchNo, batchNo).
|
||||
WhereIn(dao.BatchMain.Columns().BatchAsyncStatus, []int{consts.BatchAsyncStatusPending, consts.BatchAsyncStatusProcessing}).
|
||||
Data(do.BatchMain{
|
||||
BatchAsyncStatus: consts.BatchAsyncStatusFailed,
|
||||
SuccessCount: successCount,
|
||||
FailCount: failCount,
|
||||
RunResult: consts.BatchRunResultFailed,
|
||||
FailReason: reason,
|
||||
EndTime: gtime.Now(),
|
||||
}).Update()
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchmetadata"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/common"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type BatchExecuteMessage = common.BatchExecuteMessage
|
||||
|
||||
type SubmitResult = common.SubmitResult
|
||||
|
||||
type Submitter struct {
|
||||
operation Operation
|
||||
}
|
||||
|
||||
func NewSubmitter(operation Operation) *Submitter {
|
||||
return &Submitter{operation: operation}
|
||||
}
|
||||
|
||||
func (s *Submitter) SubmitExcel(ctx context.Context, file *ghttp.UploadFile, remark string) (*SubmitResult, error) {
|
||||
processor, err := NewProcessor(s.operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
File: file,
|
||||
BizParams: &common.SubmitOptions{Remark: strings.TrimSpace(remark)},
|
||||
SourceType: consts.BatchSourceTypeFile,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingUpload(ctx, req, processor, batchimport.PendingOptions{
|
||||
PrepareRows: func(ctx context.Context, rows []batchimport.Row) error {
|
||||
return common.PrepareAuthorizedRows(ctx, rows, func(item batchimport.Row) (*Row, error) {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return nil, gerror.New("停复机批次行类型错误")
|
||||
}
|
||||
return row, nil
|
||||
}, "停复机批次行类型错误")
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, "lifecycle")
|
||||
return common.PendingSubmitResult(result, consts.BatchAsyncStatusPending), nil
|
||||
}
|
||||
|
||||
func (s *Submitter) SubmitInput(ctx context.Context, input, remark string) (*SubmitResult, error) {
|
||||
rows, err := ParseInputRows(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
processor, err := NewProcessor(s.operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := batchimport.SubmitRequest{
|
||||
SourceChannel: batchmetadata.Console,
|
||||
ExecutionMode: batchmetadata.Bulk,
|
||||
BizParams: &common.SubmitOptions{Remark: strings.TrimSpace(remark)},
|
||||
SourceType: consts.BatchSourceTypeBatchInput,
|
||||
Operator: common.CurrentOperator(ctx),
|
||||
}
|
||||
result, err := batchimport.CreatePendingRows(ctx, req, processor, rows, batchimport.PendingOptions{
|
||||
PrepareRows: func(ctx context.Context, rows []batchimport.Row) error {
|
||||
return common.PrepareAuthorizedRows(ctx, rows, func(item batchimport.Row) (*Row, error) {
|
||||
row, ok := item.(*Row)
|
||||
if !ok || row == nil {
|
||||
return nil, gerror.New("停复机批次行类型错误")
|
||||
}
|
||||
return row, nil
|
||||
}, "停复机批次行类型错误")
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.PublishBatchMessage(ctx, result, consts.QueueBatchExecute, "lifecycle")
|
||||
return common.PendingSubmitResult(result, consts.BatchAsyncStatusPending), nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/iot/simcardbatch/common"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type Row struct {
|
||||
DetailID int64
|
||||
RowNo int
|
||||
Iccid string
|
||||
Msisdn string
|
||||
Imsi string
|
||||
Authorized bool
|
||||
ChanKey string
|
||||
StatusBefore int
|
||||
OrderNum string
|
||||
UpstreamCode int
|
||||
UpstreamReason string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (r *Row) GetRowNo() int {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
return r.RowNo
|
||||
}
|
||||
|
||||
func (r *Row) GetItemID() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.Iccid
|
||||
}
|
||||
|
||||
func (r *Row) GetErrorReasons() []string {
|
||||
if r == nil || r.Reason == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{r.Reason}
|
||||
}
|
||||
|
||||
func (r *Row) AddErrorReason(reason string) {
|
||||
if r == nil || r.Reason != "" || reason == "" {
|
||||
return
|
||||
}
|
||||
r.Reason = reason
|
||||
}
|
||||
|
||||
func (r *Row) GetIccid() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.Iccid
|
||||
}
|
||||
|
||||
func (r *Row) SetAuthorized(v bool) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
r.Authorized = v
|
||||
}
|
||||
|
||||
func (r *Row) Snapshot() *gjson.Json {
|
||||
if r == nil {
|
||||
return gjson.New(g.Map{})
|
||||
}
|
||||
return gjson.New(g.Map{
|
||||
"ICCID": r.Iccid,
|
||||
"MSISDN": r.Msisdn,
|
||||
"IMSI": r.Imsi,
|
||||
"authorized": r.Authorized,
|
||||
"chanKey": r.ChanKey,
|
||||
"statusBefore": r.StatusBefore,
|
||||
"orderNum": r.OrderNum,
|
||||
"upstreamCode": r.UpstreamCode,
|
||||
"upstreamMessage": r.UpstreamReason,
|
||||
})
|
||||
}
|
||||
|
||||
type SubmitOptions = common.SubmitOptions
|
||||
|
||||
type Task struct {
|
||||
Batch *entity.BatchMain
|
||||
Operation Operation
|
||||
Config OperationConfig
|
||||
Rows []*Row
|
||||
Operator batchimport.OperatorContext
|
||||
ImportOssID int64
|
||||
Remark string
|
||||
StartTime *gtime.Time
|
||||
ValidationOK bool
|
||||
FailureReason string
|
||||
}
|
||||
|
||||
type Outcome struct {
|
||||
Success bool
|
||||
Code int
|
||||
Message string
|
||||
OrderNum string
|
||||
}
|
||||
Reference in New Issue
Block a user