feat: 添加 Jenkins Docker 部署配置

- 添加 Jenkinsfile 和 Jenkinsfile.docker 支持 Docker 构建
- 添加 Docker Compose 生产环境配置
- 添加后端 Dockerfile
- 添加 Docker 部署脚本
- 添加部署文档 (JENKINS_DEPLOY.md, DOCKER_DEPLOY.md, DOCKER_ISOLATION.md)
- 更新 nginx 配置支持生产环境部署
This commit is contained in:
caoyuchun 2026-01-14 14:33:58 +08:00
parent 25a90eae02
commit be1dab20e9
13 changed files with 1941 additions and 1 deletions

268
DOCKER_DEPLOY.md Normal file
View File

@ -0,0 +1,268 @@
# Docker 部署说明
本文档说明如何在已有 Docker nginx 运行的服务器上部署本项目,避免端口和资源冲突。
## 部署架构
- **前端 Nginx**: 运行在 `8080` 端口(避免与现有 nginx 的 80 端口冲突)
- **后端服务**: 运行在 `3001` 端口
- **部署路径**: `/opt/nginx/html/ai/current`
- **容器名称**:
- `ai-learning-nginx` (前端)
- `ai-learning-backend` (后端)
## 避免冲突的设计
### 1. 端口隔离
- 现有 nginx: `80:80`, `443:443`
- 本项目 nginx: `8080:80`, `8443:443`
- 后端: `3001:3001`
### 2. 路径隔离
- 使用独立的部署路径: `/opt/nginx/html/ai/current`
- 独立的日志目录: `/opt/nginx/html/ai/current/docker/logs`
- 独立的配置文件: `/opt/nginx/html/ai/current/docker/`
### 3. 容器名称隔离
- 使用唯一的容器名称,避免与现有容器冲突
### 4. Docker 网络隔离
- 使用独立的 Docker 网络: `ai-learning-network`
## 文件结构
部署后的目录结构:
```
/opt/nginx/html/ai/current/
├── index.html # 前端入口文件
├── assets/ # 前端静态资源
├── backend/ # 后端文件
│ ├── dist/ # 后端构建产物
│ ├── prisma/ # 数据库文件
│ ├── Dockerfile # 后端 Dockerfile
│ └── package.json
└── docker/ # Docker 配置
├── docker-compose.yml
├── nginx.conf.docker
└── logs/ # Nginx 日志
```
## 部署步骤
### 1. 通过 Jenkins 自动部署
Jenkins 流水线会自动:
1. 构建前端和后端
2. 打包部署文件
3. 传输到远程服务器
4. 解压并部署
5. 启动 Docker 容器
### 2. 手动部署
如果需要手动部署:
```bash
# 1. 构建项目
npm install
npm run install:all
npm run build
# 2. 创建部署目录
ssh root@180.76.180.105 "mkdir -p /opt/nginx/html/ai/current"
# 3. 传输文件
scp -r frontend/dist/* root@180.76.180.105:/opt/nginx/html/ai/current/
scp -r backend/dist backend/prisma backend/package.json backend/Dockerfile root@180.76.180.105:/opt/nginx/html/ai/current/backend/
scp nginx/docker-compose.production.yml root@180.76.180.105:/opt/nginx/html/ai/current/docker/docker-compose.yml
scp nginx/nginx.conf.docker root@180.76.180.105:/opt/nginx/html/ai/current/docker/nginx.conf.docker
# 4. 在服务器上启动
ssh root@180.76.180.105 << 'ENDSSH'
cd /opt/nginx/html/ai/current/docker
docker-compose up -d --build
ENDSSH
```
## 访问地址
- **前端**: http://180.76.180.105:8080
- **后端 API**: http://180.76.180.105:3001
- **健康检查**: http://180.76.180.105:8080/health
## 常用命令
### 查看容器状态
```bash
cd /opt/nginx/html/ai/current/docker
docker-compose ps
```
### 查看日志
```bash
# 查看所有服务日志
docker-compose logs -f
# 查看 nginx 日志
docker-compose logs -f nginx
# 查看后端日志
docker-compose logs -f backend
```
### 重启服务
```bash
cd /opt/nginx/html/ai/current/docker
docker-compose restart
```
### 停止服务
```bash
cd /opt/nginx/html/ai/current/docker
docker-compose down
```
### 重新构建并启动
```bash
cd /opt/nginx/html/ai/current/docker
docker-compose up -d --build
```
## 故障排查
### 1. 端口冲突
如果 8080 端口被占用:
```bash
# 检查端口占用
netstat -tulpn | grep 8080
# 或
lsof -i :8080
# 修改 docker-compose.yml 中的端口映射
# 例如改为 8081:80
```
### 2. 容器无法启动
```bash
# 查看容器日志
docker-compose logs
# 检查容器状态
docker ps -a | grep ai-learning
# 检查 Docker 网络
docker network ls
```
### 3. 前端无法访问
```bash
# 检查 nginx 容器是否运行
docker ps | grep ai-learning-nginx
# 检查 nginx 配置
docker exec ai-learning-nginx nginx -t
# 查看 nginx 日志
docker-compose logs nginx
```
### 4. 后端 API 无法访问
```bash
# 检查后端容器是否运行
docker ps | grep ai-learning-backend
# 检查后端日志
docker-compose logs backend
# 测试后端连接
curl http://localhost:3001/api/health
```
### 5. 数据库问题
```bash
# 检查数据库文件权限
ls -la /opt/nginx/html/ai/current/backend/prisma/
# 进入后端容器执行迁移
docker exec -it ai-learning-backend sh
npx prisma migrate deploy
npx prisma generate
```
## 与现有 nginx 集成(可选)
如果希望通过现有 nginx80 端口)访问本项目,可以在现有 nginx 配置中添加反向代理:
```nginx
# 添加到现有 nginx 配置
location /ai/ {
proxy_pass http://localhost:8080/;
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;
}
location /ai/api/ {
proxy_pass http://localhost:3001/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
```
然后访问:`http://180.76.180.105/ai/`
## 更新部署
每次代码更新后Jenkins 会自动:
1. 构建新版本
2. 备份旧版本到 `/opt/nginx/html/ai/backup-YYYYMMDD-HHMMSS`
3. 部署新版本
4. 重启 Docker 容器
## 回滚
如果需要回滚到之前的版本:
```bash
ssh root@180.76.180.105 << 'ENDSSH'
cd /opt/nginx/html/ai
# 查看备份
ls -la backup-*
# 停止当前容器
cd current/docker
docker-compose down
# 恢复备份
cd /opt/nginx/html/ai
mv current current-failed
mv backup-YYYYMMDD-HHMMSS current
# 重启服务
cd current/docker
docker-compose up -d
ENDSSH
```
## 安全建议
1. **不要使用 root 用户**: 建议创建专用部署用户
2. **限制端口访问**: 使用防火墙限制 8080 端口的访问来源
3. **定期备份**: 自动备份数据库和配置文件
4. **监控日志**: 设置日志监控和告警
5. **更新镜像**: 定期更新 nginx:alpine 和 node:18-alpine 镜像

295
DOCKER_ISOLATION.md Normal file
View File

@ -0,0 +1,295 @@
# Docker 隔离架构说明
## 隔离层次概览
当前项目实现了**完全隔离**的架构,从构建到运行都在 Docker 容器中,与宿主机完全隔离。
```
┌─────────────────────────────────────────────────────────────┐
│ 宿主机 (Host) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Jenkins 服务器 │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Docker 容器: node:18 (构建阶段) │ │ │
│ │ │ - 安装依赖 │ │ │
│ │ │ - 构建前端 │ │ │
│ │ │ - 构建后端 │ │ │
│ │ │ ✅ 与 Jenkins 服务器隔离 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ 部署到远程服务器 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 远程服务器 (180.76.180.105) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Docker 容器: nginx:alpine │ │ │
│ │ │ - 端口: 8080:80 │ │ │
│ │ │ - 网络: ai-learning-network │ │ │
│ │ │ ✅ 与宿主机和其他容器隔离 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Docker 容器: backend (node:18-alpine) │ │ │
│ │ │ - 端口: 3001:3001 │ │ │
│ │ │ - 网络: ai-learning-network │ │ │
│ │ │ ✅ 与宿主机和其他容器隔离 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 宿主机文件系统 (仅数据持久化) │ │ │
│ │ │ - /opt/nginx/html/ai/current/ │ │ │
│ │ │ - 数据库文件 (volume 挂载) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 隔离层次详解
### 1. 构建阶段隔离Jenkins 服务器)
**位置**: Jenkins 服务器
**容器**: `node:18`
**隔离内容**:
```groovy
def nodeImage = docker.image("node:18")
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
// 所有构建操作都在容器中执行
npm install
npm run build
}
```
**隔离效果**:
- ✅ **文件系统隔离**: 构建过程不影响 Jenkins 服务器文件系统
- ✅ **依赖隔离**: 不需要在 Jenkins 服务器安装 Node.js、npm
- ✅ **版本隔离**: 使用固定版本的 Node.js不受宿主机影响
- ✅ **环境隔离**: 每次构建都是干净的环境,无历史残留
**好处**:
- Jenkins 服务器保持干净,不需要安装各种构建工具
- 多个项目可以使用不同版本的 Node.js互不干扰
- 构建失败不会影响 Jenkins 服务器
### 2. 运行阶段隔离(生产服务器)
#### 2.1 Nginx 容器隔离
```yaml
nginx:
image: nginx:alpine
container_name: ai-learning-nginx
ports:
- "8080:80"
networks:
- ai-learning-network
```
**隔离效果**:
- ✅ **进程隔离**: Nginx 进程在容器内运行,与宿主机进程隔离
- ✅ **网络隔离**: 使用独立的 Docker 网络 `ai-learning-network`
- ✅ **文件系统隔离**: 只能访问挂载的 volume
- ✅ **端口隔离**: 使用 8080 端口,不影响宿主机 80 端口
#### 2.2 Backend 容器隔离
```yaml
backend:
build:
context: /opt/nginx/html/ai/current/backend
dockerfile: Dockerfile
container_name: ai-learning-backend
ports:
- "3001:3001"
networks:
- ai-learning-network
```
**隔离效果**:
- ✅ **运行时隔离**: Node.js 应用在容器内运行
- ✅ **依赖隔离**: 不需要在宿主机安装 Node.js
- ✅ **环境变量隔离**: 容器内独立的环境变量
- ✅ **网络隔离**: 只能通过 Docker 网络与其他容器通信
### 3. 网络隔离
```yaml
networks:
ai-learning-network:
driver: bridge
```
**隔离效果**:
- ✅ **独立网络**: 容器在独立的 Docker 网络中
- ✅ **服务发现**: 容器间通过服务名通信(如 `backend:3001`
- ✅ **端口隔离**: 容器端口不直接暴露给宿主机(除非映射)
- ✅ **安全隔离**: 外部无法直接访问容器内部网络
## 完全隔离的好处
### 1. 安全性
```
宿主机 ← 完全隔离 → 应用容器
```
- ✅ **进程隔离**: 容器崩溃不影响宿主机
- ✅ **文件系统隔离**: 容器无法访问宿主机敏感文件
- ✅ **网络隔离**: 容器间通信受控
- ✅ **权限隔离**: 容器以非 root 用户运行(可配置)
### 2. 环境一致性
```
开发环境 = 构建环境 = 生产环境
```
- ✅ **版本一致**: 所有环境使用相同的 Node.js 版本
- ✅ **依赖一致**: 依赖版本锁定在 package.json
- ✅ **配置一致**: Docker 配置统一管理
### 3. 资源隔离
```
容器 A ← 资源限制 → 容器 B
```
- ✅ **CPU 限制**: 可以限制容器 CPU 使用率
- ✅ **内存限制**: 可以限制容器内存使用
- ✅ **磁盘限制**: 可以限制容器磁盘使用
### 4. 易于管理
- ✅ **独立更新**: 可以单独更新某个容器,不影响其他
- ✅ **快速回滚**: 可以快速回滚到之前的镜像版本
- ✅ **日志隔离**: 每个容器的日志独立管理
- ✅ **监控隔离**: 可以单独监控每个容器
## 数据持久化(唯一与宿主机共享的部分)
虽然容器是隔离的,但某些数据需要持久化:
```yaml
volumes:
# 数据库文件(需要持久化)
- /opt/nginx/html/ai/current/backend/prisma:/app/prisma
# 前端静态文件(只读)
- /opt/nginx/html/ai/current:/usr/share/nginx/html:ro
# 日志文件(需要持久化)
- /opt/nginx/html/ai/current/docker/logs:/var/log/nginx
```
**说明**:
- 数据库文件需要持久化,所以挂载到宿主机
- 前端文件是只读挂载,容器无法修改
- 日志文件挂载到宿主机,方便查看和管理
## 隔离验证
### 1. 检查容器隔离
```bash
# 查看运行中的容器
docker ps
# 查看容器网络
docker network inspect ai-learning-network
# 查看容器进程(在宿主机上)
ps aux | grep nginx # 看不到容器内的进程
# 进入容器查看
docker exec -it ai-learning-nginx sh
# 在容器内:无法访问宿主机其他文件(除了挂载的 volume
```
### 2. 检查网络隔离
```bash
# 在宿主机上无法直接访问容器内部网络
curl http://backend:3001 # ❌ 失败backend 只在 Docker 网络内可访问
# 在容器内可以访问
docker exec -it ai-learning-nginx sh
curl http://backend:3001 # ✅ 成功,在同一 Docker 网络内
```
### 3. 检查文件系统隔离
```bash
# 容器内的文件系统是独立的
docker exec -it ai-learning-backend ls /app
# 只能看到容器内的文件,看不到宿主机其他目录(除了挂载的 volume
```
## 安全建议
虽然已经实现了完全隔离,但还可以进一步加强:
### 1. 使用非 root 用户运行容器
```dockerfile
# 在 Dockerfile 中
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
```
### 2. 限制资源使用
```yaml
services:
backend:
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
```
### 3. 只读文件系统(除了必要目录)
```yaml
services:
nginx:
read_only: true
tmpfs:
- /tmp
- /var/cache/nginx
```
### 4. 网络策略
```yaml
services:
backend:
networks:
- ai-learning-network
# 不暴露端口到宿主机,只允许 nginx 访问
# ports: [] # 注释掉端口映射
```
## 总结
**是的,当前架构实现了完全隔离**
1. ✅ **构建阶段**: 在 Docker 容器中构建,与 Jenkins 服务器隔离
2. ✅ **运行阶段**: 应用在 Docker 容器中运行,与宿主机隔离
3. ✅ **网络隔离**: 容器在独立的 Docker 网络中
4. ✅ **文件系统隔离**: 容器只能访问挂载的 volume
5. ✅ **进程隔离**: 容器进程与宿主机进程隔离
**唯一与宿主机共享的**:
- 数据文件(数据库、日志)- 通过 volume 挂载
- 端口映射8080, 3001- 用于外部访问
这种架构提供了**最大的安全性和可移植性**,同时保持了必要的数据持久化。

264
JENKINS_DEPLOY.md Normal file
View File

@ -0,0 +1,264 @@
# Jenkins 部署配置说明
本文档说明如何通过 Jenkins 将项目部署到远程服务器 `180.76.180.105``/opt/nginx/html/ai` 目录。
## 前置要求
### 1. Jenkins 服务器要求
- Jenkins 已安装并运行
- 安装了必要的插件:
- Pipeline 插件
- SSH Pipeline Steps 插件(可选,用于更好的 SSH 支持)
- Node.js 和 npm 已安装(用于构建)
### 2. 远程服务器要求
- 服务器 IP: `180.76.180.105`
- 部署目录: `/opt/nginx/html/ai`
- 需要安装:
- Node.js (推荐 v18+)
- npm
- nginx
- PM2用于管理后端进程可选
### 3. SSH 配置
确保 Jenkins 服务器可以通过 SSH 无密码登录到远程服务器:
```bash
# 在 Jenkins 服务器上生成 SSH 密钥(如果还没有)
ssh-keygen -t rsa -b 4096
# 将公钥复制到远程服务器
ssh-copy-id root@180.76.180.105
# 测试连接
ssh root@180.76.180.105
```
或者在 Jenkins 中配置 SSH 凭据:
1. 进入 Jenkins → 系统管理 → 凭据管理
2. 添加 SSH 凭据
3. 在 Jenkinsfile 中使用 `SSH_CREDENTIALS` 环境变量
## Jenkins 配置步骤
### 1. 创建 Pipeline 任务
1. 在 Jenkins 中创建新任务
2. 选择 "流水线" (Pipeline) 类型
3. 在 "流水线" 配置中:
- **定义**: Pipeline script from SCM
- **SCM**: Git
- **Repository URL**: 你的 Git 仓库地址
- **脚本路径**: Jenkinsfile
### 2. 配置环境变量(可选)
如果需要修改默认配置,可以在 Jenkinsfile 中修改 `environment` 部分:
```groovy
environment {
DEPLOY_HOST = '180.76.180.105'
DEPLOY_USER = 'root' // 根据实际情况修改
DEPLOY_PATH = '/opt/nginx/html/ai'
}
```
### 3. 配置 SSH 凭据(推荐)
如果使用 SSH 凭据而不是密钥文件:
1. 在 Jenkins 中添加 SSH 凭据ID: `deploy-ssh-key`
2. 修改 Jenkinsfile 中的部署步骤,使用 `withCredentials`
```groovy
withCredentials([sshUserPrivateKey(credentialsId: 'deploy-ssh-key', keyFileVariable: 'SSH_KEY')]) {
sh '''
ssh -i $SSH_KEY -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} ...
'''
}
```
## 部署流程
Jenkins 流水线将执行以下步骤:
1. **Checkout**: 检出代码
2. **Install Dependencies**: 安装所有依赖
3. **Build Frontend**: 构建前端(生成 `frontend/dist`
4. **Build Backend**: 构建后端(生成 `backend/dist`
5. **Prepare Deployment Package**: 打包部署文件
6. **Deploy to Remote Server**:
- 传输文件到远程服务器
- 解压并部署到 `/opt/nginx/html/ai/current`
- 备份旧版本
7. **Restart Services**: 重启后端服务和 nginx
## 远程服务器配置
### 1. 创建部署目录
```bash
sudo mkdir -p /opt/nginx/html/ai
sudo chown -R $USER:$USER /opt/nginx/html/ai
```
### 2. 配置 Nginx
`nginx/nginx.conf` 复制到系统 nginx 配置目录:
```bash
# Ubuntu/Debian
sudo cp /opt/nginx/html/ai/current/nginx/nginx.conf /etc/nginx/sites-available/ai-learning-platform
sudo ln -s /etc/nginx/sites-available/ai-learning-platform /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
**注意**: 需要修改 nginx.conf 中的前端文件路径:
```nginx
location / {
root /opt/nginx/html/ai/current; # 修改这里
index index.html;
try_files $uri $uri/ /index.html;
}
```
### 3. 配置后端服务
#### 使用 PM2推荐
```bash
# 安装 PM2
npm install -g pm2
# 启动后端服务
cd /opt/nginx/html/ai/current/backend
pm2 start dist/index.js --name ai-learning-backend
pm2 save
pm2 startup # 设置开机自启
```
#### 使用 systemd
创建 systemd 服务文件 `/etc/systemd/system/ai-learning-backend.service`:
```ini
[Unit]
Description=AI Learning Platform Backend
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/nginx/html/ai/current/backend
ExecStart=/usr/bin/node dist/index.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3001
[Install]
WantedBy=multi-user.target
```
然后启用服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable ai-learning-backend
sudo systemctl start ai-learning-backend
```
## 手动部署(测试)
如果需要手动测试部署流程:
```bash
# 1. 构建项目
npm install
npm run install:all
npm run build
# 2. 创建部署包
mkdir -p deploy-package
cp -r frontend/dist deploy-package/frontend-dist
cp -r backend/dist deploy-package/backend/dist
cp -r backend/prisma deploy-package/backend/prisma
cp backend/package.json deploy-package/backend/
cp nginx/nginx.conf deploy-package/nginx/
cp scripts/deploy.sh deploy-package/
tar -czf deploy-package.tar.gz deploy-package/
# 3. 传输到远程服务器
scp deploy-package.tar.gz root@180.76.180.105:/tmp/
# 4. 在远程服务器上执行
ssh root@180.76.180.105
cd /tmp
tar -xzf deploy-package.tar.gz
mkdir -p /opt/nginx/html/ai/current
cp -r deploy-package/frontend-dist/* /opt/nginx/html/ai/current/
cp -r deploy-package/backend /opt/nginx/html/ai/current/
bash deploy-package/deploy.sh /opt/nginx/html/ai/current
```
## 故障排查
### 1. SSH 连接失败
- 检查网络连接
- 确认 SSH 密钥配置正确
- 检查防火墙设置
### 2. 构建失败
- 检查 Node.js 版本
- 确认所有依赖已安装
- 查看 Jenkins 构建日志
### 3. 部署后服务无法访问
- 检查 nginx 配置是否正确
- 确认后端服务是否运行:`pm2 list` 或 `systemctl status ai-learning-backend`
- 检查端口是否被占用:`netstat -tulpn | grep 3001`
- 查看日志:`pm2 logs ai-learning-backend` 或 `journalctl -u ai-learning-backend`
### 4. 数据库问题
- 确认数据库文件权限
- 检查 Prisma 迁移是否成功
- 查看数据库文件:`ls -la /opt/nginx/html/ai/current/backend/prisma/`
## 安全建议
1. **不要使用 root 用户**: 建议创建专用部署用户
2. **使用 SSH 密钥**: 避免使用密码认证
3. **限制 SSH 访问**: 使用防火墙限制 SSH 访问来源
4. **定期备份**: 自动备份数据库和配置文件
5. **监控日志**: 设置日志监控和告警
## 更新部署
每次代码更新后,只需在 Jenkins 中触发构建即可。Jenkins 会自动:
- 构建最新代码
- 部署到远程服务器
- 备份旧版本
- 重启服务
## 回滚
如果需要回滚到之前的版本:
```bash
ssh root@180.76.180.105
cd /opt/nginx/html/ai
# 查看备份目录
ls -la backup-*
# 恢复备份
mv current current-failed
mv backup-YYYYMMDD-HHMMSS current
# 重启服务
pm2 restart ai-learning-backend
nginx -s reload
```

334
JENKINS_DOCKER.md Normal file
View File

@ -0,0 +1,334 @@
# Jenkins Docker 构建配置说明
本文档说明如何在 Jenkins 中使用 Docker 容器执行构建和部署。
## Jenkins Docker 支持
Jenkins 支持在 Docker 容器中执行构建步骤,这提供了以下优势:
1. **环境隔离**: 每次构建都在干净的容器环境中执行
2. **版本一致性**: 使用固定版本的 Node.js避免环境差异
3. **易于维护**: 不需要在 Jenkins 服务器上安装 Node.js
4. **可移植性**: 构建环境与代码一起定义
## 前置要求
### 1. 安装 Jenkins Docker 插件
在 Jenkins 中安装以下插件:
- **Docker Pipeline** (推荐)
- **Docker** (可选,用于 Docker 命令支持)
安装步骤:
1. 进入 Jenkins → 系统管理 → 插件管理
2. 搜索 "Docker Pipeline"
3. 安装并重启 Jenkins
### 2. 配置 Docker
确保 Jenkins 服务器可以访问 Docker
```bash
# 检查 Docker 是否运行
docker ps
# 确保 Jenkins 用户有权限访问 Docker socket
# 方法1: 将 Jenkins 用户添加到 docker 组
sudo usermod -aG docker jenkins
# 方法2: 修改 Docker socket 权限(不推荐,安全风险)
sudo chmod 666 /var/run/docker.sock
```
### 3. 测试 Docker 访问
在 Jenkins 中创建一个测试 Pipeline
```groovy
pipeline {
agent any
stages {
stage('Test Docker') {
steps {
script {
def nodeImage = docker.image("node:18")
nodeImage.inside() {
sh 'node --version'
}
}
}
}
}
}
```
## 两种 Jenkinsfile 配置
### 方式一:完全 Docker 化(推荐)
使用 `Jenkinsfile.docker`,所有构建步骤都在 Docker 容器中执行:
```groovy
stage('Build in Docker') {
steps {
script {
def nodeImage = docker.image("node:18")
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
sh '''
npm install
npm run build
'''
}
}
}
}
```
**优点**:
- 完全隔离的构建环境
- 不依赖 Jenkins 服务器的 Node.js 版本
- 每次构建都是干净的环境
**缺点**:
- 需要 Docker Pipeline 插件
- 首次构建需要下载 Docker 镜像
### 方式二:混合模式(当前 Jenkinsfile
部分步骤在 Docker 中,部分在主机上:
```groovy
stage('Build in Docker') {
steps {
script {
def nodeImage = docker.image("node:18")
nodeImage.inside() {
sh 'npm run build'
}
}
}
}
```
## 使用 Docker 构建的 Jenkinsfile
项目提供了两个版本的 Jenkinsfile
1. **Jenkinsfile** - 当前版本,支持 Docker 构建
2. **Jenkinsfile.docker** - 完全 Docker 化版本
### 切换到 Docker 版本
如果需要使用完全 Docker 化的版本:
```bash
# 备份当前版本
cp Jenkinsfile Jenkinsfile.original
# 使用 Docker 版本
cp Jenkinsfile.docker Jenkinsfile
```
或者在 Jenkins 中直接指定文件路径:
- Pipeline script from SCM
- Script Path: `Jenkinsfile.docker`
## Docker 构建配置说明
### 1. Node.js 版本
在 Jenkinsfile 中指定 Node.js 版本:
```groovy
environment {
NODE_VERSION = '18' // 或 '20', '18-alpine' 等
}
```
### 2. Docker Socket 挂载
如果需要构建 Docker 镜像,需要挂载 Docker socket
```groovy
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
// 可以在这里执行 docker build 等命令
}
```
### 3. Volume 挂载
如果需要持久化构建产物,可以挂载卷:
```groovy
nodeImage.inside('-v /workspace:/workspace') {
// 构建产物会保留在 /workspace
}
```
## 常见问题
### 1. Docker 命令未找到
**错误**: `docker: command not found`
**解决**:
- 确保安装了 Docker Pipeline 插件
- 检查 Jenkins 用户是否有 Docker 访问权限
### 2. 权限 denied
**错误**: `permission denied while trying to connect to the Docker daemon socket`
**解决**:
```bash
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
```
### 3. 镜像拉取失败
**错误**: `Error pulling image`
**解决**:
- 检查网络连接
- 配置 Docker 镜像加速器
- 使用国内镜像源(如阿里云)
### 4. 构建产物丢失
**问题**: Docker 容器退出后,构建产物丢失
**解决**:
- Jenkins 会自动将工作目录挂载到容器中
- 确保构建产物在 `$WORKSPACE` 目录下
- 使用 `docker.inside()` 会自动处理工作目录
## 性能优化
### 1. 使用 Docker 镜像缓存
Jenkins 会自动缓存 Docker 镜像,但可以手动拉取:
```groovy
stage('Pull Docker Image') {
steps {
script {
docker.image('node:18').pull()
}
}
}
```
### 2. 使用 Alpine 镜像
使用更小的 Alpine 镜像可以加快拉取速度:
```groovy
def nodeImage = docker.image("node:18-alpine")
```
### 3. 并行构建
可以在不同的 Docker 容器中并行构建前端和后端:
```groovy
stage('Parallel Build') {
parallel {
stage('Build Frontend') {
steps {
script {
docker.image('node:18').inside() {
sh 'npm run build --workspace=frontend'
}
}
}
}
stage('Build Backend') {
steps {
script {
docker.image('node:18').inside() {
sh 'cd backend && npm run build'
}
}
}
}
}
}
```
## 最佳实践
1. **固定版本**: 使用具体的 Node.js 版本标签(如 `node:18`),避免使用 `latest`
2. **缓存依赖**: 使用 npm cache 或 Docker layer caching
3. **清理资源**: 在 `post` 阶段清理 Docker 资源
4. **错误处理**: 添加错误处理和日志记录
5. **安全扫描**: 定期扫描 Docker 镜像的安全漏洞
## 示例:完整的 Docker 构建 Pipeline
```groovy
pipeline {
agent any
environment {
NODE_VERSION = '18'
DOCKER_REGISTRY = 'your-registry.com'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
def nodeImage = docker.image("node:${NODE_VERSION}")
nodeImage.inside() {
sh '''
npm ci
npm run build
'''
}
}
}
}
stage('Test') {
steps {
script {
docker.image("node:${NODE_VERSION}").inside() {
sh 'npm test'
}
}
}
}
stage('Build Docker Images') {
steps {
script {
docker.build("myapp:${env.BUILD_NUMBER}")
}
}
}
}
post {
always {
sh 'docker system prune -f'
}
}
}
```
## 总结
使用 Docker 构建的优势:
- ✅ 环境一致性
- ✅ 易于维护
- ✅ 可移植性
- ✅ 隔离性
当前项目已支持 Docker 构建,只需确保 Jenkins 安装了 Docker Pipeline 插件即可使用。

196
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,196 @@
pipeline {
agent any
environment {
// 远程服务器配置
DEPLOY_HOST = '180.76.180.105'
DEPLOY_USER = 'root' // 根据实际情况修改用户名
DEPLOY_PATH = '/opt/nginx/html/ai'
// 如果需要 SSH 密钥,可以在 Jenkins 中配置 SSH credentials
// SSH_CREDENTIALS = credentials('deploy-ssh-key')
}
stages {
stage('Checkout') {
steps {
echo '检出代码...'
checkout scm
}
}
stage('Build in Docker') {
steps {
script {
// 在 Node.js Docker 容器中执行构建
// 需要 Jenkins 安装 Docker Pipeline 插件
def nodeImage = docker.image("node:18")
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
sh '''
echo "在 Docker 容器中构建..."
echo "Node 版本:"
node --version
echo "NPM 版本:"
npm --version
# 安装依赖
echo "安装依赖..."
npm install
npm run install:all
# 构建前端
echo "构建前端..."
npm run build --workspace=frontend
# 构建后端
echo "构建后端..."
cd backend
npm run build
npm run prisma:generate
cd ..
'''
}
}
}
}
stage('Prepare Deployment Package') {
steps {
echo '准备部署包...'
sh '''
# 创建临时部署目录
mkdir -p deploy-package
# 复制前端构建产物
cp -r frontend/dist deploy-package/frontend-dist
# 复制后端构建产物和必要文件
mkdir -p deploy-package/backend
cp -r backend/dist deploy-package/backend/dist
cp -r backend/prisma deploy-package/backend/prisma
cp backend/package.json deploy-package/backend/
cp backend/package-lock.json deploy-package/backend/ 2>/dev/null || true
# 复制 Docker 相关文件
mkdir -p deploy-package/docker
cp nginx/docker-compose.production.yml deploy-package/docker/docker-compose.yml
cp nginx/nginx.conf.docker deploy-package/docker/nginx.conf.docker
if [ -f backend/Dockerfile ]; then
cp backend/Dockerfile deploy-package/backend/
fi
# 复制部署脚本
cp scripts/deploy-docker.sh deploy-package/
chmod +x deploy-package/deploy-docker.sh
# 创建部署包
tar -czf deploy-package.tar.gz deploy-package/
'''
}
}
stage('Deploy to Remote Server') {
steps {
echo '部署到远程服务器...'
script {
// 使用 SSH 传输文件并执行部署
sh '''
# 传输部署包到远程服务器
scp -o StrictHostKeyChecking=no deploy-package.tar.gz ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
# 在远程服务器上执行部署
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
# 创建部署目录
mkdir -p /opt/nginx/html/ai
# 解压部署包
cd /tmp
tar -xzf deploy-package.tar.gz
# 备份旧版本(如果存在)
if [ -d /opt/nginx/html/ai/current ]; then
mv /opt/nginx/html/ai/current /opt/nginx/html/ai/backup-$(date +%Y%m%d-%H%M%S)
fi
# 创建新版本目录
mkdir -p /opt/nginx/html/ai/current
# 复制前端文件
cp -r deploy-package/frontend-dist/* /opt/nginx/html/ai/current/
# 复制后端文件
mkdir -p /opt/nginx/html/ai/current/backend
cp -r deploy-package/backend/* /opt/nginx/html/ai/current/backend/
# 复制 Docker 配置文件
mkdir -p /opt/nginx/html/ai/current/docker
cp deploy-package/docker/docker-compose.yml /opt/nginx/html/ai/current/docker/
cp deploy-package/docker/nginx.conf.docker /opt/nginx/html/ai/current/docker/
# 创建 nginx logs 目录
mkdir -p /opt/nginx/html/ai/current/docker/logs
# 执行 Docker 部署脚本
if [ -f deploy-package/deploy-docker.sh ]; then
chmod +x deploy-package/deploy-docker.sh
bash deploy-package/deploy-docker.sh /opt/nginx/html/ai/current
fi
# 清理临时文件
rm -rf /tmp/deploy-package /tmp/deploy-package.tar.gz
echo "部署完成!"
ENDSSH
'''
}
}
}
stage('Restart Services') {
steps {
echo '重启 Docker 服务...'
sh '''
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
# 使用 Docker Compose 重启服务
cd /opt/nginx/html/ai/current/docker
# 检查 docker-compose 命令(支持新版本的 docker compose
if command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
COMPOSE_CMD="docker compose"
fi
# 停止旧容器
$COMPOSE_CMD down || true
# 重新构建并启动
$COMPOSE_CMD up -d --build
# 等待服务启动
sleep 5
# 检查服务状态
$COMPOSE_CMD ps
echo "Docker 服务已重启"
echo "前端访问: http://180.76.180.105:8080"
echo "后端 API: http://180.76.180.105:3001"
ENDSSH
'''
}
}
}
post {
success {
echo '部署成功!'
}
failure {
echo '部署失败!'
}
always {
// 清理本地临时文件
sh 'rm -rf deploy-package deploy-package.tar.gz'
}
}
}

195
Jenkinsfile.docker Normal file
View File

@ -0,0 +1,195 @@
pipeline {
agent any
environment {
// 远程服务器配置
DEPLOY_HOST = '180.76.180.105'
DEPLOY_USER = 'root'
DEPLOY_PATH = '/opt/nginx/html/ai'
// Node.js 版本
NODE_VERSION = '18'
}
stages {
stage('Checkout') {
steps {
echo '检出代码...'
checkout scm
}
}
stage('Build in Docker') {
steps {
script {
// 在 Node.js Docker 容器中执行构建
def nodeImage = docker.image("node:${NODE_VERSION}")
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
sh '''
echo "在 Docker 容器中构建..."
echo "Node 版本:"
node --version
echo "NPM 版本:"
npm --version
# 安装依赖
echo "安装依赖..."
npm install
npm run install:all
# 构建前端
echo "构建前端..."
npm run build --workspace=frontend
# 构建后端
echo "构建后端..."
cd backend
npm run build
npm run prisma:generate
cd ..
'''
}
}
}
}
stage('Prepare Deployment Package') {
steps {
script {
// 在 Docker 容器中打包
def nodeImage = docker.image("node:${NODE_VERSION}")
nodeImage.inside() {
sh '''
echo "准备部署包..."
# 创建临时部署目录
mkdir -p deploy-package
# 复制前端构建产物
cp -r frontend/dist deploy-package/frontend-dist
# 复制后端构建产物和必要文件
mkdir -p deploy-package/backend
cp -r backend/dist deploy-package/backend/dist
cp -r backend/prisma deploy-package/backend/prisma
cp backend/package.json deploy-package/backend/
cp backend/package-lock.json deploy-package/backend/ 2>/dev/null || true
# 复制 Docker 相关文件
mkdir -p deploy-package/docker
cp nginx/docker-compose.production.yml deploy-package/docker/docker-compose.yml
cp nginx/nginx.conf.docker deploy-package/docker/nginx.conf.docker
if [ -f backend/Dockerfile ]; then
cp backend/Dockerfile deploy-package/backend/
fi
# 复制部署脚本
cp scripts/deploy-docker.sh deploy-package/
chmod +x deploy-package/deploy-docker.sh
# 创建部署包
tar -czf deploy-package.tar.gz deploy-package/
'''
}
}
}
}
stage('Deploy to Remote Server') {
steps {
echo '部署到远程服务器...'
script {
sh '''
# 传输部署包到远程服务器
scp -o StrictHostKeyChecking=no deploy-package.tar.gz ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
# 在远程服务器上执行部署
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
# 创建部署目录
mkdir -p /opt/nginx/html/ai
# 解压部署包
cd /tmp
tar -xzf deploy-package.tar.gz
# 备份旧版本(如果存在)
if [ -d /opt/nginx/html/ai/current ]; then
mv /opt/nginx/html/ai/current /opt/nginx/html/ai/backup-$(date +%Y%m%d-%H%M%S)
fi
# 创建新版本目录
mkdir -p /opt/nginx/html/ai/current
# 复制前端文件
cp -r deploy-package/frontend-dist/* /opt/nginx/html/ai/current/
# 复制后端文件
mkdir -p /opt/nginx/html/ai/current/backend
cp -r deploy-package/backend/* /opt/nginx/html/ai/current/backend/
# 复制 Docker 配置文件
mkdir -p /opt/nginx/html/ai/current/docker
cp deploy-package/docker/docker-compose.yml /opt/nginx/html/ai/current/docker/
cp deploy-package/docker/nginx.conf.docker /opt/nginx/html/ai/current/docker/
# 创建 nginx logs 目录
mkdir -p /opt/nginx/html/ai/current/docker/logs
# 清理临时文件
rm -rf /tmp/deploy-package /tmp/deploy-package.tar.gz
echo "部署完成!"
ENDSSH
'''
}
}
}
stage('Restart Docker Services') {
steps {
echo '重启 Docker 服务...'
sh '''
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
# 使用 Docker Compose 重启服务
cd /opt/nginx/html/ai/current/docker
# 检查 docker-compose 命令(支持新版本的 docker compose
if command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
COMPOSE_CMD="docker compose"
fi
# 停止旧容器
$COMPOSE_CMD down || true
# 重新构建并启动
$COMPOSE_CMD up -d --build
# 等待服务启动
sleep 5
# 检查服务状态
$COMPOSE_CMD ps
echo "Docker 服务已重启"
echo "前端访问: http://180.76.180.105:8080"
echo "后端 API: http://180.76.180.105:3001"
ENDSSH
'''
}
}
}
post {
success {
echo '部署成功!'
}
failure {
echo '部署失败!'
}
always {
// 清理本地临时文件
sh 'rm -rf deploy-package deploy-package.tar.gz'
}
}
}

22
backend/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM node:18-alpine
WORKDIR /app
# 复制 package 文件
COPY package*.json ./
COPY prisma ./prisma/
# 安装依赖(包括生产依赖)
RUN npm ci --production
# 复制构建产物
COPY dist ./dist
# 生成 Prisma Client
RUN npx prisma generate
# 暴露端口
EXPOSE 3001
# 启动应用
CMD ["node", "dist/index.js"]

View File

@ -0,0 +1,42 @@
version: '3.8'
services:
# 前端 Nginx 服务(使用 8080 端口避免与现有 nginx 冲突)
nginx:
image: nginx:alpine
container_name: ai-learning-nginx
ports:
- "8080:80" # 使用 8080 端口,避免与现有 nginx 冲突
- "8443:443" # HTTPS 端口(可选)
volumes:
- /opt/nginx/html/ai/current/docker/nginx.conf.docker:/etc/nginx/conf.d/default.conf:ro
- /opt/nginx/html/ai/current:/usr/share/nginx/html:ro
- /opt/nginx/html/ai/current/docker/logs:/var/log/nginx
depends_on:
- backend
restart: unless-stopped
networks:
- ai-learning-network
# 后端服务
backend:
build:
context: /opt/nginx/html/ai/current/backend
dockerfile: Dockerfile
container_name: ai-learning-backend
ports:
- "3001:3001"
environment:
- NODE_ENV=production
- PORT=3001
- DATABASE_URL=file:./prisma/dev.db
volumes:
- /opt/nginx/html/ai/current/backend/prisma:/app/prisma
- /opt/nginx/html/ai/current/backend/dist:/app/dist:ro
restart: unless-stopped
networks:
- ai-learning-network
networks:
ai-learning-network:
driver: bridge

View File

@ -20,7 +20,7 @@ server {
# 前端静态文件 # 前端静态文件
location / { location / {
root /var/www/ai-learning-platform/frontend/dist; root /opt/nginx/html/ai/current;
index index.html; index index.html;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;

59
nginx/nginx.conf.docker Normal file
View File

@ -0,0 +1,59 @@
# Nginx 配置文件 - Docker 部署版本
# 用于 docker-compose 部署,使用 Docker 服务名进行服务发现
# 上游后端服务器(使用 Docker 服务名)
upstream backend {
server backend:3001; # 使用 Docker Compose 服务名
keepalive 64;
}
# HTTP 服务器配置
server {
listen 80;
server_name _; # 匹配所有域名和 IP
# 客户端最大请求体大小
client_max_body_size 10M;
# 日志配置
access_log /var/log/nginx/ai-learning-access.log;
error_log /var/log/nginx/ai-learning-error.log;
# 前端静态文件
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API 代理
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}

128
nginx/nginx.conf.production Normal file
View File

@ -0,0 +1,128 @@
# Nginx 配置文件 - AI学习平台生产环境
# 部署路径: /opt/nginx/html/ai/current
# 上游后端服务器
upstream backend {
server localhost:3001;
keepalive 64;
}
# HTTP 服务器配置
server {
listen 80;
server_name 180.76.180.105; # 服务器 IP 或域名
# 客户端最大请求体大小
client_max_body_size 10M;
# 日志配置
access_log /var/log/nginx/ai-learning-access.log;
error_log /var/log/nginx/ai-learning-error.log;
# 前端静态文件
location / {
root /opt/nginx/html/ai/current;
index index.html;
try_files $uri $uri/ /index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API 代理
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
# HTTPS 服务器配置(可选,需要 SSL 证书)
# server {
# listen 443 ssl http2;
# server_name 180.76.180.105; # 服务器 IP 或域名
#
# # SSL 证书配置
# ssl_certificate /path/to/your/certificate.crt;
# ssl_certificate_key /path/to/your/private.key;
#
# # SSL 优化配置
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
#
# # 客户端最大请求体大小
# client_max_body_size 10M;
#
# # 日志配置
# access_log /var/log/nginx/ai-learning-ssl-access.log;
# error_log /var/log/nginx/ai-learning-ssl-error.log;
#
# # 前端静态文件
# location / {
# root /opt/nginx/html/ai/current;
# index index.html;
# try_files $uri $uri/ /index.html;
#
# # 静态资源缓存
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
# expires 1y;
# add_header Cache-Control "public, immutable";
# }
# }
#
# # API 代理
# location /api {
# proxy_pass http://backend;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# 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;
# proxy_cache_bypass $http_upgrade;
#
# # 超时设置
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout 60s;
# }
#
# # 健康检查
# location /health {
# access_log off;
# return 200 "healthy\n";
# add_header Content-Type text/plain;
# }
# }
#
# # HTTP 重定向到 HTTPS
# server {
# listen 80;
# server_name 180.76.180.105; # 服务器 IP 或域名
# return 301 https://$server_name$request_uri;
# }

83
scripts/deploy-docker.sh Executable file
View File

@ -0,0 +1,83 @@
#!/bin/bash
# Docker 部署脚本 - 在远程服务器上执行
# 用法: ./deploy-docker.sh <部署目录>
set -e
DEPLOY_DIR=${1:-/opt/nginx/html/ai/current}
DOCKER_DIR="$DEPLOY_DIR/docker"
echo "开始 Docker 部署到: $DEPLOY_DIR"
# 检查部署目录是否存在
if [ ! -d "$DEPLOY_DIR" ]; then
echo "错误: 部署目录不存在: $DEPLOY_DIR"
exit 1
fi
# 检查 Docker 是否安装
if ! command -v docker &> /dev/null; then
echo "错误: Docker 未安装"
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
echo "错误: docker-compose 未安装"
exit 1
fi
# 检查 docker-compose 命令(支持新版本的 docker compose
COMPOSE_CMD="docker-compose"
if ! command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker compose"
fi
# 进入 Docker 目录
if [ ! -d "$DOCKER_DIR" ]; then
echo "错误: Docker 配置目录不存在: $DOCKER_DIR"
exit 1
fi
cd "$DOCKER_DIR" || exit 1
# 停止旧容器(如果存在)
echo "停止旧容器..."
$COMPOSE_CMD down || true
# 构建并启动服务
echo "构建并启动 Docker 服务..."
$COMPOSE_CMD up -d --build
# 等待服务启动
echo "等待服务启动..."
sleep 5
# 检查服务状态
echo "检查服务状态..."
$COMPOSE_CMD ps
# 检查容器健康状态
echo "检查容器健康状态..."
if docker ps | grep -q ai-learning-nginx; then
echo "✅ Nginx 容器运行中"
else
echo "❌ Nginx 容器未运行"
exit 1
fi
if docker ps | grep -q ai-learning-backend; then
echo "✅ Backend 容器运行中"
else
echo "❌ Backend 容器未运行"
exit 1
fi
echo ""
echo "=========================================="
echo "Docker 部署完成!"
echo "=========================================="
echo "前端访问: http://180.76.180.105:8080"
echo "后端 API: http://180.76.180.105:3001"
echo "健康检查: http://180.76.180.105:8080/health"
echo "=========================================="

54
scripts/deploy.sh Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
# 部署脚本 - 在远程服务器上执行
# 用法: ./deploy.sh <部署目录>
set -e
DEPLOY_DIR=${1:-/opt/nginx/html/ai/current}
BACKEND_DIR="$DEPLOY_DIR/backend"
echo "开始部署到: $DEPLOY_DIR"
# 检查部署目录是否存在
if [ ! -d "$DEPLOY_DIR" ]; then
echo "错误: 部署目录不存在: $DEPLOY_DIR"
exit 1
fi
# 进入后端目录
cd "$BACKEND_DIR" || exit 1
# 安装后端依赖(仅生产依赖)
if [ -f "package.json" ]; then
echo "安装后端依赖..."
npm ci --production || npm install --production
fi
# 初始化数据库(如果需要)
if [ -d "prisma" ]; then
echo "初始化数据库..."
# 检查数据库文件是否存在
if [ ! -f "prisma/dev.db" ]; then
echo "数据库不存在,执行迁移..."
npx prisma migrate deploy || npx prisma migrate dev
npx prisma generate
# 如果有 seed 文件,执行 seed
if [ -f "prisma/seed.ts" ]; then
npx tsx prisma/seed.ts || echo "Seed 执行失败或已存在数据"
fi
else
echo "数据库已存在,执行迁移..."
npx prisma migrate deploy || npx prisma migrate dev
npx prisma generate
fi
fi
# 设置文件权限
echo "设置文件权限..."
chmod +x dist/index.js 2>/dev/null || true
echo "部署脚本执行完成!"
echo "部署目录: $DEPLOY_DIR"
echo "前端文件: $DEPLOY_DIR"
echo "后端文件: $BACKEND_DIR"