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
2064 lines
53 KiB
Markdown
2064 lines
53 KiB
Markdown
# 前端 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<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 核心数调整
|
||
|
||
# ⚠️ 日志输出到 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/
|
||
|
||
# 预期返回:
|
||
# <!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 功能正常(交互正常)
|
||
- [ ] 刷新页面不会 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"
|
||
|
||
# 应该看到:
|
||
# <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
|
||
// 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. 联系团队技术支持
|
||
|
||
---
|
||
|
||
**部署愉快!🚀**
|
||
|