1. 项目缘起:为什么要在QQ里接入OpenClaw?
最近在折腾一些自动化流程,发现很多重复性的信息查询、数据整理工作,如果能有个智能助手在聊天窗口里随时待命,效率会高很多。市面上虽然有不少机器人框架,但要么配置复杂,要么功能受限,直到我遇到了OpenClaw。它本质上是一个开源的AI智能体框架,可以理解你的自然语言指令,然后调用各种工具(比如搜索、计算、文件处理)来完成任务。想象一下,在QQ群里,你@一下机器人,说“查一下今天北京的天气”,或者“把群里刚才发的十条消息总结成会议纪要”,它就能自动完成并回复,这体验就非常丝滑。
所以,这个项目的核心目标,就是把OpenClaw这个“大脑”接入到QQ这个国民级IM平台里,打造一个属于自己或小团队的私有智能助理。整个过程涉及几个关键环节:搭建OpenClaw服务、配置QQ机器人客户端、以及让两者安全、稳定地通信。网上虽然有些零散的教程,但要么步骤不全,要么环境依赖讲得不清楚,新手很容易卡在某个环节。接下来,我就把自己从零搭建、调试到最终跑通的完整过程,包括踩过的坑和优化心得,毫无保留地分享出来。
2. 环境与工具准备:搭建你的智能核心
在开始连接之前,我们得先把OpenClaw这个核心服务跑起来。它不像一个简单的脚本,而更像一个微服务,我们需要为其准备一个合适的运行环境。
2.1 基础运行环境选择与配置
我强烈推荐使用Docker来部署OpenClaw。原因很简单:它封装了所有复杂的Python依赖、系统库和环境变量,能保证你在任何支持Docker的系统(Linux、macOS、甚至Windows WSL2)上获得完全一致的运行效果,彻底避免“在我机器上好好的”这种问题。
首先,确保你的系统已经安装了Docker和Docker Compose。以Ubuntu为例,安装命令如下:
# 更新软件包索引 sudo apt-get update # 安装依赖工具 sudo apt-get install apt-transport-https ca-certificates curl software-properties-common # 添加Docker官方GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # 添加Docker仓库 sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" # 再次更新并安装Docker sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io # 安装Docker Compose sudo apt-get install docker-compose-plugin # 验证安装 docker --version docker compose version安装完成后,将当前用户加入docker用户组,这样就不需要每次都加sudo了:
sudo usermod -aG docker $USER注意:执行完上述命令后,你需要完全退出当前终端会话并重新登录,或者重启系统,用户组变更才会生效。这是第一个容易忽略的坑。
2.2 获取与配置OpenClaw
OpenClaw的项目通常托管在GitHub上。我们通过git克隆代码库到本地:
git clone https://github.com/openclaw/openclaw.git cd openclaw进入目录后,你会看到关键的配置文件docker-compose.yml和.env.example。我们的第一步是基于示例文件创建自己的环境变量文件:
cp .env.example .env接下来,用文本编辑器(如nano或vim)打开.env文件。这里有几个核心配置项你必须关注:
# OpenAI API 配置(如果你使用GPT系列模型) OPENAI_API_KEY=sk-your-actual-api-key-here # 或者,如果你使用开源的本地模型(如通过Ollama部署) OPENAI_API_BASE=http://localhost:11434/v1 OPENAI_API_KEY=ollama # 本地模型通常不需要真实key,但字段需存在 OPENAI_MODEL_NAME=llama3.2:latest # 指定你本地运行的模型名称 # 服务端口 OPENCLAW_SERVER_PORT=8000关键点解析:
- API密钥:如果你使用OpenAI的官方接口,需要去其平台申请并付费的API Key。对于个人学习或内部使用,我更倾向于部署本地模型,比如用Ollama跑一个
llama3.2或qwen2.5,这样没有网络延迟和费用问题。只需将OPENAI_API_BASE指向你的Ollama服务地址(默认是http://localhost:11434/v1),OPENAI_MODEL_NAME填对应的模型名即可。 - 端口:
8000是OpenClaw服务默认的HTTP端口,确保它没有被其他程序占用。
2.3 启动OpenClaw服务
配置好.env文件后,使用Docker Compose一键启动所有服务:
docker compose up -d-d参数表示在后台运行。执行后,Docker会拉取必要的镜像(如果本地没有),并创建网络、启动容器。你可以用以下命令查看服务状态和日志:
# 查看容器运行状态 docker compose ps # 查看实时日志(用于调试) docker compose logs -f openclaw-server当你在日志中看到类似“Application startup complete.”或“Uvicorn running on http://0.0.0.0:8000”的信息时,说明OpenClaw服务已经成功启动。
此时,打开浏览器访问http://你的服务器IP:8000/docs,你应该能看到Swagger UI接口文档页面。这证明你的OpenClaw API服务已经在8000端口上正常监听请求了。
踩坑记录:第一次启动时,我遇到了数据库连接失败的错误。原因是
docker-compose.yml里定义的PostgreSQL服务可能比应用启动慢。解决方法是在OpenClaw服务的depends_on里增加健康检查,或者更简单粗暴的,第一次启动失败后,等十几秒再执行一次docker compose restart openclaw-server。生产环境建议完善docker-compose.yml的配置。
3. QQ机器人客户端选型与配置
现在,“大脑”已经就绪,我们需要一个“手脚”来连接QQ。这里我们选择go-cqhttp,它是一个功能强大、文档齐全且社区活跃的QQ机器人框架,使用Go语言编写,性能好,稳定性高。
3.1 下载与运行go-cqhttp
前往go-cqhttp的GitHub Release页面,根据你的操作系统下载对应的可执行文件。对于Linux服务器,通常选择go-cqhttp_linux_amd64.tar.gz。
# 假设下载到 /opt 目录 cd /opt wget https://github.com/Mrs4s/go-cqhttp/releases/download/v1.0.0-rc4/go-cqhttp_linux_amd64.tar.gz tar -zxvf go-cqhttp_linux_amd64.tar.gz cd go-cqhttp首次运行,会生成配置文件:
./go-cqhttp程序会提示选择通信方式,我们选择3: 反向WebSocket。这是因为我们希望QQ机器人作为客户端,主动连接到我们自己的OpenClaw服务(作为WebSocket服务器),这样更便于我们控制消息的处理逻辑。选择后,程序会生成config.yml文件,然后退出。
3.2 关键配置详解
用编辑器打开config.yml,我们需要修改几个核心部分:
account: # 账号配置 uin: 123456789 # 你的机器人QQ号 password: '' # 密码,不推荐明文填写。留空,首次登录用扫码。 encrypt: false # 是否启用加密,通常不需要 # 连接服务列表 servers: - ws-reverse: # 反向WebSocket服务器地址,指向我们OpenClaw服务的WebSocket端点 universal: ws://localhost:8000/qq/ws # 重连间隔 reconnect-interval: 3000 api-timeout: 60000 # API调用超时 event-timeout: 60000 # 事件上报超时配置解析与避坑:
- uin与密码:
uin填机器人的QQ号。password字段强烈建议留空。首次运行时,go-cqhttp会在终端或日志中输出一个二维码,你用手机QQ(必须是机器人账号绑定的手机)扫码即可登录,更安全。如果填密码,有账号风险。 - universal地址:这是最关键的配置。
ws://localhost:8000/qq/ws意味着go-cqhttp会尝试连接本地8000端口上的/qq/ws这个WebSocket路径。这里有个大坑:如果你的go-cqhttp和OpenClaw服务不在同一台机器上(比如OpenClaw跑在云服务器A,go-cqhttp跑在你家里的电脑B),那么localhost必须改成服务器A的公网IP或域名,并且要确保服务器的防火墙和安全组开放了8000端口。同时,OpenClaw服务配置的OPENCLAW_SERVER_HOST可能需要设置为0.0.0.0以接受外部连接。 - 协议头:注意是
ws://(非加密)还是wss://(加密)。内网测试用ws://即可。如果走公网,为了安全,强烈建议在OpenClaw服务前配置Nginx反向代理并添加SSL证书,然后这里配置wss://你的域名/qq/ws。
3.3 启动与登录机器人
配置保存后,再次运行go-cqhttp:
./go-cqhttp程序会尝试连接WebSocket服务器(此时我们的OpenClaw服务还没提供这个端点,所以会连接失败,没关系),并输出二维码。用手机QQ扫描二维码完成登录。登录成功后,控制台会显示“登录成功”等信息。此时,你可以按Ctrl+C停止程序,然后使用后台运行模式:
nohup ./go-cqhttp > cqhttp.log 2>&1 &这样机器人就在后台运行了,日志输出到cqhttp.log文件。至此,QQ机器人客户端已配置完毕,处于待命状态,等待与OpenClaw服务建立连接。
4. 核心桥梁:编写OpenClaw的QQ消息适配器
前面两步,我们分别启动了OpenClaw服务(监听HTTP)和go-cqhttp客户端(试图连接WebSocket)。但它们现在还无法通信,因为OpenClaw默认并没有处理QQ消息的WebSocket端点。我们需要在OpenClaw项目中添加一个“适配器”(Adapter),作为两者之间的翻译官和调度中心。
4.1 理解通信协议与数据流
整个数据流是这样的:
- QQ群或私聊发生事件(如收到消息) -> go-cqhttp捕获。
- go-cqhttp将事件封装成JSON格式,通过反向WebSocket连接,推送到我们指定的
universal地址(即OpenClaw的某个端点)。 - OpenClaw端的WebSocket服务器接收到JSON数据,解析出消息内容、发送者、群号等信息。
- OpenClaw将解析后的信息,交给其内部的AI智能体(Agent)去处理。智能体会理解意图,调用工具,生成回复文本。
- OpenClaw将回复文本,通过调用go-cqhttp提供的HTTP API(go-cqhttp在运行时会同时开启一个HTTP API服务,默认端口5700),发送回QQ。
- go-cqhttp接收到API调用,执行发送消息的操作。
所以,我们的适配器需要做两件事:建立WebSocket服务器接收消息,以及封装HTTP客户端来发送消息。
4.2 创建WebSocket消息处理器
在OpenClaw的项目目录下,找到一个合适的位置创建我们的QQ适配器模块。例如,在app/adapters/目录下创建qq_adapter.py。
# app/adapters/qq_adapter.py import asyncio import json import logging from typing import Dict, Any from fastapi import WebSocket, WebSocketDisconnect from sse_starlette.sse import EventSourceResponse # 假设OpenClaw有一个核心的智能体服务 from app.services.agent_service import AgentService logger = logging.getLogger(__name__) class QQWebSocketManager: def __init__(self): self.active_connections: List[WebSocket] = [] self.agent_service = AgentService() # 初始化你的智能体服务 # 用于调用go-cqhttp HTTP API的客户端,稍后实现 self.cqhttp_client = CQHttpClient() async def connect(self, websocket: WebSocket): await websocket.accept() self.active_connections.append(websocket) logger.info(f"QQ客户端已连接。当前连接数:{len(self.active_connections)}") async def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) logger.info(f"QQ客户端断开连接。当前连接数:{len(self.active_connections)}") async def receive_and_process(self, websocket: WebSocket): """ 核心方法:接收WebSocket消息,处理,并回复。 """ try: while True: # 1. 接收go-cqhttp推送的JSON数据 data = await websocket.receive_text() event = json.loads(data) logger.debug(f"收到QQ事件: {event}") # 2. 过滤出我们需要处理的消息事件 if event.get('post_type') == 'message': # 提取关键信息 message_type = event.get('message_type') # 'private' 或 'group' user_id = event.get('user_id') group_id = event.get('group_id') if message_type == 'group' else None raw_message = event.get('raw_message', '') # 原始消息字符串 message_id = event.get('message_id') # 3. 构造给智能体的提示词 # 这里可以添加一些上下文,比如告诉AI它是谁,在什么场景下 prompt = f"用户(QQ号:{user_id})在{'群' + str(group_id) if group_id else '私聊'}中说:{raw_message}\n请以助手的身份进行回复。" # 4. 调用OpenClaw智能体处理 try: # 这里调用你项目中实际处理AI请求的方法 agent_response = await self.agent_service.process_query(prompt) reply_text = agent_response.get('text', '抱歉,我暂时无法处理这个问题。') except Exception as e: logger.error(f"智能体处理失败: {e}") reply_text = "处理请求时出了点问题,请稍后再试。" # 5. 通过HTTP API将回复发送回QQ if message_type == 'private': await self.cqhttp_client.send_private_msg(user_id=user_id, message=reply_text) elif message_type == 'group': await self.cqhttp_client.send_group_msg(group_id=group_id, message=reply_text) except WebSocketDisconnect: await self.disconnect(websocket) except json.JSONDecodeError as e: logger.error(f"消息JSON解析失败: {e}, 原始数据: {data}") except Exception as e: logger.error(f"处理QQ消息时发生未知错误: {e}")4.3 实现HTTP API客户端 (CQHttpClient)
上面代码中引用的CQHttpClient需要实现,它负责与go-cqhttp的HTTP API交互。
# app/clients/cqhttp_client.py import aiohttp import logging from typing import Optional logger = logging.getLogger(__name__) class CQHttpClient: def __init__(self, base_url: str = "http://localhost:5700"): """ :param base_url: go-cqhttp HTTP API 的地址。 如果go-cqhttp和本服务不在同一机器,需改为对应IP:端口。 """ self.base_url = base_url.rstrip('/') self.session: Optional[aiohttp.ClientSession] = None async def __aenter__(self): self.session = aiohttp.ClientSession() return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self.session: await self.session.close() async def _post(self, endpoint: str, payload: dict) -> dict: """内部通用的POST请求方法""" if not self.session: self.session = aiohttp.ClientSession() url = f"{self.base_url}{endpoint}" try: async with self.session.post(url, json=payload) as resp: resp.raise_for_status() return await resp.json() except aiohttp.ClientError as e: logger.error(f"调用CQHTTP API失败 ({url}): {e}") return {'status': 'failed', 'retcode': -1, 'data': None} async def send_private_msg(self, user_id: int, message: str) -> dict: """发送私聊消息""" endpoint = "/send_private_msg" payload = { "user_id": user_id, "message": message, "auto_escape": False # 不自动转义CQ码,允许发送图片等富媒体 } return await self._post(endpoint, payload) async def send_group_msg(self, group_id: int, message: str) -> dict: """发送群消息""" endpoint = "/send_group_msg" payload = { "group_id": group_id, "message": message, "auto_escape": False } return await self._post(endpoint, payload)4.4 将适配器集成到FastAPI主应用
最后,我们需要在OpenClaw的FastAPI主应用中,创建WebSocket路由,并将我们的管理器挂载上去。通常在app/main.py或app/api/endpoints/下添加。
# app/api/endpoints/qq.py from fastapi import APIRouter, WebSocket, WebSocketDisconnect from app.adapters.qq_adapter import QQWebSocketManager import logging router = APIRouter() manager = QQWebSocketManager() @router.websocket("/qq/ws") async def websocket_endpoint(websocket: WebSocket): """ go-cqhttp反向WebSocket连接端点。 路径必须与go-cqhttp配置中的`universal`字段一致。 """ await manager.connect(websocket) try: await manager.receive_and_process(websocket) except WebSocketDisconnect: logging.info("QQ WebSocket连接已正常关闭。") except Exception as e: logging.error(f"WebSocket处理过程发生错误: {e}") finally: # 确保连接被移除 if websocket in manager.active_connections: await manager.disconnect(websocket)然后,在主应用(app/main.py)中引入这个路由:
from fastapi import FastAPI from app.api.endpoints import qq # 导入我们刚写的路由 app = FastAPI(title="OpenClaw API") # ... 其他路由注册 ... app.include_router(qq.router, tags=["QQ"])至此,OpenClaw端的适配器就编写完成了。重启OpenClaw服务,它就会在/qq/ws路径上提供一个WebSocket服务。
5. 联调测试与常见问题排查
所有部件都准备好后,就到了最激动人心也最容易出错的联调阶段。请按照以下顺序启动服务并观察日志。
5.1 启动顺序与状态检查
启动OpenClaw服务:
cd /path/to/openclaw docker compose down # 先停止旧的 docker compose up -d # 重新启动 docker compose logs -f openclaw-server确认日志无报错,并看到服务在8000端口启动成功。
启动go-cqhttp:
cd /path/to/go-cqhttp # 如果之前用nohup启动了,先找到进程kill掉 # pkill -f go-cqhttp ./go-cqhttp观察控制台输出。理想情况下,你应该看到:
[INFO] 开始尝试连接到反向WebSocket服务器 ws://localhost:8000/qq/ws...[INFO] 已连接到反向WebSocket服务器- 如果之前没登录,会显示二维码,扫码登录后显示登录成功信息。
5.2 核心问题排查链路
如果连接失败,请按照以下链路一步步排查:
问题1:go-cqhttp无法连接WebSocket (dial tcp [::1]:8000: connect: connection refused)
检查点1:OpenClaw服务是否真的在运行?
curl http://localhost:8000/docs如果无法访问,说明OpenClaw服务没起来。检查
docker compose ps和docker compose logs。检查点2:WebSocket路由是否正确注册?
# 查看OpenClaw服务注册的所有路由 # 如果你有进入容器内部查看的能力,可以: docker exec -it openclaw-openclaw-server-1 bash # 在容器内,假设你用了uvicorn,可以通过查看进程或代码确认。 # 更简单的方法:直接测试WebSocket连接 # 使用wscat工具 (需要先安装 npm install -g wscat) wscat -c ws://localhost:8000/qq/ws如果连接被拒绝或返回404,说明
/qq/ws路由没有正确添加到FastAPI应用。回头检查app/main.py中是否include_router了qq路由。检查点3:跨机器连接时的网络与防火墙
- 确保OpenClaw服务所在服务器的防火墙开放了8000端口。
- 在go-cqhttp的机器上,用
telnet <服务器IP> 8000测试TCP连通性。 - 将
config.yml中的universal地址从localhost改为服务器的真实IP。
问题2:连接成功,但收不到消息或发不出消息
检查点1:go-cqhttp日志级别默认的日志级别可能过滤了信息。修改
config.yml:log-level: debug # 设置为debug,查看更详细的事件上报日志重启go-cqhttp,在群里发消息,观察控制台是否打印出
[DEBUG] 收到事件: ...这样的日志。如果没有,可能是go-cqhttp的账号未成功接收消息(检查登录状态、账号是否被风控)。检查点2:OpenClaw适配器日志查看OpenClaw服务的日志,看是否收到了WebSocket消息。
docker compose logs -f openclaw-server | grep -i "收到QQ事件"如果收不到,说明go-cqhttp的事件没有正确推送过来。检查
config.yml中servers下的post相关配置(虽然我们用了反向WS,但有些事件过滤配置可能影响)。检查点3:HTTP API调用失败如果OpenClaw日志显示处理了消息但发送失败,查看
CQHttpClient的调用日志。确保base_url(http://localhost:5700) 正确,且go-cqhttp的HTTP API服务已开启(默认开启)。可以在浏览器访问http://localhost:5700测试,正常会返回go-cqhttp的版本信息。
问题3:智能体回复内容不符合预期或报错
检查点1:OpenAI API或本地模型查看OpenClaw日志中调用AI模型的部分。如果是网络超时、API Key无效、模型不存在等问题,这里会报错。确保你的
.env配置正确,并且对应的服务(如Ollama)正在运行且模型已下载。# 测试Ollama curl http://localhost:11434/api/tags检查点2:提示词(Prompt)构造检查
qq_adapter.py中构造的prompt是否清晰。AI的表现很大程度上取决于提示词。你可以尝试将构造好的prompt打印到日志里,看看是否包含了必要的上下文和指令。
5.3 功能验证
当一切就绪后,进行最终测试:
- 在已添加机器人的QQ群或私聊中,发送一条消息,例如:“你好,你是谁?”
- 观察go-cqhttp和OpenClaw两边的日志,确认消息流经的每个环节:接收 -> 推送WS -> OpenClaw接收 -> AI处理 -> 调用API发送 -> go-cqhttp执行发送。
- 在QQ中收到机器人的回复。
如果成功,恭喜你,一个基本的QQ智能机器人已经搭建完成!
6. 进阶优化与安全加固
基础功能跑通只是第一步,要让这个机器人稳定、可用、安全,还需要做一些优化。
6.1 消息处理与限流
直接让每个QQ消息都触发一次AI调用,成本高且可能被滥用。我们需要添加一些控制逻辑。
触发前缀:只处理以特定指令(如
/ai、@机器人)开头的消息。在qq_adapter.py的receive_and_process方法中增加判断:trigger_prefix = "/ai" if not raw_message.startswith(trigger_prefix): logger.debug(f"消息未包含触发前缀'{trigger_prefix}',忽略。") return # 去掉前缀后再交给AI处理 query = raw_message[len(trigger_prefix):].strip() prompt = f"用户提问:{query}\n请回答:"频率限制:使用
asyncio.Semaphore或第三方库如slowapi,限制同一用户或群在一定时间内的请求次数,防止刷屏和API滥用。异步处理与队列:将接收到的消息放入一个异步队列(
asyncio.Queue),由单独的消费者任务处理AI调用和回复。这样即使AI响应慢,也不会阻塞WebSocket消息的接收。
6.2 配置管理与安全性
敏感信息分离:将QQ号、API密钥、服务器地址等敏感信息从代码中剥离,全部放入
.env文件或配置中心,并通过环境变量读取。WebSocket认证:在生产环境,反向WebSocket连接应该增加简单的认证,防止未授权的客户端连接。可以在连接时验证一个Token。
# 在 websocket_endpoint 函数中 token = websocket.query_params.get("token") if token != os.getenv("QQ_WS_TOKEN"): await websocket.close(code=1008, reason="Unauthorized") return同时在go-cqhttp的
universal地址后加上?token=你的密钥。HTTPS/WSS:公网部署必须使用SSL。为你的服务器域名申请证书(可以用Let‘s Encrypt免费证书),然后在Nginx中配置反向代理,将
wss://your-domain.com/qq/ws代理到内部的ws://localhost:8000/qq/ws,将https://your-domain.com代理到http://localhost:8000。
6.3 扩展机器人能力
OpenClaw的强大之处在于其“工具调用”能力。你可以为智能体配置更多工具,让QQ机器人不仅能聊天,还能做事。
例如,在OpenClaw的智能体配置中,可以增加:
- 网络搜索工具:让机器人能回答实时信息。
- 计算器工具:处理数学问题。
- 文件读写工具:管理服务器上的文件(需严格控制权限)。
- 自定义API工具:连接你的内部业务系统。
当用户问“今天天气怎么样?”时,智能体会自动调用搜索工具,获取结果后组织语言回复。这一切都通过OpenClaw的框架自动完成,你只需要定义好工具即可。
7. 部署上线与长期维护
将整套系统部署到一台稳定的云服务器上,进行长期运行。
- 使用进程守护:不要直接用
nohup或&。使用systemd或supervisor来管理go-cqhttp和Docker Compose进程,实现开机自启、崩溃重启、日志轮转。 - 日志收集:将Docker容器日志和go-cqhttp的日志统一收集到文件或日志服务(如ELK)中,方便排查问题。
- 监控告警:监控服务器的CPU、内存、磁盘,以及两个服务的进程状态。可以写一个简单的健康检查脚本,定期测试机器人是否响应,失败则发送告警。
- 账号风控:QQ对于自动化行为有检测机制。避免机器人短时间内发送大量重复消息、频繁加群退群。如果账号被冻结,可能需要手机验证解封。准备一个备用的机器人账号很有必要。
整个搭建过程就像搭积木,核心是理解OpenClaw(智能处理中心)、go-cqhttp(QQ协议客户端)和自定义适配器(通信桥梁)三者之间的关系和数据流向。一旦跑通,你就可以在这个基础上,不断迭代智能体的能力,打造一个真正有用的私人工作助理。