新增 000228 迁移:规则表 tb_package_traffic_alert_rule(每套餐商品至多一条,无软删除,package_id 非部分唯一约束)、达量预警快照表 tb_package_traffic_alert(以主套餐使用记录 + 阈值快照为唯一键, 触发时冻结用量、额度、比例、阈值、到期时间、归属与资产快照),并为 tb_package_usage 新增扫描 范围部分索引 idx_package_usage_alert_scope;down 在预警表存在数据时阻断回滚。 新增规则维护接口 GET/POST/PUT /api/admin/package-traffic-alert-rules(仅超级管理员与平台账号): 创建校验套餐存在且真流量额度大于零,阈值为 1%~100% 的两位小数;修改只影响后续扫描,不回填也 不改写既有预警快照;全部写操作记录操作者、前后值与时间。 新增每日 06:00(Asia/Shanghai)扫描任务 package:traffic:alert:scan,与套餐临期扫描共用 data_cleanup 队列:按资产汇总当前有效套餐的真流量,分子取使用记录真已用量、分母取使用记录真总量快照,命中 主套餐规则阈值时在同一事务创建预警与可靠通知事件;重复执行以唯一冲突视为已处理,不重复投递, 不建停机锁、不调用运营商。 新增预警列表、详情与异步导出 GET /api/admin/package-traffic-alerts、GET /api/admin/package-traffic-alerts/:id、 POST /api/admin/package-traffic-alerts/export,列表与详情一律读冻结快照;新增通知类型 package.traffic.alert 与受控目标 package_traffic_alert_detail,目标解析仅对超级管理员与平台账号 返回可跳转,越权与不存在统一按资源不可见处理。 同步 OpenAPI(cmd/gendocs、cmd/api/docs.go、pkg/openapi/handlers.go)、审计动作与资源注册、上下文 健康检查证据;归档变更并同步 package-traffic-alert 主 Spec。
53 lines
2.3 KiB
Go
53 lines
2.3 KiB
Go
// Package packagetrafficalert 提供套餐真流量达量预警的领域判定规则。
|
||
//
|
||
// 判定口径固定为「真流量」:分子取套餐使用记录的真已用量,分母取套餐使用记录的真总量快照,
|
||
// 二者按资产汇总后再与主套餐规则阈值比较;全部使用整数万分比比较,不使用浮点判定,
|
||
// 避免边界(例如恰好等于阈值)因二进制浮点误差产生错误结论。
|
||
package packagetrafficalert
|
||
|
||
import "math"
|
||
|
||
// ratioScale 是万分比刻度:1% = 100,0.01% = 1。
|
||
const ratioScale = 10000
|
||
|
||
// MinThresholdPercent 与 MaxThresholdPercent 是可配置阈值百分比的闭区间端点。
|
||
const (
|
||
MinThresholdPercent = 1.0
|
||
MaxThresholdPercent = 100.0
|
||
)
|
||
|
||
// ThresholdBasisPoints 把百分比阈值换算为整数万分比(0.01% = 1)。
|
||
// 数据库以 NUMERIC(5,2) 保存两位小数,读取后先四舍五入到两位再换算,保证 1.25% 恒等于 125。
|
||
func ThresholdBasisPoints(percent float64) int64 {
|
||
return int64(math.Round(NormalizeThresholdPercent(percent) * 100))
|
||
}
|
||
|
||
// NormalizeThresholdPercent 把百分比四舍五入到两位小数,与 NUMERIC(5,2) 的存储精度一致。
|
||
func NormalizeThresholdPercent(percent float64) float64 {
|
||
return math.Round(percent*100) / 100
|
||
}
|
||
|
||
// IsValidThresholdPercent 判断百分比是否落在 1%~100% 闭区间内。
|
||
func IsValidThresholdPercent(percent float64) bool {
|
||
normalized := NormalizeThresholdPercent(percent)
|
||
return normalized >= MinThresholdPercent && normalized <= MaxThresholdPercent
|
||
}
|
||
|
||
// Decide 按资产的汇总真流量判定是否达到阈值,并返回向下取整的汇总比例万分比。
|
||
//
|
||
// usedMB 为该资产全部当前有效套餐的真已用量之和,limitMB 为同集合的真总量快照之和。
|
||
// 分母不大于零属于不可判定资产,调用方必须先跳过;此处返回未命中以避免除零。
|
||
func Decide(usedMB, limitMB, thresholdBasisPoints int64) (bool, int64) {
|
||
if limitMB <= 0 || thresholdBasisPoints <= 0 {
|
||
return false, 0
|
||
}
|
||
ratioBasisPoints := usedMB * ratioScale / limitMB
|
||
hit := usedMB*ratioScale >= thresholdBasisPoints*limitMB
|
||
return hit, ratioBasisPoints
|
||
}
|
||
|
||
// PercentFromBasisPoints 把万分比换算为保留两位小数的百分比展示值。
|
||
func PercentFromBasisPoints(basisPoints int64) float64 {
|
||
return float64(basisPoints) / 100
|
||
}
|