BJXSのBlog
首页项目归档照片墙音乐说说杂谈友链关于
封面

CatClaw 接入 QQ 机器人完整历程

写作时间:2026-06-14 21:07:24

目标: 将 QQ 机器人接入 CatClaw 系统,实现通过 QQ 与 AI 助手对话

时间: 2026-06-14

最终状态: ✅ 成功


🚀 阶段一:基础配置

  1. 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. 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. 1 域名准备

域名: catclaw.bujingxianshi.com

服务器: 阿里云 ECS (47.96.188.112)

  1. 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
  1. 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. 1 查找配置位置

问题: 找不到「消息接收」配置

官方文档说明:

  • 配置位置:「事件订阅与通知」页面
  • 需要选择「Webhook」接入方式
  • 必须使用 HTTPS
  1. 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. 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", {})

...
  1. 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"

  1. 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. 1 需求

用户希望 QQ 消息也能在网页端查看。

  1. 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耗时半个下午。

avatar

方康辉

这是一个在技术海洋上漂流的人,Vibe Coding深度患者,想创造很多有趣的东西。 我推是furina嘿嘿 o((>ω< ))o

RECOMMENDED

AI随记

2026-06-14 10:47:13

教程:如何借助AI从零制作简单Live2d模型

2026-06-15 18:52:29

Table of Contents