From b346b7e194693de33ef2ee211cb1b0a02b9475ee Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sat, 2 May 2026 03:34:31 +0800 Subject: [PATCH] Add HTTPS demo entry for microphone access - Add a self-signed HTTPS Nginx entrypoint on Docker port 4443 so browser microphone APIs can run in demo mode. - Keep the existing HTTP port 4002 unchanged while exposing container port 443 and generating the demo certificate during image build. - Update CORS defaults and Compose environment for the HTTPS frontend origin. - Clarify the report editor microphone message with localhost, HTTPS, and browser trusted-origin demo options. - Document the browser HTTP microphone limitation, HTTPS demo URL, and Chrome/Edge insecure-origin workaround in README and docs. --- .env.example | 2 +- AGENTS.md | 2 ++ Dockerfile | 8 ++++++++ README.md | 2 ++ docker-compose.yaml | 3 ++- docs/deployment.md | 22 ++++++++++++++++++--- docs/features.md | 2 +- docs/installation.md | 8 +++++--- docs/modules/report-editor.md | 2 +- docs/progress.md | 2 ++ nginx.conf | 37 +++++++++++++++++++++++++++++++++++ src/pages/ReportEditor.tsx | 2 +- 12 files changed, 81 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 04388a4..91b7992 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # Backend API development defaults. API_PORT=3100 API_BODY_LIMIT="100mb" -CORS_ORIGIN="http://localhost:3001,http://localhost:4002" +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" diff --git a/AGENTS.md b/AGENTS.md index ad08ab0..8deae09 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -309,6 +309,8 @@ npm run test:e2e API 默认 JSON/urlencoded 请求体上限为 `100mb`,由 `API_BODY_LIMIT` 控制;Nginx 同步配置了 `client_max_body_size 100m`,用于承载迁移期报告 HTML、图片/关键帧和通用文件 Data URL 上传。 +Docker 前端默认暴露 `http://localhost:4002`,并额外暴露自签名 HTTPS 演示入口 `https://localhost:4443`。浏览器麦克风 API 不能在普通局域网 HTTP 页面中由应用代码强行开启;语音听写演示优先使用 HTTPS 入口,或用 Chrome/Edge 的 `--unsafely-treat-insecure-origin-as-secure` 临时标记 HTTP 来源。 + ### `server/prisma/schema.prisma` PostgreSQL 数据模型。当前覆盖 `Tenant`、`Department`、`User`、`UserSession`、`AppSession`、`Report`、`ReportMedia`、`ReportHistory`、`Template`、模板部门授权、`FileResource`、`SystemSetting` 和 `AuditLog`。Prisma 7 连接配置在根目录 `prisma.config.ts` 中。 diff --git a/Dockerfile b/Dockerfile index a99ff75..c9ccd0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,15 @@ RUN npm run build # Production stage FROM nginx:alpine +RUN apk add --no-cache openssl \ + && mkdir -p /etc/nginx/certs \ + && openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /etc/nginx/certs/surclaw-demo.key \ + -out /etc/nginx/certs/surclaw-demo.crt \ + -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 +EXPOSE 443 CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index a7efc2f..16c76cd 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ docker-compose up -d --build ```text 前端: http://localhost:4002 +语音 HTTPS 演示入口: https://localhost:4443 API: http://localhost:3002/api/health 数据库: localhost:5433 ``` @@ -236,6 +237,7 @@ docker-compose down 部署说明: - `web` 服务使用 Nginx 托管前端 `dist/`。 +- Docker 前端同时暴露 `http://localhost:4002` 和自签名证书的 `https://localhost:4443`;麦克风听写建议使用 HTTPS 演示入口。 - `api` 服务运行 NestJS 后端,并把上传文件目录挂载到 `uploads_data` volume。 - `db` 服务运行 PostgreSQL 16。 - `nginx.conf` 已配置 SPA 路由回退、`/api` 反向代理和 `100m` 请求体上限。 diff --git a/docker-compose.yaml b/docker-compose.yaml index 4890fd1..ef594fc 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -21,7 +21,7 @@ services: environment: API_PORT: 3100 API_BODY_LIMIT: 100mb - CORS_ORIGIN: http://localhost:4002,http://localhost:3001 + 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" @@ -41,6 +41,7 @@ services: restart: unless-stopped ports: - "4002:80" + - "4443:443" depends_on: - api diff --git a/docs/deployment.md b/docs/deployment.md index 2d93739..8fce0ae 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -74,11 +74,11 @@ AI 和语音密钥由后端 Settings API 保存并由代理使用,前端不再 docker-compose up -d --build ``` -默认通过 Nginx 暴露 `http://localhost:4002`。 +默认通过 Nginx 暴露 `http://localhost:4002`,并额外暴露自签名证书的语音演示入口 `https://localhost:4443`。 当前 Compose 服务: -- `web`:前端静态站点,暴露 `http://localhost:4002`。 +- `web`:前端静态站点,暴露 `http://localhost:4002` 和 `https://localhost:4443`。 - `api`:NestJS API,暴露 `http://localhost:3002`。 - `db`:PostgreSQL 16,暴露 `localhost:5433`。 - `uploads_data`:后端文件持久化 volume。 @@ -88,7 +88,23 @@ docker-compose up -d --build - `Dockerfile` 使用 Node 构建 `dist/`。 - 运行阶段使用 `nginx:alpine` 托管静态文件。 - `Dockerfile.server` 构建并运行 NestJS API。 -- `nginx.conf` 已配置 SPA 路由回退、`/api` 反向代理和 `100m` 请求体上限。 +- `nginx.conf` 已配置 SPA 路由回退、`/api` 反向代理、`100m` 请求体上限和自签名 HTTPS 演示入口。 + +## 麦克风访问 + +浏览器不允许普通局域网 HTTP 页面调用麦克风,代码无法绕过这个限制。Docker 演示环境建议使用: + +```text +https://localhost:4443 +``` + +首次打开会看到自签名证书提示,接受后即可让浏览器开放麦克风权限。如果必须用 `http://局域网IP:4002` 演示,只能在 Chrome/Edge 启动时加临时参数,把该 HTTP 来源标记为可信: + +```bash +google-chrome --unsafely-treat-insecure-origin-as-secure=http://局域网IP:4002 --user-data-dir=/tmp/surclaw-chrome-demo +``` + +这是浏览器演示开关,不是系统安全方案;正式环境仍建议使用 HTTPS。 ## 部署边界 diff --git a/docs/features.md b/docs/features.md index 0dad97e..5615376 100644 --- a/docs/features.md +++ b/docs/features.md @@ -43,7 +43,7 @@ | 关键帧插入 | 真实集成 | 关键帧可点击插入或拖入图片占位符;上传成功后编辑器会把插入图片从 Data URL 替换为受控文件 URL。 | | AI 辅助撰写 | 真实集成 | 前端调用 `/api/ai/chat`,后端使用全局共用 Provider Key 代理 OpenAI 兼容 `/chat/completions`;需要有效 Provider 配置、模型和网络。 | | AI 差异确认 | 真实可用 | 使用 `diff` 生成左右差异,确认后写入 AI 区域。 | -| 讯飞语音听写 | 真实集成 | 前端使用麦克风采集音频并连接 `/api/speech/iat`;后端读取讯飞配置、生成鉴权 URL、补齐首帧 APPID/业务参数并转发 IAT 结果。需要浏览器权限、安全上下文(`localhost` 或 HTTPS)、有效配置和网络。 | +| 讯飞语音听写 | 真实集成 | 前端使用麦克风采集音频并连接 `/api/speech/iat`;后端读取讯飞配置、生成鉴权 URL、补齐首帧 APPID/业务参数并转发 IAT 结果。需要浏览器权限、安全上下文(`localhost` 或 HTTPS)、有效配置和网络;Docker 提供 `https://localhost:4443` 演示入口。 | | AI/语音密钥管理 | 真实集成 | AI Key 和讯飞 APIKey/APISecret 均由后端代理读取和使用;普通用户读取设置时不返回真实密钥。 | | 系统设置 | 真实集成 | `SystemSettings` 优先调用 `/api/settings/system` 读取和保存抽帧、默认模板、AI Provider、语音配置;“恢复演示出厂设置”会二次确认后调用后端 demo reset,清空报告/审计并恢复默认用户、模板和演示配置。只有开发/显式回退模式下 API 不可用才回退本地缓存。 | | 审计日志查看 | 真实集成 | 超级管理员和管理员可进入审计日志页,调用 `GET /api/audit-logs` 查看登录、报告、模板、用户、部门、设置和文件等操作;管理员只看本部门或自己相关日志。 | diff --git a/docs/installation.md b/docs/installation.md index 4524bfa..6349a94 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -28,6 +28,7 @@ docker compose version | NestJS API 本地服务 | `3100` | `npm run server:dev` 默认监听 | | Docker API 暴露端口 | `3002` | 宿主机访问 Compose API | | Docker 前端入口 | `4002` | 推荐的整套系统访问入口 | +| Docker 前端 HTTPS 演示入口 | `4443` | 自签名证书入口,推荐用于麦克风听写 | | Docker PostgreSQL | `5433` | 宿主机访问 Compose 数据库 | 如果这些端口被占用,优先修改 `.env`、`docker-compose.yaml` 和对应文档,避免回退到 `3000` 等默认端口。 @@ -45,6 +46,7 @@ docker compose up -d --build ```text http://localhost:4002 +https://localhost:4443 ``` 健康检查: @@ -56,7 +58,7 @@ curl http://localhost:3002/api/health 当前 Compose 会启动: -- `tuwen_web`:Nginx 托管前端,宿主机端口 `4002`。 +- `tuwen_web`:Nginx 托管前端,宿主机端口 `4002`;同时提供自签名 HTTPS 演示入口 `4443`。 - `tuwen_api`:NestJS API,容器内监听 `3100`,宿主机端口 `3002`。 - `tuwen_db`:PostgreSQL 16,宿主机端口 `5433`。 @@ -149,7 +151,7 @@ VITE_API_PROXY_TARGET="http://localhost:3002" 完成启动后建议按顺序验证: -1. 打开 `http://localhost:4002` 或本地开发地址 `http://localhost:3001`。 +1. 打开 `http://localhost:4002`、语音演示地址 `https://localhost:4443` 或本地开发地址 `http://localhost:3001`。 2. 使用 `admin / 123456` 登录。 3. 进入工作台,确认统计卡片能正常加载。 4. 进入“系统设置”,确认全局配置可保存。 @@ -190,7 +192,7 @@ Docker 方式应访问 `http://localhost:4002`。本地开发方式应确保 `np 检查占用: ```bash -ss -ltnp | grep -E ':3001|:3002|:3100|:4002|:5433' +ss -ltnp | grep -E ':3001|:3002|:3100|:4002|:4443|:5433' ``` 本项目不应使用 `3000` 作为默认 API 端口。如果必须改端口,同步修改 `.env`、`docker-compose.yaml`、`vite.config.ts` 或 Nginx upstream,并更新文档。 diff --git a/docs/modules/report-editor.md b/docs/modules/report-editor.md index 07ece35..fca2356 100644 --- a/docs/modules/report-editor.md +++ b/docs/modules/report-editor.md @@ -85,7 +85,7 @@ AI 面板支持两种模式: - 前端连接 `/api/speech/iat`,不再生成讯飞鉴权 URL,也不读取 APPID/APIKey/APISecret。 - 浏览器采集麦克风音频,转换为 16k PCM 后发送音频帧。 -- 启动前会检查浏览器是否支持 `navigator.mediaDevices.getUserMedia` 和 `AudioContext`;如果不是 `localhost` 或 HTTPS 等安全上下文,浏览器会禁止麦克风能力,页面会提示切换访问方式。 +- 启动前会检查浏览器是否支持 `navigator.mediaDevices.getUserMedia` 和 `AudioContext`;如果不是 `localhost` 或 HTTPS 等安全上下文,浏览器会禁止麦克风能力。Docker 演示环境可使用 `https://localhost:4443`,局域网普通 HTTP 只能通过 Chrome/Edge 演示启动参数临时标记为可信来源。 - 后端读取 Settings API 中的 `xfSpeechConfig`,连接讯飞 IAT,上游首帧由后端补齐 `common.app_id` 和默认 `business` 参数。 - 识别结果由后端转发回前端,并追加到 AI 输入框。 diff --git a/docs/progress.md b/docs/progress.md index 8c5515b..2642e2c 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -18,6 +18,7 @@ - 系统设置、抽帧策略、AI Provider、语音参数和默认模板已优先接入 Settings API,只有开发/显式回退模式下才保留本地缓存回退。 - Docker/Nginx 静态部署配置已存在。 - Docker/Nginx 与 NestJS API 已把请求体上限统一到 `100mb`,避免图文报告和文件 Data URL 上传触发默认 413。 +- Docker Nginx 已额外提供自签名 HTTPS 演示入口 `4443`,用于浏览器麦克风听写权限;普通局域网 HTTP 仍受浏览器限制。 - 开发端口已调整为 `3001`。 - 已补充 Vitest 测试框架和核心功能单元/组件测试。 - 已补充功能盘点,区分真实功能、外部集成、前端演示和预留项。 @@ -79,3 +80,4 @@ | 2026-05-02 | 将默认“腹腔镜胆囊切除术报告”后端 seed 与前端默认报告内容对齐,并把系统设置重置改为演示模式恢复出厂设置。 | | 2026-05-02 | 修正报告草稿后端校验和保存失败提示,补充麦克风启动前置检查。 | | 2026-05-02 | 增加 Nginx 和 NestJS 请求体上限配置,修复大图文报告保存 `request entity too large`。 | +| 2026-05-02 | 新增 Docker HTTPS 演示入口和麦克风访问说明,解决非安全上下文下语音听写不可启动的问题。 | diff --git a/nginx.conf b/nginx.conf index 5dd6ce6..be96f48 100644 --- a/nginx.conf +++ b/nginx.conf @@ -33,3 +33,40 @@ server { add_header Cache-Control "public, immutable"; } } + +server { + listen 443 ssl; + server_name localhost; + client_max_body_size 100m; + root /usr/share/nginx/html; + index index.html; + + ssl_certificate /etc/nginx/certs/surclaw-demo.crt; + ssl_certificate_key /etc/nginx/certs/surclaw-demo.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api:3100/api/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + 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; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index ef8b28d..9e7f473 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -1123,7 +1123,7 @@ export default function ReportEditor() { if (!mediaDevices?.getUserMedia) { alert(window.isSecureContext ? '当前浏览器不支持麦克风采集,请更换新版 Chrome/Edge 后重试。' - : '麦克风需要在 localhost 或 HTTPS 环境下使用。请通过 localhost 访问,或配置 HTTPS 后重试。'); + : '浏览器不允许在普通局域网 HTTP 页面中调用麦克风。请使用 http://localhost:4002、https://localhost:4443,或用浏览器演示参数把当前 HTTP 地址标记为可信。'); return; } if (!AudioContextClass) {