Files
junhong_cmp_fiber/internal/service/polling/config_validation.go
邹进 2ddaffe373
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m57s
docs: 轮询配置接口文档与实际行为对齐
- 匹配预览 GET 只收 query iccid,POST 只收请求体,去掉 POST 文档中多余的 query 参数
- 预览草稿 overrides 改为独立结构,字段全部可选,最多 20 条
- clear_fields 用具名类型生成数组元素枚举;ordered_ids 说明须为正整数且不重复
2026-10-24 17:41:52 +09:00

141 lines
5.6 KiB
Go
Raw 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.ProtectCheckInterval, 30},
{"卡状态检查间隔", 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
}