# 02 — HookPlugin 接入

## 1. 结论

HookPlugin 是业务系统接入 `flare-im-core` 主链和旁路事实的标准方式。消息发送权限、内容风控、撤回校验等需要影响主链的逻辑接 Hook；审计、BI、推荐特征、计费等只观察事实的逻辑优先接 Business Probe 或旁路 Hook。

Core 必须保持独立运行：Hook 配置为空时直接跳过；旁路 Hook 默认 fail-open；只有明确影响主链决策的 Hook 才允许 fail-fast。

## 2. gRPC 契约

- Package：`flare.capability.v1`
- Service：`HookPlugin`
- RPC：`Call(GenericRequest) returns (GenericResponse)`

`GenericRequest`：

| 字段 | 说明 |
|------|------|
| `operation` | 稳定 Hook 操作名，如 `flare.hook.v1.pre_send` |
| `payload` | `google.protobuf.Any`，内嵌具体 `*HookRequest` |
| `request_id` | Hook 本次调用幂等键，建议与 `x-request-id` 可关联 |
| `metadata` | 静态配置与路由附加 KV |

`GenericResponse`：

| 字段 | 说明 |
|------|------|
| `ok` | RPC 解码与插件执行是否成功 |
| `payload` | 具体 `*HookResponse` |
| `error_code` / `error_message` | 插件内部错误；业务拒绝不要放这里 |

业务拒绝应放在具体 Response 中，例如 `PreSendHookResponse.allow = false`。

## 3. Hook 全景

| Hook | operation | 触发点 | 是否可阻断 | 推荐策略 |
|------|-----------|--------|------------|----------|
| 发前校验 | `flare.hook.v1.pre_send` | seq 分配和落库前 | 是 | `fail_fast`，短超时 |
| 发后观测 | `flare.hook.v1.post_send` | 消息进入主链后 | 否 | `ignore` / `reliable_async` |
| 送达观测 | `flare.hook.v1.delivery` | 下行送达或投递回执后 | 否 | `ignore` / 采样 |
| 撤回校验/审计 | `flare.hook.v1.recall` | 撤回命令执行时 | 视策略 | 权限类 fail-fast，审计 fail-open |
| 已读观测 | `flare.hook.v1.message_read` | 已读水位更新后 | 否 | `ignore` |
| 消息表态 | `flare.hook.v1.message_reaction` | 点赞/emoji/业务表态变更 | 可选 | 白名单 fail-fast，统计 fail-open |
| 会话生命周期 | `flare.hook.v1.conversation_lifecycle` | 创建、更新、归档、解散等 | 否 | `ignore` / `reliable_async` |
| 会话成员变更 | `flare.hook.v1.conversation_member` | 入群、退群、踢人、角色、禁言 | 可选 | 业务投影前可 fail-fast，事实后 fail-open |
| 推送发前 | `flare.hook.v1.push_pre_send` | 推送任务发送前 | 是 | 模板/通道策略 |
| 推送发后 | `flare.hook.v1.push_post_send` | 推送任务入队或发送后 | 否 | 统计/计费 |
| 推送送达 | `flare.hook.v1.push_delivery` | 厂商/长连接送达后 | 否 | 送达率 |
| 在线状态 | `flare.hook.v1.presence` | presence 变化 | 否 | 风控/在线分析 |
| 自定义扩展 | `flare.hook.v1.custom` | 业务自定义事件 | 由宿主定义 | 明确 schema |

Rust 公共类型已提供消息和会话核心 Hook trait：

| Rust Trait | 用途 |
|------------|------|
| `PreSendHook` | 发信前校验与草稿补充 |
| `PostSendHook` | 发送成功后旁路 |
| `DeliveryHook` | 送达旁路 |
| `RecallHook` | 撤回校验/审计 |
| `MessageReadHook` | 已读观测 |
| `MessageReactionHook` | 消息表态 |
| `ConversationLifecycleHook` | 会话生命周期 |
| `ConversationMemberHook` | 会话成员变更 |

当前 `hooks.toml` 示例覆盖消息主链的 `pre_send/post_send/delivery/recall`。`message_read`、`message_reaction`、`conversation_lifecycle`、`conversation_member` 已作为 Rust trait、registry 执行入口和 proto operation 暴露，具体服务在对应触发点接入 registry 或 HookPlugin 调用即可，不需要业务代码侵入 Core 领域层。

## 4. 消息 Hook

### 4.1 `pre_send`

适合：

- 好友关系、拉黑、陌生人策略。
- 群成员、群禁言、群解散状态。
- 内容安全、敏感词、反垃圾。
- 补充 `metadata`，例如业务场景、风控标签、审计等级。

禁止：

- 依赖最终 `seq`。
- 写入消息事实表。
- 长耗时外呼。

拒绝约定：

```protobuf
message PreSendHookResponse {
  bool allow = 1;
  HookMessageDraft draft = 2;
  string deny_reason_code = 3;
  string deny_reason_message = 4;
}
```

`allow = false` 是业务拒绝；`GenericResponse.ok = false` 是插件执行失败。

### 4.2 `post_send`

适合：

- 审计、归档、BI。
- 推荐/增长特征。
- 计费明细。
- 发信成功后的业务通知。

禁止：

- 再决定是否允许发送。
- 反向修改消息内容和 seq。

幂等键建议：`message_id + operation`。

### 4.3 `delivery`

适合：

- websocket / push / apns / fcm 通道效果分析。
- 端类型、网络质量、区域质量统计。
- 大促或运营消息送达监控。

该 Hook 高 QPS，建议采样、批量、异步写入。

### 4.4 `recall`

适合：

- 管理员撤回、群主撤回、时间窗口校验。
- 撤回审计和合规留痕。

权限类可以 fail-fast；审计类应 fail-open。

### 4.5 `message_read`

适合：

- 已读率、未读收敛分析。
- 重要消息阅读审计。
- 群公告/工单已读统计。

禁止：

- 修改用户游标。
- 重新计算未读权威状态。

### 4.6 `message_reaction`

适合：

- emoji / 点赞 / 业务表态白名单。
- 互动统计、推荐特征。
- 禁止对特定消息类型表态。

如果只是统计，使用 fail-open；如果要拒绝非法 reaction，使用 fail-fast 并返回结构化拒绝码。

## 5. 会话 Hook

### 5.1 `conversation_lifecycle`

触发：

- 会话创建/ensure。
- 名称、头像、公告、置顶、免打扰等资料变更。
- 归档、解散、恢复、冻结。

适合：

- 同步业务目录。
- 触发系统消息。
- 审计群生命周期。
- 对外部 CRM / 工单 / 项目空间做投影。

原则：会话生命周期事实应先由 Core 或业务权威方提交，再通过 Hook / Probe 通知外部系统；旁路失败不能回滚已提交事实。

### 5.2 `conversation_member`

触发：

- 邀请、入群、退群、踢人。
- 角色变更。
- 禁言/解除禁言。
- 成员黑名单/访问控制变化。

适合：

- 成员变更审计。
- `participant_version` 变化通知。
- 业务侧成员画像、推荐、风控特征。
- 外部群目录投影。

如果成员变更由 Social 发起，Social 是业务权威；Core 只投影参与者和 `participant_version`。如果成员变更由 Core 管理 API 发起，业务 Hook 可作为策略门禁或旁路通知。

## 6. Context 与安全

Hook 调用必须同时依赖 gRPC metadata 和 `HookInvocationContext`：

| 字段 | 位置 | 说明 |
|------|------|------|
| `x-trace-id` | gRPC metadata | 链路追踪 |
| `x-request-id` | gRPC metadata | 请求级幂等，不等于 `message_id` |
| `x-tenant-id` | gRPC metadata | 租户隔离，优先级高于 body |
| `x-user-id` | gRPC metadata | 可信操作用户 |
| `request_id` | `GenericRequest` | Hook 调用幂等 |
| `operation` | `GenericRequest` | Hook 类型 |
| `payload.type_url` | `Any` | 具体请求类型 |

安全规则：

- body 中的 `operator_user_id` 只能作为业务上下文，不能替代认证。
- `tenant_id` 冲突时以 metadata / token claims 为准，并记录告警。
- 所有可重试 Hook 必须支持幂等。
- Hook 不应保存明文 token、密钥、完整设备指纹等敏感数据。

## 7. 配置建议

消息主链基础配置见 `flare-im-core/config/hooks.business.example.toml`。

Social 发信权限配置见 `flare-im-core/config/hooks.social.example.toml`。

推荐优先级：

| 优先级 | Hook |
|--------|------|
| `>= 100` | 强门禁：关系、群权限、内容风控 |
| `50-99` | 可选门禁：reaction 白名单、撤回权限补充 |
| `< 50` | 旁路：审计、BI、推荐、计费 |

推荐超时：

| Hook | 超时 |
|------|------|
| `pre_send` | 100-500ms |
| `recall` | 100-500ms |
| `post_send` | 300-1000ms |
| `delivery` / `message_read` | 50-300ms |
| `conversation_lifecycle` / `conversation_member` | 300-1000ms |

## 8. 实现 Checklist

1. 实现 `HookPlugin.Call`。
2. 按 `operation` 分发到具体 handler。
3. 解码 `Any` 为具体 `*HookRequest`。
4. 从 metadata 还原可信 `tenant_id/user_id/request_id/trace_id`。
5. 按业务语义返回具体 `*HookResponse`。
6. 对 `request_id + operation` 或 `message_id + operation` 做幂等。
7. 结构化记录拒绝码、耗时、超时、重试次数。
8. 注册服务发现名或配置直连 endpoint。

参考实现：`flare-social/flare-social-hook`。

## 9. Hook 与 Business Probe 的选择

| 需求 | 推荐 |
|------|------|
| 要拒绝发送 | `pre_send` |
| 要拒绝非法 reaction | `message_reaction` |
| 要补充撤回权限 | `recall` |
| 只做审计/BI/计费 | Business Probe 或 `post_send` |
| 只观察成员变化 | Business Probe 或 `conversation_member` |
| 需要调用业务能力并返回结果 | `CapabilityService.Dispatch` |

详细埋点模型见 [10-business-instrumentation.md](./10-business-instrumentation.md)。
