feat(backthink): 自排的成本可见面(C2 读侧,Y3)
- GET /api/v1/admin/backthink 加可选 ?trigger=(闭集=内建触发标签 + 本服务订阅的事件主题;
未知/空 ⇒ 400 invalid_trigger,不静默忽略;合法值下推到 SQL ⇒ limit 是匹配条数)
- 新端点 GET /api/v1/admin/backthink/self-wake(writeAuth):闸的只读镜像
{available,running,reason?,configFile?,gate{pending,used,limit,windowSec,default*,*IsDefault}}
三态分开(未接线 / 有配置未运行 / 运行中),读它**不消耗额度**
- 零新 env 键;旋钮源 = $DATA_DIR/config/backthink.json(**写面未做**,前置=组合根接线,已登记)
- 键名沿用设计与现有代码的 selfWakePerWindow(派工书写错成 selfWakeLimit,未改键名以免废掉主人已写的配置)
- 5 个变异(未知值静默忽略/关掉下推/恒报运行中/白名单摘键/前端把看不到画成 0/0)
⚠️ 本片改了 4 个文件面外文件(admin_query.go/routes.go/api.ts/adminSystem.ts)——已逐条说明理由
This commit is contained in:
1 parent
215eedf8e9
commit
4213b268fe
13 files changed
+1289
-43
No files matched your search
@@ -0,0 +1,70 @@
|
||||
package backthink
|
||||
|
||||
import "time"
|
||||
|
||||
// 本文件是自排闸的**只读快照**出口(design/12 §N C.5/C.2 的读侧,切片 C2)。
|
||||
//
|
||||
// 为什么需要它:面板此前**看不到**「本窗口还能自排几次」—— 那个数字只出现在
|
||||
// 被拒绝那一轮的审计文案里(`backthink.self_wake.capped` 的 result),也就是
|
||||
// 「已经被拒一次」之后才第一次可见。C.5 的闸本来就维护了一份**只为上报**的镜像
|
||||
// (selfWakeGate.granted),本文件把它接出来,判定仍然只由 limiter 做
|
||||
// (§C.5:「判定始终由 limiter 做;两者用同一个时间源与同一个窗口」)。
|
||||
//
|
||||
// 🔴 本文件**不做**任何写入、不碰滑窗件(`session.RateLimiter` 至今没有用量读口,
|
||||
// 属别人的文件面):镜像在 selfWakeGate 里已经有了,读它即可。
|
||||
|
||||
// SelfWakeStatus 是自排闸的只读快照:本窗口已用多少、上限多少、窗口多长,
|
||||
// 以及容量 1 的待跑槽是否被占用。
|
||||
//
|
||||
// 它**不参与任何判定**(判定始终由滑窗与待跑槽做),因此读它永远不改变闸的状态。
|
||||
type SelfWakeStatus struct {
|
||||
// Running 报告自排是否真的能排:未 Start / 已 Stop ⇒ false(此刻 WakeNow
|
||||
// 会返回 ErrSelfWakeUnavailable)。面板据此显示「未运行」而不是「0 次」。
|
||||
Running bool
|
||||
// Pending 报告容量 1 的待跑槽是否已占用(有一轮自排排着队还没跑)。
|
||||
Pending bool
|
||||
// Used 是本窗口已用额度,与收据 `SelfWakeReceipt.Used` **同一口径**
|
||||
// (同一次取数:过期的记录先被丢弃,再计数)。
|
||||
Used int
|
||||
// Limit 与 WindowSec 是**进程生效**的上限与窗口(已 normalize;`≤0` 的那份
|
||||
// 配置不会走到这里,见 cfg.go 的 normalize 注释)。
|
||||
Limit int
|
||||
WindowSec int
|
||||
// DefaultLimit / DefaultWindowSec 是代码默认值;LimitIsDefault / WindowIsDefault
|
||||
// 报告生效值**恰好等于**默认值。
|
||||
//
|
||||
// ⚠️ 它**不是**「配置文件里没写这个键」的断言(与 config-facts 的 isDefault
|
||||
// 同一条诚实口径:网关不读配置文件,也无从区分「配了 4」与「没配」)。
|
||||
DefaultLimit int
|
||||
DefaultWindowSec int
|
||||
LimitIsDefault bool
|
||||
WindowIsDefault bool
|
||||
}
|
||||
|
||||
// SelfWakeStatus 取一张自排闸快照。第二个返回值为 false 表示**闸不在**
|
||||
// (只有零值 / 未经 `New` 构造的 Backthink 会这样)—— 调用方**不得**把零值
|
||||
// 当成事实(不伪造「0 / 0」)。
|
||||
func (b *Backthink) SelfWakeStatus() (SelfWakeStatus, bool) {
|
||||
gate, running := b.wakeGate()
|
||||
if gate == nil {
|
||||
return SelfWakeStatus{}, false
|
||||
}
|
||||
gate.mu.Lock()
|
||||
// usedLocked 顺手丢弃过期记录,因此这里读到的与判定用的是同一个口径。
|
||||
used := gate.usedLocked()
|
||||
pending := gate.pending != nil
|
||||
limit, window := gate.limit, gate.window
|
||||
gate.mu.Unlock()
|
||||
|
||||
return SelfWakeStatus{
|
||||
Running: running,
|
||||
Pending: pending,
|
||||
Used: used,
|
||||
Limit: limit,
|
||||
WindowSec: int(window / time.Second),
|
||||
DefaultLimit: defaultSelfWakeLimit,
|
||||
DefaultWindowSec: int(defaultSelfWakeWindow / time.Second),
|
||||
LimitIsDefault: limit == defaultSelfWakeLimit,
|
||||
WindowIsDefault: window == defaultSelfWakeWindow,
|
||||
}, true
|
||||
}
|
||||
Reference in new issue
Block a user