目标: 将 QQ 机器人接入 CatClaw 系统,实现通过 QQ 与 AI 助手对话
时间: 2026-06-14
最终状态: ✅ 成功
🚀 阶段一:基础配置
- 1 环境配置
配置文件 (.env):
CHANNEL_QQ_ENABLED=true
CHANNEL_QQ_BOT_ID=your_bot_id
CHANNEL_QQ_BOT_SECRET=yout_bot_secret
CHANNEL_QQ_SANDBOX=false
🌐 阶段二:Webhook 接口问题
(解决了很久,气死我了,qqBot开发者界面要完善个人信息才能进入 高级开发的界面,而完善信息的入口很难找到)
- 1 测试 Webhook 接口
测试命令:
curl -X POST http://localhost:8000/webhook/qq \
-H "Content-Type: application/json" \
-d '{"test": true}'
遇到的问题:
- ❌ 返回 Internal Server Error (500)
错误日志:
TypeError: cannot pickle '_thread.RLock' object
根本原因:
- FastAPI 在解析 webhook 端点时,尝试 deepcopy 某个包含线程锁的对象
- 闭包变量覆盖问题
解决方案: 使用工厂函数
修改前
for path, handler in self._webhooks.items():
@app.post(path)
async def webhook_endpoint(request_data: dict, _handler=handler):
...
修改后
for path, handler in self._webhooks.items():
def create_webhook_endpoint(h):
async def webhook_endpoint(request_data: dict):
try:
result = await h(request_data)
return result
except Exception as e:
return {"code": -1, "message": str(e)}
return webhook_endpoint
app.post(path)(create_webhook_endpoint(handler))
结果: ✅ 返回 {"code":0,"message":"ok"}
🔒 阶段三:HTTPS 配置
(回调地址必须要HTTPS加密,不过出于安全也合理)
- 1 域名准备
服务器: 阿里云 ECS (47.96.188.112)
- 2 Nginx 配置
创建配置文件:
/etc/nginx/sites-available/catclaw
server {
listen 80;
server_name catclaw.bujingxianshi.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
启用配置:
ln -s /etc/nginx/sites-available/catclaw /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx
- 3 SSL 证书
使用 Let's Encrypt:
apt install certbot python3-certbot-nginx
certbot --nginx -d catclaw.bujingxianshi.com
遇到的问题:
- ❌ 无法访问 HTTPS 地址
- 🔍 原因:防火墙没有开放 443 端口
解决方案:
ufw allow 443/tcp
ufw reload
结果: ✅ HTTPS 正常工作
📱 阶段四:QQ 开放平台配置
(这个是第二坑的,藏在犄角旮旯里的)
- 1 查找配置位置
问题: 找不到「消息接收」配置
官方文档说明:
- 配置位置:「事件订阅与通知」页面
- 需要选择「Webhook」接入方式
- 必须使用 HTTPS
- 2 签名验证
QQ 官方要求:
- 使用 ed25519 签名算法(这个是最坑的,查了官方文档才知道)
- 验证请求包含 plain_token 和 event_ts
- 需要返回签名
实现代码:
def generatesignature(self, plain_token: str, event_ts: str) -> str:
import nacl.signing
使用 bot_secret 作为种子
seed = self.bot_secret.encode('utf-8')
while len(seed) < 32:
seed = seed + seed
seed = seed[:32]
生成签名密钥
signing_key = nacl.signing.SigningKey(seed)
创建消息
message = (event_ts + plain_token).encode('utf-8')
签名
signed = signing_key.sign(message)
return signed.signature.hex()
添加依赖:
requirements.txt
pynacl>=1.5.0
🔌 阶段五:消息处理修复
- 1 Gateway 参数问题
错误日志:
ERROR: [QQ] Handle C2C message error:
Gateway._handle_channel_message() got an unexpected keyword argument 'metadata'
解决方案: 修改 Gateway 方法签名
修改前
async def handlechannel_message(self, channel_name: str, sender: str, message: str) -> None:
修改后
async def handlechannel_message(self, channel_name: str, sender: str, message: str, **kwargs) -> None:
metadata = kwargs.get("metadata", {})
...
- 2 Access Token 问题
错误日志:
ERROR: [QQ] Get access token failed: 404 -
{"message":"不支持的调用","code":11001}
原因: API 端点错误
解决方案: 使用正确的端点
修改前
url = f"{self._api_base}/oauth2/token"
修改后
url = "https://bots.qq.com/app/getAppAccessToken"
- 3 消息发送问题
错误日志:
ERROR: [QQ] Send message failed: 400 -
{"message":"请求参数msg_id无效或越权","code":40034024}
原因: 发送消息时需要使用原消息的 msg_id
解决方案: 保存并传递 msg_id
接收消息时保存
msg_id = data.get("id", "")
发送消息时使用
payload = {
"content": content,
"msg_type": 0,
"msg_id": msg_id, # 使用原消息 ID
}
🔄 阶段六:跨渠道消息同步
- 1 需求
用户希望 QQ 消息也能在网页端查看。
- 2 实现方案
修改 Gateway 消息处理:
async def handlechannel_message(self, channel_name: str, sender: str, message: str, **kwargs) -> None:
metadata = kwargs.get("metadata", {})
session_id = f"{channel_name}:{sender}"
存储消息到统一的会话系统
if self.memory:
await self.memory.store_conversation(
session_id=session_id,
user_message=message,
channel=channel_name,
sender=sender,
)
response = await self.agent.run(message, session_id)
存储响应
if self.memory:
await self.memory.store_conversation(
session_id=session_id,
assistant_message=response,
channel=channel_name,
sender=sender,
)
添加 MemoryManager 方法:
async def store_conversation(self, session_id: str, user_message: str = None,
assistant_message: str = None, channel: str = "web",
sender: str = "default") -> None:
if user_message:
await self.add_message(session_id, "human", user_message, user_id=sender)
if assistant_message:
await self.add_message(session_id, "assistant", assistant_message, user_id=sender)
更新前端显示:
- 会话列表显示渠道标签(QQ/网页/飞书)
- 使用不同颜色区分渠道
✅ 最终成果
功能清单
┌─────────────┬──────┬─────────────────────┐
│ 功能 │ 状态 │ 说明 │
├─────────────┼──────┼─────────────────────┤
│ QQ 消息接收 │ ✅ │ 支持私聊和群聊 │
├─────────────┼──────┼─────────────────────┤
│ 消息回复 │ ✅ │ 正确使用 msg_id │
├─────────────┼──────┼─────────────────────┤
│ 签名验证 │ ✅ │ ed25519 算法 │
├─────────────┼──────┼─────────────────────┤
│ HTTPS 支持 │ ✅ │ Let's Encrypt 证书 │
├─────────────┼──────┼─────────────────────┤
│ 跨渠道同步 │ ✅ │ QQ 消息在网页端可见 │
├─────────────┼──────┼─────────────────────┤
│ 渠道标签 │ ✅ │ 前端显示消息来源 │
└─────────────┴──────┴─────────────────────┘
技术架构
用户 QQ 消息
↓
QQ 开放平台
↓ (Webhook HTTPS)
Nginx (443)
↓
FastAPI (8000)
↓
QQ Channel
↓
Gateway
↓
Agent (DeepSeek)
↓
回复用户 + 存储到会话系统
↓
网页端可查看
配置文件
.env:
CHANNEL_QQ_ENABLED=true
CHANNEL_QQ_BOT_ID=1903438252
CHANNEL_QQ_BOT_SECRET=xxx
CHANNEL_QQ_SANDBOX=false
Nginx:
server {
listen 443 ssl;
server_name catclaw.bujingxianshi.com;
ssl_certificate /etc/letsencrypt/live/catclaw.bujingxianshi.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/catclaw.bujingxianshi.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
}
}
最后总算搞完了,借助ai耗时半个下午。
