Files
junhong_cmp_fiber/internal/domain/stopresume/rule.go
邹进 af8249158d
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m26s
feat: 停复机规则演练与终态自动关闭轮询
统一停复机规则转正的第一次发布:总开关仍关闭,现有停复机行为不变。

- 新增停复机规则演练:GET /stop-resume-rule/dry-run 按新规则对开机与停机卡只判断不执行,返回运营商账户 × 停机原因 × 判断结果汇总与分页明细,可假设通道实名管控配置;POST /stop-resume-rule/dry-run/export 导出明细(场景 stop_resume_rule_dry_run);仅超级管理员与平台账号
- 风险停机、已销户关闭轮询不再限于独立卡,只关该卡自身轮询,不级联设备
- 错误分类规则增加 auto_disable_polling,仅终态类别可勾选;停复机(含设备停复机)命中该规则时不重试、关闭该卡轮询、回写网关扩展状态并写审计 iot_card.terminal_disable_polling
- 迁移 000250 加列与约束,并为"已销户""仅支持风险复机"置位
2026-10-25 17:52:40 +09:00

138 lines
4.8 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 stopresume 定义停复机新规则的纯判断:输入卡与资产的当前事实,输出目标动作,不读写任何外部状态。
//
// 新规则(finalize-unified-stop-resume-rule):
// - 卡或其有效绑定设备暂停轮询时不自动停复机;
// - 开机卡命中任一停机条件即停机:无套餐、流量用完、通道开启实名管控且卡未实名;
// - 停机卡同时满足有套餐、流量未用完、实名条件时复机,不看停机原因;持本周期通道阈值锁的卡拒绝复机;
// - 卡所属通道不存在(无法判断实名管控)时本次不动作。
package stopresume
import "github.com/break/junhong_cmp_fiber/pkg/constants"
// 判断结果。
const (
ResultWillResume = "will_resume" // 将复机
ResultWillStop = "will_stop" // 将停机
ResultNoAction = "no_action" // 不动作
)
// 不动作原因。
const (
NoActionPollingSuspended = "polling_suspended" // 卡或所在设备暂停轮询
NoActionConditionsMet = "conditions_met" // 开机卡满足开机条件
NoActionNoPackage = "no_package" // 停机卡无套餐(与停机原因同值,共用名称)
NoActionTrafficExhausted = "traffic_exhausted" // 停机卡流量已用完
NoActionNotRealname = "not_realname" // 停机卡所属通道开启实名管控且卡未实名
NoActionThresholdLocked = "threshold_locked" // 停机卡持本周期通道阈值锁
NoActionCarrierMissing = "carrier_missing" // 卡所属通道不存在,无法判断实名管控
NoActionUnknownNetwork = "unknown_network" // 卡网络状态既非开机也非停机
)
// Facts 是判断一张卡所需的全部事实;套餐与流量按绑定设备(有有效绑定时)或卡自身计算。
type Facts struct {
NetworkStatus int
PollingSuspended bool // 卡自身或其有效绑定设备暂停轮询
HasValidPackage bool // 存在生效中的套餐
TrafficExhausted bool // 生效或已用完的套餐全部达到流量上限
RealnameVerified bool
CarrierKnown bool // 卡所属通道存在
RealnameControl bool // 卡所属通道开启实名管控(可为假设值)
ThresholdLocked bool // 持本周期通道阈值锁
CurrentStopReason string
}
// Decision 是一张卡的判断结果。
type Decision struct {
Result string // ResultWillResume / ResultWillStop / ResultNoAction
Reason string // 将停机时为目标停机原因;不动作时为不动作原因;将复机时为空
StopReason string // 将停机时的目标停机原因
}
// Decide 按新规则判断卡的目标动作。
func Decide(f Facts) Decision {
if f.PollingSuspended {
return noAction(NoActionPollingSuspended)
}
if !f.CarrierKnown {
return noAction(NoActionCarrierMissing)
}
realnameOK := !f.RealnameControl || f.RealnameVerified
switch f.NetworkStatus {
case constants.NetworkStatusOnline:
// 停机原因优先级:流量用完 > 无套餐(仅在未用完时判断)> 未实名,与评估主流程一致。
switch {
case f.TrafficExhausted:
return stop(constants.StopReasonTrafficExhausted)
case !f.HasValidPackage:
return stop(constants.StopReasonNoPackage)
case !realnameOK:
return stop(constants.StopReasonNotRealname)
default:
return noAction(NoActionConditionsMet)
}
case constants.NetworkStatusOffline:
switch {
case !f.HasValidPackage:
return noAction(NoActionNoPackage)
case f.TrafficExhausted:
return noAction(NoActionTrafficExhausted)
case !realnameOK:
return noAction(NoActionNotRealname)
case f.ThresholdLocked:
return noAction(NoActionThresholdLocked)
default:
return Decision{Result: ResultWillResume}
}
default:
return noAction(NoActionUnknownNetwork)
}
}
func stop(reason string) Decision {
return Decision{Result: ResultWillStop, Reason: reason, StopReason: reason}
}
func noAction(reason string) Decision {
return Decision{Result: ResultNoAction, Reason: reason}
}
// ResultName 返回判断结果中文名称。
func ResultName(result string) string {
switch result {
case ResultWillResume:
return "将复机"
case ResultWillStop:
return "将停机"
case ResultNoAction:
return "不动作"
default:
return result
}
}
// ReasonName 返回停机原因或不动作原因的中文名称。
func ReasonName(reason string) string {
switch reason {
case "":
return ""
case NoActionPollingSuspended:
return "暂停轮询"
case NoActionConditionsMet:
return "满足开机条件"
case NoActionNoPackage:
return "无套餐"
case NoActionTrafficExhausted:
return "流量已用完"
case NoActionNotRealname:
return "未实名(通道开启实名管控)"
case NoActionThresholdLocked:
return "持通道阈值锁"
case NoActionCarrierMissing:
return "所属通道不存在"
case NoActionUnknownNetwork:
return "网络状态未知"
default:
return reason
}
}