外观
阿里云公网 Nginx 反向代理与静态托管
本目录只有一个文件:ai-voice-public.conf,是阿里云公网节点 aliyun 上全部公网入口的 Nginx 配置。它同时承担四件事:
- HTTPS / WSS 终结:9 个域名共用
wangijun.com的 Let's Encrypt 通配证书,80 端口整体 301 跳 443; - 前端 SPA 静态托管:
callflow-webpage、callout-webpage、callai-webpage三个 Quasar 产物由本机直接发布,不回源、不走隧道; - API 与模型端点反代:
/api/等路径转发到 FRP 回环端口(12008~12013)或本机callflow-esl(9912); - 媒体只读发布:FreeSWITCH 录音目录以
alias只读暴露。
节点整体职责、部署与更新步骤见上级手册;FRP 回环端口清单见 ../frp/README.md 与 ../../wsl-docker/README.md; 跨主机调用矩阵与安全边界见部署网络拓扑与配置。
1. server 块总览
| 域名 | 类型 | 上游 / 根目录 |
|---|---|---|
全部 9 个域名(:80) | 跳转 | return 301 https://$host$request_uri |
callflow.wangijun.com | SPA + API | root /usr/app/webpage/callflow-webpage/dist/spa;/api/ → 12011,/api/auth → 12012,/esl-api/ → 本机 9912 |
callout.wangijun.com | SPA + API | root /usr/app/webpage/callout-webpage/dist/spa;/api/ → 12012 |
callai.wangijun.com | SPA + API | root /usr/app/webpage/callai-webpage/dist/spa;/api/ → 12013,/api/auth → 12012 |
ai-voice.wangijun.com | 静态文档站 | root /usr/app/ai-voice-docs/current |
fs.wangijun.com | WSS 信令 | proxy_pass https://172.21.38.245:7443(FreeSWITCH WebRTC profile) |
asr.wangijun.com | WebSocket | 回环 12010(WSL sherpa-asr-online-server 20096) |
tts.wangijun.com | HTTP 媒体 | 回环 12008(WSL sherpa-tts-server 29080) |
emotion.wangijun.com | HTTP API | 回环 12009(WSL emotion-analysis-server 29090) |
recordings.wangijun.com | 只读媒体 | alias /usr/local/freeswitch/var/lib/freeswitch/recordings/ |
三个 SPA 站点各自还有一个 location = /health,把探活透传到对应后端的 /health,便于用 公网域名直接判断隧道与后端是否在线。
2. 三条必须理解的规则
2.1 前端是本机静态托管,不是隧道回源
SPA 产物直接放在 aliyun 本地磁盘由 Nginx 发布,因此前端更新不需要 FRP、不需要 WSL 在线, 只要 scp 覆盖 dist/spa 并 chmod -R a+rX 即可(步骤见上级手册第 3 节)。 FRP 只承载后端 API 与模型端点。
2.2 /api/auth 先于 /api/ 匹配,指向 callout-server
callflow 与 callai 两个站点都把 /api/auth 单独指到 12012(callout-server),其余 /api/ 才走各自后端。这是三端共用一套登录态的实现方式:登录、登出、/auth/me 统一由 callout-server 签发与校验。调整这两个 location 的顺序或前缀会直接导致三端登录互相踢下线。
Nginx 的前缀匹配取最长者,/api/auth 比 /api/ 长,所以无论书写顺序如何都优先命中;但为了 可读性,配置里仍把 /api/auth 写在 /api/ 之前,改动时请保持这个约定。
2.3 流式链路必须关缓冲
/api/ 三处反代都设置了:
nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;顶部的 map $http_upgrade $connection_upgrade 是 WebSocket / SSE 升级的前提。这组参数支撑 callai 的 Chat SSE 流式返回、运营监控 SSE 事件流和软电话会话事件流。去掉 proxy_buffering off 会让 SSE 表现为"整通对话结束后才一次性吐字",去掉超时会让长通话的 事件流在默认 60s 后被切断。
fs.wangijun.com 的 WSS 代理超时放宽到 86400s(SIP 注册长连接),且必须 proxy_pass https://…:7443 保持加密上游:降级成 http://…:5066 会让浏览器发出的 Via: WSS 与 Sofia 实际看到的传输不一致,表现为 WSS 握手 101 成功、REGISTER 约 60 秒后 Request Timeout。同时 FreeSWITCH internal profile 的 domains ACL 必须放行 Nginx 的源地址 172.21.38.245/32,改 ACL 后要执行 reloadacl。
3. 缓存与安全约定
location = /index.html强制no-store,location /assets/用immutable+ 一年缓存。 Quasar 产物带内容哈希,这样发版后浏览器立刻拿到新index.html,静态资源仍走强缓存。 前端更新后若页面没变,先确认是不是漏掉了这两条 location。- SPA 兜底一律
try_files $uri $uri/ /index.html(hash 路由与刷新直达都依赖它);文档站用try_files $uri $uri.html $uri/ =404以支持 VitePress 的cleanUrls。 /esl-api/带if ($http_token = "") { return 401; },只是挡掉无凭据的探测,不替代 callflow-esl 自身的http-server.authToken校验。- 录音站点
limit_except GET { deny all; }+autoindex off,只读且不可列目录。 - 文档站与录音站点都加了
X-Content-Type-Options: nosniff;文档站的/downloads/额外 强制Content-Disposition: attachment。 client_max_body_size 20m决定了号码 CSV/TSV 导入的实际上限——callout-server 侧的限制是 5 MiB,两者不一致时以更小的为准。
4. 与 docs-site/nginx.conf 的关系
ai-voice.wangijun.com 在两个文件里都有 server 块:
- 本文件内的文档站块;
../docs-site/nginx.conf(按其 README 安装为/etc/nginx/conf.d/ai-voice-docs.conf)。
两者只能启用其一。同时安装会触发 conflicting server name 警告,Nginx 只保留先加载的那个, 后续改动可能改在不生效的文件上。当前生产以本文件为准;只部署文档站的场景再用 ../docs-site/ 下那份独立配置。
5. 安装与生效
bash
# 1. 上传(本文件是完整配置,不是片段)
scp deploy/aliyun/nginx/ai-voice-public.conf aliyun:/etc/nginx/conf.d/ai-voice-public.conf
# 2. 语法校验后平滑重载,切勿直接 restart
ssh aliyun "sudo nginx -t && sudo systemctl reload nginx"校准清单(套用到新环境时逐项确认):
| 项 | 当前值 | 说明 |
|---|---|---|
| 域名 | *.wangijun.com 共 9 个 | 9 个 server_name 加 80 端口跳转块里的列表都要改 |
| 证书 | /etc/letsencrypt/live/wangijun.com/ | 通配证书;include options-ssl-nginx.conf 由 certbot 提供 |
| SPA 根目录 | /usr/app/webpage/<app>/dist/spa | 需 a+rX,属主 aivoice,否则 403/404 |
| 文档站根目录 | /usr/app/ai-voice-docs/current | 指向 releases/<release-id> 的符号链接 |
| 录音目录 | /usr/local/freeswitch/var/lib/freeswitch/recordings/ | 结尾斜杠不能少(alias 语义) |
| FreeSWITCH WSS | https://172.21.38.245:7443 | 私网地址,随 FreeSWITCH sip-ip 变化 |
| 回环端口 | 12008~12013 | 与 frpc.toml 的 remotePort 一一对应 |
6. 排障速查
| 现象 | 首查项 |
|---|---|
| SPA 打开 403 / 404 | chmod -R a+rX /usr/app/webpage/;确认 root 指到 dist/spa 而不是 dist |
| 刷新子路由 404 | location / 的 try_files … /index.html 兜底是否被改动 |
| 发版后仍是旧页面 | = /index.html 的 no-store 是否丢失;浏览器强刷验证 |
| 登录后立刻掉线 / 三端互踢 | /api/auth 是否仍指向 12012 |
| SSE 不逐字返回 | 对应 /api/ 块的 proxy_buffering off 与 map 块是否完整 |
| 长通话事件流 60s 断开 | proxy_read_timeout / proxy_send_timeout 被恢复成默认值 |
| API 502 / 504 | 先查 FRP:ss -tlnp | grep 120 看回环端口是否在监听,再查 WSL 侧容器 |
| WSS 握手 101 但 REGISTER 超时 | 上游被降级成 http://…:5066;或 FreeSWITCH domains ACL 未放行 172.21.38.245/32 |
| 文档站改配置不生效 | 是否与 docs-site/nginx.conf 的同名 server 块冲突(见第 4 节) |
更细的软电话媒体侧问题(ICE / TURN / DTLS)见 软电话 WebRTC ICE/TURN 指南与 全仓排障手册。