59eb34976a
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 已说明。
434 lines
13 KiB
Go
434 lines
13 KiB
Go
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
|
|
}
|