修改环境,支持docker 安装
This commit is contained in:
parent
1a4bb873d2
commit
fdf6d19ed7
54
DOCKER_SETUP.md
Normal file
54
DOCKER_SETUP.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Docker 镜像加速器配置
|
||||||
|
|
||||||
|
## 问题
|
||||||
|
无法从 Docker Hub 拉取镜像,出现网络超时错误。
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 方法一:配置 Docker Desktop 镜像加速器(macOS)
|
||||||
|
|
||||||
|
1. 打开 Docker Desktop
|
||||||
|
2. 点击设置(Settings/Preferences)
|
||||||
|
3. 选择 Docker Engine
|
||||||
|
4. 添加以下配置:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registry-mirrors": [
|
||||||
|
"https://docker.mirrors.ustc.edu.cn",
|
||||||
|
"https://hub-mirror.c.163.com",
|
||||||
|
"https://mirror.baidubce.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 点击 Apply & Restart
|
||||||
|
|
||||||
|
### 方法二:使用代理(如果有)
|
||||||
|
|
||||||
|
如果有代理,可以在 Docker Desktop 中配置代理:
|
||||||
|
- Settings → Resources → Proxies
|
||||||
|
- 配置 HTTP/HTTPS 代理
|
||||||
|
|
||||||
|
### 方法三:等待网络恢复
|
||||||
|
|
||||||
|
如果网络暂时有问题,可以稍后重试。
|
||||||
|
|
||||||
|
## 验证配置
|
||||||
|
|
||||||
|
配置完成后,测试拉取镜像:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull node:18-alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
## 临时解决方案
|
||||||
|
|
||||||
|
如果急需启动服务,可以先启动 nginx(已有镜像):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd nginx
|
||||||
|
docker compose up nginx -d
|
||||||
|
```
|
||||||
|
|
||||||
|
等网络恢复后再启动后端。
|
||||||
219
JENKINS_DOCKER_SETUP.md
Normal file
219
JENKINS_DOCKER_SETUP.md
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
# Jenkins Docker Pipeline 插件安装指南
|
||||||
|
|
||||||
|
## 安装步骤
|
||||||
|
|
||||||
|
### 1. 进入 Jenkins 插件管理
|
||||||
|
|
||||||
|
1. 登录 Jenkins
|
||||||
|
2. 点击 **系统管理** (Manage Jenkins)
|
||||||
|
3. 点击 **插件管理** (Manage Plugins)
|
||||||
|
|
||||||
|
### 2. 安装 Docker Pipeline 插件
|
||||||
|
|
||||||
|
#### 方式一:通过界面安装(推荐)
|
||||||
|
|
||||||
|
1. 在插件管理页面,点击 **可选插件** (Available)
|
||||||
|
2. 在搜索框输入 `Docker Pipeline`
|
||||||
|
3. 找到 **Docker Pipeline** 插件
|
||||||
|
4. 勾选插件
|
||||||
|
5. 点击 **直接安装** (Install without restart) 或 **下载待重启后安装** (Download now and install after restart)
|
||||||
|
6. 等待安装完成
|
||||||
|
7. 如果提示重启,点击 **重启 Jenkins** (Restart Jenkins)
|
||||||
|
|
||||||
|
#### 方式二:通过命令行安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 Jenkins 服务器上执行
|
||||||
|
jenkins-plugin-cli install docker-workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 验证安装
|
||||||
|
|
||||||
|
安装完成后,可以通过以下方式验证:
|
||||||
|
|
||||||
|
#### 方式一:在 Jenkins 中测试
|
||||||
|
|
||||||
|
创建一个测试 Pipeline:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
stages {
|
||||||
|
stage('Test Docker') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside() {
|
||||||
|
sh 'node --version'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果能够成功执行,说明插件安装成功。
|
||||||
|
|
||||||
|
#### 方式二:检查插件列表
|
||||||
|
|
||||||
|
1. 进入 **系统管理** → **插件管理** → **已安装**
|
||||||
|
2. 搜索 `Docker Pipeline`
|
||||||
|
3. 确认插件已安装并启用
|
||||||
|
|
||||||
|
## 配置 Docker 访问权限
|
||||||
|
|
||||||
|
### 1. 确保 Docker 服务运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 Docker 状态
|
||||||
|
sudo systemctl status docker
|
||||||
|
|
||||||
|
# 如果未运行,启动 Docker
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo systemctl enable docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置 Jenkins 用户权限
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 将 Jenkins 用户添加到 docker 组
|
||||||
|
sudo usermod -aG docker jenkins
|
||||||
|
|
||||||
|
# 重启 Jenkins
|
||||||
|
sudo systemctl restart jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 验证 Docker 访问
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 切换到 Jenkins 用户测试
|
||||||
|
sudo -u jenkins docker ps
|
||||||
|
|
||||||
|
# 如果成功,说明权限配置正确
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 插件安装失败
|
||||||
|
|
||||||
|
**问题**: 插件下载失败或安装超时
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 检查网络连接
|
||||||
|
- 更换 Jenkins 更新中心镜像源
|
||||||
|
- 手动下载插件并上传安装
|
||||||
|
|
||||||
|
### 2. Docker 命令权限被拒绝
|
||||||
|
|
||||||
|
**错误**: `permission denied while trying to connect to the Docker daemon socket`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```bash
|
||||||
|
# 将 Jenkins 用户添加到 docker 组
|
||||||
|
sudo usermod -aG docker jenkins
|
||||||
|
|
||||||
|
# 重启 Jenkins
|
||||||
|
sudo systemctl restart jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Docker 镜像拉取失败
|
||||||
|
|
||||||
|
**错误**: `Error pulling image`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 检查网络连接
|
||||||
|
- 配置 Docker 镜像加速器(如阿里云、腾讯云)
|
||||||
|
- 手动拉取镜像:`docker pull node:18`
|
||||||
|
|
||||||
|
### 4. 插件已安装但 docker 对象不可用
|
||||||
|
|
||||||
|
**问题**: 安装插件后仍然报错 `No such property: docker`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
1. 确认插件已启用(插件管理 → 已安装 → 检查状态)
|
||||||
|
2. 重启 Jenkins
|
||||||
|
3. 检查 Jenkins 日志:`/var/log/jenkins/jenkins.log`
|
||||||
|
|
||||||
|
## 安装后的效果
|
||||||
|
|
||||||
|
安装 Docker Pipeline 插件后:
|
||||||
|
|
||||||
|
1. ✅ **可以使用 `docker.image()` 方法**
|
||||||
|
```groovy
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
```
|
||||||
|
|
||||||
|
2. ✅ **可以在容器中执行构建**
|
||||||
|
```groovy
|
||||||
|
nodeImage.inside() {
|
||||||
|
sh 'npm install'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. ✅ **可以构建 Docker 镜像**
|
||||||
|
```groovy
|
||||||
|
docker.build("myapp:${env.BUILD_NUMBER}")
|
||||||
|
```
|
||||||
|
|
||||||
|
4. ✅ **可以推送镜像到仓库**
|
||||||
|
```groovy
|
||||||
|
docker.image("myapp:${env.BUILD_NUMBER}").push()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 当前 Jenkinsfile 说明
|
||||||
|
|
||||||
|
当前 Jenkinsfile 已经配置了 Docker 构建:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside() {
|
||||||
|
// 构建步骤
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 回退到主机构建
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
- 优先使用 Docker 构建(环境隔离)
|
||||||
|
- 如果 Docker 插件不可用,自动回退到主机构建
|
||||||
|
- 确保在任何情况下都能正常构建
|
||||||
|
|
||||||
|
## 推荐配置
|
||||||
|
|
||||||
|
安装插件后,建议:
|
||||||
|
|
||||||
|
1. **固定 Node.js 版本**: 使用 `node:18` 而不是 `node:latest`
|
||||||
|
2. **使用 Alpine 镜像**: 更小的镜像体积,如 `node:18-alpine`
|
||||||
|
3. **缓存依赖**: 使用 Docker layer caching 加速构建
|
||||||
|
4. **定期更新镜像**: 定期拉取最新的基础镜像
|
||||||
|
|
||||||
|
## 测试构建
|
||||||
|
|
||||||
|
安装插件后,可以运行一次构建测试:
|
||||||
|
|
||||||
|
1. 在 Jenkins 中触发构建
|
||||||
|
2. 查看构建日志,应该看到:
|
||||||
|
```
|
||||||
|
使用 Docker 容器构建...
|
||||||
|
在 Docker 容器中构建...
|
||||||
|
```
|
||||||
|
3. 如果看到这些日志,说明 Docker 构建正常工作
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
安装 **Docker Pipeline** 插件后,Jenkinsfile 中的 Docker 构建功能就可以正常使用了。插件提供了:
|
||||||
|
- Docker 镜像管理
|
||||||
|
- 容器内执行命令
|
||||||
|
- Docker 镜像构建和推送
|
||||||
|
- 完整的 Docker 集成
|
||||||
|
|
||||||
|
安装完成后,重新运行构建即可!
|
||||||
@ -1,16 +1,27 @@
|
|||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# 安装 OpenSSL(Prisma 需要)
|
||||||
|
# Alpine 3.19+ 使用 openssl3,旧版本使用 openssl1.1-compat
|
||||||
|
RUN apk add --no-cache openssl libc6-compat || \
|
||||||
|
apk add --no-cache openssl1.1-compat || \
|
||||||
|
apk add --no-cache openssl
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 复制 package 文件
|
# 先复制 shared 包(workspace 依赖)
|
||||||
COPY package*.json ./
|
COPY shared ./shared
|
||||||
COPY prisma ./prisma/
|
|
||||||
|
|
||||||
# 安装依赖(包括生产依赖)
|
# 复制 backend 的 package 文件
|
||||||
RUN npm ci --production
|
COPY backend/package*.json ./
|
||||||
|
COPY backend/prisma ./prisma/
|
||||||
|
|
||||||
|
# 安装依赖(shared 包直接使用源码,不需要安装)
|
||||||
|
# 修改 package.json 中的 shared 依赖为 file:./shared
|
||||||
|
RUN sed -i 's|"@ai-learning/shared": "\*"|"@ai-learning/shared": "file:./shared"|' package.json && \
|
||||||
|
npm install --production
|
||||||
|
|
||||||
# 复制构建产物
|
# 复制构建产物
|
||||||
COPY dist ./dist
|
COPY backend/dist ./dist
|
||||||
|
|
||||||
# 生成 Prisma Client
|
# 生成 Prisma Client
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
|
|||||||
@ -21,6 +21,7 @@ model Course {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
chapters Chapter[]
|
chapters Chapter[]
|
||||||
pathItems PathItem[]
|
pathItems PathItem[]
|
||||||
|
userProgress UserProgress[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Chapter {
|
model Chapter {
|
||||||
@ -33,6 +34,7 @@ model Chapter {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||||||
pathItems PathItem[]
|
pathItems PathItem[]
|
||||||
|
userProgress UserProgress[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model LearningPath {
|
model LearningPath {
|
||||||
@ -69,5 +71,8 @@ model UserProgress {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||||||
|
chapter Chapter? @relation(fields: [chapterId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([userId, courseId, chapterId])
|
@@unique([userId, courseId, chapterId])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
"build:with-lint": "npm run lint && tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
import { Component, ErrorInfo, ReactNode } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import { pathApi } from '../services/api';
|
import { pathApi, courseApi, chapterApi } from '../services/api';
|
||||||
import type { LearningPath } from '@ai-learning/shared';
|
import type { LearningPath, Course, Chapter } from '@ai-learning/shared';
|
||||||
|
|
||||||
|
interface PathItemWithDetails {
|
||||||
|
id: string;
|
||||||
|
pathId: string;
|
||||||
|
courseId: string;
|
||||||
|
chapterId?: string;
|
||||||
|
order: number;
|
||||||
|
type: 'course' | 'chapter';
|
||||||
|
course?: Course;
|
||||||
|
chapter?: Chapter;
|
||||||
|
}
|
||||||
|
|
||||||
export default function PathDetailPage() {
|
export default function PathDetailPage() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const [path, setPath] = useState<LearningPath | null>(null);
|
const [path, setPath] = useState<LearningPath | null>(null);
|
||||||
|
const [pathItems, setPathItems] = useState<PathItemWithDetails[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -20,7 +32,37 @@ export default function PathDetailPage() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await pathApi.getById(id);
|
const response = await pathApi.getById(id);
|
||||||
setPath(response.data);
|
const pathData = response.data;
|
||||||
|
setPath(pathData);
|
||||||
|
|
||||||
|
// 加载每个 pathItem 的详细信息
|
||||||
|
const itemsWithDetails = await Promise.all(
|
||||||
|
pathData.pathItems.map(async (item) => {
|
||||||
|
const details: PathItemWithDetails = { ...item };
|
||||||
|
|
||||||
|
// 加载课程信息
|
||||||
|
try {
|
||||||
|
const courseResponse = await courseApi.getById(item.courseId);
|
||||||
|
details.course = courseResponse.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load course ${item.courseId}:`, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是章节类型,加载章节信息
|
||||||
|
if (item.type === 'chapter' && item.chapterId) {
|
||||||
|
try {
|
||||||
|
const chapterResponse = await chapterApi.getById(item.chapterId);
|
||||||
|
details.chapter = chapterResponse.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load chapter ${item.chapterId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return details;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setPathItems(itemsWithDetails);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load path:', error);
|
console.error('Failed to load path:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -62,11 +104,11 @@ export default function PathDetailPage() {
|
|||||||
<div className="bg-white rounded-lg shadow-md p-8">
|
<div className="bg-white rounded-lg shadow-md p-8">
|
||||||
<h2 className="text-2xl font-semibold text-gray-900 mb-6">学习路径</h2>
|
<h2 className="text-2xl font-semibold text-gray-900 mb-6">学习路径</h2>
|
||||||
|
|
||||||
{!path.pathItems || path.pathItems.length === 0 ? (
|
{!pathItems || pathItems.length === 0 ? (
|
||||||
<p className="text-gray-500">路径为空</p>
|
<p className="text-gray-500">路径为空</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{path.pathItems.map((item, index) => (
|
{pathItems.map((item, index) => (
|
||||||
<div key={item.id} className="flex items-start space-x-4">
|
<div key={item.id} className="flex items-start space-x-4">
|
||||||
<div className="flex-shrink-0 w-8 h-8 bg-primary-600 text-white rounded-full flex items-center justify-center font-semibold">
|
<div className="flex-shrink-0 w-8 h-8 bg-primary-600 text-white rounded-full flex items-center justify-center font-semibold">
|
||||||
{index + 1}
|
{index + 1}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ services:
|
|||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
container_name: ai-learning-nginx
|
container_name: ai-learning-nginx
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "8080:80"
|
||||||
- "443:443"
|
- "8443:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
- ../frontend/dist:/usr/share/nginx/html:ro
|
- ../frontend/dist:/usr/share/nginx/html:ro
|
||||||
@ -21,8 +21,8 @@ services:
|
|||||||
# 后端服务
|
# 后端服务
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: ../backend
|
context: .. # 使用项目根目录作为构建上下文
|
||||||
dockerfile: Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
container_name: ai-learning-backend
|
container_name: ai-learning-backend
|
||||||
ports:
|
ports:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# Nginx 配置文件 - AI学习平台
|
# Nginx 配置文件 - AI学习平台
|
||||||
|
|
||||||
# 上游后端服务器
|
# 上游后端服务器
|
||||||
|
# 在 Docker 网络中使用服务名,本地开发使用 localhost
|
||||||
upstream backend {
|
upstream backend {
|
||||||
server localhost:3001;
|
server backend:3001;
|
||||||
keepalive 64;
|
keepalive 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ server {
|
|||||||
|
|
||||||
# 前端静态文件
|
# 前端静态文件
|
||||||
location / {
|
location / {
|
||||||
root /opt/nginx/html/ai/current;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
|||||||
86
scripts/start-local.sh
Executable file
86
scripts/start-local.sh
Executable file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 本地 Docker 启动脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== 检查 Docker ==="
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
echo "错误: Docker daemon 未运行"
|
||||||
|
echo "请先启动 Docker Desktop 或 Docker daemon"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Docker 运行正常"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 进入项目根目录
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "=== 检查并构建项目 ==="
|
||||||
|
|
||||||
|
# 检查前端是否已构建
|
||||||
|
if [ ! -d "frontend/dist" ] || [ -z "$(ls -A frontend/dist 2>/dev/null)" ]; then
|
||||||
|
echo "前端未构建,开始构建..."
|
||||||
|
npm install
|
||||||
|
npm run install:all
|
||||||
|
npm run build --workspace=frontend
|
||||||
|
else
|
||||||
|
echo "前端已构建,跳过..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查后端是否已构建
|
||||||
|
if [ ! -d "backend/dist" ] || [ -z "$(ls -A backend/dist 2>/dev/null)" ]; then
|
||||||
|
echo "后端未构建,开始构建..."
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm run prisma:generate
|
||||||
|
cd ..
|
||||||
|
else
|
||||||
|
echo "后端已构建,跳过..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 启动 Docker 容器 ==="
|
||||||
|
cd nginx
|
||||||
|
|
||||||
|
# 检查 docker-compose 命令(支持新版本的 docker compose)
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
elif docker compose version &> /dev/null 2>&1; then
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
else
|
||||||
|
echo "错误: 未找到 docker-compose 或 docker compose 命令"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 停止旧容器(如果存在)
|
||||||
|
echo "停止旧容器..."
|
||||||
|
$COMPOSE_CMD down 2>/dev/null || true
|
||||||
|
|
||||||
|
# 创建必要的目录
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# 启动容器
|
||||||
|
echo "启动容器..."
|
||||||
|
$COMPOSE_CMD up -d --build
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 等待服务启动 ==="
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查容器状态
|
||||||
|
echo "容器状态:"
|
||||||
|
$COMPOSE_CMD ps
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== 服务信息 ==="
|
||||||
|
echo "前端: http://localhost"
|
||||||
|
echo "后端 API: http://localhost:3001"
|
||||||
|
echo ""
|
||||||
|
echo "查看日志:"
|
||||||
|
echo " $COMPOSE_CMD logs -f"
|
||||||
|
echo ""
|
||||||
|
echo "停止服务:"
|
||||||
|
echo " $COMPOSE_CMD down"
|
||||||
20
scripts/stop-local.sh
Executable file
20
scripts/stop-local.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 停止本地 Docker 服务
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/../nginx"
|
||||||
|
|
||||||
|
# 检查 docker-compose 命令
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
elif docker compose version &> /dev/null 2>&1; then
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
else
|
||||||
|
echo "错误: 未找到 docker-compose 或 docker compose 命令"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "停止 Docker 容器..."
|
||||||
|
$COMPOSE_CMD down
|
||||||
|
|
||||||
|
echo "服务已停止"
|
||||||
Loading…
Reference in New Issue
Block a user