@xuanyue202/wecom-app-relay 2026.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # wecom-app-relay
2
+
3
+ 企业微信自建应用 WebSocket 中继服务器。
4
+
5
+ 将企微回调通过 WebSocket 转发给 OpenClaw extension 客户端,客户端的回复通过中继调用企微 API 发送。**extension 端无需公网 IP**。
6
+
7
+ ## 架构
8
+
9
+ ```
10
+ 企微用户 → 企微服务器 → [HTTP 回调] → wecom-app-relay (公网服务器)
11
+ ↕ [WebSocket]
12
+ OpenClaw extension (可在内网)
13
+
14
+ wecom-app-relay → [企微 API] → 企微用户
15
+ ```
16
+
17
+ ## 快速开始
18
+
19
+ ### 1. 安装
20
+
21
+ ```bash
22
+ # npm 全局安装
23
+ npm install -g @xuanyue202/wecom-app-relay
24
+
25
+ # 或 npx 直接运行
26
+ npx @xuanyue202/wecom-app-relay --config relay.config.json
27
+
28
+ # 或从源码运行
29
+ cd wecom-app-relay
30
+ pnpm install
31
+ pnpm dev -- --config relay.config.json
32
+ ```
33
+
34
+ ### 2. 创建配置文件
35
+
36
+ 创建 `relay.config.json`:
37
+
38
+ ```json
39
+ {
40
+ "host": "0.0.0.0",
41
+ "port": 9080,
42
+ "authToken": "你的认证密钥-随机生成一个长字符串",
43
+ "accounts": {
44
+ "default": {
45
+ "token": "企微回调Token",
46
+ "encodingAESKey": "企微回调EncodingAESKey(43位)",
47
+ "receiveId": "企业ID",
48
+ "corpId": "企业ID",
49
+ "corpSecret": "应用Secret",
50
+ "agentId": 1000002,
51
+ "webhookPath": "/wecom"
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ > **`authToken`** 是 extension 客户端连接中继时的认证密钥,请使用随机字符串,与企微的 token 无关。
58
+
59
+ ### 3. 启动中继
60
+
61
+ ```bash
62
+ wecom-app-relay --config relay.config.json
63
+ ```
64
+
65
+ 输出:
66
+
67
+ ```
68
+ [relay] listening on 0.0.0.0:9080
69
+ [relay] WebSocket endpoint: ws://0.0.0.0:9080/ws
70
+ [relay] Webhook endpoint: http://0.0.0.0:9080/webhook
71
+ [relay] WeCom callback [default]: http://0.0.0.0:9080/wecom
72
+ ```
73
+
74
+ ### 4. 配置企微后台
75
+
76
+ 在企微管理后台 → 应用管理 → 你的自建应用 → API 接收消息:
77
+
78
+ - **URL**:`http://你的服务器IP:9080/wecom`
79
+ - **Token**:与配置文件中 `accounts.default.token` 一致
80
+ - **EncodingAESKey**:与配置文件中 `accounts.default.encodingAESKey` 一致
81
+
82
+ 在「企业可信 IP」中添加中继服务器的公网 IP。
83
+
84
+ ### 5. 配置 OpenClaw extension
85
+
86
+ 编辑 `~/.openclaw/openclaw.json`:
87
+
88
+ ```json
89
+ {
90
+ "channels": {
91
+ "wecom-app": {
92
+ "enabled": true,
93
+ "mode": "ws-relay",
94
+ "token": "企微回调Token",
95
+ "encodingAESKey": "企微回调EncodingAESKey",
96
+ "corpId": "企业ID",
97
+ "corpSecret": "应用Secret",
98
+ "agentId": 1000002,
99
+ "wsRelayUrl": "ws://你的中继服务器IP:9080/ws",
100
+ "wsRelayWebhookUrl": "http://你的中继服务器IP:9080/webhook"
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## 配置参考
107
+
108
+ ### 配置文件字段
109
+
110
+ | 字段 | 类型 | 默认值 | 说明 |
111
+ |------|------|--------|------|
112
+ | `host` | string | `0.0.0.0` | 监听地址 |
113
+ | `port` | number | `9080` | 监听端口 |
114
+ | `authToken` | string | 必填 | WebSocket 认证密钥 |
115
+ | `accounts` | object | 必填 | 企微账户配置(支持多账户) |
116
+
117
+ ### 账户字段
118
+
119
+ | 字段 | 类型 | 说明 |
120
+ |------|------|------|
121
+ | `token` | string | 企微回调 Token |
122
+ | `encodingAESKey` | string | 企微回调加密密钥(43 位) |
123
+ | `receiveId` | string | 接收者 ID(通常等于 corpId) |
124
+ | `corpId` | string | 企业 ID |
125
+ | `corpSecret` | string | 应用 Secret |
126
+ | `agentId` | number | 应用 AgentId |
127
+ | `apiBaseUrl` | string | 企微 API 地址(默认 `https://qyapi.weixin.qq.com`) |
128
+ | `webhookPath` | string | 企微回调路径(默认 `/wecom`) |
129
+
130
+ ### 环境变量
131
+
132
+ 也可以通过环境变量配置(无配置文件时使用):
133
+
134
+ | 环境变量 | 对应字段 |
135
+ |----------|---------|
136
+ | `RELAY_AUTH_TOKEN` | `authToken` |
137
+ | `RELAY_HOST` | `host` |
138
+ | `RELAY_PORT` | `port` |
139
+ | `WECOM_CORP_ID` | `accounts.default.corpId` + `receiveId` |
140
+ | `WECOM_CORP_SECRET` | `accounts.default.corpSecret` |
141
+ | `WECOM_AGENT_ID` | `accounts.default.agentId` |
142
+ | `WECOM_TOKEN` | `accounts.default.token` |
143
+ | `WECOM_AES_KEY` | `accounts.default.encodingAESKey` |
144
+ | `WECOM_WEBHOOK_PATH` | `accounts.default.webhookPath` |
145
+
146
+ ## HTTP 端点
147
+
148
+ | 方法 | 路径 | 说明 |
149
+ |------|------|------|
150
+ | GET/POST | `/{webhookPath}` | 企微回调(GET 验证 URL,POST 接收消息) |
151
+ | POST | `/webhook` | 接收客户端响应,代调企微 API 发送 |
152
+ | GET | `/health` | 健康检查,返回连接状态 |
153
+
154
+ ## 安全特性
155
+
156
+ - **认证密钥**:WebSocket 连接需提供 `authToken`(constant-time 比较,防时序攻击)
157
+ - **认证超时**:连接后 10 秒未认证自动断开
158
+ - **心跳检测**:每 30 秒 ping,90 秒无响应断开
159
+ - **速率限制**:每分钟最多 120 条消息
160
+ - **请求体限制**:HTTP/WebSocket 消息最大 1MB
161
+ - **Session 校验**:webhook 响应必须携带有效 session_id
162
+ - **后连接顶替**:同一账户新连接自动断开旧连接
163
+ - **企微签名验证**:回调请求经过签名校验,防伪造
164
+
165
+ ## 多账户配置
166
+
167
+ 支持同时代理多个企微自建应用:
168
+
169
+ ```json
170
+ {
171
+ "authToken": "shared-auth-token",
172
+ "accounts": {
173
+ "app1": {
174
+ "token": "token-1",
175
+ "encodingAESKey": "key-1",
176
+ "receiveId": "ww_corp_1",
177
+ "corpId": "ww_corp_1",
178
+ "corpSecret": "secret-1",
179
+ "agentId": 1000002,
180
+ "webhookPath": "/wecom/app1"
181
+ },
182
+ "app2": {
183
+ "token": "token-2",
184
+ "encodingAESKey": "key-2",
185
+ "receiveId": "ww_corp_2",
186
+ "corpId": "ww_corp_2",
187
+ "corpSecret": "secret-2",
188
+ "agentId": 1000003,
189
+ "webhookPath": "/wecom/app2"
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## 协议兼容性
196
+
197
+ 兼容 [lsbot](https://github.com/ruilisi/lsbot) relay 协议。extension 客户端也可以连接 `bot.lingti.com` 公共中继服务(注意共享 IP 和凭证信任问题)。
198
+
199
+ ## 反向代理与 HTTPS
200
+
201
+ ### 有域名(推荐)
202
+
203
+ 用 Caddy 自动签发 Let's Encrypt 证书:
204
+
205
+ ```
206
+ your.domain.com {
207
+ reverse_proxy localhost:9080
208
+ }
209
+ ```
210
+
211
+ extension 配置:
212
+ ```json
213
+ {
214
+ "wsRelayUrl": "wss://your.domain.com/ws",
215
+ "wsRelayWebhookUrl": "https://your.domain.com/webhook"
216
+ }
217
+ ```
218
+
219
+ ### 无域名(纯 IP + 自签证书)
220
+
221
+ 用 Caddy 自签证书,加密传输但不防中间人:
222
+
223
+ ```
224
+ :443 {
225
+ tls internal
226
+ reverse_proxy localhost:9080
227
+ }
228
+ ```
229
+
230
+ extension 配置需加 `wsRelayInsecure: true`:
231
+ ```json
232
+ {
233
+ "wsRelayUrl": "wss://123.45.67.89/ws",
234
+ "wsRelayWebhookUrl": "https://123.45.67.89/webhook",
235
+ "wsRelayInsecure": true
236
+ }
237
+ ```
238
+
239
+ > **安全说明**:自签证书场景下,传输通道已加密,但无法验证服务器身份。wecom_raw 模式下对话内容本身仍由企微 AES-256-CBC 加密,中间人只能看到密文。
240
+
241
+ ### Nginx 方案
242
+
243
+ ```nginx
244
+ server {
245
+ listen 443 ssl;
246
+ server_name your.domain.com; # 或 _(纯 IP)
247
+
248
+ ssl_certificate /path/to/cert.pem;
249
+ ssl_certificate_key /path/to/key.pem;
250
+
251
+ location / {
252
+ proxy_pass http://127.0.0.1:9080;
253
+ proxy_http_version 1.1;
254
+ proxy_set_header Upgrade $http_upgrade;
255
+ proxy_set_header Connection "upgrade";
256
+ proxy_set_header Host $host;
257
+ proxy_read_timeout 86400s;
258
+ }
259
+ }
260
+ ```
261
+
262
+ > `proxy_read_timeout` 设大值避免 WebSocket 长连接被 nginx 超时断开。
263
+
264
+ ## 部署建议
265
+
266
+ - 可使用 systemd 或 pm2 管理进程
267
+ - 日志输出到 stdout/stderr,可接入日志收集系统