package simwarning import ( "context" "database/sql" "net/mail" "strings" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler" "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" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" ) func (s *sSimWarning) ConfigList(ctx context.Context, in *model.SimWarningConfigListParam) (list []*model.SimWarningConfigModel, totalCount int, err error) { if in == nil { in = &model.SimWarningConfigListParam{} } if in.Page <= 0 { in.Page = 1 } if in.PageSize <= 0 { in.PageSize = 20 } mod := s.configModel(ctx) columns := dao.TWarnConfig.Columns() if in.Id > 0 { mod = mod.Where(columns.Id, in.Id) } if in.ItemType > 0 { mod = mod.Where(columns.ItemType, in.ItemType) } if in.ItemId > 0 { mod = mod.Where(columns.ItemId, in.ItemId) } if in.WarnUnit > 0 { if in.WarnUnit == warnUnitDay || in.WarnUnit == warnUnitMonth { mod = mod.Where("1 = 0") } else { mod = mod.Where(columns.WarnUnit, in.WarnUnit) } } else if in.WarnType > 0 { mod = applyWarnTypeCompatibilityFilter(mod, in.WarnType) } if in.IsEnabled != nil { mod = mod.Where(columns.IsEnabled, *in.IsEnabled) } if itemCode := strings.TrimSpace(in.ItemCode); itemCode != "" { mod = mod.WhereLike(columns.ItemCode, "%"+itemCode+"%") } if keyword := strings.TrimSpace(in.Keyword); keyword != "" { mod = mod.WhereLike(columns.ItemCode, "%"+keyword+"%") } err = mod.Page(in.Page, in.PageSize). OrderDesc(columns.Id). ScanAndCount(&list, &totalCount, false) if err != nil { return nil, 0, gerror.Wrap(err, "获取预警配置列表失败,请稍后重试") } if list == nil { list = []*model.SimWarningConfigModel{} } fillConfigWarnTypes(list) return list, totalCount, nil } func (s *sSimWarning) ConfigView(ctx context.Context, in *model.SimWarningConfigViewParam) (res *model.SimWarningConfigModel, err error) { if in == nil || (in.Id <= 0 && in.ItemId <= 0) { return nil, gerror.New("配置ID和对象ID不能同时为空") } mod := s.configModel(ctx) columns := dao.TWarnConfig.Columns() if in.Id > 0 { mod = mod.Where(columns.Id, in.Id) } else { itemType := in.ItemType if itemType == 0 { itemType = warnItemTypeCard } if itemType != warnItemTypeCard && itemType != warnItemTypeFlowPool { return nil, gerror.New("预警对象类型无效") } warnUnit, err := warnUnitForConfigLookup(itemType, in.WarnType, in.WarnUnit) if err != nil { return nil, err } mod = mod.Where(columns.ItemType, itemType). Where(columns.ItemId, in.ItemId). Where(columns.WarnUnit, warnUnit) } if err = mod.Scan(&res); err != nil { return nil, gerror.Wrap(err, "获取预警配置详情失败,请稍后重试") } if res == nil || res.Id == 0 { return nil, gerror.New("预警配置不存在或无权限访问") } if res.WarnUnit == warnUnitDay || res.WarnUnit == warnUnitMonth { return nil, gerror.New("日期预警由系统默认规则处理,不支持查询用户配置") } res.WarnType = deriveWarnType(res.ItemType, res.WarnUnit) return res, nil } func (s *sSimWarning) ConfigSave(ctx context.Context, in *model.SimWarningConfigSaveParam) (res *model.SimWarningConfigSaveModel, err error) { if in == nil { return nil, gerror.New("请求参数不能为空") } user := contexts.GetUser(ctx) if user == nil || user.TenantId == "" || user.ID == 0 { return nil, gerror.New("登录信息已过期") } in.ItemCode = strings.TrimSpace(in.ItemCode) itemType := in.ItemType if itemType == 0 { itemType = warnItemTypeCard } in.ItemType = itemType if err = normalizeConfigSaveParam(in); err != nil { return nil, err } var itemCode string switch in.ItemType { case warnItemTypeCard: card, err := s.queryConfigCard(ctx, in.ItemId, in.ItemCode) if err != nil { return nil, err } in.ItemId = card.Id itemCode = strings.TrimSpace(in.ItemCode) if itemCode == "" { itemCode = strings.TrimSpace(card.Iccid) } if itemCode == "" { itemCode = strings.TrimSpace(card.Msisdn) } case warnItemTypeFlowPool: pool, err := s.queryConfigFlowPool(ctx, in.ItemId, in.ItemCode) if err != nil { return nil, err } in.ItemId = pool.Id itemCode = strings.TrimSpace(in.ItemCode) if itemCode == "" { itemCode = strings.TrimSpace(pool.FlowPoolCode) } default: return nil, gerror.New("预警对象类型无效") } if err = s.ensureConfigWritable(ctx, user.TenantId, in.Id, in.ItemType, in.ItemId, in.WarnUnit); err != nil { return nil, err } now := gtime.Now() data := buildConfigSaveData(user.TenantId, user.DeptId, user.ID, itemCode, in, now) columns := dao.TWarnConfig.Columns() _, err = dao.TWarnConfig.Ctx(ctx). Data(data). OnConflict(columns.TenantId, columns.ItemType, columns.ItemId, columns.WarnUnit). OnDuplicateEx(strings.Join([]string{ columns.Id, columns.TenantId, columns.ItemType, columns.ItemId, columns.WarnUnit, columns.CreatedDept, columns.CreatedBy, columns.CreatedAt, }, ",")). Save() if err != nil { return nil, gerror.Wrap(err, "保存预警配置失败,请稍后重试") } saved, err := s.findConfigByItem(ctx, user.TenantId, in.ItemType, in.ItemId, in.WarnUnit, true) if err != nil { return nil, err } if saved == nil || saved.Id == 0 { return nil, gerror.New("保存预警配置后未查询到配置") } return &model.SimWarningConfigSaveModel{Id: saved.Id}, nil } func (s *sSimWarning) ConfigStatus(ctx context.Context, in *model.SimWarningConfigStatusParam) (err error) { if in == nil || in.Id <= 0 { return gerror.New("配置ID不能为空") } if in.IsEnabled != 0 && in.IsEnabled != 1 { return gerror.New("启停状态无效") } if _, err = s.ConfigView(ctx, &model.SimWarningConfigViewParam{Id: in.Id}); err != nil { return err } userId := contexts.GetUserId(ctx) columns := dao.TWarnConfig.Columns() result, err := s.configModel(ctx).Where(columns.Id, in.Id).Data(do.TWarnConfig{ IsEnabled: in.IsEnabled, UpdatedBy: userId, UpdatedAt: gtime.Now(), }).Update() if err != nil { return gerror.Wrap(err, "启停预警配置失败,请稍后重试") } return ensureAffected(result, "预警配置不存在或无权限访问") } func (s *sSimWarning) ConfigDelete(ctx context.Context, in *model.SimWarningConfigDeleteParam) (err error) { if in == nil || in.Id <= 0 { return gerror.New("配置ID不能为空") } if _, err = s.ConfigView(ctx, &model.SimWarningConfigViewParam{Id: in.Id}); err != nil { return err } result, err := s.configModel(ctx).Where(dao.TWarnConfig.Columns().Id, in.Id).Delete() if err != nil { return gerror.Wrap(err, "删除预警配置失败,请稍后重试") } return ensureAffected(result, "预警配置不存在或无权限访问") } func (s *sSimWarning) configModel(ctx context.Context) *gdb.Model { return handler.Model(dao.TWarnConfig.Ctx(ctx)). Fields(warningConfigFields()...) } func (s *sSimWarning) queryConfigCard(ctx context.Context, itemId int64, itemCode string) (*entity.TSimCardInfo, error) { itemCode = strings.TrimSpace(itemCode) if itemId <= 0 && itemCode == "" { return nil, gerror.New("单卡ID或ICCID/MSISDN/IMSI不能为空") } var card *entity.TSimCardInfo mod := service.IotTSimCardInfo().Model(ctx).Fields( "id", "iccid", "msisdn", "imsi", "tenant_id", ) if itemId > 0 { mod = mod.Where("id", itemId) } else { mod = mod.Where("(iccid = ? OR msisdn = ? OR imsi = ?)", itemCode, itemCode, itemCode) } if err := mod.Scan(&card); err != nil { return nil, gerror.Wrap(err, "查询单卡失败,请稍后重试") } if card == nil || card.Id == 0 { return nil, gerror.New("单卡不存在或无权限访问") } return card, nil } func (s *sSimWarning) queryConfigFlowPool(ctx context.Context, itemId int64, itemCode string) (*entity.TSimFlowPool, error) { itemCode = strings.TrimSpace(itemCode) if itemId <= 0 && itemCode == "" { return nil, gerror.New("流量池ID或流量池编号不能为空") } var pool *entity.TSimFlowPool mod := service.IotTSimFlowPool().Model(ctx).Fields( "id", "flow_pool_code", "tenant_id", ) if itemId > 0 { mod = mod.Where("id", itemId) } else { mod = mod.Where("flow_pool_code", itemCode) } if err := mod.Scan(&pool); err != nil { return nil, gerror.Wrap(err, "查询流量池失败,请稍后重试") } if pool == nil || pool.Id == 0 { return nil, gerror.New("流量池不存在或无权限访问") } return pool, nil } func (s *sSimWarning) ensureConfigWritable(ctx context.Context, tenantId string, id int64, itemType int, itemId int64, warnUnit int) error { if id > 0 { existing, err := s.findConfigByID(ctx, tenantId, id, false) if err != nil { return err } if existing == nil || existing.Id == 0 { return gerror.New("预警配置不存在") } if existing.ItemType != itemType || existing.ItemId != itemId || existing.WarnUnit != warnUnit { return gerror.New("配置ID与对象或预警维度不匹配") } return s.ensureConfigVisible(ctx, existing.Id) } existing, err := s.findConfigByItem(ctx, tenantId, itemType, itemId, warnUnit, false) if err != nil { return err } if existing == nil || existing.Id == 0 { return nil } return s.ensureConfigVisible(ctx, existing.Id) } func (s *sSimWarning) ensureConfigVisible(ctx context.Context, id int64) error { visible, err := s.findConfigByID(ctx, "", id, true) if err != nil { return err } if visible == nil || visible.Id == 0 { return gerror.New("预警配置不存在或无权限访问") } return nil } func (s *sSimWarning) findConfigByID(ctx context.Context, tenantId string, id int64, withAuth bool) (*model.SimWarningConfigModel, error) { if id <= 0 { return nil, nil } columns := dao.TWarnConfig.Columns() mod := dao.TWarnConfig.Ctx(ctx). Fields(warningConfigFields()...). Where(columns.Id, id) if tenantId != "" { mod = mod.Where(columns.TenantId, tenantId) } if withAuth { mod = handler.Model(mod) } var res *model.SimWarningConfigModel if err := mod.Scan(&res); err != nil { return nil, gerror.Wrap(err, "查询预警配置失败,请稍后重试") } if res != nil { res.WarnType = deriveWarnType(res.ItemType, res.WarnUnit) } return res, nil } func (s *sSimWarning) findConfigByItem(ctx context.Context, tenantId string, itemType int, itemId int64, warnUnit int, withAuth bool) (*model.SimWarningConfigModel, error) { if tenantId == "" || itemId <= 0 || warnUnit <= 0 { return nil, nil } if itemType == 0 { itemType = warnItemTypeCard } columns := dao.TWarnConfig.Columns() mod := dao.TWarnConfig.Ctx(ctx). Fields(warningConfigFields()...). Where(columns.TenantId, tenantId). Where(columns.ItemType, itemType). Where(columns.ItemId, itemId). Where(columns.WarnUnit, warnUnit) if withAuth { mod = handler.Model(mod) } var res *model.SimWarningConfigModel if err := mod.Scan(&res); err != nil { return nil, gerror.Wrap(err, "查询预警配置失败,请稍后重试") } if res != nil { res.WarnType = deriveWarnType(res.ItemType, res.WarnUnit) } return res, nil } func warningConfigFields() []any { columns := dao.TWarnConfig.Columns() return []any{ columns.Id, columns.TenantId, columns.BatchNo, columns.ItemType, columns.ItemId, columns.ItemCode, columns.WarnUnit, columns.FirstWarn, columns.SecondWarn, columns.ThirdWarn, columns.WarnMethods, columns.WarnPhones, columns.WarnEmails, columns.AutoAction, columns.IsEnabled, columns.WxTemplateId, columns.Remark, columns.CreatedDept, columns.CreatedBy, columns.CreatedAt, columns.UpdatedBy, columns.UpdatedAt, columns.AppNotifyEnabled, } } func buildConfigSaveData(tenantId string, deptId int64, userId int64, resolvedItemCode string, in *model.SimWarningConfigSaveParam, now *gtime.Time) do.TWarnConfig { itemCode := strings.TrimSpace(resolvedItemCode) if itemCode == "" { itemCode = strings.TrimSpace(in.ItemCode) } isEnabled := 1 if in.IsEnabled != nil { isEnabled = *in.IsEnabled } warnMethods := normalizeConfigWarnMethods(in.WarnMethods) data := do.TWarnConfig{ TenantId: tenantId, ItemType: in.ItemType, ItemId: in.ItemId, ItemCode: itemCode, BatchNo: strings.TrimSpace(in.BatchNo), WarnUnit: in.WarnUnit, FirstWarn: in.FirstWarn, SecondWarn: in.SecondWarn, ThirdWarn: in.ThirdWarn, WarnMethods: warnMethods, WarnPhones: strings.TrimSpace(in.WarnPhones), WarnEmails: strings.TrimSpace(in.WarnEmails), AutoAction: in.AutoAction, Remark: strings.TrimSpace(in.Remark), CreatedDept: deptId, CreatedBy: userId, CreatedAt: now, UpdatedBy: userId, UpdatedAt: now, IsEnabled: isEnabled, } if in.AppNotifyEnabled != nil { data.AppNotifyEnabled = *in.AppNotifyEnabled } return data } func normalizeConfigSaveParam(in *model.SimWarningConfigSaveParam) error { in.ItemCode = strings.TrimSpace(in.ItemCode) in.WarnMethods = normalizeConfigWarnMethods(in.WarnMethods) in.WarnPhones = strings.TrimSpace(in.WarnPhones) in.WarnEmails = strings.TrimSpace(in.WarnEmails) in.Remark = strings.TrimSpace(in.Remark) if in.ItemType == 0 { in.ItemType = warnItemTypeCard } if in.ItemType != warnItemTypeCard && in.ItemType != warnItemTypeFlowPool { return gerror.New("预警对象类型无效") } if in.ItemId <= 0 && in.ItemCode == "" { if in.ItemType == warnItemTypeFlowPool { return gerror.New("流量池ID或流量池编号不能为空") } return gerror.New("单卡ID或ICCID/MSISDN/IMSI不能为空") } if in.WarnUnit == warnUnitDay || in.WarnUnit == warnUnitMonth { return gerror.New("日期预警由系统默认规则处理,不支持手动配置") } if err := normalizeConfigContacts(in); err != nil { return err } if in.IsEnabled != nil && *in.IsEnabled != 0 && *in.IsEnabled != 1 { return gerror.New("启停状态无效") } if in.AppNotifyEnabled != nil && *in.AppNotifyEnabled != 0 && *in.AppNotifyEnabled != 1 { return gerror.New("小程序订阅通知状态无效") } if in.AutoAction != 0 && in.AutoAction != warnAutoActionDataCut { return gerror.New("第三次是否断网配置无效") } if in.ItemType == warnItemTypeFlowPool && in.AutoAction != 0 { return gerror.New("流量池预警不支持自动断网") } in.WarnType = deriveWarnType(in.ItemType, in.WarnUnit) if err := validateWarnTypeUnit(in.ItemType, in.WarnType, in.WarnUnit); err != nil { return err } if in.ItemType == warnItemTypeCard && in.AutoAction == warnAutoActionDataCut && in.ThirdWarn <= 0 { return gerror.New("开启第三次断网时必须配置第三次提醒") } switch in.WarnType { case warnTypeCardTraffic, warnTypeFlowPoolPercent: if in.WarnUnit == warnUnitPercent { first := normalizePercentThreshold(in.FirstWarn) second := normalizePercentThreshold(in.SecondWarn) third := normalizePercentThreshold(in.ThirdWarn) if first > 100 || second > 100 || third > 100 { return gerror.New("百分比阈值不能大于100") } in.FirstWarn = first in.SecondWarn = second in.ThirdWarn = third } return validateTrafficThresholds(in.FirstWarn, in.SecondWarn, in.ThirdWarn) default: return gerror.New("预警类型无效") } } func normalizeConfigContacts(in *model.SimWarningConfigSaveParam) error { if hasConfigWarnMethod(in.WarnMethods, "0") { phones, err := normalizeConfigWarnPhones(in.WarnPhones) if err != nil { return err } in.WarnPhones = phones } else { in.WarnPhones = "" } if hasConfigWarnMethod(in.WarnMethods, "1") { emails, err := normalizeConfigWarnEmails(in.WarnEmails) if err != nil { return err } in.WarnEmails = emails } else { in.WarnEmails = "" } return nil } func hasConfigWarnMethod(methods string, target string) bool { for _, method := range splitConfigWarnContactList(methods) { if method == target { return true } } return false } func normalizeConfigWarnMethods(methods string) string { selected := map[string]struct{}{ warnMethodPlatform: {}, } for _, method := range splitConfigWarnContactList(methods) { switch method { case warnMethodPlatform, warnMethodEmail, warnMethodSMS, warnMethodPhone: selected[method] = 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 normalizeConfigWarnEmails(value string) (string, error) { emails := splitConfigWarnContactList(value) if len(emails) == 0 { return "", gerror.New("请选择邮箱提醒时必须填写邮箱地址") } result := make([]string, 0, len(emails)) for _, email := range emails { address, err := mail.ParseAddress(email) if err != nil || address == nil || strings.TrimSpace(address.Address) == "" { return "", gerror.Newf("邮箱格式不正确:%s", email) } result = append(result, strings.ToLower(strings.TrimSpace(address.Address))) } return strings.Join(result, ","), nil } func normalizeConfigWarnPhones(value string) (string, error) { phones := splitConfigWarnContactList(value) if len(phones) == 0 { return "", gerror.New("请选择短信提醒时必须填写短信号码") } for _, phone := range phones { if !isValidConfigWarnPhone(phone) { return "", gerror.Newf("手机号格式不正确:%s", phone) } } return strings.Join(phones, ","), nil } func isValidConfigWarnPhone(phone string) bool { if len(phone) != 11 || phone[0] != '1' { return false } for _, r := range phone { if r < '0' || r > '9' { return false } } return phone[1] >= '3' && phone[1] <= '9' } func splitConfigWarnContactList(value string) []string { parts := strings.FieldsFunc(value, func(r rune) bool { switch r { case ',', ';', ',', ';', '|', '\n', '\r', '\t', ' ': return true default: return false } }) seen := make(map[string]struct{}, len(parts)) result := make([]string, 0, len(parts)) for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } key := strings.ToLower(part) if _, exists := seen[key]; exists { continue } seen[key] = struct{}{} result = append(result, part) } return result } 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 warnUnitForConfigLookup(itemType, warnType, warnUnit int) (int, error) { if warnUnit > 0 { if warnUnit == warnUnitDay || warnUnit == warnUnitMonth { return 0, gerror.New("日期预警由系统默认规则处理,不支持查询用户配置") } if err := validateWarnTypeUnit(itemType, deriveWarnType(itemType, warnUnit), warnUnit); err != nil { return 0, err } return warnUnit, nil } switch warnType { case warnTypeFlowPoolPercent: return warnUnitPercent, nil case warnTypeCardTraffic: return 0, gerror.New("通过对象ID查询单卡流量预警时必须指定warnUnit") default: return 0, gerror.New("通过对象ID查询预警配置时必须指定warnUnit") } } func applyWarnTypeCompatibilityFilter(mod *gdb.Model, warnType int) *gdb.Model { switch warnType { case warnTypeCardTraffic: return mod.Where("item_type = ? AND warn_unit IN (?, ?)", warnItemTypeCard, warnUnitMb, warnUnitPercent) case warnTypeFlowPoolPercent: return mod.Where("item_type = ? AND warn_unit = ?", warnItemTypeFlowPool, warnUnitPercent) default: return mod.Where("1 = 0") } } func fillConfigWarnTypes(list []*model.SimWarningConfigModel) { for _, item := range list { if item == nil { continue } item.WarnType = deriveWarnType(item.ItemType, item.WarnUnit) } } func ensureAffected(result sql.Result, message string) error { affected, err := result.RowsAffected() if err != nil { return err } if affected == 0 { return gerror.New(message) } return nil }