Files
iotlcm/admin-svr/server/internal/logic/simwarning/config.go

701 lines
20 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}