Summary: - Implement intelligent multi-metric grouping detection algorithm - Add direction 1: timepoint-as-row, metric-as-column (analysis format) - Add direction 2: timepoint-as-column, metric-as-row (display format) - Fix column name pattern detection (FMA___ issue) - Maintain original Record ID order in output - Add full-select/clear buttons in UI - Integrate into TransformDialog with Radio selection - Update 3 documentation files Technical Details: - Python: detect_metric_groups(), apply_multi_metric_to_long(), apply_multi_metric_to_matrix() - Backend: 3 new methods in QuickActionService - Frontend: MultiMetricPanel.tsx (531 lines) - Total: ~1460 lines of new code Status: Fully tested and verified, ready for production
53 KiB
前端 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)
- ✅ 完善:构建路径和目录结构
📋 目录
- 为什么选择 SAE Nginx 容器部署
- 部署前准备
- 前端服务分析
- 创建 Nginx 配置
- 构建 Docker 镜像
- 本地测试验证
- 推送到 ACR
- SAE 应用配置
- 端到端测试
- 监控与维护
- 故障排查
- 注意事项与禁忌
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)
验证命令:
# 检查 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) - 后端健康检查可访问
- 后端 VPC 内网地址已获取(如
-
阿里云容器镜像服务 ACR 已开通
- 命名空间已创建(如
clinical-research) - 登录密码已设置
- 命名空间已创建(如
-
SAE 应用 准备创建
- VPC 和交换机已选择(与后端在同一 VPC)
配置信息准备
# 后端服务内网地址(关键)
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 文件:
// modules/asl/api/index.ts
const API_BASE_URL = '/api/v1/asl';
async function request<T>(url: string, options?: RequestInit): Promise<T> {
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
📝 构建流程
# ⚠️ 注意:使用 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 配置方式:
// 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'; // ✅ 相对路径(推荐)
开发环境代理配置:
// 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 配置文件 - 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. 后端地址动态注入
upstream backend {
server ${BACKEND_SERVICE_HOST}:${BACKEND_SERVICE_PORT};
}
问题:Nginx 配置文件不支持环境变量。
解决方案:在 Docker 启动时使用 envsubst 替换占位符(详见第 5 节 Dockerfile)。
2. SPA 路由支持
location / {
try_files $uri $uri/ /index.html;
}
原理:
- 用户访问
/dashboard→ Nginx 找不到dashboard文件 → 返回index.html - React Router 接管路由 → 渲染 Dashboard 组件
如果没有这个配置:
- 用户刷新
/dashboard→ Nginx 报 404 错误 ❌
3. 反向代理配置
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/)
# ==================== 阶段 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. 多阶段构建(减小镜像体积)
# 阶段 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. 环境变量替换机制
# 启动脚本
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. 健康检查
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:构建镜像
# ⚠️ 重要:使用 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"
如果构建失败:
# 常见问题 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:本地运行测试
# 运行容器(需要指定后端地址)
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:测试静态资源访问
# 测试主页
curl http://localhost:8080/
# 预期返回:
# <!doctype html>
# <html lang="zh-CN">
# ...
# </html>
# 测试健康检查
curl http://localhost:8080/health
# 预期返回:
# healthy
步骤 4:测试 SPA 路由
# 访问不存在的路由(应该返回 index.html,而不是 404)
curl http://localhost:8080/dashboard
# 预期返回:
# <!doctype html>
# <html lang="zh-CN">
# ...(与主页相同)
# </html>
# 而不是:
# <html>
# <head><title>404 Not Found</title></head>
# ...(Nginx 默认 404 页面)
步骤 5:测试反向代理(关键测试)
前提:确保本地后端服务在 http://localhost:3001 运行。
# 通过前端 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 错误
- API 请求路径是
步骤 7:清理测试容器
# 停止并删除测试容器
docker stop frontend-test
docker rm frontend-test
7. 推送到 ACR
步骤 1:登录 ACR
# 登录(使用 ACR 密码,不是阿里云账号密码)
docker login --username=your-aliyun-account registry.cn-beijing.aliyuncs.com
# 输入密码后看到:
# Login Succeeded
步骤 2:标记镜像
# 格式: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:推送镜像
# 推送指定版本
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:配置环境变量(关键步骤)
⚠️ 极其重要:必须配置后端服务的内网地址,否则容器启动失败
获取后端内网地址
-
登录 SAE 控制台 → 应用列表 → backend-service → 应用详情
-
查看"应用访问配置" → VPC 内网访问地址
复制地址,格式通常是:
172.16.0.30:3001
配置环境变量
# ⚠️ 必须配置(否则容器启动失败)
BACKEND_SERVICE_HOST=172.17.x.x
# 可选配置(默认 3001)
BACKEND_SERVICE_PORT=3001
⚠️ 重要说明:
BACKEND_SERVICE_HOST必须配置,否则容器启动时会报错退出- 不要使用主机名(如
backend-service),SAE 可能无法解析 - 必须使用后端服务的内网 IP 地址(从 SAE 控制台获取)
为什么要拆分成 Host 和 Port?
# 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:绑定自定义域名(生产推荐)
-
添加域名解析:
your-domain.com → CNAME → frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com -
配置 HTTPS 证书:
- 上传 SSL 证书(或使用免费证书)
-
强制 HTTPS:
# 在 nginx.conf 中添加重定向 server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri; }
步骤 8:部署应用
点击"部署"按钮,SAE 将:
- 从 ACR 拉取镜像(~30 秒)
- 启动容器实例(~30 秒)
- 执行健康检查(~30 秒)
- 流量切换(~10 秒)
总耗时:约 2 分钟
9. 端到端测试
步骤 1:获取应用访问地址
SAE 控制台 → 应用详情 → 应用访问配置
复制公网访问地址:
https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com
步骤 2:测试主页加载
# 使用 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 解决)
# 打开浏览器开发者工具 → 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 控制台 → 应用详情 → 日志 → 实时日志
关键日志示例:
# ✅ 正常启动
============================================
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 状态监控
# 在 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 连接数
🔧 日常维护任务
每日检查
# 1. 检查应用健康状态
# SAE 控制台 → 应用列表 → 查看运行状态(绿色为正常)
# 2. 查看错误日志
# SAE 控制台 → 应用详情 → 日志 → 筛选 error 级别
# 3. 检查访问量
# SAE 控制台 → 应用详情 → 监控 → 查看 QPS
每周任务
# 1. 查看性能指标趋势
# SAE 控制台 → 应用详情 → 监控 → 选择"最近 7 天"
# 关注:
# - 响应时间是否变慢
# - 错误率是否增加
# - 流量是否有异常波动
# 2. 查看 404 错误
# 在日志中搜索 "404",分析原因:
# - 资源确实不存在?
# - SPA 路由配置问题?
# - 外部爬虫访问不存在的路径?
每月任务
# 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:页面加载空白(最常见)
症状:
浏览器访问前端地址,看到空白页面。
排查步骤:
# 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/'(会导致路径错误)
解决方法:
# 方法 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 接管。
排查步骤:
# 1. 检查 Nginx 配置中的 try_files
# 登录 SAE Webshell:
cat /etc/nginx/nginx.conf | grep "try_files"
# 应该看到:
# try_files $uri $uri/ /index.html;
# 如果没有这一行,说明 SPA 路由配置缺失
解决方法:
# 在 nginx.conf 的 location / 块中添加:
location / {
try_files $uri $uri/ /index.html;
}
# 重新构建镜像并部署
问题 3:API 请求报错(反向代理问题)
症状:
- 前端页面正常显示
- 调用 API 时报错:
Network Error504 Gateway Timeout502 Bad Gateway
排查步骤:
# 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"
解决方法:
# 方法 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
原因:
前端代码中硬编码了后端地址,而不是使用相对路径。
排查步骤:
# 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
解决方法:
// 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 路径不正确。
排查步骤:
# 1. 查看 index.html 中的资源路径
curl https://frontend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/ | grep "assets"
# 应该看到:
# <script type="module" src="/assets/index-xxxxx.js"></script>
# 如果看到:
# <script type="module" src="./assets/index-xxxxx.js"></script> # ⚠️ 相对路径
# 或
# <script type="module" src="/app/assets/index-xxxxx.js"></script> # ❌ 错误的 base
# 2. 检查 vite.config.ts
cat vite.config.ts | grep "base"
解决方法:
// vite.config.ts
export default defineConfig({
base: '/', // ✅ 默认值,绝对路径
// base: '/app/', // ❌ 不要设置子路径(除非确实需要)
plugins: [react()],
});
// 重新构建并部署
问题 6:页面样式错乱(CSS 未加载)
症状:
页面内容显示,但样式混乱(纯文本排版)。
排查步骤:
# 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.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 未正确替换环境变量。
排查步骤:
# 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 中的启动脚本正确:
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 调用使用相对路径
// ✅ 正确做法:相对路径
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 路径配置
// ✅ 正确做法:默认根路径
export default defineConfig({
base: '/', // 绝对路径,适合大多数场景
});
// ⚠️ 仅在以下情况使用子路径:
// - 部署到 CDN 的子目录
// - 多个前端共享一个域名
export default defineConfig({
base: '/app/', // 资源路径:/app/assets/index.js
});
3. 环境变量命名规范
# ✅ 正确做法:拆分 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. 缓存策略
# ✅ 正确做法:差异化缓存
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. 健康检查端点
# ✅ 正确做法:独立的健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
}
# ❌ 错误做法:使用主页作为健康检查
# 如果前端代码有 Bug,健康检查也会失败
❌ 绝对禁止
1. 禁止在前端代码中硬编码后端地址
// ❌ 错误示例
const API_URL = 'http://172.16.0.30:3001/api/v1';
// 后果:
// - 本地开发时需要修改代码
// - 部署到生产环境时需要再次修改
// - 跨域问题
// - 无法使用 Nginx 反向代理
// ✅ 正确做法
const API_URL = '/api/v1'; // 相对路径,由 Nginx 代理
2. 禁止忽略 SPA 路由配置
# ❌ 错误配置(缺少 try_files)
location / {
root /usr/share/nginx/html;
}
# 后果:刷新页面报 404
# ✅ 正确配置
location / {
try_files $uri $uri/ /index.html;
}
3. 禁止将 index.html 设置为长期缓存
# ❌ 错误示例
location / {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 后果:部署新版本后,用户仍看到旧版本
# ✅ 正确做法
location = /index.html {
add_header Cache-Control "no-cache";
}
4. 禁止在 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 压缩
# ❌ 错误做法(不压缩)
# 传输大小:500KB JS 文件 → 500KB
# ✅ 正确做法(启用 Gzip)
gzip on;
gzip_types text/plain text/css application/javascript;
# 传输大小:500KB JS 文件 → 150KB(节省 70%)
6. 禁止在生产环境暴露敏感信息
# ❌ 错误做法
server_tokens on; # 暴露 Nginx 版本号
autoindex on; # 允许目录浏览
# ✅ 正确做法
server_tokens off;
autoindex off;
7. 禁止使用过长的超时时间
# ❌ 错误示例(30 分钟超时)
proxy_read_timeout 1800s;
# 后果:占用连接资源,影响并发能力
# ✅ 正确做法(根据实际需求)
proxy_read_timeout 300s; # 5 分钟(AI 对话足够)
8. 禁止忽略错误处理
# ❌ 错误做法(后端挂了,Nginx 直接返回 502)
proxy_pass http://backend;
# ✅ 正确做法(重试其他实例)
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
9. 禁止在 Dockerfile 中使用 root 用户运行 Nginx
# ❌ 错误做法
USER root # 安全风险
# ✅ 正确做法
USER nginx # Nginx 官方镜像默认已创建 nginx 用户
# 或者不指定,Alpine 镜像默认使用 nginx 用户
10. 禁止使用错误的 Node 版本
# ❌ 错误示例:使用 Node 18(与开发环境不一致)
FROM node:18-alpine AS builder
# 风险:package-lock.json 版本冲突
# ✅ 正确做法:使用 Node 22(与开发环境一致)
FROM node:22-alpine AS builder
# 保证依赖安装和构建环境一致
11. 禁止将日志写入文件
# ❌ 错误做法(日志写入文件)
access_log /var/log/nginx/access.log main;
# 风险:可能写满容器磁盘
# ✅ 正确做法(日志输出到标准流)
access_log /dev/stdout main;
error_log /dev/stderr warn;
# SAE 会自动收集到日志中心
12. 禁止忽略容器时区设置
# ❌ 错误做法(不设置时区,默认 UTC)
# 问题:日志时间比北京时间慢 8 小时
# ✅ 正确做法(设置为 Asia/Shanghai)
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
13. 禁止给后端地址危险的默认值
# ❌ 错误做法(给默认值)
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 镜像优化
# ❌ 错误示例:单阶段构建
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%)
📚 相关文档
🆘 获取帮助
遇到问题?
- 查看本文档的"故障排查"章节
- 查看 SAE 控制台的实时日志
- 查看 Nginx 错误日志:
/var/log/nginx/error.log - 使用浏览器开发者工具 Network 标签分析请求
- 联系团队技术支持
部署愉快!🚀