部署拓扑
一个 NestJS 应用 + 一个 React 前端 + 一个 VitePress 文档站,背后是 MySQL、MongoDB、Redis。这一页说明生产环境如何组装、如何发布、如何排障。
进程拓扑
Internet
│
▼
┌─────────────────┐
│ Nginx :80/443 │
└────────┬────────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
/api/* /socket.io/* /docs/*
│ │ │
▼ ▼ ▼
NestJS :3000 NestJS :3000 /var/www/hushwork-docs
(PM2 cluster) (Redis adapter)
│
├── MySQL
├── MongoDB(副本集 rs0)
└── Redis| 组件 | 端口 | 说明 |
|---|---|---|
| Nginx | 80 / 443 | TLS 终止、反代、静态资源 |
| hushwork API | 3000 | PM2 单实例(fork),重启约 1 秒 |
| 前端(开发) | 5173 | Vite dev server |
| 前端(生产) | — | 构建产物由 Nginx 托管 |
| 文档站 | — | 构建产物发布到 /var/www/hushwork-docs |
| MySQL | 3306 | 仅内网 |
| MongoDB | 27017 | 副本集,仅内网 |
| Redis | 6379 | 仅内网 |
PM2 配置
ecosystem.config.js:
js
module.exports = {
apps: [{
name: "hushwork",
script: "dist/apps/hushwork/main.js",
instances: 1,
exec_mode: "fork",
env_file: ".env",
output: "./logs/out.log",
error: "./logs/error.log",
kill_timeout: 5000
}]
};关键点:
- 单实例(fork):重启秒级完成,不存在新旧版本同时服务
- 不要随手打开
wait_ready:应用没有调用process.send('ready'),PM2 会白等满listen_timeout;再配合多实例滚动重启,就会出现"一次 401、一次 404"的新旧版本混跑。要做多实例零停机,必须同时在main.ts补 ready 信号 - 多实例前提:Socket.IO 需使用
RedisIoAdapter,广播才能跨实例 - 日志:
logs/out.log/logs/error.log
常用命令:
bash
pm2 start ecosystem.config.js
pm2 restart hushwork # 单实例:秒级完成
pm2 logs hushwork
pm2 statusNginx 路由
以 hushwork.ai 为例:
| 路径 | 去向 |
|---|---|
/api/ | 127.0.0.1:3000 |
/api/api-docs/ | Swagger |
/socket.io/ | 127.0.0.1:3000(WebSocket upgrade) |
/docs/ | /var/www/hushwork-docs/(VitePress) |
/admin/ | 管理端静态资源 |
/ | 前端(开发模式代理 5173;生产模式静态目录) |
文档站点的关键配置:
nginx
location = /docs { return 301 /docs/; }
location /docs/ {
alias /var/www/hushwork-docs/;
index index.html;
try_files $uri $uri/ $uri.html =404;
error_page 404 /docs/404.html;
}$uri.html 回退是 cleanUrls: true 生效的前提。
构建与发布
后端
bash
npm install
npm run build
pm2 reload hushwork前端
bash
npm run frontend:build
# 产物发布到 Nginx 托管目录文档
bash
npm run docs:api # 若 API 有变更:导出 swagger + 生成 API 文档
npm run docs:build # VitePress 构建
npm run docs:deploy # rsync 到 /var/www/hushwork-docs
docs:build只写入site/.vitepress/dist,线上不会自动更新;必须执行docs:deploy。
环境变量
核心变量(见 .env):
| 变量 | 作用 | 默认 |
|---|---|---|
NODE_ENV | 运行环境 | development |
APP_PORT | 应用端口 | 3000 |
APP_ROUTE_PREFIX | 路由前缀 | api |
| MySQL 连接 | 主机 / 端口 / 库 / 用户 / 密码 | — |
| MongoDB 连接 | URI | — |
| Redis 连接 | 主机 / 端口 / 密码 | — |
| LLM | API Key / Base URL / 模型 | — |
| COS | 对象存储配置 | — |
| 支付 | 商户号 / 密钥 / 证书 | — |
配置读取:apps/hushwork/src/config/configuration.ts 与 config.*.ts。
数据库要求
MongoDB 必须是副本集
跨层事务依赖 MongoDB 事务,因此必须运行副本集:
javascript
// mongod.conf
replication:
replSetName: rs0
security:
keyFile: /etc/mongo/keyfile
authorization: enabled初始化:
bash
mongosh --eval 'rs.initiate()'检查:
bash
node scripts/mongo-promote-replset.js
# 期望输出 rs0 / PRIMARY详细步骤见 docs/ops/mongo-replset-upgrade-runbook.md。
健康检查
| 检查 | 命令 / 地址 |
|---|---|
| 应用存活 | GET /api/a2a/agent-card → 200 |
| Swagger | GET /api/api-docs/ |
| MongoDB | node scripts/mongo-promote-replset.js |
| 状态层一致性 | npx jest ... --testPathPatterns="state-layer" |
| 整条业务 | npm run case:reference -- --commit |
发布检查清单
- [ ]
npm run build通过 - [ ] 单测与关键 e2e 通过
- [ ] 数据库迁移已执行
- [ ] 环境变量已更新
- [ ]
pm2 reload hushwork成功 - [ ]
/api/a2a/agent-card返回 200 - [ ] 日志无新增 error
- [ ] 如 API 变更:
npm run docs:api && npm run docs:build && npm run docs:deploy
常见故障
| 症状 | 可能原因 | 排查 |
|---|---|---|
| 502 Bad Gateway | 应用未启动 / 端口不对 | pm2 status、ss -ltnp | grep 3000 |
| 所有 API 404 | APP_ROUTE_PREFIX 不一致 | 检查环境变量与 Nginx 路径 |
| 事务报错 | MongoDB 不是副本集 | rs.status() |
| WebSocket 连不上 | Nginx 缺少 Upgrade 头 | 检查 proxy_set_header Connection |
| 文档 404 | 未执行 docs:deploy / 权限不足 | curl -I /docs/、检查 alias 与目录权限 |
| 文档旧页面 | 只 build 未 deploy | 重新 npm run docs:deploy |