通过企业微信消息向脚本传递短信验证码、Token 等信息
代码地址:
- GitHub:https://github.com/adrninistrator/agent-relay
- Gitee:https://gitee.com/adrninistrator/agent-relay
自动化脚本执行到需要人工输入的环节时,通过企业微信把短信验证码、动态 Token、一次性口令等
「脚本自己拿不到的信息」发给操作者;操作者回复后,脚本自动继续。
人工只需回一条消息,不用盯守,也不需要公网 IP、域名或回调地址。
一、这些场景你一定遇到过
核心诉求很明确:脚本自动执行过程中,遇到只有人能提供的信息,用最低成本把人串进来,然后自动继续。
| 场景 | 卡点 |
|---|---|
| 自动化登录某个系统 | 短信验证码只发到人手上,脚本读不到 |
| 定时任务刷新 Token | 刷新需要人工授权,任务只能中断等待 |
| RPA / 爬虫流程 | 遇到图形验证码或二次确认,人工介入后流程无法自动继续 |
| 运维脚本执行敏感操作 | 需要一次性口令 / 审批码,脚本没有任何渠道获取 |
| AI Agent 执行长流程 | 中途需要人工确认,Agent 无法「停下来等一句话」 |
这些信息有一个共同点:它只存在于人的手机里,不在脚本能访问的任何接口里。
传统做法是人工盯守、复制粘贴,或者搭一套公网回调服务------前者效率低,后者要域名、要备案、要处理加解密和签名校验。
二、解决方式
你的脚本 / 工作流 本服务 你的手机
| | |
| 1. POST /send_message | |
|-------------------------->| 2. 企业微信消息 |
| |------------------------->|
| | |
| | 3. 人工回复验证码 |
| |<-------------------------|
| 4. GET /get_reply | |
|-------------------------->| |
|<--------------------------| 5. 返回验证码 |
| | |
继续执行
脚本发一条消息到企业微信,人看到后直接回复,脚本轮询取回内容,流程继续。
几个关键优势:
- 人工侧零门槛:不用打开网页、不用登录别的系统,在企业微信里回一句话就行
- 不需要公网 IP、域名、回调地址,内网机器即可运行
- 企业微信官方长连接通道,无需处理消息加解密和签名
- 纯 HTTP 接口,Shell、Python、Java、Go、低代码平台、工作流引擎都能接入
- 消息里自带你指定的序号,多条消息并发也不会拿错
三、5 分钟接入
1. 安装依赖
bash
pip install -r requirements.txt
要求 Python 3.11+。
2. 创建并配置企业微信机器人
在企业微信客户端里按以下步骤创建机器人:
- 点击企业微信左侧边栏的「机器人」菜单
- 点击「创建机器人」,填写名称
- 选择「API 模式」
- 连接方式选择「使用长连接」
- 复制
Bot ID与Secret - 使用模式选择「仅个人使用」
- 点击「保存」
- 使用模式下的「可使用权限」不需要额外添加授权
重要 :机器人创建完成后,左侧边栏「通讯录」里会出现该机器人。
需要先向它发送任意消息,否则机器人无法主动给你发消息(企业微信的官方限制)。
拿到 Bot ID、Secret 后,编辑 config.toml:
toml
bot_id = "你的 Bot ID"
secret = "长连接专用 Secret"
chat_id = "接收消息的用户 userid"
chat_type = 1 # 1 单聊,2 群聊
reply_ttl = 300 # 消息数据保存时长(秒)
chat_id:填你自己的企业微信 useridchat_type:单聊填1,群聊填2(群聊时chat_id填群 chatid)
3. 启动服务
bash
python main.py --port 8080
看到 订阅成功 即表示机器人已上线。
4. 发一条消息试试
bash
curl -X POST http://127.0.0.1:8080/send_message \
-H "Content-Type: application/json" \
-d '{"seq":"0421","content":"请提供登录验证码"}'
json
{"errcode": 0, "errmsg": "ok", "seq": "0421"}
企业微信里会收到一条消息,末尾自带回复指引。
5. 在手机上直接回复
用户只需要在会话里直接发内容,例如:
658231
6. 脚本取回
bash
curl "http://127.0.0.1:8080/get_reply?seq=0421"
json
{"success": true, "message": "ok", "reply": "658231"}
拿到 reply 后,脚本继续执行。
四、接口速查
发送消息
POST /send_message
Content-Type: application/json
{"seq": "0421", "content": "请提供登录验证码"}
| 字段 | 说明 |
|---|---|
seq |
必填,4 位数字字符串,由调用方生成,用于关联这次回复 |
content |
必填,消息内容,UTF-8 不超过 20480 字节 |
返回:
| HTTP | 含义 |
|---|---|
200 |
发送成功,返回 {"errcode":0,"errmsg":"ok","seq":"0421"} |
400 |
seq 不是 4 位数字,或 content 为空/超长 |
409 |
该 seq 在保存时长内已被占用,换一个即可 |
502 |
企业微信返回错误(如频率超限) |
503 |
长连接尚未就绪(等服务几秒重试) |
获取回复
GET /get_reply?seq=0421 # 按序号查
GET /get_reply # 不传序号:按最后一条消息查
GET /get_reply?seq=0421&detail=true # 同时返回原始消息信息
json
{"success": true, "message": "ok", "reply": "658231"}
尚未回复时:
json
{"success": false, "message": "暂无回复", "reply": null}
脚本侧只需判断 success,true 时取 reply,false 时继续轮询。
五、写进脚本
Python:一个函数搞定
python
import json
import random
import time
import urllib.request
BASE = "http://127.0.0.1:8080"
def _post(path: str, payload: dict) -> dict:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
f"{BASE}{path}", data=data, method="POST",
headers={"Content-Type": "application/json; charset=utf-8"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read().decode("utf-8"))
def _get(path: str) -> dict:
with urllib.request.urlopen(f"{BASE}{path}", timeout=10) as resp:
return json.loads(resp.read().decode("utf-8"))
def ask_wecom(title: str, timeout: int = 300, interval: int = 2) -> str:
"""发送消息并等待人工回复,返回回复内容"""
for _ in range(10):
seq = f"{random.randint(0, 9999):04d}"
result = _post("/send_message", {
"seq": seq,
"content": f"{title}(请直接回复内容)",
})
if result.get("errcode") == 0:
break
if result.get("errcode") != 409: # 409 是序号冲突,换一个重试
raise RuntimeError(f"发送失败: {result}")
else:
raise RuntimeError("生成序号连续冲突")
deadline = time.time() + timeout
while time.time() < deadline:
reply = _get(f"/get_reply?seq={seq}")
if reply.get("success"):
return reply["reply"]
time.sleep(interval)
raise TimeoutError(f"等待「{title}」超时")
if __name__ == "__main__":
code = ask_wecom("登录验证码")
print("拿到验证码:", code)
# 接下来用 code 继续你的自动化流程
Shell:一行流
bash
#!/usr/bin/env bash
set -euo pipefail
BASE="http://127.0.0.1:8080"
SEQ=$(printf "%04d" $((RANDOM % 10000)))
curl -s -X POST "$BASE/send_message" \
-H "Content-Type: application/json" \
-d "{\"seq\":\"$SEQ\",\"content\":\"请提供登录验证码\"}" > /dev/null
for _ in $(seq 1 150); do
RESP=$(curl -s "$BASE/get_reply?seq=$SEQ")
if echo "$RESP" | grep -q '"success": true'; then
echo "$RESP" | sed -n 's/.*"reply": "\([^"]*\)".*/\1/p'
exit 0
fi
sleep 2
done
echo "等待验证码超时" >&2
exit 1
生产环境建议用语言内置 JSON 库解析,而不是
sed。
工作流 / 低代码 / AI Agent
任何支持 HTTP 节点的平台都可以直接用:
- HTTP 节点调用
POST /send_message,seq传一个 4 位随机数 - 循环节点每隔 2 秒调用
GET /get_reply?seq=... - 判断
success == true后取reply,进入下游节点 - 设置最大等待时长(如 5 分钟),超时走异常分支
六、用户怎么回复
两种方式都可以,用户怎么方便怎么来:
方式一(推荐):直接发内容
658231
方式二:两行,第一行序号、第二行内容
0421
658231
第二行是给消息指定序号。不指定时,回复会归属到最近一条消息。
消息末尾已经内置了操作提示,并用加粗标出了「直接回复即可」,用户不需要额外说明。
七、参数与限制
| 项 | 说明 |
|---|---|
seq |
4 位数字字符串,由调用方生成;同一时刻不可重复,重复会返回 409 |
| 保存时长 | 默认 5 分钟,可通过 reply_ttl 调整;超时后消息与回复被自动清理 |
| 发送频率 | 同一会话 30 条/分钟、1000 条/小时(企业微信限制) |
| 回复内容 | 仅处理文本消息 |
| 消息长度 | 内容不超过 20480 字节 |
| 默认查询 | 不传 seq 时返回最后一条消息的回复 |
| 部署形态 | 同一机器人只能保持一个长连接,服务按单实例部署 |
典型等待时长建议 :验证码类 2~5 分钟,人工审批类可放宽到 10~30 分钟,但要同步调大 reply_ttl。
八、安全与部署建议
- 只在内网暴露服务 :默认监听
127.0.0.1;需要跨机调用时绑定内网地址并用防火墙限制来源。 - 密钥不外泄 :
config.toml含机器人 Secret,不要提交到代码仓库,生产环境建议用环境变量或密钥管理服务。 - 注意日志:验证码、Token 属于敏感信息,当前实现会把回复内容打到日志里;生产环境应评估是否需要脱敏或关闭该日志。
- 设置超时兜底:脚本侧一定要有最大等待时间,避免用户一直不回复导致流程卡死。
- 常驻运行:用 systemd(Linux)或 NSSM(Windows)托管进程,避免终端关闭导致服务退出。
九、能解决什么,不能解决什么
适合:
- 一次性验证码、Token、动态口令的传递
- 人工确认类场景(同意/拒绝、审批码)
- 自动化流程中的短时人工介入
不适合:
- 大批量消息推送(受 30 条/分钟限制)
- 多轮复杂对话(这是「一问一答」的桥接层,不是聊天机器人框架)
- 强一致的高可用场景(数据在内存中,服务重启会丢失未完成的消息)
把「人」当作自动化流程里的一个可调用节点------这就是它全部的价值。