Files
junhong_cmp_fiber/internal/service/polling/config_validation.go
邹进 79b7580554
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m30s
feat: 下线设备保护期
- 后台设备停复机不再设置 1 小时保护期,也不再因保护期拒绝相反操作;单卡手动停复机去掉保护期检查
- 删除保护期一致性轮询(polling:protect)及其强制停复机方法;调度、初始化、监控、匹配预览不再包含保护期;已入队的旧任务由空处理函数直接确认,下一次发布移除
- 轮询配置接口不再接受与返回 protect_check_interval;并发配置不再返回 protect,查询、更新、重置该类型时拒绝
- 资产详情 device_protect_status 固定返回 none 并标记废弃,兼容老版本客户端
- 删除保护期 Redis key 与时长常量;停机原因 protect_period 保留供历史数据与开放接口映射
- 迁移 000249 只把 protect_check_interval、last_protect_check_at 列注释标记为已废弃,不删列不改数据
2026-10-25 17:19:10 +09:00

140 lines
5.6 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 polling
import (
"context"
"fmt"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// validatePollingConfig 校验轮询配置:间隔为 0(不检查)或不小于最小间隔;至少配置一种间隔;
// 重置日前加密的两个字段成对配置;匹配"无套餐且已销售"或"在库"的配置卡状态检查间隔必须为正数。
// 现有 Handler 未调用校验器,校验在服务层完成。
func validatePollingConfig(cfg *model.PollingConfig) error {
intervals := []struct {
name string
value *int
min int
}{
{"实名检查间隔", cfg.RealnameCheckInterval, 30},
{"流量检查间隔", cfg.CarddataCheckInterval, 60},
{"套餐检查间隔", cfg.PackageCheckInterval, 60},
{"卡状态检查间隔", cfg.CardStatusCheckInterval, 30},
}
configured := false
for _, item := range intervals {
if item.value == nil {
continue
}
configured = true
if *item.value < 0 || (*item.value > 0 && *item.value < item.min) {
return errors.New(errors.CodeInvalidParam, fmt.Sprintf("轮询配置参数不合法:%s须为 0(不检查)或不小于 %d 秒", item.name, item.min))
}
}
if !configured {
return errors.New(errors.CodeInvalidParam, "至少需要配置一种检查间隔")
}
if cfg.PackageState != nil && *cfg.PackageState != "" && !validPackageState(*cfg.PackageState) {
return errors.New(errors.CodeInvalidParam, "轮询配置参数不合法:套餐状态取值不合法")
}
if cfg.AssetStatus != nil && (*cfg.AssetStatus < constants.AssetStatusInStock || *cfg.AssetStatus > constants.AssetStatusDeactivated) {
return errors.New(errors.CodeInvalidParam, "轮询配置参数不合法:资产状态取值不合法")
}
if (cfg.PreResetWindow == nil) != (cfg.PreResetCarddataInterval == nil) {
return errors.New(errors.CodeInvalidParam, "轮询配置参数不合法:重置日前窗口长度与窗口内流量间隔须同时配置或同时为空")
}
if cfg.PreResetWindow != nil && (*cfg.PreResetWindow < 60 || *cfg.PreResetCarddataInterval < 60) {
return errors.New(errors.CodeInvalidParam, "轮询配置参数不合法:重置日前窗口长度与窗口内流量间隔不小于 60 秒")
}
if requiresCardStatusCheck(cfg) && (cfg.CardStatusCheckInterval == nil || *cfg.CardStatusCheckInterval <= 0) {
return errors.New(errors.CodeInvalidParam, "轮询配置参数不合法:匹配无套餐已销售卡或在库卡的配置必须配置卡状态检查间隔(正数),用于发现上游开机后停机")
}
return nil
}
// requiresCardStatusCheck 判断配置是否匹配必须兜底查卡状态的层:在库,或无套餐且已销售。
func requiresCardStatusCheck(cfg *model.PollingConfig) bool {
if cfg.AssetStatus != nil && *cfg.AssetStatus == constants.AssetStatusInStock {
return true
}
return cfg.AssetStatus != nil && *cfg.AssetStatus == constants.AssetStatusSold &&
cfg.PackageState != nil && *cfg.PackageState == constants.PollingPackageStateNone
}
func validPackageState(state string) bool {
for _, item := range constants.PollingPackageStates {
if item == state {
return true
}
}
return false
}
// applyPollingConfigClearFields 把指定字段清空为 NULL:间隔清空即改为继承,匹配条件清空即不限。
func applyPollingConfigClearFields(cfg *model.PollingConfig, fields []dto.PollingConfigClearField) error {
for _, field := range fields {
switch string(field) {
case "realname_check_interval":
cfg.RealnameCheckInterval = nil
case "carddata_check_interval":
cfg.CarddataCheckInterval = nil
case "package_check_interval":
cfg.PackageCheckInterval = nil
case "card_status_check_interval":
cfg.CardStatusCheckInterval = nil
case "carrier_id":
cfg.CarrierID = nil
case "package_state":
cfg.PackageState = nil
case "asset_status":
cfg.AssetStatus = nil
case "pre_reset_window":
cfg.PreResetWindow = nil
case "pre_reset_carddata_interval":
cfg.PreResetCarddataInterval = nil
default:
return errors.New(errors.CodeInvalidParam, "轮询配置参数不合法:不支持清空字段 "+string(field))
}
}
return nil
}
// priorityWarnings 返回与其他启用配置优先级相同且匹配条件可能重叠的提示;只提示不拦截。
func (s *ConfigService) priorityWarnings(ctx context.Context, cfg *model.PollingConfig) []string {
others, err := s.configStore.ListEnabled(ctx)
if err != nil {
return nil
}
var warnings []string
for _, other := range others {
if other.ID == cfg.ID || other.Priority != cfg.Priority || !conditionsMayOverlap(cfg, other) {
continue
}
warnings = append(warnings, fmt.Sprintf("与配置「%s」优先级同为 %d 且匹配条件可能重叠,相同优先级按配置 ID 升序生效", other.ConfigName, cfg.Priority))
}
return warnings
}
// conditionsMayOverlap 判断两条配置是否可能匹配同一张卡:任一维度两边都填且取值不同即不重叠。
func conditionsMayOverlap(a, b *model.PollingConfig) bool {
if a.CardCondition != "" && b.CardCondition != "" && a.CardCondition != b.CardCondition {
return false
}
if a.CardCategory != "" && b.CardCategory != "" && a.CardCategory != b.CardCategory {
return false
}
if a.CarrierID != nil && b.CarrierID != nil && *a.CarrierID != *b.CarrierID {
return false
}
if a.AssetStatus != nil && b.AssetStatus != nil && *a.AssetStatus != *b.AssetStatus {
return false
}
if a.PackageState != nil && b.PackageState != nil && *a.PackageState != *b.PackageState {
return false
}
return true
}