All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m37s
- 轮询配置新增套餐状态、资产状态、重置日前窗口与窗口内流量间隔(迁移 248),间隔三态:正数按间隔、0 不检查、空继承 - 配置按 priority, id 排序合并;在库与已销售无套餐未匹配时告警不入队 - 新增优先级整体重排、匹配预览、立即重算到期时间(异步只提前)、分层统计与重置日前加密统计接口 - 已切换日快照计量的卡流量按观测时间归属,激活前用量只记已结算 - 订单购买与实名激活补齐资产级激活锁,激活前对已切换卡结算一次流量,失败只告警不阻塞,结算不触发通道阈值判定
140 lines
5.5 KiB
Go
140 lines
5.5 KiB
Go
package polling
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"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 []string) error {
|
||
for _, field := range fields {
|
||
switch 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, "轮询配置参数不合法:不支持清空字段 "+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
|
||
}
|