All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m27s
- 每卡 Redis 采集队列(默认 N=3、跨天清空、回退容差 1 MB),攒满且不下降才写最后一个点 - 测试环境全量切换日快照计量,下线灰度名单、切换水位与卡表基线扣费;全部卡置待建起点 - 读数区分没给值与 0;有套餐的卡读数 0 也写快照与日用量 0 行;重置日比较基准为 0 照常防旧值 - 新增采集记录(按月分区保留 90 天)、人工冲正流水与后台接口、采集告警 - 扣减按采集时刻有效套餐查找,只对生效中套餐做状态迁移,查不到套餐按成功跳过 - 换货新卡(新设备下的卡)打待建起点;采集间隔上限只校验启用中的轮询配置 Change: add-card-traffic-queue-and-observed-deduction Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
1.6 KiB
Go
51 lines
1.6 KiB
Go
package cardflow
|
||
|
||
import (
|
||
"math"
|
||
"time"
|
||
)
|
||
|
||
// 队列写入判断结果,与采集记录的写入判断取值一致。
|
||
const (
|
||
QueueWrite = "written"
|
||
QueueNotFull = "queue_not_full"
|
||
QueueSlopeNegative = "slope_negative"
|
||
QueueOutOfOrder = "out_of_order"
|
||
)
|
||
|
||
// QueuePoint 是采集队列中的一个点:采集时间与运营商原始读数。
|
||
type QueuePoint struct {
|
||
ObservedAt time.Time `json:"observed_at"`
|
||
ReadingMB float64 `json:"reading_mb"`
|
||
}
|
||
|
||
// JudgeQueue 判断本次新点能否写入:新点在队尾、队列满 size 个点、相邻两点后一个 ≥ 前一个 − 回退容差。
|
||
// 不满足时返回不写原因;满足时写入的是队列最后一个点(即本次新点)。
|
||
func JudgeQueue(points []QueuePoint, newPointIsTail bool, size int, dropToleranceMB float64) string {
|
||
if !newPointIsTail {
|
||
return QueueOutOfOrder
|
||
}
|
||
if len(points) < size {
|
||
return QueueNotFull
|
||
}
|
||
for i := 1; i < len(points); i++ {
|
||
if points[i].ReadingMB < points[i-1].ReadingMB-dropToleranceMB {
|
||
return QueueSlopeNegative
|
||
}
|
||
}
|
||
return QueueWrite
|
||
}
|
||
|
||
// MaxCollectionInterval 返回启用中轮询配置流量采集间隔的上限:留 2 次冗余,
|
||
// 跨天清空开启时为 24 小时 ÷ (N+2),关闭时为最长点龄 ÷ (N+2);最长点龄为 0(不限)时不设上限,返回 0。
|
||
func MaxCollectionInterval(size int, clearOnNewDay bool, maxPointAgeHours int, spare int) time.Duration {
|
||
window := 24 * time.Hour
|
||
if !clearOnNewDay {
|
||
if maxPointAgeHours <= 0 {
|
||
return 0
|
||
}
|
||
window = time.Duration(maxPointAgeHours) * time.Hour
|
||
}
|
||
return time.Duration(math.Floor(float64(window) / float64(size+spare)))
|
||
}
|