说实话,OpenClaw 真正让我心动的点是它能帮你把“想到的事”直接变成“做完的事”。但我一开始也没想通怎么在手机上玩它,总不能随时掏电脑敲命令行吧。折腾了三天,最后用飞书当一个“遥控器”,在对话框里丢一句话,服务器上的 OpenClaw 就开始干活,再把结果直接推回手机。这篇文章就把我这三天的完整思路、配置步骤和踩过的坑一次性说清楚,适合想用手机远程操作自建代理、又不想写复杂前端的开发者看。
先简单交代一下结论:OpenClaw 并不是跑在手机上的程序,它仍然在服务器或电脑后台运行,手机只需要装一个飞书。飞书的机器人开放接口负责收消息、把消息转发给 OpenClaw、再把结果返回聊天框。整个链路看上去多了一层,但实际体验很顺,等于你拥有一个随时在线、躺在消息列表里的“私人助理”。
1. 为什么要把 OpenClaw 搬到手机上:场景与方案选型
1.1 手机端直接调用代理的几种路径
想在任何地方调用本地的 OpenClaw,第一反应往往是这几个方案:
- 直接在手机浏览器打开服务器的 Web 控制台:问题是没有消息推送,而且大多数自建项目没有针对移动端做布局,点起来很费劲。
- 用手机 SSH 进服务器敲命令:Geek 味十足,但普通场景下太不合适,光是记住各种参数和操作路径就够难受。
- 自己写一个 App:投入产出比最低,为了一个推送需求做原生客户端完全不值。
- 接一个现成的 IM 机器人:微信个人号容易封,QQ 机器人链路繁琐,飞书开放接口最友好、回调文档最清楚,所以我最后选了它。
飞书做入口还有一个隐性优势:它有完善的消息规范和时间线记录。你和 OpenClaw 的每一次交互,本身就是聊天记录,哪次任务执行了什么、结果是什么,全部自动留在对话里,比看日志还直观。
1.2 为什么选飞书做“遥控器”
飞书机器人本质上是“企业自建应用”里的一个能力包。它不要求你写任何前端页面,只要提供一个回调地址接收事件,就能完成双向通信。
对比其他即时通讯工具,飞书对开发者的友好度体现在几个地方:
- 事件订阅机制成熟,消息内容随 POST 请求直接送到你的服务器。
- 官方支持通过 API 主动发送消息,这意味着 OpenClaw 执行完任务后可以由服务端主动推送结果,而不是干等用户来轮询。
- 用户侧零学习成本,只要会发消息就会用。
用“人话”来说,飞书机器人就像一个传话筒。你把想做的事用中文或自然语言发给它,它把话原封不动传给你后端程序;后端程序处理完把答案交还给它,它再发回聊天框。整个过程用户无感,只看到一个会干活的联系人躺在列表里。
1.3 整体架构与请求链路
先看清楚整条数据流,后面配置才不会晕:
手机飞书 -> 飞书服务器 -> 你的服务器(回调地址) -> OpenClaw -> 处理结果 ^ | +------------------------ 推回聊天框 --------------------------+也就是说,飞书服务器是“总机”,你的服务器是“接线员”,OpenClaw 是真正干活的“工人”。
这里有一个硬性条件,也是很多人第一天死活搞不定的地方:你的回调地址必须公网可达。飞书服务器不可能访问你家里内网那个192.168.x.x,所以要么你有一台公网服务器,要么用内网穿透工具把本机端口暴露出去。后面第四章我会单独讲这两种方式的配置。
2. 部署前置准备:本地跑通 OpenClaw
2.1 环境要求与部署方式
我建议先别急着接飞书,先把 OpenClaw 本身跑通验证,这样后面排错时可以快速定位是“代理问题”还是“飞书链路问题”。
以我自己的部署环境为例,只列最低配参考:
- 操作系统:Ubuntu 22.04(Windows 也能跑,但更推荐 Linux 服务器)。
- 内存:2GB 以上即可,普通任务不吃资源。
- 运行时:需要安装对应语言运行时,OpenClaw 目前依赖比较常规,Python 3.9+ 或 Node.js 18+ 二选一,具体看项目 README。
- 大模型接口:OpenClaw 本质上是对接大模型和工具链的调度器,所以你需要有一个可用的模型 API 地址和密钥。
部署命令很简单,从项目仓库拉取源码后按 README 安装依赖就行:
git clone <项目仓库地址> cd openclaw # 如果项目基于 Python pip install -r requirements.txt # 如果项目基于 Node npm install至于“项目仓库地址”具体是什么,以你拿到的源码为准,我这里没法写死。整个项目也就是一个普通的开源运行时,和跑其他 Python/Node 服务的套路完全一致。
2.2 最小可运行配置
OpenClaw 启动前需要一份配置文件,比如openclaw.yaml。里面核心要填的只有两块:模型服务信息和启动参数。
model: provider: openai-compatible base_url: "https://your-model-endpoint.example.com/v1" api_key: "sk-xxxxxxxxxxxxxxxx" model_name: "your-model-name" server: host: "127.0.0.1" port: 8080 auto_start: true这里的关键点是provider: openai-compatible。OpenClaw 对模型兼容性做得很开放,绝大多数提供标准 OpenAI 协议接口的服务都能接。你手上有什么模型服务,就换成对应的 base_url 和 key。
启动运行:
python main.py或者:
npm run start启动成功后,终端会出现监听端口号,比如8080。先不要管飞书,直接在命令行里向 OpenClaw 抛一个简单任务,确认它能正常调用模型并返回结果。
2.3 先在本机验证一次对话
这一步我自己当时跳过了,后面排查时吃了大亏。你可以在项目提供的 CLI 里直接输入:
openclaw run "帮我写一段 Python 代码,读取当前目录下所有 txt 文件并按文件名排序"看它是否返回正常内容。如果这一步有问题,说明模型接口配置出错,和飞书没有关系,先把模型连通性搞定再继续。
另一个验证方式是用 HTTP 调用它的本地接口。多数这类运行时会暴露一个简单的 REST 接口,你可以用curl测:
curl -X POST http://127.0.0.1:8080/run \ -H "Content-Type: application/json" \ -d '{"task": "总结一下当前项目目录结构"}'如果本地能返回结果,说明 OpenClaw 这条线是通的,可以进入下一步接入飞书。
3. 飞书机器人创建与权限配置要点
3.1 创建企业自建应用
进入飞书开放平台后台,选择“企业自建应用”,填写应用名称和描述。这里权限是按应用维度管理的,你随便起个名字就行,比如就叫“助理小爪”。
创建完成后,你会拿到两个关键凭证:
- App ID:应用的唯一标识。
- App Secret:调用服务端 API 时的密钥,务必保密,不要泄露到公开仓库。
这两个值后面代码里要用,先记下来。
3.2 开启机器人能力
在应用功能里找到“机器人”,点击启用。启用之后,这个应用在飞书里会以一个联系人的形式出现,你可以搜索到它、给它发消息。
最后还需要发布应用版本。很多初学者在这里卡住,以为配好了就能用,实际上飞书的权限和功能必须以“发布版本”的方式正式生效。在版本发布页填写版本号(如 1.0.0),等审核通过或自行通过后,机器人才能真正上线。自己使用的话,一般即时生效。
3.3 权限与事件订阅
在“权限管理”里需要开通两项核心权限:
- 读取用户发给机器人的单聊消息。
- 以机器人的身份发送消息。
具体权限标识在权限管理页面都能搜到,关键词用“消息”“单聊”就行。配上图片说明,飞书后台的权限清单是可以逐个搜索勾选的,不用记住权限代码。
接下来是最关键的“事件订阅”设置。在“事件与回调”页面新增事件,选择接收消息,对应的事件类型是im.message.receive_v1。
事件订阅需要填三个东西:
- 请求地址 URL:https://你的公网地址/webhook/feishu
- Encrypt Key:默认留空即可(先不开启加密,等流程跑通后再考虑加)。
- Verification Token:一串随机字符串,用于校验请求来源,后面代码里要校验。
这里我强烈建议第一遍不要开 Encrypt Key。加密流程会引入 AES 解密步骤,一旦验签和解密之间出错,很难判断是网络问题还是代码问题。先裸调通,再加安全层。
3.4 安全设置:IP 白名单与密钥保护
飞书后台还提供了一个“IP 白名单”配置项。开了之后,只有来自你服务器公网 IP 的请求才会被放行。
这个功能比较适合有固定公网 IP 的服务器。如果用了内网穿透,或者家庭宽带的 IP 经常变化,建议先把白名单关掉,或者把穿透服务商的出口 IP 加进白名单,否则回调请求会被拦截,现象就是你看到“请求失败”,但服务日志里根本没有收到任何内容。
实践心得:App Secret 和 Verification Token 一定不要写死在代码里并提交到仓库。我习惯用环境变量,例如:
export FEISHU_APP_SECRET="xxxxxxxx" export FEISHU_VERIFICATION_TOKEN="yyyyyyyy"代码里读取os.environ对应变量,至少保证代码泄露后不直接造成凭证泄露。
4. 打通公网回调:让飞书能联系上你的服务
4.1 为什么公网回调是难点
飞书的事件订阅不是轮询,而是飞书服务器主动向你的回调地址发 POST 请求。这意味着你的服务必须处在公网能访问到的位置。
没有公网 IP、在局域网内跑服务的人,第一步就得解决“飞书怎么找到我”的问题。解决办法无非两种。我把两种都写出来,按自己条件选择。
4.2 方案一:有公网服务器的直接部署
如果你有一台云服务器,最简单。直接用nginx反代或者让服务直接监听0.0.0.0:8080,然后确保安全组放行端口。
以 nginx 配置为例,把飞书的回调路径转发到本地 OpenClaw 服务:
server { listen 80; server_name your-domain.example.com; location /webhook/feishu { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }改完配置后nginx -s reload即可。然后在飞书回调 URL 里填写:
http://your-domain.example.com/webhook/feishu注意一件事:飞书要求回调地址必须是 http:// 或 https://,且不能带路径参数。如果你没有域名,直接用 IP 加端口也能填,但有些网络环境会限制非标准端口,所以 80/443 最稳。
4.3 方案二:纯局域网 + 内网穿透
如果你不想买服务器,或者只是趁周末在宿舍折腾,内网穿透是更快捷的选择。这类工具的思路是:在本机运行一个小程序,建立一条到公共节点的隧道,然后给你分配一个公网域名。飞书请求这个域名,数据会被隧道转发到本机端口。
操作大致是这样:
- 到穿透工具官网注册账号。
- 下载客户端并运行。
- 执行启动命令,把本地 8080 端口映射到公网临时域名:
# 以常见的隧道工具为例 tunnel http 8080命令跑通后,工具会输出一条公网访问地址,形如:
https://xxxx.tunnel.example.com把这条地址填到飞书事件订阅的“请求地址 URL”里即可,比如:
https://xxxx.tunnel.example.com/webhook/feishu免费隧道的问题在于域名不固定,每次重启客户端域名可能变化,更适合测试。长期使用建议升级固定子域名,或直接迁到云服务器。
4.4 回调 URL 验证的完整过程
当你点击飞书后台的“保存”按钮时,飞书会立刻向你的回调地址发一个验证请求。这个请求体长这样:
{ "type": "url_verification", "challenge": "abcdef123456", "token": "yyyyyyyy" }你的服务必须原样把challenge字段返回,飞书才认为回调地址有效。这一步就是我当时卡了一下午的地方:花了大量时间搞 AES 解密,最后才发现不启用 Encrypt Key 时根本不需要解密,直接校验 token 并返回 challenge 就行。
用 Python 的 Flask 写一个最小验证服务:
from flask import Flask, request, jsonify, abort import os app = Flask(__name__) VERIFICATION_TOKEN = os.environ.get("FEISHU_VERIFICATION_TOKEN") @app.route("/webhook/feishu", methods=["POST"]) def feishu_webhook(): data = request.get_json() if not data: abort(400) # 校验来源 if data.get("token") != VERIFICATION_TOKEN: abort(403) # URL 验证:原样返回 challenge if data.get("type") == "url_verification": return jsonify({"challenge": data["challenge"]}) return jsonify({"code": 0, "msg": "success"}) if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)服务跑起来后,在飞书后台点“保存”,能看到“验证成功”的绿色提示。如果看到“请求失败”,先不要怀疑代码,用下面的命令手动模拟一次:
curl -X POST https://你的回调地址/webhook/feishu \ -H "Content-Type: application/json" \ -d '{"type":"url_verification","challenge":"test123","token":"你的Token"}'看你服务日志里有没有收到请求。如果连请求都没有,说明网络链路没打通,去检查穿透工具或安全组;如果收到了但验证不通过,再看返回格式是不是{"challenge": "test123"}。
5. 消息指令解析与 OpenClaw 联动实现
5.1 飞书消息事件的数据格式
URL 验证通过后,飞书才会把真正的用户消息事件推送到你的接口。收到的事件结构大致如下:
{ "schema": "2.0", "header": { "event_id": "evt_xxxxx", "event_type": "im.message.receive_v1", "token": "yyyyyyyy" }, "event": { "sender": { "sender_id": { "open_id": "ou_xxxxx" } }, "message": { "message_id": "om_xxxxx", "message_type": "text", "content": "{\"text\":\"帮我查一下今天的天气\"}" } } }注意content字段是一个 JSON 字符串,解析的时候要先json.loads再用。这是最常见的解析错误点。
从这份结构里,我们只需要提取四个东西:
event.message.content:用户发的文本内容。event.sender.sender_id.open_id:用户的 open_id,回复消息时用它作为接收方。event.message.message_id:消息唯一 ID,可以用来回复。header.event_type:判断是不是消息事件。
5.2 核心转发逻辑:从收到消息到调用 OpenClaw
在 Flask 服务里,收到消息事件后,先解析内容,再调用 OpenClaw,最后把结果回复给用户。最小实现如下:
import json import requests from flask import request, jsonify OPENCLAW_URL = "http://127.0.0.1:8080/run" def send_feishu_message(open_id, text): # 调用飞书发送消息接口 url = "https://open.feishu.cn/open-apis/im/v1/messages" headers = { "Authorization": "Bearer " + get_tenant_access_token(), "Content-Type": "application/json; charset=utf-8" } payload = { "receive_id": open_id, "msg_type": "text", "content": json.dumps({"text": text}) } requests.post(url, headers=headers, params={"receive_id_type": "open_id"}, json=payload) def handle_message(content, open_id): # 调用 OpenClaw resp = requests.post(OPENCLAW_URL, json={"task": content}, timeout=60) result = resp.json().get("result", "执行完成") send_feishu_message(open_id, result) @app.route("/webhook/feishu", methods=["POST"]) def feishu_webhook(): data = request.get_json() if data.get("type") == "url_verification": return jsonify({"challenge": data["challenge"]}) if data.get("header", {}).get("event_type") == "im.message.receive_v1": event = data["event"] content = json.loads(event["message"]["content"])["text"] open_id = event["sender"]["sender_id"]["open_id"] handle_message(content, open_id) return jsonify({"code": 0, "msg": "success"})上面代码里有个get_tenant_access_token(),飞书 API 调用前需要先获取企业自建应用的访问令牌。这个令牌通过 App ID 和 App Secret 换:
def get_tenant_access_token(): url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" resp = requests.post(url, json={ "app_id": os.environ["FEISHU_APP_ID"], "app_secret": os.environ["FEISHU_APP_SECRET"] }) return resp.json()["tenant_access_token"]令牌有效期内可以缓存复用,不用每次都请求。飞书的标准答案一般是 2 小时有效期,开发阶段懒得缓存也没问题,稳定运行建议加内存缓存做一层。
5.3 异步任务与超时处理
飞书的 HTTP 回调对响应时间有要求,如果服务端超过 3 秒没有返回 HTTP 响应,飞书会判定超时并重试。但 OpenClaw 执行复杂任务可能需要几十秒甚至几分钟,不可能让回调挂起等它完成。
处理方式很简单:先立刻返回“收到,正在处理”,然后异步执行任务,完成后再用 API 主动推送结果。上面代码里的handle_message应该改成线程或队列方式:
import threading def async_handle(content, open_id): send_feishu_message(open_id, "收到,任务开始处理,稍等片刻") try: resp = requests.post(OPENCLAW_URL, json={"task": content}, timeout=120) result = resp.json().get("result", "执行完成") except Exception as e: result = f"执行出错:{e}" send_feishu_message(open_id, result) @app.route("/webhook/feishu", methods=["POST"]) def feishu_webhook(): ... if event_type == "im.message.receive_v1": threading.Thread(target=async_handle, args=(content, open_id)).start() return jsonify({"code": 0, "msg": "success"})这样回调瞬间返回成功,飞书不会反复重试;用户端则先看到“收到”,几秒或几十秒后收到真正的结果。多任务并发时注意别把线程开太猛,简单场景用线程池或queue更稳。
5.4 手机端实际体验
配置完成后,手机上的飞书打开你和机器人“助理小爪”的单聊对话,发送任意一段话,例如:
你:帮我把这个目录下的图片按时间排序,生成一个 HTML 相册 机器人:收到,任务开始处理,稍等片刻 机器人:已完成,相册已生成,地址是 http://your-server:8080/gallery.html如果你需要执行定时任务,比如每天早上九点推送摘要,也可以在 OpenClaw 侧挂一个计划任务,到点主动调用飞书 API 推送。手机端和服务器端的连接不需要保持在线,飞书服务器就是天然的“中间人”,只要你的服务在线即可。
6. 三天踩坑实录:常见问题排查与避坑技巧
6.1 回调验证一直不成功
这是我遇到的第一个大坑,现象是飞书后台一直报“请求失败”,服务日志里却没有收到任何请求。后来定位到是内网穿透工具把域名换了,回调地址填的是旧域名,新请求全打到空气上。
排查思路分三步:
- 先确认公网地址是否可用:直接在浏览器访问回调地址,看能否打开。
- 用 curl 模拟 POST 请求,观察服务日志是否有打印。
- 如果 curl 能收到、飞书收不到,十有八九是穿透或安全组拦了飞书出口 IP。
另一个原因是返回的 challenge 格式不对。必须返回 JSON 字符串{"challenge": "xxxx"},不是直接返回值,也不是加一层封装。
6.2 机器人不回复消息
功能开通了、回调也验证成功,但发消息给它一直没有反应。排查重点:
- 事件是否真的订阅上了。在飞书后台“事件与回调”页面确认
im.message.receive_v1已添加。 - 单聊是否需要 @机器人。飞书单聊不需要,直接把机器人当联系人发消息即可;群聊里如果要触发,需要先 @机器人。
- 应用版本是否已经发布。权限和事件订阅的配置,在版本发布后才会生效,这一点最容易忽略。
- 消息解析是否报错。看服务日志里有没有
KeyError,大概率是content字段没有二次json.loads。
6.3 中文乱码和换行
返回给飞书的消息如果出现中文乱码,基本可以断定是 HTTP 响应头里charset没有指定 UTF-8。在 Flask 中返回jsonify默认会带 UTF-8,一般不会出问题;如果你自己拼 JSON 字符串,记得在请求头里加:
"Content-Type": "application/json; charset=utf-8"还有换行问题:想让消息分段显示,在content的文本内容里用\n,而不是在 Python 字符串里直接肉眼换行。我用json.dumps({"text": "第一行\n第二行"})就能正常展示。
6.4 长任务、并发与限频
飞书对主动消息 API 有限频控制,默认每分钟调用次数有上限。如果 OpenClaw 同时处理多个任务、短时间内连发多条消息,可能会触发限频导致部分消息没送达。我在实际使用中加了一个简单的发送队列:每次发消息前判断当前时间窗口内已发数量,超了就先存队列等下一波。
另外注意,多个用户并发使用同一个机器人时,线程方式会互相抢资源。建议给每个任务塞队列消费,而不是无限开线程。我后续把它改成了concurrent.futures.ThreadPoolExecutor(max_workers=4),实测稳定很多。
6.5 安全与密钥保护
接飞书后相当于给外界开了一个命令入口,如果被陌生人拿到地址,理论上只要你能干的事它也能让机器人干。所以至少要做三件事:
- 校验 Verification Token,拒绝非法请求。
- 把日志里打印的 App Secret 打码,避免误喷到日志聚合平台。
- 在消息处理器里加一个“唤醒词”,只有消息以特定前缀开头才调用 OpenClaw,避免误触发或恶意触发。
“唤醒词”方案就是把handle_message()开头改成:
if not content.startswith("/assistant"): return这样日常聊天不会误触发,只有你主动说“/assistant 帮我看看服务器磁盘”才执行。实际用起来体验不差,安全收益很高。
6.6 调试技巧:先 mock,再联调
整个链路里最花时间的往往是“网络问题”和“业务问题”混在一起。我的调试顺序是:
- 先用 curl 向本机 OpenClaw 接口发任务,确认业务可用。
- 再 curl 向本地 Flask 服务发模拟飞书事件,确认代码解析正确。
- 最后才用飞书后台“调试台”功能模拟事件推送,确认端到端链路通。
飞书开放平台有调试台,可以直接选择事件并模拟发送,配合服务端日志能快速定位问题。如果你也准备折腾这个,强烈建议按这个顺序走,不要跳过第二步直接改回调 URL,否则你会像我一样把时间全耗在“飞书为什么不回复”上面。
最后分享一个小技巧:在配置回调地址时,先在代码里写一个临时调试接口,把收到的所有请求体原样打印并保存到文件,无论飞书发来的是 verification 还是 message 事件,你都能第一时间看到完整结构。这三天里我大部分时间都花在“猜请求长什么样子”上面,看完原始数据后一切都变得简单了。OpenClaw 加飞书的组合,本质上是“消息即服务”的一个变体现成方案。后面你还可以在这个基础上扩展多轮对话、定时推送、甚至让多个用户共用同一个机器人,玩法的上限取决于你的想象力。