chore: 上线前测试修复与部署配置更新
- fix(test): 添加 useSearchParams mock,修正联系链接断言 - style(nav): 将"联系我们"改为"联系" - chore(deploy): 更新 Nginx 配置和部署文档 - style(logo): 更新 Logo SVG 文件 - feat(scripts): 添加字体处理和站点配置脚本
This commit was merged in pull request #9.
This commit is contained in:
+453
-404
@@ -1,475 +1,524 @@
|
||||
# 部署文档
|
||||
### 一、Nginx 配置文件(3 个)
|
||||
|
||||
## 部署概述
|
||||
#### 1. [nginx-static-production.conf](computer:///sessions/69e889f08f8c93c2514713f1/workspace/nginx-static-production.conf)
|
||||
**用途**: 生产环境完整 Nginx 主配置文件(含反向代理),是 `docker-compose-nginx.yml` 挂载的配置。
|
||||
|
||||
项目采用 Next.js 静态导出模式,构建生成纯静态 HTML 文件,可部署到任何静态文件服务器或 CDN。
|
||||
**关键内容**:
|
||||
- **全局配置**: `worker_processes auto`,使用 epoll,连接数 1024,Docker DNS 解析器 `127.0.0.11`
|
||||
- **安全头**: X-Frame-Options、X-XSS-Protection、X-Content-Type-Options、Referrer-Policy
|
||||
- **限流**: `limit_req_zone` 全局限速 100r/s
|
||||
- **Gzip 压缩**: 级别 6,覆盖 text/css/json/javascript/xml 等类型
|
||||
- **Upstream 定义**: `gitea_app` (gitea:3000)、`jenkins_app` (jenkins:8080)
|
||||
- **虚拟主机**:
|
||||
- `novalon.cn / www.novalon.cn` -- 主站静态文件服务,根目录 `/var/www/novalon`,SSL/TLS 1.2+1.3,HSTS 2年,`_next/static/` 和字体/图片缓存 1 年,`try_files $uri $uri.html $uri/ /404.html`
|
||||
- `git.f.novalon.cn` -- 反向代理到 Gitea(端口 3000)
|
||||
- `ci.f.novalon.cn` -- 反向代理到 Jenkins(端口 8080),超时 60s
|
||||
- **SSL**: Let's Encrypt ACME challenge 支持,独立子域名证书路径
|
||||
|
||||
## 构建配置
|
||||
#### 2. [nginx-static.conf](computer:///sessions/69e889f08f8c93c2514713f1/workspace/nginx-static.conf)
|
||||
**用途**: 简化版 Nginx 配置,仅包含主站静态服务,用于 `Dockerfile` 构建镜像。
|
||||
|
||||
### Next.js 配置
|
||||
**关键内容**:
|
||||
- 仅配置 `novalon.cn / www.novalon.cn`,无 Gitea/Jenkins 反向代理
|
||||
- 静态根目录 `/usr/share/nginx/html`
|
||||
- SSL 配置较简化(`ssl_ciphers HIGH:!aNULL:!MD5`)
|
||||
- 包含与 production 版相同的安全头、Gzip、静态资源缓存策略
|
||||
- 额外包含 `Permissions-Policy` 头(禁用摄像头/麦克风/地理位置)
|
||||
|
||||
```typescript
|
||||
// next.config.ts
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'export', // 静态导出模式
|
||||
distDir: 'dist', // 输出目录
|
||||
images: {
|
||||
unoptimized: true, // 静态导出需要禁用图片优化
|
||||
},
|
||||
compress: true,
|
||||
poweredByHeader: false,
|
||||
reactStrictMode: true,
|
||||
};
|
||||
#### 3. [nginx-internal.conf](computer:///sessions/69e889f08f8c93c2514713f1/workspace/nginx-internal.conf)
|
||||
**用途**: 内部/开发环境配置,监听端口 3000,无 SSL。
|
||||
|
||||
**关键内容**:
|
||||
- 监听 `localhost:3000`,根目录 `/var/www/novalon`
|
||||
- 无 SSL、无 HSTS、无 ACME challenge
|
||||
- 包含基本的 Gzip、安全头、静态资源缓存
|
||||
- 用于 `Dockerfile.static` 构建的内部容器
|
||||
|
||||
---
|
||||
|
||||
### 二、Docker 相关文件(5 个)
|
||||
|
||||
#### 4. [Dockerfile](computer:///sessions/69e889f08f8c93c2514713f1/workspace/Dockerfile)
|
||||
**用途**: 多阶段构建 -- Node.js 编译 + Nginx 静态服务。
|
||||
|
||||
**关键内容**:
|
||||
- **构建阶段**: `node:20-alpine`,`npm ci` 安装依赖,`npm run build` 构建
|
||||
- **运行阶段**: `nginx:alpine`,将 `dist/` 复制到 `/usr/share/nginx/html`,挂载 `nginx-static.conf`
|
||||
- 暴露端口 80
|
||||
|
||||
#### 5. [Dockerfile.static](computer:///sessions/69e889f08f8c93c2514713f1/workspace/Dockerfile.static)
|
||||
**用途**: 纯静态文件 Nginx 容器(无构建阶段),用于内部部署。
|
||||
|
||||
**关键内容**:
|
||||
- 基础镜像 `nginx:alpine`
|
||||
- 将 `html/` 目录复制到 `/var/www/novalon`
|
||||
- 挂载 `nginx-internal.conf` 到 `/etc/nginx/conf.d/default.conf`
|
||||
- 暴露端口 3000
|
||||
|
||||
#### 6. [docker-compose.yml](computer:///sessions/69e889f08f8c93c2514713f1/workspace/docker-compose.yml)
|
||||
**用途**: 基础 Docker Compose 配置,使用 `Dockerfile` 构建。
|
||||
|
||||
**关键内容**:
|
||||
- 服务名 `novalon-website`,镜像标签 `novalon-website:1.0.0`
|
||||
- 容器名 `novalon-website`
|
||||
- 端口映射 `80:80` 和 `443:443`
|
||||
- 挂载 `nginx-static.conf` 和 `ssl/` 目录(只读)
|
||||
|
||||
#### 7. [docker-compose.server.yml](computer:///sessions/69e889f08f8c93c2514713f1/workspace/docker-compose.server.yml)
|
||||
**用途**: 服务器端 Docker Compose 配置,使用 `Dockerfile.static` 构建。
|
||||
|
||||
**关键内容**:
|
||||
- 服务名 `novalon-website`,镜像标签 `novalon-website:latest`
|
||||
- 容器名 `novalon-website`
|
||||
- 端口映射 `3000:3000`
|
||||
- 使用外部网络 `novalon-network`(bridge 驱动)
|
||||
|
||||
#### 8. [docker-compose-nginx.yml](computer:///sessions/69e889f08f8c93c2514713f1/workspace/docker-compose-nginx.yml)
|
||||
**用途**: 独立 Nginx 反向代理容器配置(生产环境网关)。
|
||||
|
||||
**关键内容**:
|
||||
- 服务名 `nginx`,使用官方 `nginx:alpine` 镜像
|
||||
- **容器名 `novalon-nginx-secure`**
|
||||
- 端口映射 `80:80` 和 `443:443`
|
||||
- 挂载:
|
||||
- `nginx-static-production.conf` -> `/etc/nginx/nginx.conf`(只读)
|
||||
- `ssl/` -> `/etc/nginx/ssl`(只读)
|
||||
- `logs/` -> `/var/log/nginx`
|
||||
- `../certbot` -> `/var/www/certbot`(ACME 验证)
|
||||
- `../novalon-static` -> `/var/www/novalon`(只读,静态文件)
|
||||
- 使用外部网络 `novalon-network`
|
||||
|
||||
---
|
||||
|
||||
### 三、部署脚本(2 个)
|
||||
|
||||
#### 9. [deploy.sh](computer:///sessions/69e889f08f8c93c2514713f1/workspace/deploy.sh)
|
||||
**用途**: 完整部署脚本 -- SSH 到远程服务器执行 Docker 构建+部署。
|
||||
|
||||
**关键内容**:
|
||||
- 默认服务器 `139.155.109.62`(root 用户),部署路径 `/home/novalon/docker-app/novalon-website`
|
||||
- 容器名 `novalon-website`,Nginx 容器名 `novalon-nginx`
|
||||
- 流程: 部署前检查 -> SSH 验证 -> SCP 上传文件 -> 服务器端 SSL 配置 -> `docker-compose build --no-cache` -> `docker-compose up -d` -> 健康检查 -> SSL 自动续期 cron 配置
|
||||
- 支持命令行参数: `-i`(IP)、`-u`(用户)、`-p`(项目名)、`-c`(容器名)、`-v`(版本号)
|
||||
|
||||
#### 10. [deploy-dist.sh](computer:///sessions/69e889f08f8c93c2514713f1/workspace/deploy-dist.sh)
|
||||
**用途**: 轻量部署脚本 -- 仅上传预构建的 `dist/` 目录到服务器。
|
||||
|
||||
**关键内容**:
|
||||
- 同一服务器 `139.155.109.62`,部署到 `/home/novalon/docker-app/novalon-website/dist`
|
||||
- 流程: 检查 dist 目录 -> SSH 验证 -> 备份旧版本 -> rsync 上传 -> 设置文件权限 -> 验证 -> 清理旧备份(保留最近 3 个)
|
||||
- 适用于已有 Nginx 容器运行、仅需更新静态文件的场景
|
||||
|
||||
---
|
||||
|
||||
### 四、引用 "novalon-nginx" / "novalon-website" 的其他文件(29 个)
|
||||
|
||||
除上述 10 个核心文件外,以下文件也引用了这两个关键词,按类别归纳:
|
||||
|
||||
| 类别 | 文件路径 |
|
||||
|------|----------|
|
||||
| **文档** | `README.md`, `DEPLOYMENT.md`, `docs/deployment.md`, `docs/deployment/DEPLOYMENT.md`, `docs/deployment/phase1-deployment-guide.md`, `docs/deployment/rollback-procedure.md`, `docs/development/getting-started.md`, `docs/STRUCTURE_PLAN.md`, `docs/PRODUCTION_RELEASE_REPORT.md`, `docs/PRODUCTION_DEPLOYMENT_LIGHTWEIGHT.md`, `docs/PRODUCTION_DEPLOYMENT.md`, `docs/PERFORMANCE_OPTIMIZATION.md`, `docs/OPTIMIZATION_REPORT.md`, `docs/MONITORING_SETUP.md`, `docs/MONITORING_QUICKSTART.md`, `docs/HMR-ERROR-SOLUTIONS.md`, `docs/CDN_CONFIGURATION.md` |
|
||||
| **脚本** | `scripts/ssl-wildcard-dns.sh`, `scripts/ssl-individual-http.sh`, `scripts/ssl-individual-http-v2.sh`, `scripts/setup-wildcard-ssl.sh`, `scripts/monitoring/setup-monitoring.sh`, `scripts/deploy-wildcard-domain.sh`, `scripts/deploy-subdomain-ssl.sh` |
|
||||
|
||||
---
|
||||
|
||||
### 五、架构总结(2026-04-22 更新)
|
||||
|
||||
该项目的部署架构已优化为**单层容器**模式:
|
||||
|
||||
#### 当前架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ novalon-nginx-secure (唯一容器) - 端口 80/443 │
|
||||
│ │
|
||||
│ nginx-static-production.conf (主配置) │
|
||||
│ └── include /etc/nginx/conf.d/*.conf; │
|
||||
│ │
|
||||
│ conf.d/ (模块化配置目录) │
|
||||
│ ├── git.f.novalon.cn.conf (Gitea 反向代理) │
|
||||
│ ├── ci.f.novalon.cn.conf (Jenkins 反向代理) │
|
||||
│ └── *.novalon.cn.conf (产品站点配置) │
|
||||
│ │
|
||||
│ sites/ (产品静态文件目录) │
|
||||
│ ├── product-a/ │
|
||||
│ └── product-b/ │
|
||||
│ │
|
||||
│ 挂载卷: │
|
||||
│ - ./conf.d → /etc/nginx/conf.d │
|
||||
│ - ./sites → /var/www/sites │
|
||||
│ - ../novalon-static → /var/www/novalon │
|
||||
│ - ./ssl → /etc/nginx/ssl │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 构建命令
|
||||
#### 架构优化说明
|
||||
|
||||
**已移除的组件**:
|
||||
- `novalon-website` 容器(原端口 3000)-- 功能已合并到 `novalon-nginx-secure` 容器
|
||||
|
||||
**优化收益**:
|
||||
- 资源节省:减少一个容器实例
|
||||
- 架构简化:单一网关,职责明确
|
||||
- 配置模块化:每个服务独立配置文件,互不影响
|
||||
|
||||
#### 新增自动化脚本
|
||||
|
||||
| 脚本 | 用途 |
|
||||
|------|------|
|
||||
| `scripts/add-product-site.sh` | 一键添加新产品站点(配置文件 + 静态文件) |
|
||||
| `scripts/ssl-product-site.sh` | 为产品站点申请 SSL 证书 |
|
||||
|
||||
#### 添加新产品站点流程
|
||||
|
||||
```bash
|
||||
# 开发模式(不导出)
|
||||
npm run dev
|
||||
# 1. 添加 DNS A 记录: product-a.novalon.cn → 服务器IP
|
||||
|
||||
# 生产构建(静态导出)
|
||||
npm run build
|
||||
# 2. 运行添加脚本
|
||||
./scripts/add-product-site.sh product-a ./product-a-files
|
||||
|
||||
# 输出目录
|
||||
dist/
|
||||
# 3. 申请 SSL 证书
|
||||
./scripts/ssl-product-site.sh product-a.novalon.cn
|
||||
|
||||
# 4. 重载 Nginx
|
||||
docker exec novalon-nginx-secure nginx -s reload
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
---
|
||||
|
||||
### 必需配置
|
||||
### 历史架构(已废弃)
|
||||
|
||||
```env
|
||||
# .env.production
|
||||
RESEND_API_KEY=re_xxxxx
|
||||
COMPANY_EMAIL=contact@novalon.cn
|
||||
~~该项目的部署架构为**双层容器**模式~~:
|
||||
|
||||
~~1. **内层**: `novalon-website` 容器(端口 3000)-- 由 `Dockerfile.static` 构建,仅提供静态文件服务~~
|
||||
~~2. **外层**: `novalon-nginx-secure` 容器(端口 80/443)-- 由 `docker-compose-nginx.yml` 启动,使用 `nginx-static-production.conf` 作为完整网关,负责 SSL 终止、静态文件服务、以及到 Gitea (`git.f.novalon.cn`) 和 Jenkins (`ci.f.novalon.cn`) 的反向代理~~
|
||||
|
||||
~~两个容器通过外部 Docker 网络 `novalon-network`(bridge 驱动)互联。域名 `novalon.cn` / `www.novalon.cn` 指向主站静态资源,SSL 证书通过 Let's Encrypt 获取并配置了自动续期 cron 任务。~~
|
||||
|
||||
现在我对你的生产环境架构有了清晰的了解。让我再确认几个关键细节:
|
||||
|
||||
现在我对你的架构完全清楚了。在给出方案之前,我需要确认几个关键信息:
|
||||
|
||||
好的,需求明确了。让我整理完整的部署方案。
|
||||
|
||||
## 任务理解
|
||||
|
||||
- **目标**:在现有 `novalon-nginx` 网关上,为 6-15 个独立 HTML 产品官网配置子域名路由 + SSL
|
||||
- **约束**:每个产品用独立子域名(如 `product-a.novalon.cn`),SSL 需要新申请
|
||||
- **当前架构**:`novalon-nginx-secure` 容器(nginx:alpine)作为网关,静态文件通过 volume 挂载
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 推荐方案:Nginx 模板化 + 目录约定 + Let's Encrypt 自动化
|
||||
|
||||
核心思路是利用你现有的 `novalon-nginx-secure` 网关容器,通过**目录约定 + 配置模板**来管理多个产品站点,避免为每个产品创建独立容器。
|
||||
|
||||
#### 1. 目录结构约定
|
||||
|
||||
在服务器上按以下结构组织产品页面:
|
||||
|
||||
```
|
||||
/home/novalon/docker-app/novalon-nginx/
|
||||
├── nginx-static-production.conf # 主配置(include 子配置)
|
||||
├── conf.d/ # 产品站点配置目录(新增)
|
||||
│ ├── product-a.novalon.cn.conf
|
||||
│ ├── product-b.novalon.cn.conf
|
||||
│ └── ...
|
||||
├── ssl/ # SSL 证书目录
|
||||
│ ├── novalon.cn/
|
||||
│ ├── git.f.novalon.cn/
|
||||
│ ├── ci.f.novalon.cn/
|
||||
│ ├── product-a.novalon.cn/ # 每个产品独立子目录
|
||||
│ └── product-b.novalon.cn/
|
||||
├── sites/ # 产品静态文件目录(新增)
|
||||
│ ├── product-a/
|
||||
│ │ └── index.html
|
||||
│ ├── product-b/
|
||||
│ │ └── index.html
|
||||
│ └── ...
|
||||
└── docker-compose-nginx.yml
|
||||
```
|
||||
|
||||
### 可选配置
|
||||
#### 2. Nginx 主配置修改
|
||||
|
||||
```env
|
||||
NODE_ENV=production
|
||||
NEXT_PUBLIC_SITE_URL=https://www.novalon.cn
|
||||
```
|
||||
|
||||
### 环境变量说明
|
||||
|
||||
| 变量名 | 必需 | 描述 |
|
||||
|--------|------|------|
|
||||
| `RESEND_API_KEY` | 是 | Resend 邮件服务 API 密钥 |
|
||||
| `COMPANY_EMAIL` | 是 | 公司接收邮件的邮箱地址 |
|
||||
| `NODE_ENV` | 否 | 环境标识 |
|
||||
| `NEXT_PUBLIC_SITE_URL` | 否 | 网站公开 URL |
|
||||
|
||||
## 部署平台
|
||||
|
||||
### 1. Vercel 部署(推荐)
|
||||
|
||||
**优势:**
|
||||
- 零配置部署
|
||||
- 自动 HTTPS
|
||||
- 全球 CDN
|
||||
- 预览部署
|
||||
- 边缘函数支持
|
||||
|
||||
**部署步骤:**
|
||||
|
||||
1. 连接 Git 仓库
|
||||
2. 配置环境变量
|
||||
3. 部署设置:
|
||||
- Build Command: `npm run build`
|
||||
- Output Directory: `dist`
|
||||
- Install Command: `npm install`
|
||||
|
||||
**vercel.json 配置:**
|
||||
|
||||
```json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist",
|
||||
"framework": "nextjs",
|
||||
"regions": ["hkg1"]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 静态文件服务器部署
|
||||
|
||||
**适用场景:**
|
||||
- Nginx
|
||||
- Apache
|
||||
- IIS
|
||||
- 云存储(阿里云 OSS、腾讯云 COS)
|
||||
|
||||
**Nginx 配置示例:**
|
||||
在 `nginx-static-production.conf` 的 `http {}` 块末尾添加一行 include:
|
||||
|
||||
```nginx
|
||||
# 在 http {} 块末尾,现有 server 块之后添加:
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
```
|
||||
|
||||
#### 3. 产品站点配置模板
|
||||
|
||||
每个产品一个独立配置文件,例如 `conf.d/product-a.novalon.cn.conf`:
|
||||
|
||||
```nginx
|
||||
# 产品A官网 - product-a.novalon.cn
|
||||
server {
|
||||
listen 80;
|
||||
server_name www.novalon.cn novalon.cn;
|
||||
root /var/www/novalon-website/dist;
|
||||
index index.html;
|
||||
server_name product-a.novalon.cn;
|
||||
|
||||
# 强制 HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name www.novalon.cn novalon.cn;
|
||||
root /var/www/novalon-website/dist;
|
||||
index index.html;
|
||||
server_name product-a.novalon.cn;
|
||||
|
||||
# SSL 证书
|
||||
ssl_certificate /etc/nginx/ssl/novalon.cn.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/novalon.cn.key;
|
||||
ssl_certificate /etc/nginx/ssl/product-a.novalon.cn/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/product-a.novalon.cn/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
# 安全头部
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;" always;
|
||||
|
||||
# Gzip 压缩
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
gzip_min_length 1000;
|
||||
root /var/www/sites/product-a;
|
||||
index index.html;
|
||||
|
||||
# 静态资源缓存
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|ico|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# HTML 不缓存
|
||||
location ~* \.html$ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# SPA 路由支持
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/ =404;
|
||||
limit_req zone=general burst=20 nodelay;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# 404 页面
|
||||
error_page 404 /404.html;
|
||||
|
||||
access_log /var/log/nginx/product-a-access.log;
|
||||
error_log /var/log/nginx/product-a-error.log;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Docker 部署
|
||||
#### 4. Docker Compose 修改
|
||||
|
||||
**Dockerfile:**
|
||||
|
||||
```dockerfile
|
||||
# 构建阶段
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 运行阶段
|
||||
FROM nginx:alpine
|
||||
|
||||
# 复制构建产物
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# 复制 Nginx 配置
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
**构建和运行:**
|
||||
|
||||
```bash
|
||||
# 构建镜像
|
||||
docker build -t novalon-website .
|
||||
|
||||
# 运行容器
|
||||
docker run -d -p 80:80 --name novalon novalon-website
|
||||
```
|
||||
|
||||
### 4. 云存储部署
|
||||
|
||||
**阿里云 OSS:**
|
||||
|
||||
1. 创建 OSS Bucket
|
||||
2. 配置静态网站托管
|
||||
3. 上传 `dist/` 目录内容
|
||||
4. 配置自定义域名
|
||||
5. 配置 HTTPS 证书
|
||||
|
||||
**腾讯云 COS:**
|
||||
|
||||
1. 创建 COS Bucket
|
||||
2. 开启静态网站功能
|
||||
3. 上传构建产物
|
||||
4. 配置 CDN 加速
|
||||
|
||||
## CI/CD 流水线
|
||||
|
||||
### Woodpecker CI 配置
|
||||
在 `docker-compose-nginx.yml` 中添加新的 volume 挂载:
|
||||
|
||||
```yaml
|
||||
# .woodpecker.yml
|
||||
pipeline:
|
||||
install:
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- npm ci
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: novalon-nginx-secure
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx-static-production.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./conf.d:/etc/nginx/conf.d:ro # 新增:产品站点配置
|
||||
- ./ssl:/etc/nginx/ssl:ro
|
||||
- ./logs:/var/log/nginx
|
||||
- ../certbot:/var/www/certbot
|
||||
- ../novalon-static:/var/www/novalon:ro
|
||||
- ./sites:/var/www/sites:ro # 新增:产品静态文件
|
||||
networks:
|
||||
- novalon-network
|
||||
|
||||
lint:
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- npm run lint
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
build:
|
||||
image: node:18-alpine
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
commands:
|
||||
- npm run build
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- main
|
||||
|
||||
e2e-tests:
|
||||
image: node:18-alpine
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
CI: true
|
||||
commands:
|
||||
- cd e2e
|
||||
- npm ci
|
||||
- npx playwright install --with-deps chromium
|
||||
- npm run test:smoke
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
deploy:
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- npm install -g vercel
|
||||
- vercel --prod --token=$VERCEL_TOKEN
|
||||
secrets:
|
||||
- vercel_token
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- main
|
||||
networks:
|
||||
novalon-network:
|
||||
driver: bridge
|
||||
external: true
|
||||
```
|
||||
|
||||
## 部署检查清单
|
||||
#### 5. SSL 证书申请脚本
|
||||
|
||||
### 部署前检查
|
||||
|
||||
- [ ] 环境变量已配置
|
||||
- [ ] 构建成功无错误
|
||||
- [ ] E2E 测试通过
|
||||
- [ ] ESLint 检查通过
|
||||
- [ ] 图片资源已优化
|
||||
- [ ] 死链检查通过
|
||||
|
||||
### 部署后验证
|
||||
|
||||
- [ ] 首页正常加载
|
||||
- [ ] 所有页面可访问
|
||||
- [ ] 表单提交正常
|
||||
- [ ] 移动端适配正常
|
||||
- [ ] HTTPS 证书有效
|
||||
- [ ] 性能指标达标
|
||||
- [ ] SEO 元数据正确
|
||||
|
||||
### 性能指标
|
||||
|
||||
| 指标 | 目标值 |
|
||||
|------|--------|
|
||||
| LCP | < 2.5s |
|
||||
| FID | < 100ms |
|
||||
| CLS | < 0.1 |
|
||||
| TTFB | < 600ms |
|
||||
| 首屏加载 | < 3s |
|
||||
|
||||
## 回滚策略
|
||||
|
||||
### Vercel 回滚
|
||||
为每个产品子域名申请 Let's Encrypt 证书:
|
||||
|
||||
```bash
|
||||
# 列出部署历史
|
||||
vercel ls
|
||||
#!/bin/bash
|
||||
# scripts/ssl-product-site.sh
|
||||
# 用法: ./ssl-product-site.sh product-a.novalon.cn
|
||||
|
||||
# 回滚到指定版本
|
||||
vercel rollback [deployment-url]
|
||||
DOMAIN=$1
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
echo "用法: $0 <subdomain>.novalon.cn"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CERTBOT_DIR="/home/novalon/docker-app/certbot"
|
||||
SSL_DIR="/home/novalon/docker-app/novalon-nginx/ssl"
|
||||
|
||||
# 创建证书目录
|
||||
mkdir -p "${SSL_DIR}/${DOMAIN}"
|
||||
|
||||
# 申请证书(使用 standalone 模式,需临时停止 nginx 的 80 端口)
|
||||
docker run --rm \
|
||||
-p 80:80 \
|
||||
-v "${CERTBOT_DIR}:/var/www/certbot" \
|
||||
-v "/etc/letsencrypt:/etc/letsencrypt" \
|
||||
certbot/certbot certonly \
|
||||
--webroot \
|
||||
--webroot-path /var/www/certbot \
|
||||
-d "${DOMAIN}" \
|
||||
--email admin@novalon.cn \
|
||||
--agree-tos \
|
||||
--no-eff-email
|
||||
|
||||
# 复制证书到 nginx ssl 目录
|
||||
cp "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" "${SSL_DIR}/${DOMAIN}/"
|
||||
cp "/etc/letsencrypt/live/${DOMAIN}/privkey.pem" "${SSL_DIR}/${DOMAIN}/"
|
||||
|
||||
echo "✅ ${DOMAIN} 证书申请完成"
|
||||
```
|
||||
|
||||
### 静态服务器回滚
|
||||
#### 6. 一键添加新产品脚本
|
||||
|
||||
```bash
|
||||
# 保留历史版本
|
||||
/var/www/novalon-website/
|
||||
├── current -> releases/20260307-1
|
||||
├── releases/
|
||||
│ ├── 20260307-1/
|
||||
│ ├── 20260306-1/
|
||||
│ └── 20260305-1/
|
||||
└── shared/
|
||||
#!/bin/bash
|
||||
# scripts/add-product-site.sh
|
||||
# 用法: ./add-product-site.sh product-a /path/to/product-a-files
|
||||
|
||||
# 回滚操作
|
||||
ln -sfn releases/20260306-1 current
|
||||
PRODUCT_NAME=$1
|
||||
FILES_SOURCE=$2
|
||||
|
||||
if [ -z "$PRODUCT_NAME" ] || [ -z "$FILES_SOURCE" ]; then
|
||||
echo "用法: $0 <product-name> <html-files-directory>"
|
||||
echo "示例: $0 product-a ./product-a-website"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DOMAIN="${PRODUCT_NAME}.novalon.cn"
|
||||
NGINX_DIR="/home/novalon/docker-app/novalon-nginx"
|
||||
CONF_DIR="${NGINX_DIR}/conf.d"
|
||||
SITES_DIR="${NGINX_DIR}/sites"
|
||||
SSL_DIR="${NGINX_DIR}/ssl"
|
||||
|
||||
# 1. 创建目录
|
||||
mkdir -p "${SITES_DIR}/${PRODUCT_NAME}"
|
||||
mkdir -p "${SSL_DIR}/${DOMAIN}"
|
||||
mkdir -p "${CONF_DIR}"
|
||||
|
||||
# 2. 复制静态文件
|
||||
cp -r "${FILES_SOURCE}/"* "${SITES_DIR}/${PRODUCT_NAME}/"
|
||||
|
||||
# 3. 生成 Nginx 配置
|
||||
cat > "${CONF_DIR}/${DOMAIN}.conf" << 'CONF_TEMPLATE'
|
||||
# 产品官网 - {{DOMAIN}}
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{DOMAIN}};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name {{DOMAIN}};
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/{{DOMAIN}}/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/{{DOMAIN}}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
root /var/www/sites/{{PRODUCT_NAME}};
|
||||
index index.html;
|
||||
|
||||
location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|ico|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
limit_req zone=general burst=20 nodelay;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
access_log /var/log/nginx/{{PRODUCT_NAME}}-access.log;
|
||||
error_log /var/log/nginx/{{PRODUCT_NAME}}-error.log;
|
||||
}
|
||||
CONF_TEMPLATE
|
||||
|
||||
# 替换模板变量
|
||||
sed -i "s/{{DOMAIN}}/${DOMAIN}/g" "${CONF_DIR}/${DOMAIN}.conf"
|
||||
sed -i "s/{{PRODUCT_NAME}}/${PRODUCT_NAME}/g" "${CONF_DIR}/${DOMAIN}.conf"
|
||||
|
||||
echo "✅ 产品站点 ${PRODUCT_NAME} 配置完成"
|
||||
echo ""
|
||||
echo "后续步骤:"
|
||||
echo " 1. 添加 DNS A 记录: ${DOMAIN} -> 服务器IP"
|
||||
echo " 2. 申请 SSL 证书: ./ssl-product-site.sh ${DOMAIN}"
|
||||
echo " 3. 重载 Nginx: docker exec novalon-nginx-secure nginx -s reload"
|
||||
```
|
||||
|
||||
## 监控与告警
|
||||
#### 7. 完整部署流程
|
||||
|
||||
### 推荐工具
|
||||
添加一个新产品站点的完整步骤:
|
||||
|
||||
| 工具 | 用途 |
|
||||
```
|
||||
步骤 1: DNS 解析
|
||||
→ 在域名服务商添加 A 记录: product-a.novalon.cn → 139.155.109.62
|
||||
|
||||
步骤 2: 上传文件 + 生成配置
|
||||
→ ./add-product-site.sh product-a ./product-a-html-files
|
||||
|
||||
步骤 3: 申请 SSL 证书
|
||||
→ ./ssl-product-site.sh product-a.novalon.cn
|
||||
|
||||
步骤 4: 验证配置 + 重载 Nginx
|
||||
→ docker exec novalon-nginx-secure nginx -t
|
||||
→ docker exec novalon-nginx-secure nginx -s reload
|
||||
|
||||
步骤 5: 验证访问
|
||||
→ curl -I https://product-a.novalon.cn
|
||||
```
|
||||
|
||||
### 方案优势
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| Vercel Analytics | 性能监控 |
|
||||
| Sentry | 错误监控 |
|
||||
| Uptime Robot | 可用性监控 |
|
||||
| Google Search Console | SEO 监控 |
|
||||
| **零额外容器** | 所有产品站点复用现有 `novalon-nginx-secure` 网关 |
|
||||
| **模板化** | 一键脚本生成配置,避免手动复制粘贴出错 |
|
||||
| **隔离性** | 每个产品独立配置文件、独立日志、独立 SSL 证书目录 |
|
||||
| **可扩展** | 新增产品只需 3 条命令 |
|
||||
| **统一管理** | 所有配置集中在 `conf.d/` 目录,便于审查和维护 |
|
||||
| **资源高效** | 不需要为每个 HTML 页面启动独立容器 |
|
||||
|
||||
### 告警配置
|
||||
### 注意事项
|
||||
|
||||
```yaml
|
||||
# Uptime Robot 配置示例
|
||||
monitors:
|
||||
- name: Novalon Website
|
||||
url: https://www.novalon.cn
|
||||
type: https
|
||||
interval: 300
|
||||
alert_contacts:
|
||||
- email: admin@novalon.cn
|
||||
```
|
||||
|
||||
## 安全配置
|
||||
|
||||
### 安全头部
|
||||
|
||||
```http
|
||||
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;
|
||||
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
||||
```
|
||||
|
||||
### HTTPS 配置
|
||||
|
||||
- 使用 TLS 1.2 或更高版本
|
||||
- 配置 HSTS
|
||||
- 启用 OCSP Stapling
|
||||
- 使用强加密套件
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 构建优化
|
||||
|
||||
1. **代码分割**
|
||||
- 动态导入非首屏组件
|
||||
- 路由级别分割
|
||||
|
||||
2. **资源优化**
|
||||
- 图片压缩和格式转换
|
||||
- CSS 压缩
|
||||
- JavaScript 压缩
|
||||
|
||||
3. **缓存策略**
|
||||
- 静态资源长缓存
|
||||
- HTML 不缓存
|
||||
- API 响应适当缓存
|
||||
|
||||
### CDN 配置
|
||||
|
||||
```
|
||||
# CDN 缓存规则
|
||||
*.js, *.css -> 缓存 1 年
|
||||
*.jpg, *.png -> 缓存 1 年
|
||||
*.woff, *.woff2 -> 缓存 1 年
|
||||
*.html -> 不缓存
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
**1. 页面 404 错误**
|
||||
- 检查静态文件是否正确上传
|
||||
- 检查 Nginx 配置的 root 路径
|
||||
- 检查 SPA 路由配置
|
||||
|
||||
**2. 样式加载失败**
|
||||
- 检查 CSS 文件路径
|
||||
- 检查 Content-Security-Policy 配置
|
||||
- 清除浏览器缓存
|
||||
|
||||
**3. 表单提交失败**
|
||||
- 检查 API 路由是否正常
|
||||
- 检查环境变量配置
|
||||
- 检查 CORS 配置
|
||||
|
||||
**4. 性能问题**
|
||||
- 检查图片是否优化
|
||||
- 检查 CDN 是否生效
|
||||
- 检查服务器响应时间
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# Nginx 访问日志
|
||||
tail -f /var/log/nginx/access.log
|
||||
|
||||
# Nginx 错误日志
|
||||
tail -f /var/log/nginx/error.log
|
||||
|
||||
# Vercel 日志
|
||||
vercel logs [deployment-url]
|
||||
```
|
||||
|
||||
## 维护计划
|
||||
|
||||
### 定期任务
|
||||
|
||||
| 任务 | 频率 |
|
||||
|------|------|
|
||||
| 依赖更新 | 每月 |
|
||||
| 安全扫描 | 每周 |
|
||||
| 性能测试 | 每周 |
|
||||
| 备份验证 | 每月 |
|
||||
| SSL 证书更新 | 到期前 30 天 |
|
||||
|
||||
### 更新流程
|
||||
|
||||
1. 创建更新分支
|
||||
2. 执行依赖更新
|
||||
3. 运行测试套件
|
||||
4. 部署到预览环境
|
||||
5. 验证功能正常
|
||||
6. 合并到主分支
|
||||
7. 自动部署到生产环境
|
||||
1. **DNS 解析必须先完成**:SSL 证书申请需要域名已指向服务器 IP
|
||||
2. **证书续期**:建议在现有 cron 任务中加入产品子域名的自动续期
|
||||
3. **如果未来产品数量超过 15 个**,建议升级为泛域名证书(`*.novalon.cn`),可大幅简化证书管理
|
||||
4. **文件更新**:更新某个产品的 HTML 文件后,只需替换 `sites/product-x/` 下的文件,无需重启 Nginx(静态文件通过 volume 挂载)
|
||||
Reference in New Issue
Block a user