阿里云公网 Nginx 反向代理与静态托管
本目录只有一个文件:ai-voice-public.conf,是阿里云公网节点 aliyun 上全部公网入口的 Nginx 配置。它同时承担四件事:
- HTTPS / WSS 终结:8 个域名共用
wangijun.com的 Let's Encrypt 通配证书,80 端口整体 301 跳 443; - 前端 SPA 静态托管:
callflow-webpage与callout-webpage两个 Quasar 产物由本机直接发布,不回源、不走隧道; - API 与模型端点反代:
/api/等路径转发到 FRP 回环端口(12008~12012)或本机callflow-esl(9912); - 媒体只读发布:FreeSWITCH 录音目录以
alias只读暴露。
节点整体职责、部署与更新步骤见上级手册;FRP 回环端口清单见 ../frp/README.md 与 ../../wsl-docker/README.md; 跨主机调用矩阵与安全边界见部署网络拓扑与配置。
1. server 块总览
| 域名 | 类型 | 上游 / 根目录 |
|---|---|---|
全部 8 个域名(: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,= /verto → FreeSWITCH 8081 |
callout.wangijun.com | SPA + API | root /usr/app/webpage/callout-webpage/dist/spa;/api/ → 12012 |
ai-voice.wangijun.com | 静态文档站 | root /usr/app/ai-voice-docs/current |
fs.wangijun.com | WSS 信令 | / → https://172.21.38.245:7443(Sofia wss-binding);/verto → http://172.21.38.245:8081(mod_verto) |
asr.wangijun.com | WebSocket | 回环 12010(WSL sherpa-asr-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 即可(步骤见上级手册第 4 节)。 FRP 只承载后端 API 与模型端点。
2.2 /api/auth 先于 /api/ 匹配,指向 callout-server
callflow 站点把 /api/auth 单独指到 12012(callout-server),其余 /api/ 才走各自后端。这是两端共用一套登录态的实现方式:登录、登出、/auth/me 统一由 callout-server 签发与校验。调整这两个 location 的顺序或前缀会直接导致两端登录互相踢下线。
Nginx 的前缀匹配取最长者,/api/auth 比 /api/ 长,所以无论书写顺序如何都优先命中;但为了 可读性,配置里仍把 /api/auth 写在 /api/ 之前,改动时请保持这个约定。
2.3 流式链路必须关缓冲
/api/ 两处反代(callflow 与 callout)都设置了:
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 升级的前提。这组参数支撑 callout AI 策略中心的 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。
2.4 两条 WebRTC 信令链路互不通用
fs.wangijun.com 下同时代理了两个不同的 FreeSWITCH WebRTC 端点,上游协议恰好相反:
| 路径 | 上游 | 协议 | 使用方 |
|---|---|---|---|
/(默认) | https://172.21.38.245:7443 | SIP over WSS(Sofia wss-binding) | 坐席软电话(use-sip-softphone,取 agents.sipWsUrl) |
/verto | http://172.21.38.245:8081 | Verto JSON-RPC over WS(mod_verto) | callflow-webpage /verto-videophone(use-verto-client) |
Sofia 那条必须保持 https:// 上游(原因见 2.3);Verto 那条必须是 http:// 明文上游: verto.conf.xml 的 default-v4 profile 同时绑了明文 8081 和 secure="true" 的 8082, 公网链路是「Nginx 终结 TLS → 明文 8081」,8082 那份自签 wss.pem 不参与公网访问, 上游写成 https://…:8082 会因证书校验与重复加密握手失败。
callflow.wangijun.com 上另有一条精确匹配的 location = /verto,让工作台不换域名也能连 Verto。精确匹配意味着 /verto/ 或 /verto/xxx 都不命中,改前端连接地址时要留意。
Verto 只承担信令;媒体仍走 external_rtp_ip 与 coturn,与 SIP 软电话共用 30000-30199/UDP 和同一套 ICE/TURN 配置,排障方式完全一致。
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 块:
deploy/aliyun/docs-site/nginx.conf(按 阿里云文档站部署说明 安装为/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 共 8 个 | 8 个 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 变化 |
| FreeSWITCH Verto | http://172.21.38.245:8081 | 同一台 FreeSWITCH 的明文端口,TLS 在 Nginx 终结 |
| 回环端口 | 12008~12012 | 与 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 |
| Verto 视频页连不上 / 立即 1006 断开 | /verto 上游是否被写成 https://…:8082;工作台域名下是精确匹配 = /verto,带尾斜杠或子路径不命中 |
| 文档站改配置不生效 | 是否与 docs-site/nginx.conf 的同名 server 块冲突(见第 4 节) |
更细的软电话媒体侧问题(ICE / TURN / DTLS)见 软电话 WebRTC ICE/TURN 指南与 全仓排障手册。