# 消息路由编号原理

## 一、四层编号体系

| 层级 | 编号类型 | 格式 | 用途 |
|------|----------|------|------|
| 外部平台 | `external_id` | QQ号 / TG user_id | 外部账号标识 |
| 内部用户 | `internal_user` | 用户目录名 | 映射到 `users/<name>/` |
| 推送队列 | `push_id` | `push_{uuid12}` | 异步推送任务追踪 |
| API 对账 | `echo` | `{action}_{uuid}` | OneBot 请求-响应配对 |

## 二、外部 → 内部：身份映射

### 映射链路

```
外部消息到达 → 提取 platform + external_id
  │
  ▼
IdentityStore.resolve_onebot / resolve_telegram
  │
  │  查找顺序（三级回退）:
  │    1. identity_map.json → mappings["qq:12345"]
  │    2. identity_map.json → mappings["onebot:12345"]
  │    3. config.json → platforms.onebot.bound_users["12345"]
  │
  ▼
返回 identity dict:
  {
    "platform": "onebot",
    "external_id": "12345",
    "internal_user": "username",
    "display_name": "username",
    "role": "admin" | "user"
  }
```

### 配置文件

```
message/identity/identity_map.json     ← 高级映射（可选）
{
  "mappings": {
    "qq:12345": {"internal_user": "username", "display_name": "显示名"},
    "tg:67890": {"internal_user": "username"}
  }
}

message/config.json                    ← 简化映射（部署友好）
{
  "platforms": {
    "onebot": {"bound_users": {"12345": "username"}},
    "telegram": {"bound_users": {"67890": "username"}}
  }
}
```

### 查找优先级

```
resolve_onebot(user_id="12345"):
  ① identity_map.json → "qq:12345"
  ② identity_map.json → "onebot:12345"
  ③ identity_map.json → "12345"
  ④ config.json → bound_users["qq:12345"]
  ⑤ config.json → bound_users["onebot:12345"]
  ⑥ config.json → bound_users["12345"]
  ⑦ 全部未命中 → 返回 None → 消息被丢弃
```

## 三、消息路由完整流程

### OneBot (QQ/NapCat)

```
NapCat WebSocket 事件
  │
  ▼
_handle_raw(raw)
  ├─ echo 字段存在 → API 响应，匹配 _pending[echo]
  ├─ post_type == "meta_event" → 忽略
  └─ post_type == "message" → _handle_message_event(msg)
       │
       ▼
  identity = resolve_onebot(user_id) → 未映射则丢弃
       │
       ▼
  _check_access() → 权限检查（群聊需 @机器人等）
       │
       ▼
  构建 source dict:
  {"platform":"onebot", "external_id":"12345", "chat_type":"private|group",
   "chat_id":"12345|67890", "message_id":123456, "group_id":"67890|""}
       │
       ▼
  下载附件 → 保存到 users/<name>/history/file/
       │
       ▼
  is_command? → /cron, /plan → _handle_command()
  否 → agent.chat(username, text, source, attachments)
       │
       ▼
  _reply() → 按 max_message_length 分片 → send_private_msg / send_group_msg
```

### Telegram

```
Telegram getUpdates 长轮询
  │
  ▼
_handle_update(update)
  ├─ "message" → _handle_message(msg)
  ├─ "edited_message" → 暂时忽略
  └─ "callback_query" → _handle_callback_query(cq)
       │
       ▼
  identity = resolve_telegram(user_id, chat_id)
       │
       ▼
  _check_access() → 权限检查
       │
       ▼
  构建 source dict:
  {"platform":"telegram", "external_id":"67890", "chat_type":"private|group|supergroup",
   "chat_id":"67890", "message_id":42, "group_id":"67890|""}
       │
       ▼
  下载附件 → getFile → Telegram CDN → 保存到 users/<name>/history/file/
       │
       ▼
  agent.chat() 或 _handle_command()
       │
       ▼
  _reply() → sendMessage (分片 + 速率限制)
```

## 四、推送队列编号

### ID 生成

```python
task_id = f"push_{uuid.uuid4().hex[:12]}"
# 例如: push_a1b2c3d4e5f6
```

### 文件存储

```
message/push_queue/
├── push_a1b2c3d4e5f6.json    ← pending
├── push_7890abcdef12.json    ← completed
└── push_deadbeef0000.json    ← failed
```

### 状态机

```
enqueue() → pending
  ├─ dispatch_push() 成功 → completed
  └─ dispatch_push() 失败 → attempts++
       ├─ attempts < retry_times → 仍 pending，下次重试
       └─ attempts >= retry_times → failed
```

## 五、OneBot Echo 对账

### 问题
NapCat 正向 WebSocket 全双工：事件接收和 API 调用共用同一连接。

### 解决方案

```python
# 发送
echo = f"{action}_{uuid.uuid4().hex}"
fut = loop.create_future()
self._pending[echo] = fut
await ws.send({"action": action, "params": params, "echo": echo})
response = await asyncio.wait_for(fut, timeout=api_timeout)

# 接收
echo = msg.get("echo")
if echo and echo in self._pending:
    self._pending.pop(echo).set_result(msg)  # 解除 await
else:
    _handle_message_event(msg)  # 普通事件
```

## 六、Source 标记与闭环

每条外部消息携带 `source` 贯穿整个处理链：

```
外部消息 → 构建 source → AgentService.chat() 注入前缀
  → 定时任务创建时携带 source → tasks/<id>.json 保存 source
  → 任务完成 → _enqueue_task_result() 读取 source
  → 推送回原平台原会话
```

## 七、完整编号关系图

```
QQ用户(QQ号:12345)  TG用户(TG ID:67890)
       │                    │
       ▼                    ▼
  OneBot Router       Telegram Router
       │                    │
       ▼                    ▼
      IdentityStore (external_id → internal_user)
              │
              ▼
      AgentService (username → users/<name>/)
              │
              ▼
      PushQueue (push_id → platform + chat_id → 路由)
```
