feat: 支持 HTTP 回调和 WebSocket 长连接两种模式
新增功能: - 通过 FEISHU_MODE 配置切换模式 (http/websocket) - 安装飞书 SDK (@larksuiteoapi/node-sdk) - WebSocket 模式支持内网部署(无需公网 IP) - 新增 WEBSOCKET.md 配置指南 更新: - README.md 添加两种模式说明 - .env.example 添加 FEISHU_MODE 配置 - 健康检查返回当前模式信息
This commit is contained in:
@@ -4,6 +4,11 @@ FEISHU_APP_SECRET=xxxxxxxxxxxxxx
|
|||||||
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxx
|
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxx
|
||||||
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxx
|
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
# 飞书事件接收模式:http (HTTP 回调) 或 websocket (WebSocket 长连接)
|
||||||
|
# HTTP 回调:需要公网 IP/域名,配置简单
|
||||||
|
# WebSocket:不需要公网 IP,内网可用,需要飞书 SDK
|
||||||
|
FEISHU_MODE=http
|
||||||
|
|
||||||
# 七牛云配置(可选,也可通过卡片配置)
|
# 七牛云配置(可选,也可通过卡片配置)
|
||||||
QINIU_ACCESS_KEY=YOUR_ACCESS_KEY
|
QINIU_ACCESS_KEY=YOUR_ACCESS_KEY
|
||||||
QINIU_SECRET_KEY=YOUR_SECRET_KEY
|
QINIU_SECRET_KEY=YOUR_SECRET_KEY
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -46,13 +46,29 @@
|
|||||||
|
|
||||||
### 3. 配置事件订阅
|
### 3. 配置事件订阅
|
||||||
|
|
||||||
|
**方式一:HTTP 回调(默认)**
|
||||||
|
|
||||||
1. 进入"事件订阅"页面
|
1. 进入"事件订阅"页面
|
||||||
2. 开启"启用事件订阅"
|
2. 选择 **"HTTP 回调"** 方式
|
||||||
3. 填写请求地址:`https://your-domain.com/feishu/event`
|
3. 开启"启用事件订阅"
|
||||||
|
4. 填写请求地址:`https://your-domain.com/feishu/event`
|
||||||
|
5. 配置订阅事件:
|
||||||
|
- `im.message.receive_v1` - 接收消息
|
||||||
|
6. 保存后复制 Verification Token 和 Encrypt Key
|
||||||
|
|
||||||
|
**方式二:WebSocket 长连接(内网推荐)**
|
||||||
|
|
||||||
|
1. 进入"事件订阅"页面
|
||||||
|
2. 选择 **"WebSocket 长连接"** 方式
|
||||||
|
3. 开启"启用事件订阅"
|
||||||
4. 配置订阅事件:
|
4. 配置订阅事件:
|
||||||
- `im.message.receive_v1` - 接收消息
|
- `im.message.receive_v1` - 接收消息
|
||||||
- `im.message.group_at_msg.receive_v1` - 群组 @ 消息(可选)
|
|
||||||
5. 保存后复制 Verification Token 和 Encrypt Key
|
5. 保存后复制 Verification Token 和 Encrypt Key
|
||||||
|
6. 在 `.env` 中设置 `FEISHU_MODE=websocket`
|
||||||
|
|
||||||
|
> **💡 提示:** WebSocket 模式不需要公网 IP,适合内网部署。
|
||||||
|
>
|
||||||
|
> 详细配置请查看 [`WEBSOCKET.md`](./WEBSOCKET.md)
|
||||||
|
|
||||||
### 4. 配置机器人
|
### 4. 配置机器人
|
||||||
|
|
||||||
@@ -168,6 +184,11 @@ FEISHU_APP_SECRET=xxxxxxxxxxxxxx
|
|||||||
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxx
|
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxx
|
||||||
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxx
|
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
# 飞书事件接收模式:http (HTTP 回调) 或 websocket (WebSocket 长连接)
|
||||||
|
# - http: 需要公网 IP/域名,配置简单
|
||||||
|
# - websocket: 不需要公网 IP,内网可用
|
||||||
|
FEISHU_MODE=http
|
||||||
|
|
||||||
# 七牛云配置(可选,也可通过卡片配置)
|
# 七牛云配置(可选,也可通过卡片配置)
|
||||||
QINIU_ACCESS_KEY=xxxxxxxxxxxxxx
|
QINIU_ACCESS_KEY=xxxxxxxxxxxxxx
|
||||||
QINIU_SECRET_KEY=xxxxxxxxxxxxxx
|
QINIU_SECRET_KEY=xxxxxxxxxxxxxx
|
||||||
@@ -241,7 +262,8 @@ qiniu-feishu-bot/
|
|||||||
├── README.md # 项目说明
|
├── README.md # 项目说明
|
||||||
├── DEPLOY.md # 详细部署指南(Linux/macOS/Windows)
|
├── DEPLOY.md # 详细部署指南(Linux/macOS/Windows)
|
||||||
├── WINDOWS.md # Windows 专用指南
|
├── WINDOWS.md # Windows 专用指南
|
||||||
└── NGINX.md # Nginx 反向代理部署指南
|
├── NGINX.md # Nginx 反向代理部署指南
|
||||||
|
└── WEBSOCKET.md # WebSocket 长连接模式配置指南
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
213
WEBSOCKET.md
Normal file
213
WEBSOCKET.md
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
# WebSocket 长连接模式配置指南
|
||||||
|
|
||||||
|
## 📡 两种模式对比
|
||||||
|
|
||||||
|
| 特性 | HTTP 回调 | WebSocket 长连接 |
|
||||||
|
|------|----------|-----------------|
|
||||||
|
| 公网 IP | ✅ 需要 | ❌ 不需要 |
|
||||||
|
| 域名 | ✅ 需要 | ❌ 不需要 |
|
||||||
|
| HTTPS | ✅ 推荐 | ❌ 不需要 |
|
||||||
|
| 内网部署 | ❌ 困难 | ✅ 简单 |
|
||||||
|
| 实时性 | 好 | 更好 |
|
||||||
|
| 配置复杂度 | 简单 | 中等 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 配置 WebSocket 模式
|
||||||
|
|
||||||
|
### 1️⃣ 飞书开放平台配置
|
||||||
|
|
||||||
|
1. 访问 https://open.feishu.cn/
|
||||||
|
2. 进入你的应用管理页面
|
||||||
|
3. 点击"事件订阅"
|
||||||
|
4. **选择"WebSocket 长连接"方式**
|
||||||
|
5. 开启"启用事件订阅"
|
||||||
|
6. 添加订阅事件:
|
||||||
|
- `im.message.receive_v1` - 接收消息
|
||||||
|
7. 保存并复制:
|
||||||
|
- Verification Token
|
||||||
|
- Encrypt Key
|
||||||
|
|
||||||
|
### 2️⃣ 修改配置文件
|
||||||
|
|
||||||
|
编辑 `.env` 文件:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# 飞书配置
|
||||||
|
FEISHU_APP_ID=cli_xxxxxxxxxx
|
||||||
|
FEISHU_APP_SECRET=xxxxxxxxxxxxxx
|
||||||
|
FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxx
|
||||||
|
FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
# 设置为 WebSocket 模式
|
||||||
|
FEISHU_MODE=websocket
|
||||||
|
|
||||||
|
# 七牛云配置
|
||||||
|
QINIU_ACCESS_KEY=xxxxxxxxxxxxxx
|
||||||
|
QINIU_SECRET_KEY=xxxxxxxxxxxxxx
|
||||||
|
QINIU_BUCKET=your-bucket-name
|
||||||
|
QINIU_REGION=z0
|
||||||
|
QINIU_DOMAIN=https://your-cdn.com
|
||||||
|
|
||||||
|
# 服务配置
|
||||||
|
PORT=3030
|
||||||
|
NODE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3️⃣ 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/qiniu-feishu-bot
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4️⃣ 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 PM2
|
||||||
|
pm2 restart qiniu-bot
|
||||||
|
|
||||||
|
# 或直接启动
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5️⃣ 查看日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PM2 日志
|
||||||
|
pm2 logs qiniu-bot
|
||||||
|
|
||||||
|
# 应该看到:
|
||||||
|
# 🚀 七牛云上传机器人启动 (WebSocket 长连接模式)
|
||||||
|
# 📡 WebSocket 已启动
|
||||||
|
# ✅ WebSocket 连接成功
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 故障排查
|
||||||
|
|
||||||
|
### WebSocket 连接失败
|
||||||
|
|
||||||
|
**检查配置:**
|
||||||
|
```bash
|
||||||
|
# 验证 .env 配置
|
||||||
|
cat .env | grep FEISHU
|
||||||
|
|
||||||
|
# 确认 FEISHU_MODE=websocket
|
||||||
|
```
|
||||||
|
|
||||||
|
**查看日志:**
|
||||||
|
```bash
|
||||||
|
pm2 logs qiniu-bot --lines 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见错误:**
|
||||||
|
|
||||||
|
1. **验证失败**
|
||||||
|
- 检查 Verification Token 是否正确
|
||||||
|
- 检查 Encrypt Key 是否正确
|
||||||
|
|
||||||
|
2. **连接被拒绝**
|
||||||
|
- 检查防火墙是否允许出站连接
|
||||||
|
- 确认服务器能访问外网
|
||||||
|
|
||||||
|
3. **认证失败**
|
||||||
|
- 检查 App ID 和 App Secret
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 监控 WebSocket 状态
|
||||||
|
|
||||||
|
### 健康检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3030/health
|
||||||
|
```
|
||||||
|
|
||||||
|
返回:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"timestamp": "2026-03-05T08:00:00.000Z",
|
||||||
|
"mode": "websocket",
|
||||||
|
"port": 3030
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 连接状态
|
||||||
|
|
||||||
|
查看 PM2 日志中的连接状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pm2 logs qiniu-bot | grep -E "(WebSocket|连接|open|close)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 切换模式
|
||||||
|
|
||||||
|
### 从 HTTP 切换到 WebSocket
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 修改 .env
|
||||||
|
nano .env
|
||||||
|
# 设置 FEISHU_MODE=websocket
|
||||||
|
|
||||||
|
# 2. 重启服务
|
||||||
|
pm2 restart qiniu-bot
|
||||||
|
|
||||||
|
# 3. 验证
|
||||||
|
pm2 logs qiniu-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从 WebSocket 切换到 HTTP
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 修改 .env
|
||||||
|
nano .env
|
||||||
|
# 设置 FEISHU_MODE=http
|
||||||
|
|
||||||
|
# 2. 重启服务
|
||||||
|
pm2 restart qiniu-bot
|
||||||
|
|
||||||
|
# 3. 在飞书开放平台改回 HTTP 回调方式
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 最佳实践
|
||||||
|
|
||||||
|
### WebSocket 模式适用场景
|
||||||
|
|
||||||
|
- ✅ 内网服务器(无公网 IP)
|
||||||
|
- ✅ 开发测试环境
|
||||||
|
- ✅ 不想配置域名和 HTTPS
|
||||||
|
- ✅ 需要更好的实时性
|
||||||
|
|
||||||
|
### HTTP 回调模式适用场景
|
||||||
|
|
||||||
|
- ✅ 云服务器(有公网 IP)
|
||||||
|
- ✅ 生产环境
|
||||||
|
- ✅ 已有域名和 HTTPS
|
||||||
|
- ✅ 需要更可控的连接管理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 注意事项
|
||||||
|
|
||||||
|
1. **WebSocket 长连接会保持在线状态**
|
||||||
|
- 确保服务器网络稳定
|
||||||
|
- 断线会自动重连(5 秒间隔)
|
||||||
|
|
||||||
|
2. **两种方式不能同时使用**
|
||||||
|
- 通过 `FEISHU_MODE` 配置选择
|
||||||
|
- 飞书开放平台也要对应配置
|
||||||
|
|
||||||
|
3. **健康检查始终可用**
|
||||||
|
- 无论哪种模式,`/health` 端点都工作
|
||||||
|
- 可用于监控服务状态
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🍙 祝你使用愉快!**
|
||||||
164
package-lock.json
generated
164
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@larksuiteoapi/node-sdk": "^1.5.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2"
|
"express": "^4.18.2"
|
||||||
@@ -17,6 +18,94 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@larksuiteoapi/node-sdk": {
|
||||||
|
"version": "1.59.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@larksuiteoapi/node-sdk/-/node-sdk-1.59.0.tgz",
|
||||||
|
"integrity": "sha512-sBpkruTvZDOxnVtoTbepWKRX0j1Y1ZElQYu0x7+v088sI9pcpbVp6ZzCGn62dhrKPatzNyCJyzYCPXPYQWccrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "~1.13.3",
|
||||||
|
"lodash.identity": "^3.0.0",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"lodash.pickby": "^4.6.0",
|
||||||
|
"protobufjs": "^7.2.6",
|
||||||
|
"qs": "^6.14.2",
|
||||||
|
"ws": "^8.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/aspromise": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/base64": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/codegen": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/eventemitter": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/fetch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.1",
|
||||||
|
"@protobufjs/inquire": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/float": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/inquire": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/path": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/pool": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/utf8": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
|
||||||
|
"integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
@@ -563,6 +652,30 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.identity": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.merge": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.pickby": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -686,6 +799,30 @@
|
|||||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/protobufjs": {
|
||||||
|
"version": "7.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
|
||||||
|
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.2",
|
||||||
|
"@protobufjs/base64": "^1.1.2",
|
||||||
|
"@protobufjs/codegen": "^2.0.4",
|
||||||
|
"@protobufjs/eventemitter": "^1.1.0",
|
||||||
|
"@protobufjs/fetch": "^1.1.0",
|
||||||
|
"@protobufjs/float": "^1.0.2",
|
||||||
|
"@protobufjs/inquire": "^1.1.0",
|
||||||
|
"@protobufjs/path": "^1.1.2",
|
||||||
|
"@protobufjs/pool": "^1.1.0",
|
||||||
|
"@protobufjs/utf8": "^1.1.0",
|
||||||
|
"@types/node": ">=13.7.0",
|
||||||
|
"long": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@@ -924,6 +1061,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||||
|
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
@@ -950,6 +1093,27 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||||
|
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"author": "饭团 🍙",
|
"author": "饭团 🍙",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@larksuiteoapi/node-sdk": "^1.5.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2"
|
"express": "^4.18.2"
|
||||||
|
|||||||
95
src/index.js
95
src/index.js
@@ -8,6 +8,7 @@
|
|||||||
* 2. 支持交互式卡片上传
|
* 2. 支持交互式卡片上传
|
||||||
* 3. 支持命令触发上传
|
* 3. 支持命令触发上传
|
||||||
* 4. 配置管理
|
* 4. 配置管理
|
||||||
|
* 5. 支持 HTTP 回调和 WebSocket 长连接两种模式
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
@@ -21,8 +22,14 @@ const { QiniuUploader } = require('./qiniu-uploader');
|
|||||||
const { UploadCard } = require('./cards/upload-card');
|
const { UploadCard } = require('./cards/upload-card');
|
||||||
const { ConfigCard } = require('./cards/config-card');
|
const { ConfigCard } = require('./cards/config-card');
|
||||||
|
|
||||||
|
// 飞书 SDK(WebSocket 模式)
|
||||||
|
const { Api, eventSubscription } = require('@larksuiteoapi/node-sdk');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3030;
|
||||||
|
|
||||||
|
// 运行模式:'http' 或 'websocket'
|
||||||
|
const MODE = (process.env.FEISHU_MODE || 'http').toLowerCase();
|
||||||
|
|
||||||
// 中间件
|
// 中间件
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -299,13 +306,89 @@ app.post('/feishu/event', handleFeishuEvent);
|
|||||||
|
|
||||||
// 健康检查
|
// 健康检查
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
mode: MODE,
|
||||||
|
port: PORT
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============ 启动服务 ============
|
// ============ 启动服务 ============
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
function startHTTPMode() {
|
||||||
log(`🚀 七牛云上传机器人启动`);
|
app.listen(PORT, () => {
|
||||||
|
log(`🚀 七牛云上传机器人启动 (HTTP 回调模式)`);
|
||||||
log(`📍 端口:${PORT}`);
|
log(`📍 端口:${PORT}`);
|
||||||
log(`🔗 事件地址:https://your-domain.com/feishu/event`);
|
log(`🔗 事件地址:http://your-domain.com:${PORT}/feishu/event`);
|
||||||
});
|
log(`💡 提示:在飞书开放平台配置事件订阅地址为上述地址`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startWebSocketMode() {
|
||||||
|
log(`🚀 七牛云上传机器人启动 (WebSocket 长连接模式)`);
|
||||||
|
log(`💡 提示:在飞书开放平台选择 "WebSocket 长连接" 方式`);
|
||||||
|
|
||||||
|
// 创建飞书客户端
|
||||||
|
const client = new Api({
|
||||||
|
appId: process.env.FEISHU_APP_ID,
|
||||||
|
appSecret: process.env.FEISHU_APP_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建 WebSocket 长连接
|
||||||
|
const ws = eventSubscription({
|
||||||
|
appId: process.env.FEISHU_APP_ID,
|
||||||
|
appSecret: process.env.FEISHU_APP_SECRET,
|
||||||
|
encryptKey: process.env.FEISHU_ENCRYPT_KEY,
|
||||||
|
verificationToken: process.env.FEISHU_VERIFICATION_TOKEN,
|
||||||
|
logLevel: 'info',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听消息事件
|
||||||
|
ws.on('im.message.receive_v1', async (data) => {
|
||||||
|
log('收到消息事件');
|
||||||
|
await handleMessage(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听连接状态
|
||||||
|
ws.on('open', () => {
|
||||||
|
log('✅ WebSocket 连接成功');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
log('❌ WebSocket 连接关闭,5 秒后重连...');
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
ws.start();
|
||||||
|
} catch (e) {
|
||||||
|
log('重连失败:', e.message);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', (error) => {
|
||||||
|
log('❌ WebSocket 错误:', error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动 WebSocket 连接
|
||||||
|
try {
|
||||||
|
ws.start();
|
||||||
|
log('📡 WebSocket 已启动');
|
||||||
|
} catch (error) {
|
||||||
|
log('❌ WebSocket 启动失败:', error.message);
|
||||||
|
log('💡 请检查飞书配置是否正确');
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP 服务器仍然运行(用于健康检查)
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
log(`📍 健康检查端口:${PORT}`);
|
||||||
|
log(`🔗 健康检查地址:http://localhost:${PORT}/health`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置启动对应模式
|
||||||
|
if (MODE === 'websocket') {
|
||||||
|
startWebSocketMode();
|
||||||
|
} else {
|
||||||
|
startHTTPMode();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user