- 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)——已逐条说明理由
71 lines
3.2 KiB
Go
71 lines
3.2 KiB
Go
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
|
||
}
|