feat(channels): 流控可配 —— 陌生人私聊额度走账号配置 directMsgPerMin(裁决 13)
- 后端:账号顶层 directMsgPerMin(缺失/0 = 保护性默认 6 条/分;正值 = 该值;负值 = 关闭并留带 source 的 WARN)
- 日志如实报出**实际生效**额度(adapter.(*FlowGate).DirectPerMin()),告警文案不再写死 6
- 面板:渠道配置表单加字段(含帮助文案)+ 回显/写回链路(LIMIT_KEYS 四键)+ 账号卡片摘要
- 文案纠错:AdapterConfigSummary 的 msgPerMin 由「私聊每分钟上限」改为「非主人消息每分钟总上限」(原描述是误导),并列补 directMsgPerMin 行
- 变异实测:缺键丢默认 / 负值当 1 条每分 / 前端吞负值 —— 三个都精确打红并还原
- ⚠️ 已知缺口(下一片补,gateway 面):GET/PUT /admin/channels 尚未下发/解码该键 ⇒ 面板暂时回显空、保存会把 -1 重置为 0
This commit is contained in:
1 parent
ba17820040
commit
a057a0f61f
18 files changed
+339
-70
No files matched your search
@@ -41,18 +41,19 @@ func (b *Bridge) gateFor(a adapter.PlatformAdapter) *adapter.FlowGate {
|
||||
// (每群每分钟 6 条、上限 50),负值则关闭限制——
|
||||
// 这是对 CHANNELS_JSON 账号的向后兼容约定(design/12 §6 实现要点)。
|
||||
//
|
||||
// **私聊(陌生人)限流例外:它不依赖任何配置键**(主人 2026-10-05 裁决③),
|
||||
// 恒定取保护性默认 FlowDirectDefault —— 原本没有任何配置的账号(如 qq-obv11)
|
||||
// 对陌生人私聊是完全敞口,而裁决要求「陌生人必须有流控」,所以这里不能
|
||||
// 因为「没配」而静默关掉。主人私聊在 onMessage 就被绕过本闸门,不受影响。
|
||||
// **私聊(陌生人)限流走 directMsgPerMin**(主人 2026-10-05 裁决③与裁决 13
|
||||
// 「流控时间要可配置」):它与群聊同一条读法,但缺省/0 一律取保护性默认
|
||||
// FlowDirectDefault —— 原本没有任何配置的账号(如 qq-obv11)对陌生人私聊是完全
|
||||
// 敞口,而裁决要求「陌生人必须有流控」,所以这里绝不能因为「没配」而静默关掉。
|
||||
// 主人私聊在 onMessage 就被绕过本闸门,不受影响。
|
||||
func (b *Bridge) gateOptions(a adapter.PlatformAdapter) adapter.FlowGateOptions {
|
||||
cfg := a.Config()
|
||||
opts := adapter.FlowGateOptions{
|
||||
Review: b.review,
|
||||
OnPruned: b.onRejected,
|
||||
Now: b.now,
|
||||
DirectMsgPerMin: adapter.FlowDirectDefault,
|
||||
DirectMsgPerMin: directLimit(cfg, b.flowLog),
|
||||
}
|
||||
cfg := a.Config()
|
||||
if grp, ok := cfgInt(cfg, "groupMsgPerMin"); ok {
|
||||
opts.GroupMsgPerMin = flowOrDefault(grp, adapter.FlowLimitDefault)
|
||||
}
|
||||
@@ -65,6 +66,29 @@ func (b *Bridge) gateOptions(a adapter.PlatformAdapter) adapter.FlowGateOptions
|
||||
return opts
|
||||
}
|
||||
|
||||
// directLimit 解析账号级 directMsgPerMin(非主人私聊 = 陌生人的每分钟每发送者上限),
|
||||
// 语义与 flowOrDefault 逐字同口径:**键缺失、非整数或 0 ⇒ 保护性默认
|
||||
// adapter.FlowDirectDefault;正值 ⇒ 该值;负值 ⇒ 0(关闭该限制)**。
|
||||
//
|
||||
// 与群聊的唯一区别是缺省侧:缺省绝不解释成「关闭」,否则一个漏配的键就会
|
||||
// 把陌生人私聊重新变成全敞口(主人 2026-10-05 裁决③的既定裁决)。负值关闭
|
||||
// 只有手写负数才能到达,属主人的**显式**选择,因此这里留一条带 source 的 WARN
|
||||
// 日志 —— 流控被关必须在日志里可追溯,不许静默发生。
|
||||
func directLimit(cfg map[string]any, log *slog.Logger) int {
|
||||
v, ok := cfgInt(cfg, directMsgPerMinKey)
|
||||
if !ok {
|
||||
return adapter.FlowDirectDefault
|
||||
}
|
||||
if v < 0 {
|
||||
if log != nil {
|
||||
log.Warn("non-owner private rate limit turned off by account config",
|
||||
"key", directMsgPerMinKey, "value", v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return flowOrDefault(v, adapter.FlowDirectDefault)
|
||||
}
|
||||
|
||||
// flowOrDefault 把配置的限制值映射为实际生效的限制:负值
|
||||
// 关闭限制(0),零采用保护性默认值,正值则
|
||||
// 固定为该值。这就是「没有该字段的旧账号保持原有行为或
|
||||
@@ -99,6 +123,13 @@ func cfgInt(m map[string]any, key string) (int, bool) {
|
||||
}
|
||||
|
||||
const (
|
||||
// directMsgPerMinKey 是账号级「非主人私聊(陌生人)每分钟每发送者上限」的配置键
|
||||
// (design/12 §6 策略三;主人 2026-10-05 裁决 13)。它与 groupMsgPerMin 同一条读法:
|
||||
// 由 cmd/cyrene 的 namedAdapter 从账号字段透出到 Config(),桥在这里解析。
|
||||
directMsgPerMinKey = "directMsgPerMin"
|
||||
// flowgateLogSource 是本文件日志的来源标记,使「流控解析结果」跨模块可定位
|
||||
// (项目规范 §五:禁止无来源裸日志)。
|
||||
flowgateLogSource = "services/channelbridge/flowgate"
|
||||
// reviewMaxTokens 限定审查模型的输出,使冗长的队列清单绝不会
|
||||
// 产出超长补全(design/12 §6 策略四 成本护栏)。
|
||||
reviewMaxTokens = 256
|
||||
|
||||
Reference in new issue
Block a user