package warningconfig 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/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" ) const ( warnItemTypeCard = 1 warnItemTypeFlowPool = 2 warnUnitMb = 1 warnUnitPercent = 2 warnUnitDay = 3 warnUnitMonth = 4 warnTypeCardTraffic = 1 warnTypeExpireDay = 2 warnTypeExpireMonth = 3 warnTypeFlowPoolPercent = 4 warnAutoActionDataCut = 3 warnMessageDefaultType = warnMethodPlatform ) type Processor struct { config batchimport.ProcessorConfig itemType int } func NewProcessor(itemTypeInput ...int) *Processor { itemType := warnItemTypeCard if len(itemTypeInput) > 0 { itemType = normalizeItemType(itemTypeInput[0]) } return &Processor{ itemType: itemType, config: batchimport.ProcessorConfig{ Code: "sim_warning_config", BatchType: consts.BatchTypeSimWarningConfig, BatchNoPrefix: consts.BatchNoPrefixSimWarningConfig, TaskName: consts.ResourceBatchDownloadTaskSimWarningConfig, ExportScene: consts.ResourceExportSceneSimWarningConfig, ImportDir: consts.SimWarningConfigSubDirImportExcel, ResultDir: consts.SimWarningConfigSubDirResultExcel, MaxFileSize: consts.SimWarningConfigMaxFileSize, TemplateHeaders: templateHeaders(itemType), RequiredHeaders: requiredHeaders(itemType), }, } } func (p *Processor) Config() batchimport.ProcessorConfig { return p.config } func (p *Processor) ParseRows(ctx context.Context, filePath string) ([]batchimport.Row, error) { return ParseExcelFileRows(ctx, filePath, p.itemType) } func (p *Processor) ValidateRows(ctx context.Context, rows []batchimport.Row) error { if p.itemType == warnItemTypeFlowPool { return p.validateFlowPoolRows(rows) } return p.validateCardRows(rows) } func (p *Processor) validateCardRows(rows []batchimport.Row) error { firstByIccid := make(map[string]*Row, len(rows)) for _, item := range rows { if item == nil { continue } row, ok := item.(*Row) if !ok || row == nil { item.AddErrorReason("预警配置行类型错误") continue } row.ItemType = warnItemTypeCard row.Iccid = strings.TrimSpace(row.Iccid) row.Msisdn = strings.TrimSpace(row.Msisdn) row.Imsi = strings.TrimSpace(row.Imsi) if row.Iccid == "" { row.AddErrorReason("ICCID不能为空") continue } if first, exists := firstByIccid[row.Iccid]; exists { row.AddErrorReason("ICCID重复") first.AddErrorReason("ICCID重复") continue } firstByIccid[row.Iccid] = row if row.ConfigSet && row.Reason == "" { if err := validateOptions(&row.Config); err != nil { row.AddErrorReason(err.Error()) } } } return nil } func (p *Processor) validateFlowPoolRows(rows []batchimport.Row) error { firstByCode := make(map[string]*Row, len(rows)) for _, item := range rows { if item == nil { continue } row, ok := item.(*Row) if !ok || row == nil { item.AddErrorReason("预警配置行类型错误") continue } row.ItemType = warnItemTypeFlowPool row.FlowPoolCode = strings.TrimSpace(row.FlowPoolCode) if row.FlowPoolCode == "" { row.AddErrorReason("流量池编号不能为空") continue } if first, exists := firstByCode[row.FlowPoolCode]; exists { row.AddErrorReason("流量池编号重复") first.AddErrorReason("流量池编号重复") continue } firstByCode[row.FlowPoolCode] = row if row.ConfigSet && row.Reason == "" { if err := validateOptions(&row.Config); err != nil { row.AddErrorReason(err.Error()) } } } return nil } func (p *Processor) BuildBatchInfo(ctx context.Context, req batchimport.SubmitRequest, result *batchimport.Result, importOss *model.SysOssSaveContentModel, resultOss *model.SysOssSaveContentModel, downloadId int64) *gjson.Json { options, _ := batchInfoOptions(req.BizParams) scene := submitScene(req.BizParams) info := g.Map{ "operation": "sim_warning_config", "configName": warningConfigName(options.ItemType, options.WarnUnit), "submitScene": scene, "itemType": options.ItemType, "warnUnit": options.WarnUnit, "firstWarn": options.FirstWarn, "secondWarn": options.SecondWarn, "thirdWarn": options.ThirdWarn, "warnMethods": options.WarnMethods, "warnPhones": options.WarnPhones, "warnEmails": options.WarnEmails, "autoAction": options.AutoAction, "isEnabled": options.IsEnabled, "remark": options.Remark, "rowConfig": scene == "excel" || scene == "input", "resultFile": g.Map{"status": "pending"}, } if result != nil && result.ImportFileOssId > 0 { info["importFileOssId"] = result.ImportFileOssId } else if importOss != nil && importOss.OssId > 0 { info["importFileOssId"] = importOss.OssId } if resultOss != nil && resultOss.OssId > 0 { info["resultFileOssId"] = resultOss.OssId } if downloadId > 0 { info["resultDownloadId"] = downloadId } return gjson.New(info) } func OptionsFromParams(params any) (Options, error) { switch in := params.(type) { case *model.TSimWarningConfigExcelSubmitParam: if in == nil { return Options{}, gerror.New("预警配置参数不能为空") } return NormalizeContextOptions(in.ItemType, in.WarnUnit) case *model.TSimWarningConfigInputSubmitParam: if in == nil { return Options{}, gerror.New("预警配置参数不能为空") } return NormalizeOptions(&in.TSimWarningConfigApplyOptions) case *model.TSimWarningConfigRangeSubmitParam: if in == nil { return Options{}, gerror.New("预警配置参数不能为空") } return NormalizeOptions(&in.TSimWarningConfigApplyOptions) default: return Options{}, gerror.New("预警配置参数不能为空") } } func batchInfoOptions(params any) (Options, error) { if in, ok := params.(*model.TSimWarningConfigInputSubmitParam); ok && in != nil { contextOptions, err := NormalizeContextOptions(in.ItemType, in.WarnUnit) if err != nil { return Options{}, err } if in.FirstWarn <= 0 { return contextOptions, nil } } return OptionsFromParams(params) } func ValidateSubmitOptions(params any) error { _, err := OptionsFromParams(params) return err } func NormalizeOptions(in *model.TSimWarningConfigApplyOptions) (Options, error) { if in == nil { return Options{}, gerror.New("预警配置参数不能为空") } itemType := normalizeItemType(in.ItemType) options := Options{ ItemType: itemType, WarnType: deriveWarnType(itemType, in.WarnUnit), WarnUnit: in.WarnUnit, FirstWarn: in.FirstWarn, SecondWarn: in.SecondWarn, ThirdWarn: in.ThirdWarn, WarnMethods: normalizeWarnMethods(in.WarnMethods), WarnPhones: strings.TrimSpace(in.WarnPhones), WarnEmails: strings.TrimSpace(in.WarnEmails), WarnEmailCode: strings.TrimSpace(in.WarnEmailCode), AutoAction: in.AutoAction, IsEnabled: 1, Remark: strings.TrimSpace(in.Remark), } if in.IsEnabled != nil { options.IsEnabled = *in.IsEnabled } if err := validateOptions(&options); err != nil { return Options{}, err } return options, nil } func validateOptions(options *Options) error { if options == nil { return gerror.New("预警配置参数不能为空") } if options.ItemType != warnItemTypeCard && options.ItemType != warnItemTypeFlowPool { return gerror.New("预警对象类型无效") } if options.WarnUnit == warnUnitDay || options.WarnUnit == warnUnitMonth { return gerror.New("日期预警由系统默认规则处理,不支持手动配置") } if options.IsEnabled != 0 && options.IsEnabled != 1 { return gerror.New("启停状态无效") } if options.AutoAction != 0 && options.AutoAction != warnAutoActionDataCut { return gerror.New("第三次是否断网配置无效") } if err := validateNotificationOptions(options); err != nil { return err } if err := validateWarnTypeUnit(options.ItemType, options.WarnType, options.WarnUnit); err != nil { return err } if options.ItemType == warnItemTypeFlowPool && options.AutoAction != 0 { return gerror.New("流量池预警不支持第三次断网") } if options.AutoAction == warnAutoActionDataCut && options.ThirdWarn <= 0 { return gerror.New("开启第三次断网时必须配置第三次提醒") } switch options.WarnType { case warnTypeCardTraffic: if options.WarnUnit == warnUnitPercent { options.FirstWarn = normalizePercentThreshold(options.FirstWarn) options.SecondWarn = normalizePercentThreshold(options.SecondWarn) options.ThirdWarn = normalizePercentThreshold(options.ThirdWarn) if options.FirstWarn > 100 || options.SecondWarn > 100 || options.ThirdWarn > 100 { return gerror.New("百分比阈值不能大于100") } } return validateTrafficThresholds(options.FirstWarn, options.SecondWarn, options.ThirdWarn) case warnTypeFlowPoolPercent: options.FirstWarn = normalizePercentThreshold(options.FirstWarn) options.SecondWarn = normalizePercentThreshold(options.SecondWarn) options.ThirdWarn = normalizePercentThreshold(options.ThirdWarn) if options.FirstWarn > 100 || options.SecondWarn > 100 || options.ThirdWarn > 100 { return gerror.New("百分比阈值不能大于100") } return validateTrafficThresholds(options.FirstWarn, options.SecondWarn, options.ThirdWarn) default: return gerror.New("预警类型无效") } } func validateTrafficThresholds(first, second, third float64) error { if first <= 0 { return gerror.New("第一档阈值必须大于0") } if second < 0 || third < 0 { return gerror.New("预警阈值不能小于0") } if second > 0 && second <= first { return gerror.New("流量预警第二档阈值必须大于第一档") } if third > 0 { if second <= 0 { return gerror.New("配置第三档时必须先配置第二档") } if third <= second { return gerror.New("流量预警第三档阈值必须大于第二档") } } return nil } func normalizePercentThreshold(value float64) float64 { if value > 0 && value <= 1 { return value * 100 } return value } func normalizeWarnMethods(methods string) string { selected := map[string]struct{}{ warnMethodPlatform: {}, } for _, method := range splitWarnList(methods) { switch strings.ToLower(method) { case warnMethodPlatform, "平台消息", "站内提醒", "站内消息": selected[warnMethodPlatform] = struct{}{} case warnMethodEmail, "邮箱": selected[warnMethodEmail] = struct{}{} case warnMethodSMS, "短信": selected[warnMethodSMS] = struct{}{} case warnMethodPhone, "电话": selected[warnMethodPhone] = struct{}{} } } ordered := []string{ warnMethodPlatform, warnMethodEmail, warnMethodSMS, warnMethodPhone, } result := make([]string, 0, len(ordered)) for _, method := range ordered { if _, ok := selected[method]; ok { result = append(result, method) } } return strings.Join(result, ",") } func submitScene(params any) string { switch params.(type) { case *model.TSimWarningConfigExcelSubmitParam: return "excel" case *model.TSimWarningConfigInputSubmitParam: return "input" case *model.TSimWarningConfigRangeSubmitParam: return "range" default: return "" } } func warningConfigName(itemType, warnUnit int) string { warnType := deriveWarnType(itemType, warnUnit) switch warnType { case warnTypeFlowPoolPercent: return "流量池预警配置" case warnTypeCardTraffic: return "流量预警配置" case warnTypeExpireDay: return "日期预警(天)配置" case warnTypeExpireMonth: return "日期预警(月)配置" default: return "SIM卡预警配置" } } func normalizeItemType(itemType int) int { if itemType == 0 { return warnItemTypeCard } return itemType } func deriveWarnType(itemType, warnUnit int) int { if itemType == warnItemTypeFlowPool { return warnTypeFlowPoolPercent } switch warnUnit { case warnUnitMb, warnUnitPercent: return warnTypeCardTraffic case warnUnitDay: return warnTypeExpireDay case warnUnitMonth: return warnTypeExpireMonth default: return 0 } } func validateWarnTypeUnit(itemType, warnType, warnUnit int) error { switch warnType { case warnTypeCardTraffic: if itemType != warnItemTypeCard { return gerror.New("单卡流量预警只能配置单卡对象") } if warnUnit != warnUnitMb && warnUnit != warnUnitPercent { return gerror.New("单卡流量预警仅支持MB或百分比单位") } case warnTypeExpireDay: return gerror.New("日期预警由系统默认规则处理,不支持手动配置") case warnTypeExpireMonth: return gerror.New("日期预警由系统默认规则处理,不支持手动配置") case warnTypeFlowPoolPercent: if itemType != warnItemTypeFlowPool { return gerror.New("流量池预警只能配置流量池对象") } if warnUnit != warnUnitPercent { return gerror.New("流量池预警仅支持百分比单位") } default: return gerror.New("预警类型无效") } return nil }