# 前端 Nginx - SAE 容器部署完全指南 **文档版本**: v1.2 (修正:Node 版本、Vite 版本、日志输出、时区) **创建时间**: 2025-12-13 **最后修订**: 2025-12-13 **适用范围**: AIclinicalresearch 平台 - 前端静态资源服务(frontend-v2) **目标读者**: 运维工程师、前端开发工程师 **部署目标**: 阿里云 SAE(Serverless 应用引擎)Nginx 容器部署 **v1.2 更新日志**: - 🔴 修正:Node 版本从 18 改为 22(与开发环境 v22.18.0 一致) - ✅ 确认:Vite 版本 7.2(package.json 真实版本) - ⚠️ 修正:日志输出到 stdout/stderr(SAE 最佳实践) - ⚠️ 新增:容器时区设置(Asia/Shanghai) - ⚠️ 强化:后端地址必须显式配置(不给危险默认值) **v1.1 更新日志**: - 🛑 修正:基于 **frontend-v2** 目录(不是 frontend) - ✅ 更新:React 19 + Vite 6 + Ant Design 6.0 - ✅ 新增:Ant Design X 2.1 说明(AI 对话组件) - ✅ 新增:模块化架构说明(framework + modules) - ✅ 完善:构建路径和目录结构 --- ## 📋 目录 1. [为什么选择 SAE Nginx 容器部署](#1-为什么选择-sae-nginx-容器部署) 2. [部署前准备](#2-部署前准备) 3. [前端服务分析](#3-前端服务分析) 4. [创建 Nginx 配置](#4-创建-nginx-配置) 5. [构建 Docker 镜像](#5-构建-docker-镜像) 6. [本地测试验证](#6-本地测试验证) 7. [推送到 ACR](#7-推送到-acr) 8. [SAE 应用配置](#8-sae-应用配置) 9. [端到端测试](#9-端到端测试) 10. [监控与维护](#10-监控与维护) 11. [故障排查](#11-故障排查) 12. [注意事项与禁忌](#12-注意事项与禁忌) --- ## 1. 为什么选择 SAE Nginx 容器部署 ### ✅ 核心优势 | 优势 | 说明 | 对您的价值 | |------|------|----------| | **极高性能** | Nginx 处理静态文件效率远高于 Node.js(serve 或 next start) | 首屏加载快,用户体验好 | | **SPA 路由支持** | React Router 刷新页面不会 404 | `try_files` 原生支持 | | **反向代理** | Nginx 将 `/api` 转发到后端,前端无感知 | 零 CORS 配置,简化开发 | | **缓存控制** | 精细控制静态资源缓存策略 | 减少带宽消耗,加速加载 | | **私有化就绪** | Docker 镜像可直接交付给医院 | 满足医疗数据合规要求 | | **统一架构** | 与后端、Python 微服务统一使用 Docker | 一套部署流程,降低运维成本 | ### 🎯 为什么不用 OSS + CDN? 根据之前的讨论,我们明确了医疗科研项目的特殊性: | 对比项 | OSS + CDN | SAE Nginx 容器 | 评分 | |-------|-----------|----------------|------| | **私有化部署** | ❌ 无法在医院内网部署 OSS | ✅ Docker 镜像可直接交付 | **容器胜** | | **架构统一性** | ❌ 前端 OSS,后端 SAE,割裂 | ✅ 全部 SAE 容器,统一管理 | **容器胜** | | **CORS 处理** | ⚠️ 前端需要处理跨域 | ✅ Nginx 反向代理,前端无感 | **容器胜** | | **成本(小规模)** | ✅ 存储费用极低 | ⚠️ 需要运行实例(~100元/月) | **OSS 胜** | | **全球加速** | ✅ CDN 节点覆盖全球 | ❌ 单地域部署 | **OSS 胜** | | **医疗场景适配** | ❌ 不适合内网环境 | ✅ 完美适配私有化 | **容器胜** | **结论**:对于需要私有化部署的医疗科研产品,SAE Nginx 容器是唯一选择。 --- ## 2. 部署前准备 ### ✅ 前置条件检查清单 #### 本地开发环境 - [ ] Docker Desktop 已安装并运行(版本 20.10+) - [ ] Node.js 已安装(版本 18+) - [ ] 前端代码已拉取到本地 - [ ] 前端在本地能正常启动(`npm run dev`) - [ ] 前端能成功构建(`npm run build`) 验证命令: ```bash # 检查 Docker docker --version # 输出示例:Docker version 24.0.6 # 检查 Node.js node --version # 输出示例:v18.17.0 # 检查 npm npm --version # 输出示例:9.6.7 # 测试构建 cd frontend npm run build # 应该成功生成 dist/ 目录 ``` #### 阿里云资源 - [ ] **后端服务(SAE)** 已部署并运行 - 后端 VPC 内网地址已获取(如 `http://172.17.x.x:3001`) - 后端健康检查可访问 - [ ] **阿里云容器镜像服务 ACR** 已开通 - 命名空间已创建(如 `clinical-research`) - 登录密码已设置 - [ ] **SAE 应用** 准备创建 - VPC 和交换机已选择(与后端在同一 VPC) #### 配置信息准备 ```bash # 后端服务内网地址(关键) BACKEND_SERVICE_URL=http://172.17.x.x:3001 # 如果需要配置环境变量(可选) # VITE_API_BASE_URL 在构建时注入(很少使用) ``` --- ## 3. 前端服务分析 ### 📦 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | **React** | 19.2+ | UI 框架(最新版本) | | **Vite** | 7.2+ | 构建工具(极快,最新版本)| | **TypeScript** | 5.9+ | 类型安全 | | **Ant Design** | 6.0+ | UI 组件库(最新版本) | | **Ant Design X** | 2.1+ | AI 对话组件(新增) | | **React Router** | 7.9+ | 路由管理(SPA) | | **React Query** | 5.90+ | 数据获取和缓存 | | **Axios** | 1.13+ | HTTP 客户端 | | **AG Grid** | 34.3+ | 数据表格(DC 模块) | | **TailwindCSS** | 3.4+ | CSS 框架 | | **Zustand** | 5.0+ | 状态管理 | **⚠️ 版本说明**: - **Node.js**: v22.18.0(开发环境真实版本) - **Vite**: 7.2.2(package.json 真实版本,最新稳定版) - 所有版本均来自 `frontend-v2/package.json` 真实代码 ### 📊 架构设计(模块化) ``` frontend-v2/ ├── src/ │ ├── framework/ # 框架层 │ │ ├── layout/ # 主布局 + 顶部导航 │ │ ├── router/ # 路由守卫 │ │ ├── permission/ # 权限管理 │ │ └── modules/ # 模块注册表 │ │ │ ├── modules/ # 业务模块层 │ │ ├── aia/ # AI 智能问答 │ │ ├── pkb/ # 个人知识库 │ │ ├── asl/ # AI 智能文献 │ │ ├── dc/ # 数据清洗整理 │ │ ├── ssa/ # 智能统计分析 │ │ ├── st/ # 统计分析工具 │ │ └── rvw/ # 稿件审查系统 │ │ │ └── shared/ # 共享能力层 │ ├── components/ # 共享组件(Chat 组件等) │ ├── api/ # 共享 API 工具 │ ├── hooks/ # 共享 Hooks │ └── utils/ # 工具函数 ``` ### 📊 API 调用方式(模块化设计) **每个模块有独立的 API 文件**: ```typescript // modules/asl/api/index.ts const API_BASE_URL = '/api/v1/asl'; async function request(url: string, options?: RequestInit): Promise { const response = await fetch(`${API_BASE_URL}${url}`, { ...options, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }); return response.json(); } // modules/dc/api/toolC.ts const BASE_URL = '/api/v1/dc/tool-c'; // 使用 axios axios.post(`${BASE_URL}/upload`, formData); ``` **关键设计**: - ✅ 每个模块独立管理自己的 API(模块化) - ✅ 使用相对路径 `/api/v1/...`(不硬编码后端地址) - ✅ 混合使用 fetch 和 axios(根据模块需求) - ✅ 生产环境由 Nginx 反向代理到后端 - ✅ 无需处理 CORS(同源请求) **示例请求流程**: ``` ASL 模块:GET /api/v1/asl/projects ↓ Nginx 反向代理 ↓ 后端服务:http://172.17.x.x:3001/api/v1/asl/projects ``` ### 📝 构建流程 ```bash # ⚠️ 注意:使用 frontend-v2 目录(不是 frontend) cd frontend-v2 # 1. 安装依赖 npm install # 2. 构建生产版本 npm run build # 实际执行:tsc -b && vite build # 3. 产物目录结构 dist/ ├── index.html # 入口 HTML(约 1KB) ├── assets/ │ ├── index-xxxxx.js # 主 JS 文件(约 800KB,已压缩) │ ├── index-xxxxx.css # 主 CSS 文件(约 100KB,含 Ant Design + TailwindCSS) │ ├── vendor-xxxxx.js # 第三方库(React、Ant Design 等) │ └── *.svg/png/... # 其他资源 └── vite.svg # Favicon # 预期构建时间:30-60 秒(取决于机器性能) # 产物总大小:约 2-3MB(压缩后) ``` **构建产物分析**: | 文件类型 | 大小(压缩前) | 大小(压缩后) | 说明 | |---------|--------------|--------------|------| | index.html | 1KB | 1KB | 入口 HTML | | index-xxxxx.js | ~2.5MB | ~800KB | 业务代码 | | vendor-xxxxx.js | ~3MB | ~1MB | 第三方库 | | index-xxxxx.css | ~300KB | ~100KB | 样式文件 | | 其他资源 | ~500KB | ~200KB | 图标、字体等 | | **总计** | **~6.3MB** | **~2.1MB** | Gzip 压缩后 | ### 🔍 当前 API 配置分析 **frontend-v2 的 API 配置方式**: ```typescript // modules/asl/api/index.ts const API_BASE_URL = '/api/v1/asl'; // ✅ 相对路径(推荐) // modules/dc/api/toolC.ts const BASE_URL = '/api/v1/dc/tool-c'; // ✅ 相对路径(推荐) // modules/dc/api/toolB.ts const BASE_URL = '/api/v1/dc/tool-b'; // ✅ 相对路径(推荐) ``` **开发环境代理配置**: ```typescript // vite.config.ts export default defineConfig({ server: { host: '0.0.0.0', port: 3000, proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true, }, }, }, }); ``` **生产环境配置策略**: - ✅ **不需要**设置任何环境变量(如 `VITE_API_BASE_URL`) - ✅ 所有模块都使用相对路径 `/api/v1/...` - ✅ 使用 Nginx 反向代理统一处理 - ✅ 开发环境通过 Vite proxy 代理到后端 - ✅ 生产环境通过 Nginx 代理到后端 **完美设计**: - ✅ 无需在构建时注入环境变量 - ✅ 同一套代码适用于开发和生产 - ✅ 支持私有化部署(不依赖外部配置) --- ## 4. 创建 Nginx 配置 ### 📝 创建 nginx.conf **⚠️ 重要:在 `frontend-v2/` 目录下创建 `nginx.conf`(不是 `frontend/`)** ```nginx # Nginx 配置文件 - AI临床研究平台前端服务 # 用途:托管 React SPA + 反向代理后端 API user nginx; worker_processes auto; # 自动根据 CPU 核心数调整 # ⚠️ 日志输出到 stderr(SAE 会自动收集) error_log /dev/stderr warn; pid /var/run/nginx.pid; events { worker_connections 1024; # 每个 worker 进程的最大连接数 use epoll; # Linux 下使用 epoll(高性能) } http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # ⚠️ 日志输出到 stdout(SAE 会自动收集,避免磁盘写满) access_log /dev/stdout main; # 性能优化 sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # Gzip 压缩(减少传输大小) gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; gzip_disable "msie6"; # 上游后端服务(Backend Service) upstream backend { # ⚠️ 重要:这里的地址在部署时需要替换为真实的后端内网地址 # SAE 部署时,通过环境变量注入,详见 Dockerfile server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT} fail_timeout=30s max_fails=3; # 如果有多个后端实例(负载均衡) # server 172.17.x.x:3001 weight=1; # server 172.16.0.31:3001 weight=1; keepalive 32; # 保持连接池 } server { listen 80; server_name _; # 接受所有域名 # 根目录(React 构建产物) root /usr/share/nginx/html; index index.html; # 字符集 charset utf-8; # ==================== 静态资源处理 ==================== # 主页面(index.html)- 不缓存 location = / { try_files /index.html =404; add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } # JS/CSS 文件 - 强缓存(文件名带 hash) location ~* \.(js|css)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # 图片/字体文件 - 强缓存 location ~* \.(png|jpg|jpeg|gif|ico|svg|webp|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # ==================== API 反向代理 ==================== # 后端 API 代理(关键配置) location /api/ { # 代理到后端服务 proxy_pass http://backend; # 保留原始请求信息 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; # 超时配置(AI 对话、文件处理可能耗时较长) proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; # 缓冲配置 proxy_buffering off; # 关闭缓冲(实时流式响应) proxy_request_buffering off; # 支持大文件上传 # WebSocket 支持(如果后续需要) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 错误处理 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; proxy_next_upstream_tries 2; } # ==================== SPA 路由支持 ==================== # React Router 路由回退 # 所有非文件请求都返回 index.html(SPA 的核心) location / { try_files $uri $uri/ /index.html; # 禁用缓存(用户刷新时总是获取最新页面) add_header Cache-Control "no-cache, no-store, must-revalidate"; } # ==================== 安全加固 ==================== # 隐藏 Nginx 版本号 server_tokens off; # 禁止访问隐藏文件 location ~ /\. { deny all; access_log off; log_not_found off; } # 禁止访问特定文件 location ~* \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)$ { deny all; } # ==================== 健康检查 ==================== # SAE 健康检查端点 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } # Nginx 状态页(用于监控) location /nginx_status { stub_status on; access_log off; # 仅允许内网访问 allow 10.0.0.0/8; allow 172.17.0.0/16; allow 192.168.0.0/16; deny all; } # ==================== 错误页面 ==================== error_page 404 /index.html; # SPA 路由回退 error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } ``` ### 🔍 配置关键点解析 #### 1. 后端地址动态注入 ```nginx upstream backend { server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}; } ``` **问题**:Nginx 配置文件不支持环境变量。 **解决方案**:在 Docker 启动时使用 `envsubst` 替换占位符(详见第 5 节 Dockerfile)。 #### 2. SPA 路由支持 ```nginx location / { try_files $uri $uri/ /index.html; } ``` **原理**: - 用户访问 `/dashboard` → Nginx 找不到 `dashboard` 文件 → 返回 `index.html` - React Router 接管路由 → 渲染 Dashboard 组件 **如果没有这个配置**: - 用户刷新 `/dashboard` → Nginx 报 404 错误 ❌ #### 3. 反向代理配置 ```nginx location /api/ { proxy_pass http://backend; } ``` **请求流程**: ``` 浏览器:GET /api/v1/projects ↓ Nginx:接收请求 ↓ Nginx:proxy_pass http://backend ↓ 后端服务:http://172.17.x.x:3001/api/v1/projects ↓ 后端返回数据 ↓ Nginx 转发给浏览器 ``` **优势**: - ✅ 前端和后端同源(都是 `https://your-domain.com`) - ✅ 无需处理 CORS - ✅ 前端代码无需知道后端真实地址 #### 4. 缓存策略 | 资源类型 | 缓存策略 | 原因 | |---------|---------|------| | `index.html` | `no-cache` | 确保用户总能获取最新版本 | | `.js` / `.css` | `1y` + `immutable` | 文件名带 hash,内容不变 | | 图片/字体 | `1y` + `immutable` | 静态资源,不会改变 | **为什么 index.html 不缓存?** ``` 用户首次访问: ↓ 下载 index.html(引用 index-abc123.js) ↓ 部署新版本:index.html 更新(引用 index-xyz789.js) ↓ 用户再次访问: - 如果 index.html 被缓存 → 仍加载 index-abc123.js(旧版本) ❌ - 如果 index.html 不缓存 → 加载 index-xyz789.js(新版本) ✅ ``` --- ## 5. 构建 Docker 镜像 ### 📝 创建 Dockerfile **⚠️ 重要:在 `frontend-v2/` 目录下创建 `Dockerfile`(不是 `frontend/`)** ```dockerfile # ==================== 阶段 1: 构建阶段 ==================== # ⚠️ 使用 Node 22(与开发环境一致,避免 package-lock.json 版本冲突) FROM node:22-alpine AS builder # 设置工作目录 WORKDIR /app # 1. 复制依赖文件 COPY package*.json ./ # 2. 安装依赖 # 使用国内镜像加速(可选) # RUN npm config set registry https://registry.npmmirror.com RUN npm ci --only=production=false # 3. 复制源代码 COPY . . # 4. 构建生产版本 # ⚠️ 注意:如果需要在构建时注入环境变量,在这里设置 ARG # ARG VITE_API_BASE_URL # ENV VITE_API_BASE_URL=$VITE_API_BASE_URL RUN npm run build # 验证构建产物 RUN ls -la /app/dist/ # ==================== 阶段 2: 运行阶段 ==================== FROM nginx:1.25-alpine # 安装必要工具(包括时区数据) RUN apk add --no-cache \ bash \ gettext \ curl \ tzdata # 设置容器时区为上海(否则日志时间会比北京时间慢 8 小时) RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone # 创建 Nginx 配置目录 RUN mkdir -p /etc/nginx/templates # 1. 复制 Nginx 配置模板(支持环境变量替换) COPY nginx.conf /etc/nginx/templates/nginx.conf.template # 2. 复制构建产物到 Nginx 默认目录 COPY --from=builder /app/dist /usr/share/nginx/html # 3. 创建启动脚本(处理环境变量替换) RUN cat > /docker-entrypoint.sh <<'EOF' #!/bin/bash set -e # ⚠️ 关键:不给默认值,强制在 SAE 控制台配置 # 如果未配置,报错退出(避免使用错误的后端地址) if [ -z "$BACKEND_SERVICE_HOST" ]; then echo "❌ ERROR: BACKEND_SERVICE_HOST environment variable is required!" echo "Please configure it in SAE console with backend internal IP (e.g., 172.16.0.30)" exit 1 fi if [ -z "$BACKEND_SERVICE_PORT" ]; then echo "⚠️ WARNING: BACKEND_SERVICE_PORT not set, using default: 3001" export BACKEND_SERVICE_PORT=3001 fi echo "============================================" echo "Starting Frontend Nginx Service" echo "Backend Service: ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}" echo "Container Timezone: $(cat /etc/timezone)" echo "Current Time: $(date)" echo "============================================" # 使用 envsubst 替换 Nginx 配置中的环境变量 envsubst '${BACKEND_SERVICE_HOST} ${BACKEND_SERVICE_PORT}' \ < /etc/nginx/templates/nginx.conf.template \ > /etc/nginx/nginx.conf # 验证 Nginx 配置 nginx -t # 启动 Nginx exec nginx -g 'daemon off;' EOF RUN chmod +x /docker-entrypoint.sh # 健康检查 HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD curl -f http://localhost/health || exit 1 # 暴露端口 EXPOSE 80 # 启动命令 ENTRYPOINT ["/docker-entrypoint.sh"] ``` ### 📝 创建 .dockerignore **⚠️ 重要:在 `frontend-v2/` 目录下创建 `.dockerignore`(不是 `frontend/`)** ``` # Node.js node_modules npm-debug.log yarn-error.log .npm .yarn # 开发文件 .env .env.* *.local # 构建产物(Dockerfile 中会重新生成) dist # 测试文件 test tests *.test.ts *.test.tsx *.spec.ts *.spec.tsx coverage .nyc_output # 文档和临时文件 docs *.md !README.md .vscode .idea .DS_Store Thumbs.db # Git .git .gitignore .gitattributes # CI/CD .github .gitlab-ci.yml .travis.yml # 日志 *.log logs # 临时文件 temp tmp *.swp *.swo *~ # 编辑器配置 .editorconfig .prettierrc .eslintrc* # TypeScript 配置(保留 tsconfig.json,其他忽略) tsconfig.tsbuildinfo # Vite .vite vite.config.*.timestamp-* ``` ### 📝 Dockerfile 关键设计解析 #### 1. 多阶段构建(减小镜像体积) ```dockerfile # 阶段 1:构建(Node.js 环境) FROM node:22-alpine AS builder # ⚠️ 使用 Node 22(与开发环境一致) # 体积:~200MB(临时) RUN npm run build # 阶段 2:运行(Nginx 环境) FROM nginx:1.25-alpine # 体积:~25MB(最终) COPY --from=builder /app/dist /usr/share/nginx/html ``` **效果对比**: - ❌ 单阶段构建:~700MB(包含 Node.js + npm + 所有依赖) - ✅ 多阶段构建:~40-60MB(仅 Nginx + 静态文件) **为什么使用 Node 22**: - 开发环境使用 Node 22.18.0+ - `package-lock.json` 由 Node 22 生成(Lockfile version 3) - Node 18 可能导致依赖安装警告或失败 - 保证构建环境与开发环境完全一致 #### 2. 环境变量替换机制 ```bash # 启动脚本 envsubst '${BACKEND_SERVICE_HOST} ${BACKEND_SERVICE_PORT}' \ < /etc/nginx/templates/nginx.conf.template \ > /etc/nginx/nginx.conf ``` **流程**: ``` nginx.conf.template(模板): server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}; ↓ envsubst 替换 nginx.conf(最终配置): server 172.17.x.x:3001; ``` #### 3. 健康检查 ```dockerfile HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD curl -f http://localhost/health || exit 1 ``` **SAE 会使用这个健康检查**: - ✅ 容器启动后 30 秒开始检查 - ✅ 每 30 秒检查一次 - ✅ 连续失败 3 次 → 标记为不健康 → SAE 重启容器 --- ## 6. 本地测试验证 ### 步骤 1:构建镜像 ```bash # ⚠️ 重要:使用 frontend-v2 目录(不是 frontend) cd frontend-v2 # 确认当前目录正确 pwd # 应该输出:.../AIclinicalresearch/frontend-v2 # 确认 package.json 存在 cat package.json | grep '"name"' # 应该输出:"name": "frontend-v2" # 构建镜像(需要 3-5 分钟) docker build -t frontend-service:v1.0.0 . # 查看镜像大小 docker images frontend-service:v1.0.0 # 预期大小:~40-60MB(Alpine 基础镜像 + 构建产物) # 查看镜像详情 docker inspect frontend-service:v1.0.0 | grep -A 5 "Size" ``` **如果构建失败**: ```bash # 常见问题 1:npm install 超时 # 解决:使用国内镜像源 # 在 Dockerfile 的 npm ci 之前添加: RUN npm config set registry https://registry.npmmirror.com # 常见问题 2:构建产物为空 # 解决:检查 package.json 中的 build 脚本 cat package.json | grep '"build"' # 应该输出: "build": "tsc -b && vite build", # 常见问题 3:TypeScript 编译错误 # 解决:检查是否有类型错误 npm run build # 在 Docker 外先测试构建 # 常见问题 4:nginx.conf 文件找不到 # 解决:确保 nginx.conf 在 frontend-v2/ 目录下 ls nginx.conf ``` ### 步骤 2:本地运行测试 ```bash # 运行容器(需要指定后端地址) docker run -d \ --name frontend-test \ -p 8080:80 \ -e BACKEND_SERVICE_HOST=host.docker.internal \ -e BACKEND_SERVICE_PORT=3001 \ frontend-service:v1.0.0 # 查看启动日志 docker logs frontend-test # 应该看到: # ============================================ # Starting Frontend Nginx Service # Backend Service: host.docker.internal:3001 # ============================================ # nginx: the configuration file /etc/nginx/nginx.conf syntax is ok # nginx: configuration file /etc/nginx/nginx.conf test is successful ``` **注意**:`host.docker.internal` 是 Docker Desktop 特有的域名,指向宿主机。 ### 步骤 3:测试静态资源访问 ```bash # 测试主页 curl http://localhost:8080/ # 预期返回: # # # ... # # 测试健康检查 curl http://localhost:8080/health # 预期返回: # healthy ``` ### 步骤 4:测试 SPA 路由 ```bash # 访问不存在的路由(应该返回 index.html,而不是 404) curl http://localhost:8080/dashboard # 预期返回: # # # ...(与主页相同) # # 而不是: # # 404 Not Found # ...(Nginx 默认 404 页面) ``` ### 步骤 5:测试反向代理(关键测试) **前提**:确保本地后端服务在 `http://localhost:3001` 运行。 ```bash # 通过前端 Nginx 访问后端 API curl http://localhost:8080/api/v1/health # 预期返回(后端的响应): { "status": "healthy", "timestamp": "2025-12-13T10:30:00.000Z", "database": { "status": "connected" } } # 查看 Nginx 日志,确认代理成功 docker logs frontend-test 2>&1 | grep "/api/v1/health" # 应该看到: # 172.17.0.1 - - [13/Dec/2025:10:30:00 +0000] "GET /api/v1/health HTTP/1.1" 200 123 ``` ### 步骤 6:浏览器测试 打开浏览器,访问 `http://localhost:8080` **测试清单**: - [ ] 页面能正常加载(不是空白页) - [ ] 样式正确显示(CSS 加载成功) - [ ] JavaScript 功能正常(交互正常) - [ ] 刷新页面不会 404(SPA 路由正常) - [ ] 浏览器开发者工具 Network 标签: - [ ] API 请求路径是 `/api/v1/...`(相对路径) - [ ] API 请求状态是 200(代理成功) - [ ] 没有 CORS 错误 ### 步骤 7:清理测试容器 ```bash # 停止并删除测试容器 docker stop frontend-test docker rm frontend-test ``` --- ## 7. 推送到 ACR ### 步骤 1:登录 ACR ```bash # 登录(使用 ACR 密码,不是阿里云账号密码) docker login --username=your-aliyun-account registry.cn-beijing.aliyuncs.com # 输入密码后看到: # Login Succeeded ``` ### 步骤 2:标记镜像 ```bash # 格式:registry地址/命名空间/仓库名:版本号 docker tag frontend-service:v1.0.0 \ registry.cn-beijing.aliyuncs.com/clinical-research/frontend-service:v1.0.0 # 同时打一个 latest 标签 docker tag frontend-service:v1.0.0 \ registry.cn-beijing.aliyuncs.com/clinical-research/frontend-service:latest ``` ### 步骤 3:推送镜像 ```bash # 推送指定版本 docker push registry.cn-beijing.aliyuncs.com/clinical-research/frontend-service:v1.0.0 # 推送 latest docker push registry.cn-beijing.aliyuncs.com/clinical-research/frontend-service:latest # 推送过程需要 1-3 分钟(镜像很小) ``` ### 步骤 4:验证推送成功 登录阿里云控制台 → 容器镜像服务 → 镜像仓库 → `frontend-service` - 应该看到版本:`v1.0.0` 和 `latest` - 镜像大小:~30-50MB - 推送时间:刚才的时间 --- ## 8. SAE 应用配置 ### 步骤 1:创建应用 **阿里云控制台** → **SAE** → **应用列表** → **创建应用** | 配置项 | 值 | 说明 | |-------|-----|------| | **应用名称** | `frontend-service` | 前端服务 | | **命名空间** | 选择已创建的命名空间 | 与后端服务同一命名空间 | | **VPC** | 选择后端所在 VPC | 必须与后端在同一 VPC | | **交换机** | 选择可用区 | 建议多可用区 | | **应用实例规格** | 0.5核1G | 前端Nginx占用资源很少 | | **实例数量** | 2 | 最小 2 个实例(高可用) | **为什么只需要 0.5核1G?** | 服务类型 | 推荐规格 | 原因 | |---------|---------|------| | Node.js 后端 | 2核4G | 需要处理 AI 对话、数据库查询 | | Python 微服务 | 2核4G | PDF 提取是 CPU 密集型 | | **Nginx 前端** | **0.5核1G** | **仅提供静态文件,消耗极少** | ### 步骤 2:配置镜像 | 配置项 | 值 | |-------|-----| | **镜像类型** | 容器镜像服务企业版实例 | | **镜像仓库** | `registry.cn-beijing.aliyuncs.com/clinical-research/frontend-service` | | **镜像版本** | `v1.0.0` | | **镜像拉取策略** | 总是拉取镜像 | ### 步骤 3:配置端口 | 配置项 | 值 | |-------|-----| | **容器端口** | `80` | | **协议** | TCP | ### 步骤 4:配置环境变量(关键步骤) **⚠️ 极其重要:必须配置后端服务的内网地址,否则容器启动失败** #### 获取后端内网地址 1. **登录 SAE 控制台** → **应用列表** → **backend-service** → **应用详情** 2. **查看"应用访问配置"** → **VPC 内网访问地址** 复制地址,格式通常是: ``` 172.16.0.30:3001 ``` #### 配置环境变量 ```bash # ⚠️ 必须配置(否则容器启动失败) BACKEND_SERVICE_HOST=172.17.x.x # 可选配置(默认 3001) BACKEND_SERVICE_PORT=3001 ``` **⚠️ 重要说明**: - `BACKEND_SERVICE_HOST` **必须配置**,否则容器启动时会报错退出 - 不要使用主机名(如 `backend-service`),SAE 可能无法解析 - 必须使用后端服务的**内网 IP 地址**(从 SAE 控制台获取) **为什么要拆分成 Host 和 Port?** ```nginx # nginx.conf 中的配置: upstream backend { server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}; } # 如果不拆分,写成: server ${BACKEND_SERVICE_URL}; # ❌ 无法解析 http://172.16.0.30:3001 # 拆分后: server 172.17.x.x:3001; # ✅ 正确 ``` ### 步骤 5:配置健康检查 **SAE 控制台** → **应用配置** → **健康检查** | 配置项 | 值 | 说明 | |-------|-----|------| | **检查方式** | HTTP 请求 | | | **检查路径** | `/health` | Nginx 健康检查端点 | | **检查端口** | 80 | 与容器端口一致 | | **检查协议** | HTTP | | | **初始延迟** | 30 秒 | Nginx 启动很快 | | **检查间隔** | 10 秒 | | | **超时时间** | 3 秒 | | | **不健康阈值** | 3 次 | 连续失败 3 次标记为不健康 | | **健康阈值** | 2 次 | 连续成功 2 次标记为健康 | ### 步骤 6:配置弹性伸缩 **SAE 控制台** → **应用配置** → **弹性伸缩** | 配置项 | 值 | 说明 | |-------|-----|------| | **最小实例数** | 2 | 高可用保证 | | **最大实例数** | 5 | 前端流量波动通常不大 | | **扩容条件** | CPU > 60% 持续 3 分钟 | Nginx 很少达到这个阈值 | | **缩容条件** | CPU < 20% 持续 5 分钟 | | **注意**:前端 Nginx 很少需要扩容,因为静态文件服务极其高效。 ### 步骤 7:配置公网访问(可选) **SAE 控制台** → **应用配置** → **应用访问配置** #### 选项 A:使用 SAE 提供的公网地址(临时测试) - **优势**:免费,立即可用 - **劣势**:域名难记,HTTPS 证书是 SAE 的 #### 选项 B:绑定自定义域名(生产推荐) 1. **添加域名解析**: ``` your-domain.com → CNAME → frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com ``` 2. **配置 HTTPS 证书**: - 上传 SSL 证书(或使用免费证书) 3. **强制 HTTPS**: ```nginx # 在 nginx.conf 中添加重定向 server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri; } ``` ### 步骤 8:部署应用 点击"部署"按钮,SAE 将: 1. 从 ACR 拉取镜像(~30 秒) 2. 启动容器实例(~30 秒) 3. 执行健康检查(~30 秒) 4. 流量切换(~10 秒) **总耗时**:约 2 分钟 --- ## 9. 端到端测试 ### 步骤 1:获取应用访问地址 **SAE 控制台** → **应用详情** → **应用访问配置** 复制公网访问地址: ``` https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com ``` ### 步骤 2:测试主页加载 ```bash # 使用 curl 测试 curl -I https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/ # 预期返回: # HTTP/2 200 # content-type: text/html # cache-control: no-cache, no-store, must-revalidate # ... # 完整响应 curl https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/ | head -n 10 # 应该看到 HTML 内容 ``` ### 步骤 3:浏览器完整测试 打开浏览器,访问前端地址: #### 测试清单 A:页面基础功能 - [ ] **主页能正常加载** - 看到登录页面或首页 - 样式正确显示(不是纯文本) - 图片和图标正常显示 - [ ] **路由跳转正常** - 点击导航菜单,URL 变化 - 页面内容更新 - 浏览器后退/前进按钮正常工作 - [ ] **刷新页面不报错** - 访问 `/dashboard` - 按 F5 刷新 - 页面正常显示(不是 404) #### 测试清单 B:API 调用测试 - [ ] **登录功能** - 输入用户名和密码 - 点击登录 - 查看浏览器开发者工具 → Network 标签 - 应该看到:`POST /api/v1/auth/login` → 状态 200 - [ ] **数据加载** - 访问项目列表页 - 查看 Network 标签 - 应该看到:`GET /api/v1/projects` → 状态 200 - 列表数据正常显示 - [ ] **文件上传** - 尝试上传一个 PDF 文档 - 查看 Network 标签 - 应该看到:`POST /api/v1/knowledge-bases/.../documents` → 状态 200 - 上传进度正常显示 #### 测试清单 C:性能测试 - [ ] **首屏加载时间** - 清除浏览器缓存 - 刷新页面 - 查看 Network 标签 → 底部统计 - 总时间应 < 3 秒(国内访问) - [ ] **静态资源缓存** - 第一次访问:所有资源从服务器加载 - 第二次访问:JS/CSS 文件显示"(from disk cache)" - index.html 显示状态 200(每次都重新获取) - [ ] **Gzip 压缩** - 查看 Response Headers - 应该看到:`Content-Encoding: gzip` - JS 文件大小减少 60-70% ### 步骤 4:测试反向代理(验证 CORS 解决) ```bash # 打开浏览器开发者工具 → Console # 执行以下代码: fetch('/api/v1/health') .then(res => res.json()) .then(data => console.log(data)); # 预期输出: # {status: "healthy", timestamp: "...", ...} # 如果看到 CORS 错误: # ❌ Access to fetch at '/api/v1/health' from origin 'https://...' has been blocked by CORS policy # → 说明 Nginx 反向代理配置有问题 # 正确的情况: # ✅ 请求成功,无任何 CORS 错误 ``` ### 步骤 5:移动端测试(可选) 使用手机浏览器访问前端地址: - [ ] 页面能自适应移动端屏幕 - [ ] 触摸交互正常 - [ ] 页面加载速度可接受 --- ## 10. 监控与维护 ### 📊 SAE 自带监控 #### 1. 实时监控 **SAE 控制台** → **应用详情** → **监控** **关键指标**: | 指标 | 健康阈值 | 告警阈值 | 说明 | |------|---------|---------|------| | **CPU 使用率** | < 30% | > 60% | Nginx 极少超过 30% | | **内存使用率** | < 50% | > 80% | Nginx 内存占用很低 | | **请求 QPS** | - | - | 了解访问量 | | **平均响应时间** | < 50ms | > 200ms | 静态文件响应极快 | | **错误率** | < 0.1% | > 1% | 监控 404/50x 错误 | | **实例数量** | 2+ | - | 确保高可用 | **性能基准(参考)**: ``` 静态 HTML:响应时间 10-20ms JS/CSS 文件:响应时间 10-30ms API 代理:响应时间 50-500ms(取决于后端) 首屏加载(全部资源):1-3秒 ``` #### 2. 日志查看 **SAE 控制台** → **应用详情** → **日志** → **实时日志** **关键日志示例**: ```bash # ✅ 正常启动 ============================================ Starting Frontend Nginx Service Backend Service: 172.17.x.x:3001 ============================================ nginx: configuration file /etc/nginx/nginx.conf test is successful # ✅ 正常请求(静态资源) 172.31.0.10 - - [13/Dec/2025:10:30:00 +0000] "GET / HTTP/1.1" 200 1234 172.31.0.10 - - [13/Dec/2025:10:30:01 +0000] "GET /assets/index-xxxxx.js HTTP/1.1" 200 567890 # ✅ 正常请求(API 代理) 172.31.0.10 - - [13/Dec/2025:10:30:02 +0000] "GET /api/v1/projects HTTP/1.1" 200 8765 # ⚠️ 警告日志(404 Not Found) 172.31.0.10 - - [13/Dec/2025:10:30:03 +0000] "GET /favicon.ico HTTP/1.1" 404 153 # ❌ 错误日志(后端连接失败) 2025/12/13 10:30:04 [error] 7#7: *1 connect() failed (111: Connection refused) while connecting to upstream client: 172.31.0.10, server: _, request: "GET /api/v1/projects HTTP/1.1" upstream: "http://172.17.x.x:3001/api/v1/projects" ``` #### 3. Nginx 状态监控 ```bash # 在 SAE Webshell 中执行(或通过内网访问) curl http://localhost/nginx_status # 输出示例: # Active connections: 15 # server accepts handled requests # 123456 123456 567890 # Reading: 2 Writing: 5 Waiting: 8 ``` **指标含义**: - `Active connections`: 当前活跃连接数 - `accepts`: 已接受的连接总数 - `handled`: 已处理的连接总数 - `requests`: 已处理的请求总数 - `Reading`: 正在读取请求头的连接数 - `Writing`: 正在写入响应的连接数 - `Waiting`: 空闲的 Keep-Alive 连接数 ### 🔧 日常维护任务 #### 每日检查 ```bash # 1. 检查应用健康状态 # SAE 控制台 → 应用列表 → 查看运行状态(绿色为正常) # 2. 查看错误日志 # SAE 控制台 → 应用详情 → 日志 → 筛选 error 级别 # 3. 检查访问量 # SAE 控制台 → 应用详情 → 监控 → 查看 QPS ``` #### 每周任务 ```bash # 1. 查看性能指标趋势 # SAE 控制台 → 应用详情 → 监控 → 选择"最近 7 天" # 关注: # - 响应时间是否变慢 # - 错误率是否增加 # - 流量是否有异常波动 # 2. 查看 404 错误 # 在日志中搜索 "404",分析原因: # - 资源确实不存在? # - SPA 路由配置问题? # - 外部爬虫访问不存在的路径? ``` #### 每月任务 ```bash # 1. 更新前端代码(如有新版本) # 在本地重新构建镜像: cd frontend npm run build docker build -t frontend-service:v1.0.1 . docker push registry.cn-beijing.aliyuncs.com/clinical-research/frontend-service:v1.0.1 # 2. 在 SAE 中更新镜像 # SAE 控制台 → 应用详情 → 部署 # 选择新镜像版本:v1.0.1 # 灰度发布:先更新 1 个实例,观察 5 分钟后全量发布 # 3. 清理旧镜像(节省 ACR 存储空间) # ACR 控制台 → 镜像仓库 → frontend-service # 删除 30 天前的旧版本(保留最近 3 个版本) # 4. 检查 SSL 证书有效期(如果使用自定义域名) # SAE 控制台 → 应用详情 → 应用访问配置 → HTTPS 设置 # 证书到期前 30 天更新 ``` ### 🚨 告警配置 **云监控** → **应用监控** → **创建告警规则** **推荐告警规则**: | 告警项 | 阈值 | 通知方式 | |-------|------|---------| | CPU 使用率 > 60% 持续 5 分钟 | 告警 | 钉钉/邮件 | | 内存使用率 > 80% 持续 5 分钟 | 告警 | 钉钉/邮件 | | 错误率 > 1% 持续 3 分钟 | 紧急 | 短信+钉钉 | | 实例健康检查失败 > 3 次 | 紧急 | 短信+钉钉 | | 平均响应时间 > 500ms 持续 5 分钟 | 告警 | 钉钉/邮件 | --- ## 11. 故障排查 ### 问题 1:页面加载空白(最常见) **症状**: 浏览器访问前端地址,看到空白页面。 **排查步骤**: ```bash # 1. 检查 HTML 是否正确返回 curl -I https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/ # 如果返回 404 或 50x,说明 Nginx 配置有问题 # 2. 检查构建产物是否存在 # 登录 SAE Webshell: ls -la /usr/share/nginx/html/ # 应该看到: # index.html # assets/index-xxxxx.js # assets/index-xxxxx.css # 如果 dist/ 目录为空,说明构建阶段失败 # 3. 查看浏览器开发者工具 Console # 如果看到: # Uncaught SyntaxError: Unexpected token '<' # 说明 JS 文件路径错误,返回了 HTML 而不是 JS # 4. 检查 Vite 构建配置 # vite.config.ts 中的 base 路径 # 应该是:base: '/'(默认) # 不要写成:base: '/app/'(会导致路径错误) ``` **解决方法**: ```bash # 方法 1:重新构建镜像(本地验证) cd frontend npm run build ls -la dist/ # 确认构建产物存在 docker build -t frontend-service:v1.0.1 . # 方法 2:检查 Dockerfile 是否正确复制了 dist/ # 确保有这一行: COPY --from=builder /app/dist /usr/share/nginx/html ``` ### 问题 2:刷新页面报 404(SPA 路由问题) **症状**: - 访问 `/` 正常 - 点击链接跳转到 `/dashboard` 正常 - 刷新页面(F5)→ 404 Not Found **原因**: Nginx 找不到 `/dashboard` 文件,应该返回 `index.html` 让 React Router 接管。 **排查步骤**: ```bash # 1. 检查 Nginx 配置中的 try_files # 登录 SAE Webshell: cat /etc/nginx/nginx.conf | grep "try_files" # 应该看到: # try_files $uri $uri/ /index.html; # 如果没有这一行,说明 SPA 路由配置缺失 ``` **解决方法**: ```nginx # 在 nginx.conf 的 location / 块中添加: location / { try_files $uri $uri/ /index.html; } # 重新构建镜像并部署 ``` ### 问题 3:API 请求报错(反向代理问题) **症状**: - 前端页面正常显示 - 调用 API 时报错: - `Network Error` - `504 Gateway Timeout` - `502 Bad Gateway` **排查步骤**: ```bash # 1. 检查后端服务是否运行 # SAE 控制台 → 应用列表 → backend-service → 查看状态 # 2. 测试后端内网地址是否可达 # 登录前端应用的 Webshell: curl http://172.17.x.x:3001/api/v1/health # 如果返回错误,说明: # - 后端服务未启动 # - 内网地址配置错误 # - VPC 网络不通 # 3. 检查 Nginx 配置中的 upstream cat /etc/nginx/nginx.conf | grep -A 5 "upstream backend" # 应该看到正确的后端地址: # server 172.17.x.x:3001 fail_timeout=30s max_fails=3; # 4. 查看 Nginx 错误日志 tail -f /var/log/nginx/error.log | grep "upstream" ``` **解决方法**: ```bash # 方法 1:更新环境变量 # SAE 控制台 → frontend-service → 应用配置 → 环境变量 # 确认: BACKEND_SERVICE_HOST=172.17.x.x # 正确的内网 IP BACKEND_SERVICE_PORT=3001 # 重启应用使环境变量生效 # 方法 2:测试内网连通性 # 在前端 Webshell 中: telnet 172.17.x.x 3001 # 如果连接失败,检查: # - 后端和前端是否在同一 VPC # - 安全组规则是否允许访问 ``` ### 问题 4:CORS 错误(反向代理未生效) **症状**: 浏览器 Console 显示: ``` Access to fetch at 'http://backend-service:3001/api/v1/projects' from origin 'https://frontend-service.com' has been blocked by CORS policy ``` **原因**: 前端代码中硬编码了后端地址,而不是使用相对路径。 **排查步骤**: ```bash # 1. 检查前端代码中的 API 调用 # src/api/request.ts cat src/api/request.ts | grep "baseURL" # ❌ 错误示例: # baseURL: 'http://backend-service:3001/api/v1' # ✅ 正确示例: # baseURL: '/api/v1' # 2. 检查浏览器 Network 标签 # 正确的请求路径应该是: # https://frontend-service.com/api/v1/projects # 而不是: # http://backend-service:3001/api/v1/projects ``` **解决方法**: ```typescript // src/api/request.ts const request = axios.create({ baseURL: '/api/v1', // ✅ 使用相对路径 timeout: 30000, }); // 重新构建并部署 ``` ### 问题 5:静态资源 404(路径问题) **症状**: - HTML 能加载 - JS/CSS 文件 404 Not Found - 浏览器 Console 显示: ``` GET https://frontend-service.com/assets/index-xxxxx.js 404 (Not Found) ``` **原因**: Vite 构建配置中的 `base` 路径不正确。 **排查步骤**: ```bash # 1. 查看 index.html 中的资源路径 curl https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/ | grep "assets" # 应该看到: # # 如果看到: # # ⚠️ 相对路径 # 或 # # ❌ 错误的 base # 2. 检查 vite.config.ts cat vite.config.ts | grep "base" ``` **解决方法**: ```typescript // vite.config.ts export default defineConfig({ base: '/', // ✅ 默认值,绝对路径 // base: '/app/', // ❌ 不要设置子路径(除非确实需要) plugins: [react()], }); // 重新构建并部署 ``` ### 问题 6:页面样式错乱(CSS 未加载) **症状**: 页面内容显示,但样式混乱(纯文本排版)。 **排查步骤**: ```bash # 1. 检查浏览器 Network 标签 # 查看 CSS 文件是否成功加载 # 如果显示 404,参考问题 5 # 2. 检查 CSS 文件的 MIME 类型 curl -I https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/assets/index-xxxxx.css # 应该看到: # Content-Type: text/css # 如果看到: # Content-Type: application/octet-stream # ❌ 错误的 MIME 类型 # 3. 检查 Nginx 配置 cat /etc/nginx/nginx.conf | grep "include.*mime.types" ``` **解决方法**: ```nginx # 确保 nginx.conf 中包含: http { include /etc/nginx/mime.types; # ✅ 必需 default_type application/octet-stream; ... } ``` ### 问题 7:环境变量未替换 **症状**: Nginx 启动失败,错误日志显示: ``` nginx: [emerg] host not found in upstream "${BACKEND_SERVICE_HOST}" in /etc/nginx/nginx.conf:45 ``` **原因**: `envsubst` 未正确替换环境变量。 **排查步骤**: ```bash # 1. 检查启动脚本 cat /docker-entrypoint.sh | grep "envsubst" # 2. 检查最终生成的 nginx.conf cat /etc/nginx/nginx.conf | grep "server.*backend" # 应该看到: # server 172.17.x.x:3001; # 如果看到: # server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}; # ❌ 未替换 ``` **解决方法**: ```dockerfile # 确保 Dockerfile 中的启动脚本正确: envsubst '${BACKEND_SERVICE_HOST} ${BACKEND_SERVICE_PORT}' \ < /etc/nginx/templates/nginx.conf.template \ > /etc/nginx/nginx.conf # 注意: # - 单引号包裹变量名 # - 使用 .template 文件作为源 # - 输出到 /etc/nginx/nginx.conf ``` --- ## 12. 注意事项与禁忌 ### ✅ 最佳实践 #### 1. **API 调用使用相对路径** ```typescript // ✅ 正确做法:相对路径 const request = axios.create({ baseURL: '/api/v1', }); // 前端:https://your-domain.com // API:https://your-domain.com/api/v1/... // 同源请求,无 CORS 问题 // ❌ 错误做法:硬编码后端地址 const request = axios.create({ baseURL: 'http://backend-service:3001/api/v1', }); // 跨域请求,会有 CORS 问题 ``` #### 2. **Vite base 路径配置** ```typescript // ✅ 正确做法:默认根路径 export default defineConfig({ base: '/', // 绝对路径,适合大多数场景 }); // ⚠️ 仅在以下情况使用子路径: // - 部署到 CDN 的子目录 // - 多个前端共享一个域名 export default defineConfig({ base: '/app/', // 资源路径:/app/assets/index.js }); ``` #### 3. **环境变量命名规范** ```bash # ✅ 正确做法:拆分 Host 和 Port BACKEND_SERVICE_HOST=172.17.x.x BACKEND_SERVICE_PORT=3001 # ❌ 错误做法:完整 URL BACKEND_SERVICE_URL=http://172.17.x.x:3001 # Nginx 无法解析协议前缀 ``` #### 4. **缓存策略** ```nginx # ✅ 正确做法:差异化缓存 location = /index.html { add_header Cache-Control "no-cache"; # 每次都获取最新 } location ~* \.(js|css)$ { expires 1y; # 文件名带 hash,可以长期缓存 } # ❌ 错误做法:全部缓存或全部不缓存 add_header Cache-Control "public, max-age=31536000"; # index.html 也缓存 1 年 add_header Cache-Control "no-cache"; # 所有文件都不缓存(浪费带宽) ``` #### 5. **健康检查端点** ```nginx # ✅ 正确做法:独立的健康检查端点 location /health { access_log off; return 200 "healthy\n"; } # ❌ 错误做法:使用主页作为健康检查 # 如果前端代码有 Bug,健康检查也会失败 ``` ### ❌ 绝对禁止 #### 1. **禁止在前端代码中硬编码后端地址** ```typescript // ❌ 错误示例 const API_URL = 'http://172.16.0.30:3001/api/v1'; // 后果: // - 本地开发时需要修改代码 // - 部署到生产环境时需要再次修改 // - 跨域问题 // - 无法使用 Nginx 反向代理 // ✅ 正确做法 const API_URL = '/api/v1'; // 相对路径,由 Nginx 代理 ``` #### 2. **禁止忽略 SPA 路由配置** ```nginx # ❌ 错误配置(缺少 try_files) location / { root /usr/share/nginx/html; } # 后果:刷新页面报 404 # ✅ 正确配置 location / { try_files $uri $uri/ /index.html; } ``` #### 3. **禁止将 index.html 设置为长期缓存** ```nginx # ❌ 错误示例 location / { expires 1y; add_header Cache-Control "public, immutable"; } # 后果:部署新版本后,用户仍看到旧版本 # ✅ 正确做法 location = /index.html { add_header Cache-Control "no-cache"; } ``` #### 4. **禁止在 Nginx 配置中使用未经环境变量替换的占位符** ```nginx # ❌ 错误示例(直接写占位符) upstream backend { server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT}; } # 如果没有 envsubst 替换,Nginx 会启动失败 # ✅ 正确做法(使用 envsubst) envsubst '${BACKEND_SERVICE_HOST} ${BACKEND_SERVICE_PORT}' \ < nginx.conf.template > /etc/nginx/nginx.conf ``` #### 5. **禁止忽略 Gzip 压缩** ```nginx # ❌ 错误做法(不压缩) # 传输大小:500KB JS 文件 → 500KB # ✅ 正确做法(启用 Gzip) gzip on; gzip_types text/plain text/css application/javascript; # 传输大小:500KB JS 文件 → 150KB(节省 70%) ``` #### 6. **禁止在生产环境暴露敏感信息** ```nginx # ❌ 错误做法 server_tokens on; # 暴露 Nginx 版本号 autoindex on; # 允许目录浏览 # ✅ 正确做法 server_tokens off; autoindex off; ``` #### 7. **禁止使用过长的超时时间** ```nginx # ❌ 错误示例(30 分钟超时) proxy_read_timeout 1800s; # 后果:占用连接资源,影响并发能力 # ✅ 正确做法(根据实际需求) proxy_read_timeout 300s; # 5 分钟(AI 对话足够) ``` #### 8. **禁止忽略错误处理** ```nginx # ❌ 错误做法(后端挂了,Nginx 直接返回 502) proxy_pass http://backend; # ✅ 正确做法(重试其他实例) proxy_next_upstream error timeout http_502 http_503; proxy_next_upstream_tries 2; ``` #### 9. **禁止在 Dockerfile 中使用 root 用户运行 Nginx** ```dockerfile # ❌ 错误做法 USER root # 安全风险 # ✅ 正确做法 USER nginx # Nginx 官方镜像默认已创建 nginx 用户 # 或者不指定,Alpine 镜像默认使用 nginx 用户 ``` #### 10. **禁止使用错误的 Node 版本** ```dockerfile # ❌ 错误示例:使用 Node 18(与开发环境不一致) FROM node:18-alpine AS builder # 风险:package-lock.json 版本冲突 # ✅ 正确做法:使用 Node 22(与开发环境一致) FROM node:22-alpine AS builder # 保证依赖安装和构建环境一致 ``` #### 11. **禁止将日志写入文件** ```nginx # ❌ 错误做法(日志写入文件) access_log /var/log/nginx/access.log main; # 风险:可能写满容器磁盘 # ✅ 正确做法(日志输出到标准流) access_log /dev/stdout main; error_log /dev/stderr warn; # SAE 会自动收集到日志中心 ``` #### 12. **禁止忽略容器时区设置** ```dockerfile # ❌ 错误做法(不设置时区,默认 UTC) # 问题:日志时间比北京时间慢 8 小时 # ✅ 正确做法(设置为 Asia/Shanghai) RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone ``` #### 13. **禁止给后端地址危险的默认值** ```bash # ❌ 错误做法(给默认值) export BACKEND_SERVICE_HOST=${BACKEND_SERVICE_HOST:-backend-service} # 风险:可能连接到错误的后端 # ✅ 正确做法(强制要求配置) if [ -z "$BACKEND_SERVICE_HOST" ]; then echo "ERROR: BACKEND_SERVICE_HOST is required!" exit 1 fi ``` #### 14. **禁止忽略 Docker 镜像优化** ```dockerfile # ❌ 错误示例:单阶段构建 FROM node:22 RUN npm install RUN npm run build CMD ["nginx"] # 镜像大小:~600MB # ✅ 正确做法:多阶段构建 FROM node:22-alpine AS builder RUN npm run build FROM nginx:1.25-alpine COPY --from=builder /app/dist /usr/share/nginx/html # 镜像大小:~40-60MB(节省 90%) ``` --- ## 📚 相关文档 - [05-Node.js后端-SAE容器部署指南.md](./05-Node.js后端-SAE容器部署指南.md) - [04-Python微服务-SAE容器部署指南.md](./04-Python微服务-SAE容器部署指南.md) - [03-Dify-ECS部署完全指南.md](./03-Dify-ECS部署完全指南.md) --- ## 🆘 获取帮助 **遇到问题?** 1. 查看本文档的"故障排查"章节 2. 查看 SAE 控制台的实时日志 3. 查看 Nginx 错误日志:`/var/log/nginx/error.log` 4. 使用浏览器开发者工具 Network 标签分析请求 5. 联系团队技术支持 --- **部署愉快!🚀**