Files
iotlcm/worker-svr/internal/repository/trafficcutoff/repository.go
T

750 lines
24 KiB
Go

package trafficcutoff
import (
"context"
"sort"
"strings"
"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"
"github.com/gogf/gf/v2/os/gtime"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/batchresult"
trafficcutoff "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/trafficcutoff"
)
const (
resultFileStatusSuccess = "success"
resultFileStatusFailed = "failed"
validationFailureReason = "批次校验未通过,已跳过流量断网更新"
)
type Repository struct {
db gdb.DB
resultFiles batchresult.Generator
lockEnabled bool
}
func New() *Repository {
return &Repository{resultFiles: batchresult.New(), lockEnabled: true}
}
func (r *Repository) database() gdb.DB {
if r.db != nil {
return r.db
}
return g.DB()
}
func (r *Repository) applyLock(model *gdb.Model) *gdb.Model {
if r.lockEnabled {
return model.LockUpdate()
}
return model
}
func (r *Repository) Load(ctx context.Context, batchNo string) (*trafficcutoff.Task, error) {
batchNo = strings.TrimSpace(batchNo)
if batchNo == "" {
return nil, gerror.New("traffic cutoff batch number is empty")
}
var main entity.BatchMain
if err := r.database().Model(dao.BatchMain.Table()).Safe().Ctx(ctx).
Where(dao.BatchMain.Columns().BatchNo, batchNo).
Where(dao.BatchMain.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusProcessing).
Scan(&main); err != nil {
return nil, gerror.Wrap(err, "query traffic cutoff batch main")
}
if main.Id == 0 {
return nil, gerror.New("processing traffic cutoff batch does not exist")
}
var rawDetails []*entity.BatchDetail
if err := r.database().Model(dao.BatchDetail.Table()).Safe().Ctx(ctx).
Where(dao.BatchDetail.Columns().BatchNo, batchNo).
Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusRunning).
OrderAsc(dao.BatchDetail.Columns().RowNo).
Scan(&rawDetails); err != nil {
return nil, gerror.Wrap(err, "query running traffic cutoff details")
}
if len(rawDetails) == 0 {
return nil, gerror.New("traffic cutoff batch has no running details")
}
trafficCutoffStatus, err := readTrafficCutoffStatus(main.BatchInfo)
if err != nil {
return nil, err
}
task := &trafficcutoff.Task{
Batch: trafficcutoff.Batch{
ID: main.Id,
BatchNo: main.BatchNo,
BatchType: main.BatchType,
TenantID: main.TenantId,
DeptID: main.DeptId,
CreatedBy: main.CreatedBy,
CreatedDept: main.CreatedDept,
StartTime: main.StartTime,
BatchInfo: main.BatchInfo,
},
TrafficCutoffStatus: trafficCutoffStatus,
TrafficCutoffThreshold: readTrafficCutoffThreshold(main.BatchInfo),
ThresholdUnit: readThresholdUnit(main.BatchInfo),
Details: make([]*trafficcutoff.Detail, 0, len(rawDetails)),
}
for _, raw := range rawDetails {
if raw == nil {
continue
}
task.Details = append(task.Details, buildDetail(raw))
}
if len(task.Details) == 0 {
return nil, gerror.New("traffic cutoff batch has no readable running details")
}
if err := r.attachCards(ctx, task); err != nil {
return nil, err
}
return task, nil
}
func buildDetail(raw *entity.BatchDetail) *trafficcutoff.Detail {
detail := &trafficcutoff.Detail{
ID: raw.Id,
RowNo: raw.RowNo,
ICCID: strings.TrimSpace(raw.ItemId),
Reason: strings.TrimSpace(raw.ErrorReason),
}
if raw.Snapshot != nil {
if value := firstSnapshot(raw.Snapshot, "ICCID", "iccid"); value != "" {
detail.ICCID = value
}
detail.InputMSISDN = firstSnapshot(raw.Snapshot, "MSISDN", "msisdn", "inputMsisdn", "input_msisdn")
detail.InputIMSI = firstSnapshot(raw.Snapshot, "IMSI", "imsi", "inputImsi", "input_imsi")
detail.TrafficCutoffCrossMonthHold = raw.Snapshot.Get("trafficCutoffCrossMonthHold").Int()
detail.MSISDN = detail.InputMSISDN
detail.IMSI = detail.InputIMSI
detail.Authorized = raw.Snapshot.Get("authorized").Bool()
}
return detail
}
func firstSnapshot(snapshot *gjson.Json, keys ...string) string {
if snapshot == nil {
return ""
}
for _, key := range keys {
if value := strings.TrimSpace(snapshot.Get(key).String()); value != "" {
return value
}
}
return ""
}
func (r *Repository) attachCards(ctx context.Context, task *trafficcutoff.Task) error {
cards, err := r.loadCards(ctx, task.Batch.TenantID, detailICCIDList(task.Details))
if err != nil {
return err
}
attachCards(task, cards)
return nil
}
func (r *Repository) loadCards(ctx context.Context, tenantID string, iccids []string) (map[string]*entity.TSimCardInfo, error) {
result := make(map[string]*entity.TSimCardInfo)
iccids = uniqueSortedStrings(iccids)
if len(iccids) == 0 {
return result, nil
}
var cards []*entity.TSimCardInfo
if err := r.database().Model(dao.TSimCardInfo.Table()).Safe().Ctx(ctx).
Where(dao.TSimCardInfo.Columns().TenantId, tenantID).
WhereIn(dao.TSimCardInfo.Columns().Iccid, iccids).
Scan(&cards); err != nil {
return nil, gerror.Wrap(err, "query traffic cutoff SIM cards")
}
for _, card := range cards {
if card != nil && strings.TrimSpace(card.Iccid) != "" {
result[strings.TrimSpace(card.Iccid)] = card
}
}
return result, nil
}
func attachCards(task *trafficcutoff.Task, cards map[string]*entity.TSimCardInfo) {
for _, detail := range task.Details {
if detail == nil {
continue
}
card := cards[strings.TrimSpace(detail.ICCID)]
if card == nil {
detail.Card = nil
continue
}
detail.Card = &trafficcutoff.Card{
ID: card.Id,
ICCID: strings.TrimSpace(card.Iccid),
MSISDN: strings.TrimSpace(card.Msisdn),
IMSI: strings.TrimSpace(card.Imsi),
BossCardStatus: card.BossCardStatus,
TrafficCutoffStatus: card.TrafficCutoffStatus,
TrafficCutoffThreshold: card.TrafficCutoffThreshold,
}
detail.MSISDN = detail.Card.MSISDN
detail.IMSI = detail.Card.IMSI
detail.BossCardStatusBefore = detail.Card.BossCardStatus
detail.TrafficCutoffStatusBefore = detail.Card.TrafficCutoffStatus
detail.TrafficCutoffThresholdBefore = detail.Card.TrafficCutoffThreshold
}
}
func (r *Repository) SaveValidationFailure(ctx context.Context, task *trafficcutoff.Task) error {
if task == nil {
return gerror.New("traffic cutoff task is nil")
}
reason := strings.TrimSpace(task.FailureReason)
if reason == "" {
reason = validationFailureReason
}
now := gtime.Now()
return r.database().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
if err := updateDetailsFailed(ctx, tx, task, reason); err != nil {
return err
}
return updateMainFailed(ctx, tx, task, len(task.Details), reason, now)
})
}
func (r *Repository) CommitBusiness(ctx context.Context, task *trafficcutoff.Task) error {
if task == nil {
return gerror.New("traffic cutoff task is nil")
}
now := gtime.Now()
iccids := uniqueSortedStrings(detailICCIDList(task.Details))
return r.database().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
cards, err := r.lockCards(ctx, tx, task.Batch.TenantID, iccids)
if err != nil {
return gerror.Wrap(err, "lock traffic cutoff SIM cards")
}
attachCards(task, cards)
if !validateLockedTask(task) {
reason := strings.TrimSpace(task.FailureReason)
if reason == "" {
reason = validationFailureReason
}
if err = updateDetailsFailed(ctx, tx, task, reason); err != nil {
return err
}
return updateMainFailed(ctx, tx, task, len(task.Details), reason, now)
}
ids := detailCardIDs(task.Details)
result, err := tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
Where(dao.TSimCardInfo.Columns().TenantId, task.Batch.TenantID).
WhereIn(dao.TSimCardInfo.Columns().Id, ids).
Data(do.TSimCardInfo{
TrafficCutoffStatus: task.TrafficCutoffStatus,
TrafficCutoffThreshold: task.TrafficCutoffThreshold,
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff fields")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff card update result")
}
if int(affected) != len(ids) {
return gerror.New("some traffic cutoff cards changed before update")
}
if err = markCrossMonthCutoffTriggered(ctx, tx, task); err != nil {
return err
}
for _, detail := range task.Details {
if detail == nil {
continue
}
detail.TrafficCutoffStatusAfter = task.TrafficCutoffStatus
detail.TrafficCutoffThresholdAfter = task.TrafficCutoffThreshold
}
if err = updateDetailsSuccess(ctx, tx, task); err != nil {
return err
}
return updateMainSuccess(ctx, tx, task, len(task.Details), now)
})
}
func markCrossMonthCutoffTriggered(ctx context.Context, tx gdb.TX, task *trafficcutoff.Task) error {
ids := make([]int64, 0, len(task.Details))
for _, detail := range task.Details {
if detail != nil && detail.Reason == "" && detail.TrafficCutoffCrossMonthHold == 1 && detail.Card != nil {
ids = append(ids, detail.Card.ID)
}
}
ids = uniqueSortedInt64(ids)
if len(ids) == 0 {
return nil
}
result, err := tx.Ctx(ctx).Model(dao.TSimCardInfo.Table()).
Where(dao.TSimCardInfo.Columns().TenantId, task.Batch.TenantID).
WhereIn(dao.TSimCardInfo.Columns().Id, ids).
Where("traffic_cutoff_cross_month_hold", 1).
Data(do.TSimCardTrafficCutoffConfig{
TrafficCutoffTriggeredAt: gtime.Now(),
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff triggered time")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff triggered time update result")
}
if int(affected) != len(ids) {
return gerror.New("some traffic cutoff triggered times changed before update")
}
return nil
}
func (r *Repository) lockCards(ctx context.Context, tx gdb.TX, tenantID string, iccids []string) (map[string]*entity.TSimCardInfo, error) {
result := make(map[string]*entity.TSimCardInfo)
if len(iccids) == 0 {
return result, nil
}
var cards []*entity.TSimCardInfo
if err := r.applyLock(tx.Ctx(ctx).Model(dao.TSimCardInfo.Table())).
Where(dao.TSimCardInfo.Columns().TenantId, tenantID).
WhereIn(dao.TSimCardInfo.Columns().Iccid, iccids).
Scan(&cards); err != nil {
return nil, err
}
for _, card := range cards {
if card != nil && strings.TrimSpace(card.Iccid) != "" {
result[strings.TrimSpace(card.Iccid)] = card
}
}
return result, nil
}
func validateLockedTask(task *trafficcutoff.Task) bool {
if task == nil || len(task.Details) == 0 {
if task != nil {
task.FailureReason = "流量断网批次无有效明细"
}
return false
}
if task.TrafficCutoffStatus != 1 || task.TrafficCutoffThreshold <= 0 {
task.FailureReason = validationFailureReason
for _, detail := range task.Details {
if detail != nil {
detail.Reason = validationFailureReason
}
}
return false
}
hasFailure := false
statusSet := make(map[int]struct{})
for _, detail := range task.Details {
if detail == nil {
hasFailure = true
continue
}
detail.Reason = ""
if detail.Card == nil {
detail.Reason = "ICCID不存在或不属于当前租户"
} else if !detail.Authorized {
detail.Reason = "ICCID不在当前操作员授权范围内"
} else if detail.InputMSISDN != "" && detail.InputMSISDN != detail.Card.MSISDN {
detail.Reason = "MSISDN与卡档案不一致"
} else if detail.InputIMSI != "" && detail.InputIMSI != detail.Card.IMSI {
detail.Reason = "IMSI与卡档案不一致"
} else if detail.Card.BossCardStatus == trafficcutoff.BossCardStatusCanceled {
detail.Reason = "ICCID已销户"
}
if detail.Reason != "" {
hasFailure = true
continue
}
statusSet[detail.Card.BossCardStatus] = struct{}{}
}
if len(statusSet) > 1 {
for _, detail := range task.Details {
if detail != nil && detail.Reason == "" {
detail.Reason = "同一批次内卡状态不一致"
}
}
hasFailure = true
}
if !hasFailure {
task.ValidationOK = true
return true
}
for _, detail := range task.Details {
if detail != nil && detail.Reason == "" {
detail.Reason = "同一批次存在无效流量断网明细,已跳过更新"
}
}
task.FailureReason = validationFailureReason
return false
}
func updateDetailsFailed(ctx context.Context, tx gdb.TX, task *trafficcutoff.Task, fallback string) error {
for _, detail := range task.Details {
if detail == nil {
continue
}
if strings.TrimSpace(detail.Reason) == "" {
detail.Reason = fallback
}
result, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
Where(dao.BatchDetail.Columns().Id, detail.ID).
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusRunning).
Data(do.BatchDetail{
DetailStatus: consts.BatchDetailStatusFailed,
ErrorReason: detail.Reason,
Snapshot: detail.Snapshot(task),
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff failed detail")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff failed detail result")
}
if affected != 1 {
return gerror.Newf("traffic cutoff detail %d is no longer running", detail.ID)
}
}
return nil
}
func updateDetailsSuccess(ctx context.Context, tx gdb.TX, task *trafficcutoff.Task) error {
for _, detail := range task.Details {
if detail == nil {
continue
}
result, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
Where(dao.BatchDetail.Columns().Id, detail.ID).
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
Where(dao.BatchDetail.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Where(dao.BatchDetail.Columns().DetailStatus, consts.BatchDetailStatusRunning).
Data(do.BatchDetail{
DetailStatus: consts.BatchDetailStatusSuccess,
ErrorReason: "",
Snapshot: detail.Snapshot(task),
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff success detail")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff success detail result")
}
if affected != 1 {
return gerror.Newf("traffic cutoff detail %d is no longer running", detail.ID)
}
}
return nil
}
func updateMainFailed(ctx context.Context, tx gdb.TX, task *trafficcutoff.Task, total int, reason string, now *gtime.Time) error {
result, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
Where(dao.BatchMain.Columns().BatchNo, task.Batch.BatchNo).
Where(dao.BatchMain.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusProcessing).
Data(do.BatchMain{
BatchAsyncStatus: consts.BatchAsyncStatusFailed,
TotalCount: total,
ValidCount: total,
SuccessCount: 0,
FailCount: total,
RunResult: consts.BatchRunResultFailed,
FailReason: reason,
BatchInfo: buildBatchInfo(task, resultFileStatusFailed, 0, 0, ""),
EndTime: now,
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff batch main failed")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff failed main result")
}
if affected != 1 {
return gerror.New("traffic cutoff batch is no longer processing")
}
task.Batch.BatchInfo = buildBatchInfo(task, resultFileStatusFailed, 0, 0, "")
return nil
}
func updateMainSuccess(ctx context.Context, tx gdb.TX, task *trafficcutoff.Task, total int, now *gtime.Time) error {
info := buildBatchInfo(task, "pending", 0, 0, "")
result, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
Where(dao.BatchMain.Columns().BatchNo, task.Batch.BatchNo).
Where(dao.BatchMain.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Where(dao.BatchMain.Columns().BatchAsyncStatus, consts.BatchAsyncStatusProcessing).
Data(do.BatchMain{
BatchAsyncStatus: consts.BatchAsyncStatusSuccess,
TotalCount: total,
ValidCount: total,
SuccessCount: total,
FailCount: 0,
RunResult: consts.BatchRunResultSuccess,
FailReason: "",
BatchInfo: info,
EndTime: now,
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff batch main success")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff success main result")
}
if affected != 1 {
return gerror.New("traffic cutoff batch is no longer processing")
}
task.Batch.BatchInfo = info
return nil
}
func (r *Repository) SaveResultFile(ctx context.Context, task *trafficcutoff.Task, request batchresult.Request) error {
if task == nil {
return gerror.New("traffic cutoff task is nil")
}
artifact, err := r.artifactGenerator().Create(ctx, request)
if err != nil {
return r.updateResultFileStatus(ctx, task, resultFileStatusFailed, 0, 0, err.Error())
}
now := gtime.Now()
var (
ossID int64
downloadID int64
)
txErr := r.database().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
var insertErr error
ossID, insertErr = tx.Ctx(ctx).Model(dao.SysOss.Table()).Data(do.SysOss{
TenantId: task.Batch.TenantID,
FileName: artifact.FileName,
OriginalName: artifact.OriginalName,
FileSuffix: artifact.FileSuffix,
Path: artifact.Path,
Url: artifact.URL,
CreatedDept: task.Batch.CreatedDept,
CreatedBy: task.Batch.CreatedBy,
UpdatedBy: task.Batch.CreatedBy,
Service: "local",
Md5: artifact.MD5,
FileSize: artifact.Size,
}).InsertAndGetId()
if insertErr != nil {
return gerror.Wrap(insertErr, "insert traffic cutoff result OSS")
}
downloadID, insertErr = tx.Ctx(ctx).Model(dao.TResourceBatchDownload.Table()).Data(do.TResourceBatchDownload{
BatchNo: task.Batch.BatchNo,
TaskName: batchresult.TaskName(task.Batch.BatchType),
ResourceType: consts.ResourceTypeExcel,
ExportScene: task.Batch.BatchType,
TotalCount: len(task.Details),
SuccessCount: task.SuccessCount(),
FailCount: task.FailCount(),
DownloadStatus: consts.DownloadStatusGenerated,
OssId: ossID,
FileName: artifact.FileName,
FileExt: artifact.FileSuffix,
FileUrl: artifact.URL,
FilePath: artifact.Path,
FileSize: artifact.Size,
FileMd5: artifact.MD5,
DownloadCount: 0,
MaxDownloadCount: 0,
DeptId: task.Batch.CreatedDept,
StartTime: task.Batch.StartTime,
FinishTime: now,
TenantId: task.Batch.TenantID,
CreatedDept: task.Batch.CreatedDept,
CreatedBy: task.Batch.CreatedBy,
UpdatedBy: task.Batch.CreatedBy,
}).InsertAndGetId()
if insertErr != nil {
return gerror.Wrap(insertErr, "insert traffic cutoff result download")
}
return updateBatchInfoOnly(ctx, tx, task, buildBatchInfo(task, resultFileStatusSuccess, ossID, downloadID, ""))
})
if txErr != nil {
batchresult.RemoveLocalFile(artifact)
return r.updateResultFileStatus(ctx, task, resultFileStatusFailed, 0, 0, txErr.Error())
}
return nil
}
func (r *Repository) artifactGenerator() batchresult.Generator {
if r.resultFiles != nil {
return r.resultFiles
}
return batchresult.New()
}
func (r *Repository) updateResultFileStatus(ctx context.Context, task *trafficcutoff.Task, status string, ossID, downloadID int64, reason string) error {
return r.database().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
return updateBatchInfoOnly(ctx, tx, task, buildBatchInfo(task, status, ossID, downloadID, reason))
})
}
func updateBatchInfoOnly(ctx context.Context, tx gdb.TX, task *trafficcutoff.Task, info *gjson.Json) error {
result, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).
Where(dao.BatchMain.Columns().BatchNo, task.Batch.BatchNo).
Where(dao.BatchMain.Columns().BatchType, consts.BatchTypeTrafficCutoff).
Data(do.BatchMain{
BatchInfo: info,
UpdatedBy: task.Batch.CreatedBy,
}).Update()
if err != nil {
return gerror.Wrap(err, "update traffic cutoff result file status")
}
affected, err := result.RowsAffected()
if err != nil {
return gerror.Wrap(err, "read traffic cutoff result file update result")
}
if affected != 1 {
return gerror.New("traffic cutoff batch disappeared while saving result file")
}
task.Batch.BatchInfo = info
return nil
}
func buildBatchInfo(task *trafficcutoff.Task, resultStatus string, ossID, downloadID int64, reason string) *gjson.Json {
info := make(map[string]any)
if task.Batch.BatchInfo != nil {
for key, value := range task.Batch.BatchInfo.Map() {
info[key] = value
}
}
info["operation"] = "dataCut"
info["trafficCutoffStatus"] = task.TrafficCutoffStatus
info["trafficCutoffThreshold"] = task.TrafficCutoffThreshold
info["thresholdUnit"] = task.ThresholdUnit
if resultStatus != "" {
resultFile := map[string]any{"status": resultStatus}
if ossID > 0 {
resultFile["ossId"] = ossID
}
if downloadID > 0 {
resultFile["downloadId"] = downloadID
}
if strings.TrimSpace(reason) != "" {
resultFile["error"] = reason
}
info["resultFile"] = resultFile
}
return gjson.New(info)
}
func readTrafficCutoffStatus(info *gjson.Json) (int, error) {
if info == nil || info.IsNil() {
return 0, gerror.New("traffic cutoff batch missing trafficCutoffStatus")
}
if value := info.Get("trafficCutoffStatus"); !value.IsNil() {
return value.Int(), nil
}
return 0, gerror.New("traffic cutoff batch missing trafficCutoffStatus")
}
func readTrafficCutoffThreshold(info *gjson.Json) float64 {
if info == nil || info.IsNil() {
return 0
}
if value := info.Get("trafficCutoffThreshold"); !value.IsNil() {
return value.Float64()
}
return info.Get("cutoffThreshold").Float64()
}
func readThresholdUnit(info *gjson.Json) string {
if info != nil {
if value := strings.TrimSpace(info.Get("thresholdUnit").String()); value != "" {
return value
}
}
return "MB"
}
func detailICCIDList(details []*trafficcutoff.Detail) []string {
iccids := make([]string, 0, len(details))
for _, detail := range details {
if detail != nil && strings.TrimSpace(detail.ICCID) != "" {
iccids = append(iccids, strings.TrimSpace(detail.ICCID))
}
}
return iccids
}
func detailCardIDs(details []*trafficcutoff.Detail) []int64 {
ids := make([]int64, 0, len(details))
for _, detail := range details {
if detail != nil && detail.Card != nil {
ids = append(ids, detail.Card.ID)
}
}
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
return ids
}
func uniqueSortedInt64(values []int64) []int64 {
if len(values) == 0 {
return nil
}
seen := make(map[int64]struct{}, len(values))
result := make([]int64, 0, len(values))
for _, value := range values {
if value <= 0 {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
result = append(result, value)
}
sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
return result
}
func uniqueSortedStrings(values []string) []string {
if len(values) == 0 {
return nil
}
seen := make(map[string]struct{}, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
sort.Strings(out)
return out
}
var _ trafficcutoff.Repository = (*Repository)(nil)