Files
AskaEth 4213b268fe 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)——已逐条说明理由
2026-10-06 21:48:49 +08:00

65 lines
3.2 KiB
Go
Raw Permalink 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 backthink
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
)
// 本文件是「触发源取值域」的**唯一真源**:`Record.Trigger`(= `Trigger.Source` 字符串)
// 可能出现的取值,以及本服务订阅哪些事件主题。
//
// 为什么要独立出来:admin 的 `GET /api/v1/admin/backthink?trigger=` **服务端筛选**
// (design/12 §N C.6,切片 C2)必须**如实拒绝未知取值**(400),而「未知」只能相对
// 一个闭集判断。若在网关里另抄一份主题清单,新增订阅主题时就会出现「数据里有这个取值、
// 筛选却报错」的静默漂移 —— 因此订阅表与筛选白名单从同一处取。
//
// ⚠️ 与 design/12 §C.6「`trigger` 的取值域不封闭」**不矛盾**:那条约束的是**显示**口径
// (客户端按字面值显示、不做闭集校验、不隐藏不认识的取值);这里定义的是**筛选白名单**
// —— 面向的是「筛选参数打错了要报错,而不是静默返回一个空列表」(静默的空列表会被
// 读成「这段时间没有这一类的思考」,那正是本片要避免的假事实)。
// 内建触发标签。它们是 `Trigger.Source` 的字面值,也是 `Record.Trigger` 的落库值,
// 生产构造点分别是 heartbeatTrigger / startupTrigger(trigger.go)。
const (
// HeartbeatSource 是周期性自主反思(含启动轮之外的每次心跳)的标签。
HeartbeatSource = "heartbeat"
// StartupSource 是进程启动后第一轮思考的标签(裁决 07.4.4)。
StartupSource = "startup"
)
// EventSubjects 返回本服务订阅的事件主题 —— **唯一真源**:`subscribe` 用它注册订阅,
// `TriggerFilterValues` 用它拼筛选白名单,因此新增一个订阅主题时两处同时生效
// (不会出现「数据库里有新主题的记录、筛选却 400」的漂移)。
func EventSubjects() []string {
return []string{iot.DeviceUpdateSubject, SubjectReminderTrigger, SubjectSystemEvent}
}
// TriggerFilterValues 返回 `Record.Trigger` 可能出现的**全部取值**(闭集):
// 三个内建标签(心跳 / 启动 / 自排)+ 订阅的事件主题。
//
// 它是 admin 筛选参数的白名单,也是「面板可选项」的真源;顺序稳定,便于报错文案
// 与客户端下拉复用。
func TriggerFilterValues() []string {
return append([]string{HeartbeatSource, StartupSource, SelfWakeSource}, EventSubjects()...)
}
// IsTriggerValue 报告 v 是否是 `Record.Trigger` 可能出现的取值(白名单判定)。
func IsTriggerValue(v string) bool {
for _, x := range TriggerFilterValues() {
if x == v {
return true
}
}
return false
}
// ListByTrigger 按触发源读取记录(服务端筛选的读口,design/12 §N C.6 / 切片 C2)。
// 它把筛选**下推**到存储层:`limit` 因此是「**匹配**的记录条数」,而不是
// 「先取一页再筛掉不匹配的」(后者会给面板一个静默的假空值)。
//
// 委托给 `*Store`(自己不认识 SQL);`List` 的语义、排序、空串口径一字不改。
func (b *Backthink) ListByTrigger(ctx context.Context, trigger string, limit int) ([]Record, error) {
return b.store.ListByTrigger(ctx, trigger, limit)
}