初始提交: Qihu-AIFlow v2.0.1 完整项目

- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
This commit is contained in:
qyyfw committed 2026-07-02 14:40:46 +08:00
commit 78ff261b5a
199 files changed
+69126

No files matched your search

@@ -0,0 +1,498 @@
# 测试用例 — AstrBot频道优化(P0-P2 全部完成)
**测试日期**: 2026-06-01
**测试工程师**: 测试工程师
**测试依据**: 项目负责人指定的AstrBot频道优化需求 + 前后端代码分析
**需求范围**: 统一Webhook端点(6平台)、消息组件系统(5种组件)、平台注册机制(6个适配器)、MessageSession统一标识、Pipeline管道(5 Stage)、Redis事件总线、二维码localhost根因修复(config.py三级防护)
---
## 1. 需求依据
- PRD 路径: `/data/share/project/docs/PRD.md`
- 本轮优化范围(项目负责人指定):
1. 统一Webhook端点 — 为6个IM平台(企业微信/飞书/钉钉/Telegram/Slack/WhatsApp)提供统一的 `/webhook/{platform}` 入口
2. 消息组件系统 — 5种消息组件(Plain/Image/At/Reply/File),支持序列化/反序列化
3. 平台注册机制 — 使用 `@register_platform_adapter` 装饰器标准化注册6个适配器
4. MessageSession统一会话标识 — `platform:type:session_id` 格式
5. Pipeline管道 — 5 Stage 链式处理(WakingCheck→RateLimit→ContentSafety→Process→Respond)
6. Redis事件总线 — 基于Redis List的生产者-消费者消息队列
7. 二维码localhost根因修复 — `config.py` 三级防护(环境变量→.env→硬编码域名)
---
## 2. 代码功能模块概览
| 模块 | 职责 | 代码位置 |
|------|------|---------|
| Config配置模块 | APP_ACCESS_URL三级防护,全局配置管理 | `app/config.py:36-63` |
| Pipeline管道系统 | 5 Stage链式消息处理,PipelineScheduler调度器 | `app/pipeline.py` (349行) |
| Redis事件总线 | MessageEvent序列化,生产者-消费者模式 | `app/event_bus.py` (293行) |
| 消息组件系统 | 5种组件(Plain/Image/At/Reply/File),序列化工厂 | `app/message_components.py` (340行) |
| 平台注册机制 | @register_platform_adapter装饰器,全局注册表 | `app/platform_adapter.py` (126行) |
| 6个平台适配器 | wecom/feishu/dingtalk/telegram/slack/whatsapp | `app/platform_adapters/*.py` |
| MessageSession | 统一会话标识 platform:type:session_id | `app/message_session.py` (55行) |
| 统一Webhook端点 | POST /webhook/{platform},适配器+EventBus集成 | `app/api/channels.py:1453-1579` |
| 频道QR二维码 | 微信ClawBot API优先,本地qrcode库回退 | `app/api/channels.py:420-498` |
| Webhook平台列表 | GET /webhook/platforms 列出所有支持平台 | `app/api/channels.py:1582-1597` |
---
## 3. 需求与代码差异说明
### 3.1 PRD中未在代码中体现的内容
无。本轮优化的7个功能点在代码中均已有完整实现。
### 3.2 PRD中未覆盖但代码中存在的功能模块
| 编号 | 模块名称 | 代码位置 | 建议 |
|------|---------|---------|------|
| N1 | 旧版webhook处理函数(`_handle_*_webhook`) | `app/api/channels.py:1600-1918` | 与适配器模式重复,本次已统一到适配器架构 |
| N2 | Pipeline单元测试(自测代码) | `app/pipeline.py:202-347` | 内建9个单元测试,建议产品确认纳入CI |
| N3 | EventBus单元测试(自测代码) | `app/event_bus.py:182-292` | 内建7个单元测试,建议产品确认纳入CI |
| N4 | 频道消息组件与webhook解析的桥接 | 跨文件 | 适配器 `parse_webhook()` 返回dict而非BaseMessageComponent链,存在集成断点 |
---
## 4. 测试用例
### 4.1 统一Webhook端点 (Q1)
#### TC-WH-01 [P0] 验证统一Webhook端点存在且路径正确
- **前置条件**: 后端服务运行在18080端口
- **步骤**:
[1] 向 `POST /app/api/channels/webhook/{platform}` 各平台发送请求
[2] 验证返回404之外的HTTP状态码(400表示平台不支持但端点存在)
[3] 逐个平台验证:wecom/feishu/dingtalk/telegram/slack/whatsapp
- **预期结果**: 所有6个平台的webhook端点均可访问(返回400"不支持的平台"或正常响应)
- **备注**: 请求体为空时,FastAPI可能返回422(Body缺失),这也是端点存在的证明
#### TC-WH-02 [P0] 验证Webhook端点消息接收和记录
- **前置条件**: 已有活跃的微信频道
- **步骤**:
[1] 构造合法的微信平台webhook消息payload
[2] 发送POST到 `/app/api/channels/webhook/wecom`
[3] 验证返回 `code: 0` 且 `data.handled=true`
[4] 验证返回包含 `message_id`、`session` 和 `platform` 字段
[5] 检查数据库中 `channel_messages` 表存在对应记录
- **预期结果**: 消息成功接收并写入数据库;返回标准webhook响应格式
- **备注**: 验证从webhook入站到数据库记录的完整链路
#### TC-WH-03 [P0] 验证Webhook平台验证(飞书challenge/ Slack url_verification)
- **前置条件**: 后端服务运行
- **步骤**:
[1] 向 `/webhook/feishu` 发送 `{"challenge": "test_challenge_123"}` 的POST请求
[2] 验证返回 `{"challenge": "test_challenge_123"}`
[3] 向 `/webhook/slack` 发送 `{"type": "url_verification", "challenge": "slack_challenge_456"}`
[4] 验证返回 `{"challenge": "slack_challenge_456"}`
- **预期结果**: 飞书和Slack的URL验证正确返回challenge值
- **备注**: URL验证是各平台接入的第一步,必须正确处理
#### TC-WH-04 [P1] 验证Webhook对未注册平台的拒绝
- **前置条件**: 后端服务运行
- **步骤**:
[1] 向 `/webhook/unknown_platform` 发送POST请求
[2] 验证返回400状态码
[3] 验证错误消息包含"不支持的平台"和可用平台列表
- **预期结果**: 未注册平台被正确拒绝,返回明确错误
#### TC-WH-05 [P1] 验证无活跃频道时Webhook行为
- **前置条件**: 所有wecom频道已断开或删除
- **步骤**:
[1] 向 `/webhook/wecom` 发送消息payload
[2] 验证返回 `code: 0` 且 `data.handled=false`
[3] 验证消息包含"无活跃的wecom频道"
- **预期结果**: 优雅处理无活跃频道的情况,不报500错误
#### TC-WH-06 [P0] 验证Webhook支持平台列表接口
- **前置条件**: 6个适配器已注册
- **步骤**:
[1] 发送GET请求到 `/app/api/channels/webhook/platforms`
[2] 验证返回 `code: 0`
[3] 验证 `data.platforms` 包含6个平台条目
[4] 验证每个平台条目包含name/display_name/description/support能力字段
- **预期结果**: 返回完整的6个平台元数据
---
### 4.2 消息组件系统 (Q2)
#### TC-MC-01 [P0] 验证Plain文本组件序列化/反序列化
- **前置条件**: 消息组件系统代码可导入
- **步骤**:
[1] 创建 `Plain(text="你好世界")`
[2] 调用 `to_dict()` 序列化
[3] 验证返回 `{"type": "plain", "text": "你好世界"}`
[4] 调用 `Plain.from_dict(serialized)` 反序列化
[5] 验证 `plain_obj.text == "你好世界"`
- **预期结果**: Plain组件序列化/反序列化正确
#### TC-MC-02 [P0] 验证Image图片组件序列化/反序列化
- **前置条件**: 同上
- **步骤**:
[1] 创建 `Image(url="https://example.com/img.png", width=800, height=600)`
[2] 调用 `to_dict()` 验证url/width/height字段
[3] 调用 `Image.from_dict()` 反序列化验证
- **预期结果**: Image组件序列化/反序列化正确
#### TC-MC-03 [P0] 验证At提及组件序列化/反序列化
- **前置条件**: 同上
- **步骤**:
[1] 创建 `At(user_id="user_001", display_name="张三")`
[2] 序列化验证user_id和display_name
[3] 反序列化验证
[4] 测试不带display_name的场景
- **预期结果**: At组件正确处理user_id和可选的display_name
#### TC-MC-04 [P0] 验证Reply回复组件序列化/反序列化
- **前置条件**: 同上
- **步骤**:
[1] 创建 `Reply(message_id="msg_001", content_preview="原消息预览")`
[2] 序列化验证message_id和content_preview
[3] 反序列化验证
- **预期结果**: Reply组件序列化/反序列化正确
#### TC-MC-05 [P0] 验证File文件组件序列化/反序列化
- **前置条件**: 同上
- **步骤**:
[1] 创建 `File(filename="report.pdf", url="https://example.com/report.pdf", size=102400)`
[2] 序列化验证filename/url/size
[3] 验证不带size时的序列化
- **预期结果**: File组件序列化/反序列化正确
#### TC-MC-06 [P0] 验证消息链整体序列化/反序列化 (message_chain_to_dict/from_dict)
- **前置条件**: 同上
- **步骤**:
[1] 构建复合消息链: `[Reply(...), Plain(...), At(...), Image(...)]`
[2] 调用 `message_chain_to_dict(chain)` 生成字典列表
[3] 调用 `message_chain_from_dict(dict_list)` 恢复组件链
[4] 验证每个组件的类型和关键字段正确
- **预期结果**: 完整消息链循环序列化正确
#### TC-MC-07 [P1] 验证辅助提取函数
- **前置条件**: 同上
- **步骤**:
[1] 构建包含多个组件的消息链
[2] 测试 `extract_plain_text()` 正确拼接所有Plain文本
[3] 测试 `extract_image_urls()` 提取所有图片URL
[4] 测试 `extract_at_user_ids()` 提取所有@用户ID
[5] 测试 `has_component_type()` 对存在和不存在的类型
- **预期结果**: 所有辅助提取函数正确
#### TC-MC-08 [P1] 验证未知组件类型降级处理
- **前置条件**: 同上
- **步骤**:
[1] 构造 `[{"type": "unknown_type", "data": "test"}]` 传入 `message_chain_from_dict`
[2] 验证返回结果为Plain组件,text为原始dict的字符串表示
- **预期结果**: 未知类型优雅降级,不抛异常
---
### 4.3 平台注册机制 (Q3)
#### TC-PR-01 [P0] 验证6个适配器均已正确注册
- **前置条件**: 6个适配器文件已导入
- **步骤**:
[1] 从 `platform_adapter` 导入 `platform_registry` 和 `platform_cls_map`
[2] 验证 `len(platform_registry) == 6`
[3] 验证注册表包含所有6个平台: `{"wecom", "feishu", "dingtalk", "telegram", "slack", "whatsapp"}`
[4] 验证 `platform_cls_map` 中的每个类都是 `BasePlatformAdapter` 的子类
- **预期结果**: 6个适配器全部正确注册,name和class映射完整
#### TC-PR-02 [P0] 验证平台适配器工厂函数
- **前置条件**: 适配器已注册
- **步骤**:
[1] 对每个平台调用 `get_platform_adapter(name, config={})`
[2] 验证返回的实例是其对应适配器类的实例
[3] 验证每个适配器 `get_status()` 返回 "stopped"
[4] 调用 `start()` 后验证 `get_status()` 返回 "running"
- **预期结果**: 工厂函数正确创建适配器实例,状态管理正常
#### TC-PR-03 [P0] 验证平台元数据完整性
- **前置条件**: 适配器已注册
- **步骤**:
[1] 对每个平台获取 `platform_registry[name]`
[2] 验证元数据包含: name/display_name/description/icon/version
[3] 验证能力标记: support_streaming/support_image/support_file/support_voice
[4] 验证不同平台的能力标记符合实际情况: 飞书streaming=true, 企业微信streaming=false等
- **预期结果**: 所有平台元数据字段完整正确
#### TC-PR-04 [P0] 验证list_platforms API
- **前置条件**: 适配器已注册
- **步骤**:
[1] 调用 `list_platforms()`
[2] 验证返回6个PlatformMetadata实例
[3] 验证每个实例的name字段唯一且非空
- **预期结果**: 列出所有已注册平台
#### TC-PR-05 [P1] 验证未注册平台的错误处理
- **前置条件**: 适配器已注册
- **步骤**:
[1] 调用 `get_platform_adapter("unknown_platform", {})`
[2] 验证抛出 `ValueError` 异常
[3] 验证异常消息包含"未知平台"和可用平台列表
- **预期结果**: 未注册平台被正确拒绝,有清晰错误提示
#### TC-PR-06 [P1] 验证装饰器中非BasePlatformAdapter子类的拒绝
- **前置条件**: 适配器代码
- **步骤**:
[1] 确认代码中 `register_platform_adapter` 装饰器的类型检查逻辑存在
[2] 验证 `issubclass(cls, BasePlatformAdapter)` 检查在第93行
- **预期结果**: 装饰器强制类型约束,非适配器子类会抛出TypeError
---
### 4.4 MessageSession统一会话标识 (Q4)
#### TC-MS-01 [P0] 验证MessageSession创建和字符串化
- **前置条件**: message_session模块可用
- **步骤**:
[1] 创建 `MessageSession(platform_name="wecom", session_type=SessionType.GROUP, session_id="group_123")`
[2] 验证 `unified_msg_origin` 返回 `"wecom:group:group_123"`
[3] 创建 `MessageSession(platform_name="feishu", session_type=SessionType.FRIEND, session_id="user_456")`
[4] 验证 `unified_msg_origin` 返回 `"feishu:friend:user_456"`
- **预期结果**: 统一会话标识格式化为 `platform:type:session_id`
#### TC-MS-02 [P0] 验证from_str解析
- **前置条件**: 同上
- **步骤**:
[1] 调用 `MessageSession.from_str("telegram:friend:user_789")`
[2] 验证 platform_name="telegram", session_type=SessionType.FRIEND, session_id="user_789"
[3] 调用 `MessageSession.from_str("slack:channel:ch_001")`
[4] 验证 session_type=SessionType.CHANNEL
- **预期结果**: 正确从字符串解析,所有SessionType枚举值均支持
#### TC-MS-03 [P0] 验证from_channel_event自动推断
- **前置条件**: 同上
- **步骤**:
[1] 有group_id时: `from_channel_event("wecom", "ch_001", "user_001", group_id="grp_001")`
[2] 验证返回GROUP类型,session_id="grp_001"
[3] 无group_id时: `from_channel_event("feishu", "ch_002", "user_002")`
[4] 验证返回FRIEND类型,session_id="user_002"
- **预期结果**: 自动根据group_id判断会话类型
#### TC-MS-04 [P1] 验证hashable/equality行为
- **前置条件**: 同上
- **步骤**:
[1] 创建 `ms1 = MessageSession("wecom", SessionType.FRIEND, "user_001")`
[2] 创建 `ms2 = MessageSession("wecom", SessionType.FRIEND, "user_001")`
[3] 验证 `ms1 == ms2` 为True
[4] 验证 `hash(ms1) == hash(ms2)`
[5] 验证可以放入set去重: `len({ms1, ms2}) == 1`
- **预期结果**: MessageSession支持相等比较和哈希,可用作集合元素
#### TC-MS-05 [P1] 验证from_str格式错误处理
- **前置条件**: 同上
- **步骤**:
[1] 调用 `MessageSession.from_str("invalid_format")`
[2] 验证抛出 `ValueError` 异常
[3] 验证异常消息包含正确的格式说明
- **预期结果**: 格式错误的字符串被正确拒绝
---
### 4.5 Pipeline管道系统 (Q5)
#### TC-PL-01 [P0] 验证Pipeline默认5阶段按顺序执行
- **前置条件**: pipeline模块可用
- **步骤**:
[1] 创建 `PipelineScheduler()` 默认管道
[2] 构造完整上下文(含message/platform/sender_id/is_private等)
[3] 调用 `pipeline.execute(context)`
[4] 验证返回 `success=True`
[5] 验证 `pipeline_log` 包含5条记录,顺序为: waking_check → rate_limit_check → content_safety_check → process → respond
- **预期结果**: 5个阶段按正确顺序执行,pipeline_log记录完整
#### TC-PL-02 [P0] 验证WakingCheck阶段:私聊自动唤醒
- **前置条件**: 同上
- **步骤**:
[1] 创建私聊场景: `is_private=True`
[2] 执行WakingCheckStage.process()
[3] 验证 `should_respond=True`, `stop=False`
- **预期结果**: 私聊消息自动唤醒AI响应
#### TC-PL-03 [P0] 验证WakingCheck阶段:群聊@bot唤醒
- **前置条件**: 同上
- **步骤**:
[1] 创建群聊场景: `is_private=False`, message="@bot 帮我查询"
[2] 执行WakingCheckStage.process()
[3] 验证 `should_respond=True`, `stop=False`
- **预期结果**: 群聊中@bot后唤醒AI
#### TC-PL-04 [P0] 验证WakingCheck阶段:群聊不@bot则停止
- **前置条件**: 同上
- **步骤**:
[1] 创建群聊场景: `is_private=False`, message="今天天气不错"
[2] 执行WakingCheckStage.process()
[3] 验证 `should_respond=False`, `stop=True`
- **预期结果**: 群聊中未@bot时停止管道
#### TC-PL-05 [P0] 验证RateLimit阶段:限流生效
- **前置条件**: 同上
- **步骤**:
[1] 设置 `RateLimitCheckStage(max_per_minute=3)`
[2] 同一sender_id连续发送4次
[3] 验证前3次返回success=True
[4] 验证第4次返回success=False, error含"频繁", stop=True
- **预期结果**: 超过限制后被正确限流
#### TC-PL-06 [P1] 验证ContentSafety阶段:正常内容通过
- **前置条件**: 同上
- **步骤**:
[1] 发送内容为"正常的聊天内容"
[2] 验证success=True, stop=False
- **预期结果**: 正常内容不受影响
#### TC-PL-07 [P1] 验证Pipeline阶段异常处理
- **前置条件**: 同上
- **步骤**:
[1] 创建抛出异常的BrokenStage
[2] 执行PipelineScheduler(stages=[BrokenStage()])
[3] 验证返回success=False
[4] 验证error消息包含阶段名和异常信息
- **预期结果**: stage异常被正确捕获,返回结构化错误
#### TC-PL-08 [P1] 验证Pipeline阶段动态增删
- **前置条件**: 同上
- **步骤**:
[1] 创建空stages的管道
[2] 调用 `add_stage()` 添加WakingCheckStage
[3] 调用 `add_stage()` 插入到位置0
[4] 验证阶段顺序正确
[5] 调用 `remove_stage("waking_check")` 移除
[6] 验证移除后管道阶段列表正确
- **预期结果**: 动态增删阶段正常
---
### 4.6 Redis事件总线 (Q6)
#### TC-EB-01 [P0] 验证MessageEvent创建和序列化
- **前置条件**: Redis可用
- **步骤**:
[1] 创建MessageEvent(platform="feishu", channel_id="ch_001", content="测试", sender_id="user_001")
[2] 调用 `to_dict()` 序列化
[3] 验证返回字典包含所有9个字段
[4] 验证timestamp字段存在且为整数
- **预期结果**: MessageEvent序列化包含完整字段,含自动生成的timestamp
#### TC-EB-02 [P0] 验证MessageEvent反序列化
- **前置条件**: 同上
- **步骤**:
[1] 从to_dict结果调用 `MessageEvent.from_dict(dict_data)`
[2] 验证所有字段完全恢复
[3] 特别验证platform/channel_id/content/sender_id正确
- **预期结果**: 序列化→反序列化循环后数据一致
#### TC-EB-03 [P0] 验证RedisEventBus事件发布
- **前置条件**: Redis可用
- **步骤**:
[1] 创建RedisEventBus实例
[2] 创建MessageEvent并调用 `bus.publish(event)`
[3] 验证返回 `True`(发布成功)
[4] 调用 `bus.queue_size()` 验证队列长度 ≥1
- **预期结果**: 事件成功写入Redis List
#### TC-EB-04 [P0] 验证RedisEventBus事件消费
- **前置条件**: Redis可用
- **步骤**:
[1] 创建RedisEventBus并启动1个消费者
[2] 注册feishu平台的handler
[3] 发布一个feishu平台事件
[4] 等待1秒后验证handler被调用
[5] 验证handler接收到的事件字段正确
[6] 调用 `bus.stop()` 停止消费者
- **预期结果**: 事件被正确消费并分发给对应平台handler
#### TC-EB-05 [P0] 验证全局单例
- **前置条件**: 同上
- **步骤**:
[1] 调用 `get_event_bus()` 两次
[2] 验证两个引用指向同一个实例(`bus1 is bus2`)
- **预期结果**: 全局单例正确
#### TC-EB-06 [P1] 验证订阅通配符 `*`
- **前置条件**: Redis可用
- **步骤**:
[1] 注册 `*` 通配符handler
[2] 发布不同平台(feishu/wecom)的事件
[3] 验证通配符handler都能接收到
[4] 清理
- **预期结果**: 通配符订阅接收所有平台事件
#### TC-EB-07 [P1] 验证多消费者并发
- **前置条件**: Redis可用
- **步骤**:
[1] 启动2个消费者线程
[2] 发布多个事件
[3] 验证所有事件被消费,不重复不丢失
[4] 停止消费者
- **预期结果**: 多消费者并发正常工作
#### TC-EB-08 [P1] 验证Redis队列清理
- **前置条件**: Redis可用
- **步骤**:
[1] 清理测试key: `DEL aiflow:event_queue`
[2] 验证 `queue_size() == 0`
- **预期结果**: 队列清理后为空
---
### 4.7 二维码URL非localhost (Q7)
#### TC-QR-01 [P0] 验证config.py三级防护机制存在
- **前置条件**: config.py可读取
- **步骤**:
[1] 检查 `APP_ACCESS_URL` 赋值逻辑
[2] 验证第40-41行:首选环境变量 `os.getenv("APP_ACCESS_URL")`
[3] 验证第43-58行:临时域名检测(`team-coding`/`localhost`/`127.0.0.1`/`0.0.0.0`),检测到后从.env覆盖
[4] 验证第61-63行:最终兜底为 `https://bot.qihuyun.com`
- **预期结果**: 三级防护完整:环境变量→.env→硬编码正式域名
#### TC-QR-02 [P0] 验证二维码生成API返回非localhost的URL
- **前置条件**: 频道已创建
- **步骤**:
[1] 调用 `POST /app/api/channels/authorize/scan?platform=wechat` 生成二维码
[2] 验证返回的 `qr_url` 字段存在(base64 data URI)
[3] 通过API调用确认绑定URL使用 `APP_ACCESS_URL`,非localhost
[4] 验证API响应中的二维码图片不编码localhost地址
- **预期结果**: 二维码包含生产地址,不含localhost
#### TC-QR-03 [P1] 验证临时域名检测模式列表
- **前置条件**: config.py可读取
- **步骤**:
[1] 验证 `_TEMP_DOMAIN_PATTERNS` 包含: "team-coding", "localhost", "127.0.0.1", "0.0.0.0"
[2] 测试 "http://s-3686137677310083075-773.team-coding:8080" → 应被识别为临时
[3] 测试 "https://bot.qihuyun.com" → 不应被识别为临时
[4] 测试 "http://localhost:8080" → 应被识别为临时
- **预期结果**: 临时域名检测准确
#### TC-QR-04 [P1] 验证`.env`文件不存在的容错
- **前置条件**: 模拟场景
- **步骤**:
[1] 确认代码中 `.env` 读取在try-except块中
[2] 验证当 `.env` 不存在或读取失败时不抛异常
[3] 验证最终仍走到兜底域名
- **预期结果**: .env缺失不会导致启动失败
---
## 5. 测试用例统计
| 模块 | P0用例 | P1用例 | 合计 |
|------|--------|--------|------|
| Q1: 统一Webhook端点 | 4 | 2 | 6 |
| Q2: 消息组件系统 | 6 | 2 | 8 |
| Q3: 平台注册机制 | 4 | 2 | 6 |
| Q4: MessageSession | 3 | 2 | 5 |
| Q5: Pipeline管道 | 5 | 3 | 8 |
| Q6: Redis事件总线 | 5 | 3 | 8 |
| Q7: 二维码URL | 2 | 2 | 4 |
| **合计** | **29** | **16** | **45** |
---
> **文档状态**: 已完成
> **创建日期**: 2026-06-01
> **基于**: 后端代码功能分析 + 项目负责人指定优化范围