Document public reverse proxy deployment
- Add README instructions for deploying the full report system through local Docker port 4002, frpc, Nginx Proxy Manager, and sstwbg.example.com. - Document required HTTPS, WebSocket, request-size, timeout, health-check, login, AI, video-frame, and speech verification steps. - Add TRUST_PROXY support so secure session cookies work behind public HTTPS reverse proxies. - Preserve upstream X-Forwarded-Proto through the container Nginx API proxy. - Allow Docker Compose session and trust-proxy variables to be overridden for public deployments. - Update deployment and Docker docs with the same public reverse-proxy guidance.
This commit is contained in:
@@ -5,6 +5,7 @@ CORS_ORIGIN="http://localhost:3001,http://localhost:4002,https://localhost:4443"
|
||||
DATABASE_URL="postgresql://surclaw:surclaw_dev_password@localhost:5433/surclaw?schema=public"
|
||||
SESSION_SECRET="change-me-in-production"
|
||||
SESSION_COOKIE_SECURE="false"
|
||||
TRUST_PROXY="false"
|
||||
FILE_STORAGE_DIR="./uploads"
|
||||
RUN_DB_MIGRATIONS="true"
|
||||
RUN_DB_SEED="true"
|
||||
|
||||
79
README.md
79
README.md
@@ -126,6 +126,7 @@ cp .env.example .env.local
|
||||
- `DATABASE_URL`:PostgreSQL 连接串。Docker Compose 暴露到宿主机的默认端口是 `5433`,容器内部仍使用 `db:5432`。
|
||||
- `SESSION_SECRET`:后端 Session Cookie 签名密钥,生产环境必须替换。
|
||||
- `SESSION_COOKIE_SECURE`:是否只通过 HTTPS 发送 Session Cookie。本地 HTTP/Compose 默认 `false`。
|
||||
- `TRUST_PROXY`:是否信任反向代理传入的 `X-Forwarded-*` 头。`# XXX` 公网 HTTPS 经过 Nginx Proxy Manager、frpc/frps 或其他反向代理转发时建议设为 `true`,否则 `SESSION_COOKIE_SECURE=true` 时登录 Cookie 可能无法正确写入。
|
||||
- `FILE_STORAGE_DIR`:后端文件存储目录。Docker Compose 默认挂载到 `/app/uploads`。
|
||||
- `VITE_API_PROXY_TARGET`:Vite 开发服务器的 `/api` 代理目标。直接运行 `npm run server:dev` 时用 `http://localhost:3100`;连接 Docker Compose API 时用 `http://localhost:3002`。
|
||||
- `VITE_ENABLE_LOCAL_FALLBACK`:是否允许生产构建继续使用浏览器本地兼容回退。开发模式默认启用,生产默认关闭。
|
||||
@@ -248,6 +249,84 @@ docker-compose down
|
||||
- `nginx.conf` 已支持 `/api/speech/iat` WebSocket upgrade。
|
||||
完整 Docker 说明见 [docs/docker.md](./docs/docker.md)。
|
||||
|
||||
## 公网反向代理部署
|
||||
|
||||
<!-- # XXX 公网部署新增:适用于本机 Docker 4002 端口通过 frpc 映射到公网服务器,再由 Nginx Proxy Manager 绑定 sstwbg.example.com 的部署链路。 -->
|
||||
|
||||
推荐公网链路:
|
||||
|
||||
```text
|
||||
浏览器 https://sstwbg.example.com
|
||||
-> 公网服务器 Nginx Proxy Manager
|
||||
-> frps/frpc 映射端口
|
||||
-> 本机 Docker web:4002
|
||||
-> 容器 Nginx /api 代理
|
||||
-> api:3100
|
||||
```
|
||||
|
||||
本机部署并确认 Docker 服务正常:
|
||||
|
||||
```bash
|
||||
# XXX 在部署机器上使用当前最新版分支。
|
||||
git fetch origin
|
||||
git checkout surclaw-system-backendized-20260502
|
||||
git pull --ff-only origin surclaw-system-backendized-20260502
|
||||
|
||||
# XXX 公网 HTTPS 部署建议替换 Session 密钥,并信任外层反向代理。
|
||||
export SESSION_SECRET="替换为足够长的随机字符串"
|
||||
export SESSION_COOKIE_SECURE="true"
|
||||
export TRUST_PROXY="true"
|
||||
|
||||
docker-compose up -d --build
|
||||
curl http://127.0.0.1:4002/api/health
|
||||
```
|
||||
|
||||
如果使用 frpc 把本机 `4002` 映射到公网服务器,示例配置如下,端口号按你的 frps 实际开放端口调整:
|
||||
|
||||
```toml
|
||||
# XXX frpc 示例:把本机 Docker 前端入口 4002 暴露到公网服务器的一个内网监听端口。
|
||||
[[proxies]]
|
||||
name = "surclaw-web"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 4002
|
||||
remotePort = 4002
|
||||
```
|
||||
|
||||
Nginx Proxy Manager 中为 `sstwbg.example.com` 新建 Proxy Host:
|
||||
|
||||
- `Domain Names`:`sstwbg.example.com`
|
||||
- `Scheme`:`http`
|
||||
- `Forward Hostname / IP`:frps 可访问到的映射地址,通常是 `127.0.0.1`、公网服务器内网 IP 或 frps 指定监听地址。
|
||||
- `Forward Port`:frpc 暴露出来的 `remotePort`,例如 `4002`。
|
||||
- `Websockets Support`:开启。`# XXX` 语音识别使用 `/api/speech/iat` WebSocket,必须透传 Upgrade。
|
||||
- `Block Common Exploits`:开启。
|
||||
- `SSL`:申请或绑定 `sstwbg.example.com` 证书,开启 `Force SSL`。`# XXX` 浏览器麦克风权限要求 HTTPS,普通公网 HTTP 下语音识别不可用。
|
||||
|
||||
Nginx Proxy Manager 的 `Advanced` 可加入:
|
||||
|
||||
```nginx
|
||||
# XXX 图文报告、关键帧和模板图片可能较大,公网代理请求体上限需与容器 Nginx/API 保持一致。
|
||||
client_max_body_size 100m;
|
||||
|
||||
# XXX WebSocket 语音听写需要较长连接时间。
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
```
|
||||
|
||||
公网验收顺序:
|
||||
|
||||
```text
|
||||
1. 打开 https://sstwbg.example.com/api/health,确认 API 健康检查可访问。
|
||||
2. 打开 https://sstwbg.example.com 登录 admin / 123456。
|
||||
3. 进入系统设置,确认 AI Provider 和讯飞语音配置有效。
|
||||
4. 进入报告编辑页,测试上传视频、自动抽帧、报告保存、AI 对话和语音听写。
|
||||
5. 浏览器控制台执行 window.isSecureContext,应返回 true;否则语音麦克风权限不会开放。
|
||||
```
|
||||
|
||||
如果登录失败或刷新后掉登录,优先检查 `SESSION_SECRET` 是否稳定、`SESSION_COOKIE_SECURE=true` 时是否同时设置了 `TRUST_PROXY=true`,以及外层 NPM 是否把 `X-Forwarded-Proto: https` 传给后端链路。
|
||||
如果只有语音识别失败,优先检查 NPM 的 `Websockets Support`、HTTPS 证书、浏览器麦克风权限、系统设置中的讯飞 APPID/APIKey/APISecret,以及后端容器是否能访问讯飞 `wss://iat-api.xfyun.cn/v2/iat`。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 前端登录、工作台统计、报告读写、报告媒体引用、模板读写、字段库、模板图片资源、视频/关键帧文件、用户管理、部门模板授权、系统设置、签名文件、AI 对话和语音听写已接入真实后端 Session/API/代理。
|
||||
|
||||
@@ -28,8 +28,10 @@ services:
|
||||
API_BODY_LIMIT: 100mb
|
||||
CORS_ORIGIN: http://localhost:4002,https://localhost:4443,http://localhost:3001
|
||||
DATABASE_URL: postgresql://surclaw:surclaw_dev_password@db:5432/surclaw?schema=public
|
||||
SESSION_SECRET: change-me-in-production
|
||||
SESSION_COOKIE_SECURE: "false"
|
||||
# XXX Public HTTPS reverse-proxy deployments can override these from the shell or .env.
|
||||
SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production}
|
||||
SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-false}
|
||||
TRUST_PROXY: ${TRUST_PROXY:-false}
|
||||
FILE_STORAGE_DIR: /app/uploads
|
||||
RUN_DB_MIGRATIONS: "true"
|
||||
RUN_DB_SEED: "true"
|
||||
|
||||
@@ -64,6 +64,7 @@ AI 和语音密钥由后端 Settings API 保存并由代理使用,前端不再
|
||||
- `DATABASE_URL`:PostgreSQL 连接串。Docker Compose 暴露到宿主机的默认端口是 `5433`,容器内部仍使用 `db:5432`。
|
||||
- `SESSION_SECRET`:Session Cookie 签名密钥。
|
||||
- `SESSION_COOKIE_SECURE`:是否只通过 HTTPS 发送 Session Cookie。本地 HTTP/Compose 默认 `false`,生产 HTTPS 应设为 `true`。
|
||||
- `TRUST_PROXY`:是否信任反向代理传入的 `X-Forwarded-*` 头。`# XXX` 公网 HTTPS 经过 Nginx Proxy Manager、frpc/frps 或其他反向代理转发时建议设为 `true`。
|
||||
- `FILE_STORAGE_DIR`:后端文件目录。Docker Compose 默认 `/app/uploads`,并挂载到 `uploads_data` volume。
|
||||
- `RUN_DB_MIGRATIONS`:Docker API 容器启动时是否执行 `prisma migrate deploy`,默认 `true`。
|
||||
- `RUN_DB_SEED`:Docker API 容器启动时是否执行 `prisma db seed`,默认 `true`。
|
||||
@@ -97,6 +98,38 @@ docker-compose up -d --build
|
||||
|
||||
更完整的 Docker 说明、生产变量、证书和备份恢复见 [Docker 化部署](./docker.md)。
|
||||
|
||||
## 公网反向代理
|
||||
|
||||
<!-- # XXX 公网部署新增:适用于本机 Docker 4002 端口经 frpc 映射到公网服务器,再由 Nginx Proxy Manager 绑定域名。 -->
|
||||
|
||||
推荐链路:
|
||||
|
||||
```text
|
||||
浏览器 https://sstwbg.example.com
|
||||
-> 公网服务器 Nginx Proxy Manager
|
||||
-> frps/frpc 映射端口
|
||||
-> 本机 Docker web:4002
|
||||
-> 容器 Nginx /api
|
||||
-> api:3100
|
||||
```
|
||||
|
||||
公网部署建议变量:
|
||||
|
||||
```bash
|
||||
# XXX HTTPS 生产入口建议开启安全 Cookie,并让后端信任外层代理协议头。
|
||||
export SESSION_SECRET="替换为足够长的随机字符串"
|
||||
export SESSION_COOKIE_SECURE="true"
|
||||
export TRUST_PROXY="true"
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
Nginx Proxy Manager 代理 `sstwbg.example.com` 时:
|
||||
|
||||
- 代理目标指向 frpc 暴露的 `4002` 映射端口。
|
||||
- 开启 `Websockets Support`,否则 `/api/speech/iat` 语音 WebSocket 会失败。
|
||||
- 绑定 SSL 证书并开启 `Force SSL`,否则浏览器不会开放公网麦克风权限。
|
||||
- Advanced 中建议设置 `client_max_body_size 100m;`、`proxy_read_timeout 3600s;`、`proxy_send_timeout 3600s;`。
|
||||
|
||||
## 麦克风访问
|
||||
|
||||
浏览器不允许普通局域网 HTTP 页面调用麦克风,代码无法绕过这个限制。Docker 演示环境建议使用:
|
||||
|
||||
@@ -110,8 +110,34 @@ https://localhost:4443
|
||||
- 使用医院内网域名。
|
||||
- 通过可信 CA 或内网 CA 签发证书。
|
||||
- 把 `SESSION_COOKIE_SECURE` 设为 `true`。
|
||||
- 把 `TRUST_PROXY` 设为 `true`。`# XXX` 如果 HTTPS 在 Nginx Proxy Manager、frpc/frps 等外层代理终止,后端需要信任 `X-Forwarded-Proto` 才能正确写入安全 Cookie。
|
||||
- 把 `CORS_ORIGIN` 改成真实前端 HTTPS 来源。
|
||||
|
||||
## 公网域名反向代理
|
||||
|
||||
<!-- # XXX 公网部署新增:适用于本机 Docker 4002 端口经 frpc 映射到公网服务器,再由 Nginx Proxy Manager 绑定 sstwbg.example.com。 -->
|
||||
|
||||
如果使用 `本机 Docker 4002 -> frpc -> 公网服务器 Nginx Proxy Manager -> sstwbg.example.com`,推荐流程:
|
||||
|
||||
```bash
|
||||
# XXX 使用公网 HTTPS 入口时,compose 变量可从 shell 或 .env 覆盖。
|
||||
export SESSION_SECRET="替换为足够长的随机字符串"
|
||||
export SESSION_COOKIE_SECURE="true"
|
||||
export TRUST_PROXY="true"
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
Nginx Proxy Manager 配置要点:
|
||||
|
||||
- `Domain Names` 使用 `sstwbg.example.com`。
|
||||
- `Forward Hostname / IP` 指向 frpc 在公网服务器暴露的地址。
|
||||
- `Forward Port` 填 frpc 暴露的端口,例如 `4002`。
|
||||
- 开启 `Websockets Support`,保证 `/api/speech/iat` 语音听写 WebSocket 可升级。
|
||||
- SSL 页签绑定证书并开启 `Force SSL`,保证浏览器开放麦克风权限。
|
||||
- Advanced 中建议设置 `client_max_body_size 100m;`、`proxy_read_timeout 3600s;`、`proxy_send_timeout 3600s;`。
|
||||
|
||||
公网验收先访问 `https://sstwbg.example.com/api/health`,再登录并测试报告保存、视频抽帧、AI 对话和语音听写。
|
||||
|
||||
## 生产部署前必须修改
|
||||
|
||||
`docker-compose.yaml` 当前适合演示和院内试运行,生产前至少修改:
|
||||
@@ -121,6 +147,7 @@ https://localhost:4443
|
||||
- `SESSION_SECRET`:替换为高强度随机值。
|
||||
- `CORS_ORIGIN`:只保留真实前端来源。
|
||||
- `SESSION_COOKIE_SECURE`:HTTPS 部署时设为 `true`。
|
||||
- `TRUST_PROXY`:经反向代理提供 HTTPS 时设为 `true`。
|
||||
- `RUN_DB_SEED`:不需要每次启动 seed 时可设为 `false`。
|
||||
- AI/讯飞演示凭据:通过系统设置替换为正式凭据;已经暴露过的密钥应轮换。
|
||||
- HTTPS 证书:替换自签名本机证书。
|
||||
|
||||
10
nginx.conf
10
nginx.conf
@@ -3,6 +3,12 @@ map $http_upgrade $connection_upgrade {
|
||||
'' close;
|
||||
}
|
||||
|
||||
# XXX Preserve the public HTTPS scheme when an outer reverse proxy terminates TLS.
|
||||
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||
default $http_x_forwarded_proto;
|
||||
'' $scheme;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
@@ -25,7 +31,7 @@ server {
|
||||
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_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
@@ -62,7 +68,7 @@ server {
|
||||
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_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
|
||||
@@ -15,6 +15,16 @@ const bootstrap = async () => {
|
||||
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||||
const port = Number(process.env.API_PORT ?? 3100);
|
||||
const bodyLimit = process.env.API_BODY_LIMIT ?? '100mb';
|
||||
const trustProxy = process.env.TRUST_PROXY;
|
||||
|
||||
if (trustProxy && trustProxy !== 'false') {
|
||||
// # XXX Public reverse-proxy deployments need Express to trust X-Forwarded-* headers.
|
||||
const numericTrustProxy = Number(trustProxy);
|
||||
app
|
||||
.getHttpAdapter()
|
||||
.getInstance()
|
||||
.set('trust proxy', trustProxy === 'true' ? 1 : Number.isNaN(numericTrustProxy) ? trustProxy : numericTrustProxy);
|
||||
}
|
||||
|
||||
app.setGlobalPrefix('api');
|
||||
app.use(json({ limit: bodyLimit }));
|
||||
|
||||
Reference in New Issue
Block a user