feat(channels): QQ 非消息事件全枚举投递 + systemnotice 补 request/interaction/unknown 类
- 适配器不再整类丢弃:notice/request/meta_event/message_sent/未知 post_type/JSON 解析失败( decode_error ) 一律归一化投递并留痕
- notice 映射补 group_ban/sub_type 全枚举,未知类型走 NoticeUnknown 兜底(fail-safe 只登记、绝不猜、绝不放行)
- 不投影 flag/comment/file/duration;归一化 DEBUG 只记结构(脱敏,变异实测可打红)
- 传输层端到端已覆盖(真实 http+server 上报路径:加好友请求/心跳/未知类型)
- ⚠️ 主人 2026-10-05 决定「先不连 QQ」(风控)⇒ 本片为通用红线(绝不静默丢弃)+ 现成证据链,QQ 侧后续增量工作暂停
This commit is contained in:
1 parent
ec3f6e810a
commit
ba17820040
9 files changed
+1315
-193
No files matched your search
@@ -179,42 +179,51 @@ func (a *Adapter) readConn(ctx context.Context, conn *wsConn) {
|
||||
}
|
||||
|
||||
// handlePayload parses a text frame, converting message events into normalized
|
||||
// UnifiedMessage and dispatching them. notice(系统通知)上报同样被归一化后投递
|
||||
// ——它们带 ContentType=ContentAction 的结构化标记,由 channelbridge 的「传入前
|
||||
// 审查」决定是否 / 如何进入她的上下文(design/12 §6 策略一补充);其余非 message
|
||||
// 上报(message_sent 回声、meta_event 心跳等)一律丢弃。丢弃前一律留一条**脱敏结构
|
||||
// DEBUG**(logInboundFrame):上游到底有没有把这一帧推过来,是排障时最先要回答的问题,
|
||||
// 而「被丢弃」与「从未到达」在日志里必须能分开。唯一的例外是 action 回执(空 post_type
|
||||
// + echo):它交给等待中的主动 action(见 probe.go),不再走入站日志。
|
||||
// UnifiedMessage and dispatching them. **消息之外的一切上报同样被归一化后投递**
|
||||
// (design/26 §1.1 / §3.1:notice / request / meta_event / message_sent 与未知
|
||||
// post_type 一律不许静默丢弃)——它们带 ContentType=ContentAction 的结构化标记,
|
||||
// 由 channelbridge 的「传入前审查」决定是否 / 如何进入她的上下文,并由留存层落库计数
|
||||
// (design/26 §5)。丢弃前一律留一条**脱敏结构 DEBUG**(logInboundFrame),成功投递的
|
||||
// 另加一条归一化结果 DEBUG(logNormalizedEvent):上游到底有没有把这一帧推过来,
|
||||
// 是排障时最先要回答的问题,而「被丢弃」与「从未到达」在日志里必须能分开。
|
||||
//
|
||||
// 唯一的例外是**我们自己主动 action 的回执**(空 post_type + echo):它交给等待中的
|
||||
// 主动 action(见 probe.go),不是上游推来的事件,因此不进事件流水线(但它照样落
|
||||
// 脱敏结构日志)。载荷解析失败的帧**同样是事件**(decode_error 兜底),照样投递。
|
||||
func (a *Adapter) handlePayload(ctx context.Context, payload []byte) {
|
||||
// 先给 action 回执(空 post_type + echo)一个归宿:WS 上的 get_status 等主动
|
||||
// action 靠 echo 关联等待表(见 probe.go)。命中即由调用方消费,不再走入站日志;
|
||||
// 没有等待者的散落回执仍落回下面的脱敏结构 DEBUG。
|
||||
// action 靠 echo 关联等待表(见 probe.go)。命中即由调用方消费;没有等待者的散落
|
||||
// 回执仍落回下面的脱敏结构 DEBUG。
|
||||
if a.routeActionResponse(payload) {
|
||||
return
|
||||
}
|
||||
ev, err := parseEvent(payload)
|
||||
if err != nil {
|
||||
a.log.Debug("non-event payload ignored", "err", err)
|
||||
a.log.Debug("undecodable inbound frame",
|
||||
"err", err, "bytes", len(payload))
|
||||
a.dispatch(ctx, decodeErrorMessage(payload))
|
||||
return
|
||||
}
|
||||
a.logInboundFrame(ev)
|
||||
switch ev.PostType {
|
||||
case postTypeMessage:
|
||||
if isActionReceipt(ev) {
|
||||
return
|
||||
}
|
||||
if ev.PostType == postTypeMessage {
|
||||
msg := a.toUnified(ev, payload)
|
||||
if err := msg.Validate(); err != nil {
|
||||
a.log.Warn("invalid unified message dropped", "err", err)
|
||||
return
|
||||
}
|
||||
a.dispatch(ctx, msg)
|
||||
case postTypeNotice:
|
||||
msg := a.toNoticeMessage(ev, payload)
|
||||
if err := msg.Validate(); err != nil {
|
||||
a.log.Warn("invalid notice event dropped", "err", err)
|
||||
return
|
||||
}
|
||||
a.dispatch(ctx, msg)
|
||||
return
|
||||
}
|
||||
a.logNormalizedEvent(ctx, ev)
|
||||
msg := a.toEventMessage(ev, payload)
|
||||
if err := msg.Validate(); err != nil {
|
||||
a.log.Warn("invalid non-message event dropped", "post_type", ev.PostType, "err", err)
|
||||
return
|
||||
}
|
||||
a.dispatch(ctx, msg)
|
||||
}
|
||||
|
||||
// dispatch routes an inbound message to the registered push handler, falling
|
||||
|
||||
Reference in new issue
Block a user