Files
AIclinicalresearch/docs/05-部署文档/_archive-2025首次部署/06-前端Nginx-SAE容器部署指南.md
HaHafeng 6124c7abc6 docs(platform): Add database documentation system and restructure deployment docs
Completed:
- Add 6 core database documents (docs/01-平台基础层/07-数据库/)
  Architecture overview, migration history, environment comparison,
  tech debt tracking, seed data management, PostgreSQL extensions
- Restructure deployment docs: archive 20 legacy files to _archive-2025/
- Create unified daily operations manual (01-日常更新操作手册.md)
- Add pending deployment change tracker (03-待部署变更清单.md)
- Update database development standard to v3.0 (three iron rules)
- Fix Prisma schema type drift: align @db.* annotations with actual DB
  IIT: UUID/Timestamptz(6), SSA: Timestamp(6)/VarChar(20/50/100)
- Add migration: 20260227_align_schema_with_db_types (idempotent ALTER)
- Add Cursor Rule for auto-reminding deployment change documentation
- Update system status guide v6.4 with deployment and DB doc references
- Add architecture consultation docs (Prisma guide, SAE deployment guide)

Technical details:
- Manual migration due to shadow DB limitation (TD-001 in tech debt)
- Deployment docs reduced from 20+ scattered files to 3 core documents
- Cursor Rule triggers on schema.prisma, package.json, Dockerfile changes

Made-with: Cursor
2026-02-27 14:35:25 +08:00

2064 lines
53 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端 Nginx - SAE 容器部署完全指南
**文档版本**: v1.2 (修正Node 版本、Vite 版本、日志输出、时区)
**创建时间**: 2025-12-13
**最后修订**: 2025-12-13
**适用范围**: AIclinicalresearch 平台 - 前端静态资源服务frontend-v2
**目标读者**: 运维工程师、前端开发工程师
**部署目标**: 阿里云 SAEServerless 应用引擎Nginx 容器部署
**v1.2 更新日志**
- 🔴 修正Node 版本从 18 改为 22与开发环境 v22.18.0 一致)
- ✅ 确认Vite 版本 7.2package.json 真实版本)
- ⚠️ 修正:日志输出到 stdout/stderrSAE 最佳实践)
- ⚠️ 新增容器时区设置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.jsserve 或 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.2package.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<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
```
### 📝 构建流程
```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 核心数调整
# ⚠️ 日志输出到 stderrSAE 会自动收集)
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"';
# ⚠️ 日志输出到 stdoutSAE 会自动收集,避免磁盘写满)
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.htmlSPA 的核心)
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接收请求
Nginxproxy_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-60MBAlpine 基础镜像 + 构建产物)
# 查看镜像详情
docker inspect frontend-service:v1.0.0 | grep -A 5 "Size"
```
**如果构建失败**
```bash
# 常见问题 1npm 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",
# 常见问题 3TypeScript 编译错误
# 解决:检查是否有类型错误
npm run build # 在 Docker 外先测试构建
# 常见问题 4nginx.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/
# 预期返回:
# <!doctype html>
# <html lang="zh-CN">
# ...
# </html>
# 测试健康检查
curl http://localhost:8080/health
# 预期返回:
# healthy
```
### 步骤 4测试 SPA 路由
```bash
# 访问不存在的路由(应该返回 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` 运行。
```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 功能正常(交互正常)
- [ ] 刷新页面不会 404SPA 路由正常)
- [ ] 浏览器开发者工具 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
#### 测试清单 BAPI 调用测试
- [ ] **登录功能**
- 输入用户名和密码
- 点击登录
- 查看浏览器开发者工具 → 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刷新页面报 404SPA 路由问题)
**症状**
- 访问 `/` 正常
- 点击链接跳转到 `/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;
}
# 重新构建镜像并部署
```
### 问题 3API 请求报错(反向代理问题)
**症状**
- 前端页面正常显示
- 调用 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
# - 安全组规则是否允许访问
```
### 问题 4CORS 错误(反向代理未生效)
**症状**
浏览器 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"
# 应该看到:
# <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"
```
**解决方法**
```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
// APIhttps://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. 联系团队技术支持
---
**部署愉快!🚀**