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 }) }