416 lines
10 KiB
Markdown
416 lines
10 KiB
Markdown
# 云端最小回调壳部署方案
|
||
|
||
## 一、目标
|
||
|
||
**阶段目标**:在备案域名上部署最小可用回调壳,使企业微信能完成 URL 校验与回调联调。
|
||
|
||
**最小功能范围**:
|
||
- ✅ `/api/wecom/callback` GET 校验(兼容 `signature`/`msg_signature`)
|
||
- ✅ `/api/wecom/callback` POST 密文消息回调(验签、解密、echo 回复)
|
||
- ✅ 结构化日志 + trace_id
|
||
- ✅ Nginx 反代 + HTTPS(Let's Encrypt)
|
||
- ⏸️ 数据库(可先不启用,但接口与配置要预留)
|
||
- ⏸️ Admin 后台(可先占位)
|
||
|
||
---
|
||
|
||
## 二、架构
|
||
|
||
```
|
||
企业微信 → HTTPS (443) → Nginx → Backend (8000)
|
||
↓
|
||
PostgreSQL (可选)
|
||
```
|
||
|
||
**服务清单**:
|
||
- `backend`: Python 3.12 + FastAPI + Uvicorn(最小回调壳)
|
||
- `nginx`: 反代 + HTTPS(Let's Encrypt)
|
||
- `db`: PostgreSQL 16(可选,先不启用)
|
||
|
||
---
|
||
|
||
## 三、环境变量配置
|
||
|
||
### 3.1 必需变量(`.env`)
|
||
|
||
```bash
|
||
# ============ Backend ============
|
||
API_HOST=0.0.0.0
|
||
API_PORT=8000
|
||
|
||
# Database(可选,先不启用)
|
||
DATABASE_URL=postgresql+asyncpg://wecom:wecom_secret@db:5432/wecom_ai
|
||
DATABASE_URL_SYNC=postgresql://wecom:wecom_secret@db:5432/wecom_ai
|
||
|
||
# JWT(admin 登录,可选)
|
||
JWT_SECRET=your-jwt-secret-change-in-production
|
||
JWT_ALGORITHM=HS256
|
||
JWT_EXPIRE_MINUTES=60
|
||
|
||
# WeCom Callback(必须,从企业微信管理后台获取)
|
||
WECOM_CORP_ID=你的企业ID
|
||
WECOM_AGENT_ID=你的应用AgentId
|
||
WECOM_SECRET=你的应用Secret(可选,用于主动发送消息)
|
||
WECOM_TOKEN=你的Token(必须与企微后台一致)
|
||
WECOM_ENCODING_AES_KEY=你的43位密钥(必须与企微后台一致)
|
||
|
||
# WeCom API
|
||
WECOM_API_BASE=https://qyapi.weixin.qq.com
|
||
WECOM_API_TIMEOUT=10
|
||
WECOM_API_RETRIES=2
|
||
|
||
# Log
|
||
LOG_LEVEL=INFO
|
||
LOG_JSON=true
|
||
|
||
# ============ Nginx ============
|
||
# 域名(必须,备案域名)
|
||
DOMAIN=your-domain.com
|
||
|
||
# SSL(Let's Encrypt)
|
||
SSL_EMAIL=your-email@example.com
|
||
```
|
||
|
||
### 3.2 关键变量说明
|
||
|
||
| 变量 | 说明 | 来源 |
|
||
|------|------|------|
|
||
| `WECOM_TOKEN` | 企业微信回调 Token | 企微后台 → 应用 → 接收消息 → Token |
|
||
| `WECOM_ENCODING_AES_KEY` | 43 位 Base64 编码密钥 | 企微后台 → 应用 → 接收消息 → EncodingAESKey |
|
||
| `WECOM_CORP_ID` | 企业 ID | 企微后台 → 我的企业 → 企业信息 |
|
||
| `WECOM_AGENT_ID` | 应用 AgentId | 企微后台 → 应用管理 → 自建应用 → 应用详情 |
|
||
| `DOMAIN` | 备案域名 | 你的域名服务商 |
|
||
|
||
---
|
||
|
||
## 四、部署步骤
|
||
|
||
### 4.1 前置条件
|
||
|
||
1. **备案域名**:已备案且主体关联的域名(例如:`api.yourdomain.com`)
|
||
2. **服务器**:Linux(Ubuntu 20.04+ / CentOS 7+),公网 IP,开放 80/443 端口
|
||
3. **Docker**:已安装 Docker 和 docker-compose
|
||
4. **GitHub**:代码已推送到 GitHub(用于 CI/CD)
|
||
|
||
### 4.2 服务器初始化
|
||
|
||
```bash
|
||
# 1. 安装 Docker 和 docker-compose
|
||
curl -fsSL https://get.docker.com | sh
|
||
sudo usermod -aG docker $USER
|
||
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||
sudo chmod +x /usr/local/bin/docker-compose
|
||
|
||
# 2. 克隆项目(或通过 CI/CD 部署)
|
||
git clone https://github.com/your-org/wecom-ai-assistant.git
|
||
cd wecom-ai-assistant
|
||
|
||
# 3. 创建 .env 文件
|
||
cp .env.example .env
|
||
# 编辑 .env,填入上述必需变量
|
||
```
|
||
|
||
### 4.3 配置 Nginx + HTTPS
|
||
|
||
#### 方案 A:使用 Certbot(Let's Encrypt)
|
||
|
||
```bash
|
||
# 1. 安装 Certbot
|
||
sudo apt-get update
|
||
sudo apt-get install certbot python3-certbot-nginx
|
||
|
||
# 2. 先启动 HTTP 服务(用于验证)
|
||
docker-compose up -d backend
|
||
|
||
# 3. 配置 Nginx(临时 HTTP 配置)
|
||
# 编辑 deploy/nginx.conf,添加 server_name
|
||
# 然后运行:docker-compose up -d nginx
|
||
|
||
# 4. 获取 SSL 证书
|
||
sudo certbot --nginx -d your-domain.com -d www.your-domain.com --email your-email@example.com --agree-tos --non-interactive
|
||
|
||
# 5. 更新 nginx.conf,使用 Certbot 生成的配置
|
||
# Certbot 会自动修改 /etc/nginx/sites-available/default
|
||
# 将配置复制到 deploy/nginx.conf,或使用 volume 挂载
|
||
```
|
||
|
||
#### 方案 B:手动配置 Nginx + Let's Encrypt
|
||
|
||
创建 `deploy/nginx-ssl.conf`:
|
||
|
||
```nginx
|
||
events { worker_connections 1024; }
|
||
|
||
http {
|
||
upstream backend {
|
||
server backend:8000;
|
||
}
|
||
|
||
# HTTP → HTTPS 重定向
|
||
server {
|
||
listen 80;
|
||
server_name your-domain.com www.your-domain.com;
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
|
||
# HTTPS
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name your-domain.com www.your-domain.com;
|
||
|
||
# SSL 证书(Let's Encrypt)
|
||
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
|
||
# /api -> backend
|
||
location /api/ {
|
||
proxy_pass http://backend;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 30s;
|
||
}
|
||
|
||
# 健康检查
|
||
location /health {
|
||
proxy_pass http://backend/health;
|
||
access_log off;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
更新 `docker-compose.yml`:
|
||
|
||
```yaml
|
||
nginx:
|
||
image: nginx:alpine
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./deploy/nginx-ssl.conf:/etc/nginx/nginx.conf:ro
|
||
- /etc/letsencrypt:/etc/letsencrypt:ro # SSL 证书
|
||
depends_on:
|
||
- backend
|
||
```
|
||
|
||
### 4.4 启动服务
|
||
|
||
```bash
|
||
# 1. 构建镜像
|
||
docker-compose build backend
|
||
|
||
# 2. 启动服务(最小回调壳:只启动 backend + nginx)
|
||
docker-compose up -d backend nginx
|
||
|
||
# 3. 检查日志
|
||
docker-compose logs -f backend
|
||
```
|
||
|
||
### 4.5 验证服务
|
||
|
||
```bash
|
||
# 1. 检查服务状态
|
||
docker-compose ps
|
||
|
||
# 2. 检查健康检查
|
||
curl https://your-domain.com/health
|
||
|
||
# 3. 检查回调接口(应返回 400,因为缺少参数)
|
||
curl https://your-domain.com/api/wecom/callback
|
||
```
|
||
|
||
---
|
||
|
||
## 五、本地验证
|
||
|
||
### 5.1 本地启动最小回调壳
|
||
|
||
```bash
|
||
# 1. 启动后端(不启动 db/admin)
|
||
docker-compose up -d backend
|
||
|
||
# 2. 检查日志
|
||
docker-compose logs backend
|
||
|
||
# 3. 测试 GET 校验(模拟企业微信)
|
||
# 注意:需要正确的 signature/timestamp/nonce/echostr
|
||
curl "http://localhost:8000/api/wecom/callback?signature=xxx×tamp=123&nonce=abc&echostr=xxx"
|
||
```
|
||
|
||
### 5.2 本地测试 POST 回调
|
||
|
||
```bash
|
||
# 使用企业微信官方测试工具生成测试请求
|
||
# 或使用 curl 模拟(需要正确的签名和加密)
|
||
curl -X POST "http://localhost:8000/api/wecom/callback?msg_signature=xxx×tamp=123&nonce=abc" \
|
||
-H "Content-Type: application/xml" \
|
||
-d '<xml><Encrypt><![CDATA[加密内容]]></Encrypt></xml>'
|
||
```
|
||
|
||
### 5.3 验证日志格式
|
||
|
||
检查日志输出是否符合结构化日志格式:
|
||
|
||
```json
|
||
{
|
||
"timestamp": "2025-02-05T10:00:00Z",
|
||
"level": "INFO",
|
||
"message": "wecom verify success",
|
||
"trace_id": "abc123",
|
||
"echostr_length": 43
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、线上验证
|
||
|
||
### 6.1 企业微信后台配置
|
||
|
||
1. **登录企业微信管理后台**:https://work.weixin.qq.com
|
||
2. **进入应用设置**:应用管理 → 自建应用 → 选择你的应用
|
||
3. **配置回调 URL**:
|
||
- 接收消息服务器 URL:`https://your-domain.com/api/wecom/callback`
|
||
- Token:与 `.env` 中的 `WECOM_TOKEN` **完全一致**
|
||
- EncodingAESKey:与 `.env` 中的 `WECOM_ENCODING_AES_KEY` **完全一致**
|
||
- 消息加解密方式:**安全模式**
|
||
4. **点击保存**
|
||
|
||
### 6.2 验证 GET 校验
|
||
|
||
保存后,企业微信会立即发送 GET 请求验证。观察后端日志:
|
||
|
||
```bash
|
||
docker-compose logs -f backend
|
||
```
|
||
|
||
**成功日志**:
|
||
```
|
||
INFO: wecom verify success {"trace_id": "...", "echostr_length": 43}
|
||
```
|
||
|
||
**失败日志**:
|
||
```
|
||
WARNING: wecom verify failed {"trace_id": "...", "timestamp": "...", "nonce": "..."}
|
||
```
|
||
|
||
如果验证失败,检查:
|
||
- Token 是否一致
|
||
- EncodingAESKey 是否一致
|
||
- 域名是否可访问(`curl https://your-domain.com/api/wecom/callback`)
|
||
|
||
### 6.3 验证 POST 回调
|
||
|
||
1. **在企业微信中发送测试消息**:
|
||
- 打开企业微信客户端
|
||
- 找到你配置的应用
|
||
- 发送文本消息:`你好,测试一下`
|
||
|
||
2. **观察后端日志**:
|
||
```bash
|
||
docker-compose logs -f backend
|
||
```
|
||
|
||
**成功日志**:
|
||
```json
|
||
{
|
||
"timestamp": "2025-02-05T10:00:00Z",
|
||
"level": "INFO",
|
||
"message": "wecom message received",
|
||
"trace_id": "abc123",
|
||
"external_userid": "external_userid_xxx",
|
||
"msgid": "123456",
|
||
"msg_type": "text",
|
||
"content_summary": "你好,测试一下"
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"timestamp": "2025-02-05T10:00:01Z",
|
||
"level": "INFO",
|
||
"message": "wecom reply sent",
|
||
"trace_id": "abc123",
|
||
"external_userid": "external_userid_xxx",
|
||
"msgid": "123456",
|
||
"reply_summary": "已收到:你好,测试一下"
|
||
}
|
||
```
|
||
|
||
3. **检查企业微信客户端**:应收到回复:`已收到:你好,测试一下`
|
||
|
||
---
|
||
|
||
## 七、常见问题
|
||
|
||
### 7.1 GET 校验失败
|
||
|
||
**原因**:
|
||
- Token 不一致
|
||
- EncodingAESKey 不一致
|
||
- 签名算法错误
|
||
|
||
**解决**:
|
||
1. 检查 `.env` 中的 `WECOM_TOKEN` 和 `WECOM_ENCODING_AES_KEY`
|
||
2. 确保与企微后台配置**完全一致**(包括大小写、空格)
|
||
3. 重启后端:`docker-compose restart backend`
|
||
|
||
### 7.2 POST 回调失败
|
||
|
||
**原因**:
|
||
- 签名验证失败
|
||
- 解密失败
|
||
- XML 解析失败
|
||
|
||
**解决**:
|
||
1. 检查日志中的错误信息
|
||
2. 确认 EncodingAESKey 正确
|
||
3. 确认消息加解密方式为**安全模式**
|
||
|
||
### 7.3 HTTPS 证书问题
|
||
|
||
**原因**:
|
||
- Let's Encrypt 证书未正确配置
|
||
- 证书过期
|
||
|
||
**解决**:
|
||
1. 检查证书:`sudo certbot certificates`
|
||
2. 续期证书:`sudo certbot renew`
|
||
3. 重启 Nginx:`docker-compose restart nginx`
|
||
|
||
### 7.4 域名无法访问
|
||
|
||
**原因**:
|
||
- DNS 未解析
|
||
- 防火墙未开放 80/443 端口
|
||
- Nginx 配置错误
|
||
|
||
**解决**:
|
||
1. 检查 DNS:`nslookup your-domain.com`
|
||
2. 检查端口:`netstat -tlnp | grep -E '80|443'`
|
||
3. 检查 Nginx 日志:`docker-compose logs nginx`
|
||
|
||
---
|
||
|
||
## 八、下一步
|
||
|
||
完成最小回调壳部署后,按以下顺序逐步接入:
|
||
|
||
1. ✅ **最小回调壳**(当前阶段)
|
||
2. ⏭️ **数据库接入**(会话与消息入库)
|
||
3. ⏭️ **Admin 后台**(会话列表、工单、知识库)
|
||
4. ⏭️ **FAQ/RAG**(智能回复)
|
||
|
||
---
|
||
|
||
## 九、参考文档
|
||
|
||
- [企业微信回调配置](./wecom.md)
|
||
- [企业微信测试指南](./wecom-test-guide.md)
|
||
- [GitHub Actions CI/CD](../deploy/ci/github-actions.yml)
|