外观
部署
字数
1351 字
预计阅读
6 分钟
说明
目前的部署方式是编译产物 + 反向代理。Docker 镜像和 docker-compose 方案会在以后的版本中提供。
构建
bash
pnpm i --frozen-lockfile
pnpm -r build产物位置:
- 服务端:
apps/server/dist/,工作目录为apps/server,启动命令是node dist/main.js - 前端:
apps/web/dist/,纯静态文件,由反向代理托管
生产环境变量
在服务器上至少设置这些项。普通配置写在 apps/server/.env,账号和密钥写在 apps/server/.env.local(不要两边都写,同一个变量以 .env 为准,见环境变量)。也可以全部用进程环境变量,它的优先级最高:
ini
# apps/server/.env
NODE_ENV=production
DB_HOST=…
DB_NAME=…
REDIS_HOST=…
TRUST_PROXY=反向代理的地址 # 不要填整个内网网段
SWAGGER_ENABLED=false # 生产环境建议关闭接口文档
# apps/server/.env.local
APP_SECRET=至少32位随机字符串 # 泄露后要更换,更换后已加密的第三方密钥无法解密
DB_USER=…
DB_PASSWORD=…
REDIS_PASSWORD=…首次部署的数据初始化:
bash
pnpm db:migrate # 先执行迁移
pnpm db:seed # 再写入种子数据警告
生产环境不要用 db:reset,它会删除所有表。(NODE_ENV=production 时这个命令本身也会拒绝执行。)
反向代理(nginx)
服务端只监听 127.0.0.1,由反向代理对外提供访问。需要配置这几个路径:
| 路径 | 转发到 | 说明 |
|---|---|---|
/ | apps/web/dist/ | SPA 页面,用 try_files $uri /index.html 处理前端路由 |
/api/ | http://127.0.0.1:3000 | 接口 |
/socket.io/ | http://127.0.0.1:3000 | WebSocket,需要设置 Upgrade 和 Connection 头 |
/files/ | 本地存储的 public/ 目录 | 公开文件(头像、公告图片)直链访问 |
示例:
nginx
server {
listen 443 ssl;
server_name admin.example.com;
root /srv/qiwu/apps/web/dist;
location / {
try_files $uri /index.html;
add_header Content-Security-Policy "…与 SPA_CSP 一致…" always;
add_header X-Content-Type-Options nosniff always;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr; # 覆盖,不要追加
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /files/ {
alias /srv/qiwu/apps/server/data/upload/public/;
add_header X-Content-Type-Options nosniff always;
}
}要点:
X-Forwarded-For要用$remote_addr覆盖,不能追加客户端传来的值,否则 IP 黑名单、限流都可能被绕过。前端的内容安全策略(CSP)只在
apps/web/csp.ts中定义一处,nginx 下发的值必须和它完全一致。打印当前值的命令:bashnode -e "import('./apps/web/csp.ts').then((m) => console.log(m.SPA_CSP))"私有目录
upload/private/永远不要做静态映射,私有文件只能通过鉴权接口下载。
使用 S3 存储时
如果在 系统管理 → 文件管理 → 存储配置 里启用了 S3(阿里云 OSS、腾讯云 COS、Cloudflare R2、MinIO 等兼容服务),不管有没有开启浏览器直传,都要做下面的设置。存在 S3 上的私有文件,下载和预览都由浏览器跟随跳转,直接从 S3 读取(60 秒有效的临时链接):
构建前端和打印 CSP 时,都用
CSP_CONNECT_SRC声明 S3 的地址,再把打印出的值填进 nginx 的Content-Security-Policy(构建产物里不含 CSP,浏览器只认 nginx 下发的这一份):bashCSP_CONNECT_SRC=https://bucket.s3.example.com pnpm --filter @qiwu/web build CSP_CONNECT_SRC=https://bucket.s3.example.com node -e "import('./apps/web/csp.ts').then((m) => console.log(m.SPA_CSP))"powershell$env:CSP_CONNECT_SRC = "https://bucket.s3.example.com" pnpm --filter @qiwu/web build node -e "import('./apps/web/csp.ts').then((m) => console.log(m.SPA_CSP))" Remove-Item Env:CSP_CONNECT_SRC存储桶的 CORS 规则要允许本站源的
PUT和GET请求。给
staging/目录设置一条生命周期规则(比如 1 天后过期),用来清理上传了但没有确认的文件。自建服务如果用了非标准端口(比如 MinIO 的 9000),要在服务端环境变量中登记:
OUTBOUND_S3_PORTS=9000。
OAuth2 与单点登录
要让第三方系统通过单点登录(OAuth2)接入时,部署上要注意两点:
/sso要回退到index.html。授权同意页/sso是前端页面,第三方会把用户的浏览器直接带到https://<后台域名>/sso?…。上面 nginx 示例里location /的这一行已经包含它,不用另外配置:nginxtry_files $uri /index.html;如果你的配置只对部分路径做回退,要把
/sso加上,否则用户打开授权链接会看到 404。TRUST_PROXY要设对。这几个接口按来源 IP 限流,IP 取自TRUST_PROXY信任的代理传来的X-Forwarded-For:接口 每个 IP 每分钟 GET/POST /api/oauth2/authorize(同意页调用)各 120 次 POST /api/oauth2/token600 次 POST /api/oauth2/introspect、/api/oauth2/revoke各 1200 次 默认值
loopback只信任本机。nginx 和服务端在同一台机器上时不用改;反向代理或负载均衡在别的机器上时,要把TRUST_PROXY设成它的地址。设错了,所有用户和第三方都会算在代理这一个 IP 上,很快就会收到 429。
/api/oauth2/token、/introspect、/revoke、/userinfo 由第三方的后端直接调用,不需要配置跨域(CORS)。第三方登记的回调地址在生产环境必须是 https://。接入步骤见 OAuth2 接入指南。
单实例说明
v1 按单实例设计:限流计数存在进程内存中,实时推送也没有跨实例广播。需要多实例部署时,要加上 Socket.IO 的 Redis 适配器,并把限流改为 Redis 存储。