diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index cfb8192..7c45d3b 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,6 +1,39 @@ # IDC设备管理系统 - 安装部署指南 -## 快速开始 +## 快速开始(推荐) + +### 一键部署脚本(交互式) + +我们提供了交互式安装脚本,自动完成环境检测、依赖安装、数据库初始化和服务启动。 + +```bash +# 方式一:使用 npm +npm run deploy + +# 方式二:直接运行 +node install.js +``` + +**脚本功能:** +- ✅ 自动检测 Node.js、npm、PM2、Nginx +- ✅ Linux 系统支持自动安装 Node.js(交互式) +- ✅ 交互式配置数据库(SQLite/MySQL) +- ✅ 交互式选择前端部署方式(Nginx/PM2 serve) +- ✅ 自动安装项目依赖 +- ✅ 自动初始化数据库 +- ✅ 自动构建前端项目 +- ✅ 使用 PM2 启动和管理服务 + +**Linux 自动安装 Node.js:** +- 支持 Ubuntu、Debian、CentOS、RHEL、Fedora、Arch +- 自动检测发行版并使用对应安装方式 +- 无需手动下载,一键完成 + +--- + +## 传统手动部署 + +如需手动部署,请参考以下步骤: ### 1. 克隆项目 @@ -43,11 +76,83 @@ cd frontend && npm run dev | 项目 | 要求 | |------|------| -| Node.js | ≥14.0.0 | -| npm | ≥6.0.0 | +| Node.js | ≥14.0.0(推荐 20.x LTS) | +| npm | ≥6.0.0(随 Node.js 安装) | | 操作系统 | Windows 10/11、macOS、Linux | | 内存 | 开发:4GB+ / 生产:8GB+ | +### Node.js 安装 + +#### Linux(自动安装) +运行 `node install.js`,选择自动安装即可。 + +#### Linux(手动安装) + +**Ubuntu/Debian:** +```bash +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt-get install -y nodejs +``` + +**CentOS/RHEL/Fedora:** +```bash +curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - +sudo yum install -y nodejs +``` + +**Arch Linux:** +```bash +sudo pacman -S nodejs npm +``` + +**使用 nvm(推荐开发者):** +```bash +# 安装 nvm +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + +# 安装 Node.js 20 +nvm install 20 +nvm use 20 +``` + +#### Windows + +**方式一:官方安装包(推荐)** +1. 访问: https://nodejs.org/ +2. 下载 LTS 版本(推荐 v20.x) +3. 运行安装包,按向导完成安装 + +**方式二:nvm-windows(推荐开发者)** +1. 下载安装 nvm-windows: https://github.com/coreybutler/nvm-windows/releases +2. 执行: `nvm install 20` +3. 执行: `nvm use 20` + +**方式三:Winget(Windows 10/11)** +```powershell +winget install OpenJS.NodeJS.LTS +``` + +#### macOS + +**方式一:Homebrew(推荐)** +```bash +brew install node@ +``` + +**方式二:官方安装包** +1. 访问: https://nodejs.org/ +2. 下载 macOS 安装包并安装 + +**方式三:nvm(推荐开发者)** +```bash +# 安装 nvm +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + +# 安装 Node.js 20 +nvm install 20 +nvm use 20 +``` + --- ## 开发环境部署 @@ -111,9 +216,22 @@ proxy: { ## 生产环境部署 -### 后端部署 +### 方式一:一键部署脚本(推荐) -#### 1. 配置环境变量 +```bash +npm run deploy +``` + +按提示选择: +- 数据库类型:SQLite 或 MySQL +- 前端部署方式:Nginx(推荐)或 PM2 serve +- 是否自动安装 Nginx(Windows) + +### 方式二:手动部署 + +#### 后端部署 + +##### 1. 配置环境变量 ```bash cd backend @@ -132,13 +250,13 @@ MYSQL_PASSWORD=secure_password MYSQL_DATABASE=idc_management ``` -#### 2. 安装生产依赖 +##### 2. 安装生产依赖 ```bash npm install ``` -#### 3. 配置数据库 +##### 3. 配置数据库 ```sql CREATE DATABASE idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; @@ -147,7 +265,13 @@ GRANT ALL PRIVILEGES ON idc_management.* TO 'idc_user'@'localhost'; FLUSH PRIVILEGES; ``` -#### 4. 使用PM2管理进程 +##### 4. 初始化数据库 + +```bash +node scripts/init-database.js +``` + +##### 5. 使用PM2管理进程 ```bash npm install -g pm2 @@ -156,9 +280,9 @@ pm2 startup pm2 save ``` -### 前端部署 +#### 前端部署 -#### 1. 构建生产版本 +##### 1. 构建生产版本 ```bash cd frontend @@ -166,14 +290,21 @@ npm install npm run build ``` -#### 2. 部署静态文件 +##### 2. 部署静态文件 +**Nginx 方式:** ```bash sudo cp -r dist/* /var/www/idc-frontend/ sudo chown -R www-data:www-data /var/www/idc-frontend sudo chmod -R 755 /var/www/idc-frontend ``` +**PM2 serve 方式:** +```bash +npm install -g serve +pm2 start serve --name "idc-frontend" -- -s dist -l 3000 +``` + ### Nginx配置 #### 1. 创建配置文件 @@ -192,6 +323,18 @@ server { root /var/www/idc-frontend; index index.html; + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + location / { try_files $uri $uri/ /index.html; } @@ -202,6 +345,17 @@ server { 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_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + location /uploads { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + client_max_body_size 50M; } } ``` @@ -223,6 +377,39 @@ sudo certbot --nginx -d your-domain.com --- +## 服务管理 + +### PM2 管理命令 + +```bash +pm2 status # 查看服务状态 +pm2 logs idc-backend # 查看后端日志 +pm2 logs idc-frontend # 查看前端日志(PM2方式) +pm2 restart idc-backend # 重启后端服务 +pm2 stop idc-backend # 停止后端服务 +pm2 delete idc-backend # 删除后端服务 +pm2 save # 保存当前进程列表 +pm2 startup # 配置开机自启 +``` + +### Nginx 管理命令 + +**Linux:** +```bash +sudo nginx -t # 测试配置 +sudo systemctl restart nginx # 重启服务 +sudo systemctl status nginx # 查看状态 +``` + +**Windows:** +```cmd +nginx -t # 测试配置 +nginx -s reload # 重载配置 +nginx -s stop # 停止Nginx +``` + +--- + ## 数据库配置 ### SQLite(开发环境) @@ -285,6 +472,86 @@ crontab -e --- +## 更新升级 + +### 方式一:一键更新脚本(推荐) + +```bash +npm run update +``` + +功能:自动备份 → 拉取代码 → 更新依赖 → 重建前端 → 重启服务 + +### 方式二:手动更新 + +#### 1. 备份数据 + +```bash +mysqldump -u idc_user -p idc_management > backup.sql +``` + +#### 2. 拉取最新代码 + +**从GitHub拉取**: +```bash +git pull origin master +``` + +**从Gitee拉取**: +```bash +git pull origin master +``` + +#### 3. 更新依赖 + +```bash +cd backend && npm install +cd ../frontend && npm install && npm run build +``` + +#### 4. 重启服务 + +```bash +pm2 restart idc-backend +sudo systemctl restart nginx +``` + +--- + +## 监控维护 + +### 查看日志 + +```bash +pm2 logs idc-backend +tail -f /var/log/nginx/idc_assest-error.log +``` + +### 健康检查 + +```bash +curl http://localhost:8000/health +``` + +### 性能优化 + +**PM2配置**(`deploy/ecosystem.config.js`): +```javascript +module.exports = { + apps: [{ + name: 'idc-backend', + script: './server.js', + cwd: './backend', + instances: 1, + exec_mode: 'fork', + max_memory_restart: '1G', + autorestart: true + }] +}; +``` + +--- + ## 常见问题 ### 端口冲突 @@ -316,90 +583,46 @@ npm install - 验证连接参数 - 确认数据库已创建 -### PM2命令 +### 部署脚本问题 -```bash -pm2 status # 查看状态 -pm2 logs idc-backend # 查看日志 -pm2 restart idc-backend # 重启服务 -pm2 stop idc-backend # 停止服务 -pm2 delete idc-backend # 删除服务 -``` +**Linux 自动安装 Node.js 失败:** +- 检查 sudo 权限 +- 检查网络连接 +- 尝试手动安装后重新运行脚本 -### Nginx命令 - -```bash -sudo nginx -t # 测试配置 -sudo systemctl restart nginx # 重启服务 -sudo systemctl status nginx # 查看状态 -``` +**Nginx 未安装(选择 Nginx 部署时):** +- Windows:脚本会询问是否自动下载安装 +- Linux/macOS:脚本会显示安装命令,需手动安装 --- -## 更新升级 +## 项目结构 -### 1. 备份数据 - -```bash -mysqldump -u idc_user -p idc_management > backup.sql ``` - -### 2. 拉取最新代码 - -**从GitHub拉取**: -```bash -git pull origin master -``` - -**从Gitee拉取**: -```bash -git pull origin master -``` - -### 3. 更新依赖 - -```bash -cd backend && npm install -cd ../frontend && npm install && npm run build -``` - -### 4. 重启服务 - -```bash -pm2 restart idc-backend -sudo systemctl restart nginx -``` - ---- - -## 监控维护 - -### 查看日志 - -```bash -pm2 logs idc-backend -tail -f /var/log/nginx/idc_assest-error.log -``` - -### 健康检查 - -```bash -curl http://localhost:8000/health -``` - -### 性能优化 - -**PM2配置**(`ecosystem.config.js`): -```javascript -module.exports = { - apps: [{ - name: 'idc-backend', - script: 'server.js', - instances: 'max', - exec_mode: 'cluster', - max_memory_restart: '1G' - }] -}; +idc_assest/ +├── backend/ # 后端服务 +│ ├── middleware/ # 中间件 +│ ├── models/ # 数据模型 +│ ├── routes/ # API路由 +│ ├── scripts/ # 数据库脚本 +│ ├── uploads/ # 文件上传目录 +│ ├── server.js # 服务入口 +│ └── package.json +├── frontend/ # 前端应用 +│ ├── src/ +│ │ ├── api/ # API接口 +│ │ ├── components/ # 组件 +│ │ ├── pages/ # 页面 +│ │ └── utils/ # 工具函数 +│ ├── dist/ # 构建输出 +│ └── package.json +├── deploy/ # 部署配置 +│ ├── ecosystem.config.js # PM2配置 +│ └── nginx-idc.conf # Nginx配置 +├── install.js # 交互式安装脚本 ⭐ +├── update.js # 一键更新脚本 ⭐ +├── package.json +└── DEPLOYMENT.md # 本文件 ``` --- diff --git a/README.md b/README.md index 18d57a5..d164035 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,13 @@ jigui/ │ ├── routes/ # API路由 │ ├── middleware/ # 中间件 │ └── server.js # 服务入口 +├── deploy/ # 部署配置 ⭐ NEW +│ ├── ecosystem.config.js # PM2配置 +│ └── nginx-idc.conf # Nginx配置 ├── docs/ # 项目文档 │ └── api/ # 接口文档 +├── install.js # 交互式安装脚本 ⭐ NEW +├── update.js # 一键更新脚本 ⭐ NEW ├── README.md # 项目说明 ├── CHANGELOG.md # 版本记录 └── DEPLOYMENT.md # 部署指南 @@ -71,28 +76,99 @@ jigui/ ## 快速开始 -### 环境要求 +### 方式一:一键部署脚本(推荐)⭐ NEW -- Node.js ≥14.0.0 -- npm ≥6.0.0 或 pnpm ≥8.0.0 -- 操作系统:Windows 10/11、macOS、Linux - -### 安装步骤 +我们提供了交互式安装脚本,自动完成所有部署步骤: ```bash -# 1. 安装后端依赖 +# 克隆项目 +git clone https://github.com/gituib/idc_assest.git +cd idc_assest + +# 运行交互式安装脚本 +npm run deploy +# 或 +node install.js +``` + +**脚本功能:** +- ✅ 自动检测 Node.js、npm、PM2、Nginx +- ✅ **Linux 支持自动安装 Node.js**(交互式) +- ✅ 交互式配置数据库(SQLite/MySQL) +- ✅ 交互式选择运行环境(development/production) +- ✅ 交互式选择前端部署方式(Nginx/PM2 serve) +- ✅ 自动安装项目依赖 +- ✅ 自动初始化数据库 +- ✅ 自动构建前端项目 +- ✅ 使用 PM2 启动和管理服务 + +**交互配置示例:** +``` +▶ 环境检测 +✓ Node.js v20.11.0 +✓ npm 10.2.4 +✓ PM2 已安装 + +▶ 数据库配置 +选择数据库类型: + 1. SQLite(零配置,适合开发/小规模) + 2. MySQL(生产环境推荐) +请选择 (1): 1 + +▶ 服务配置 +后端服务端口 (8000): +选择运行环境: + 1. production(生产模式,性能优化,推荐正式部署) + 2. development(开发模式,详细日志,便于调试) +请选择 (1): 1 +前端部署方式: + 1. Nginx(性能最优,推荐生产环境) + 2. PM2 serve(简单快捷,无需额外安装) +请选择 (1): 1 + +▶ 配置确认 +部署配置摘要: + 数据库类型: sqlite + 后端端口: 8000 + 运行环境: production + 前端部署: nginx + 前端端口: 80 +确认以上配置并开始部署? (Y/n): Y + +✓ 后端环境变量文件已生成 (.env) +✓ PM2 配置文件已生成 (deploy/ecosystem.config.js) +✓ Nginx 配置文件已生成 (deploy/nginx-idc.conf) +✓ 安装部署完成! +``` + +### 方式二:手动安装 + +#### 环境要求 + +- Node.js ≥14.0.0(推荐 20.x LTS) +- npm ≥6.0.0 +- 操作系统:Windows 10/11、macOS、Linux + +#### 安装步骤 + +```bash +# 1. 克隆项目 +git clone https://github.com/gituib/idc_assest.git +cd idc_assest + +# 2. 安装后端依赖 cd backend npm install -# 2. 安装前端依赖 +# 3. 安装前端依赖 cd ../frontend npm install -# 3. 配置环境变量 +# 4. 配置环境变量 cd ../backend cp .env.example .env -# 4. 启动服务 +# 5. 启动服务 # 启动后端(端口8000) npm run dev @@ -105,6 +181,32 @@ npm run dev - 前端应用:http://localhost:3000 - 后端API:http://localhost:8000/api +## 更新升级 + +### 一键更新(推荐)⭐ NEW + +```bash +npm run update +``` + +功能:自动备份 → 拉取代码 → 更新依赖 → 重建前端 → 重启服务 + +### 手动更新 + +```bash +# 拉取最新代码 +git pull + +# 更新后端 +cd backend && npm install + +# 更新前端并构建 +cd ../frontend && npm install && npm run build + +# 重启服务 +pm2 restart idc-backend +``` + ## 主要功能 ### 机房管理 @@ -179,6 +281,22 @@ npm run dev - 配置SSL证书 - 建议使用PM2管理进程 +### 服务管理命令 + +```bash +# 查看服务状态 +pm2 status + +# 查看日志 +pm2 logs idc-backend + +# 重启服务 +pm2 restart idc-backend + +# 停止服务 +pm2 stop idc-backend +``` + ## 版本历史 完整版本记录请参考 [CHANGELOG.md](CHANGELOG.md)。 diff --git a/backend/initDeviceFields.js b/backend/initDeviceFields.js index c0bf88f..c10b676 100644 --- a/backend/initDeviceFields.js +++ b/backend/initDeviceFields.js @@ -185,15 +185,8 @@ const defaultDeviceFields = [ async function initDeviceFields() { try { console.log('开始初始化设备字段配置...'); - - // 连接数据库 - await sequelize.authenticate(); - console.log('数据库连接成功'); - - // 同步表结构 - await sequelize.sync(); - console.log('数据库表结构同步完成'); - + // 注意:数据库连接和表结构同步已在 server.js 中完成,这里直接初始化数据 + // 批量创建默认字段 for (const field of defaultDeviceFields) { // 检查字段是否已存在 diff --git a/backend/routes/systemSettings.js b/backend/routes/systemSettings.js index 0645fa0..0e9e098 100644 --- a/backend/routes/systemSettings.js +++ b/backend/routes/systemSettings.js @@ -17,7 +17,7 @@ const initDefaultSettings = async () => { { settingKey: 'max_login_attempts', settingValue: JSON.stringify(5), settingType: 'number', category: 'general', description: '最大登录尝试次数', isEditable: true }, { settingKey: 'maintenance_mode', settingValue: JSON.stringify(false), settingType: 'boolean', category: 'general', description: '维护模式', isEditable: true }, { settingKey: 'frontend_port', settingValue: JSON.stringify(3000), settingType: 'number', category: 'general', description: '前端服务端口(修改后需重启前端服务)', isEditable: true }, - + // 外观设置 { settingKey: 'primary_color', settingValue: JSON.stringify('#667eea'), settingType: 'string', category: 'appearance', description: '主题主色调', isEditable: true }, { settingKey: 'secondary_color', settingValue: JSON.stringify('#764ba2'), settingType: 'string', category: 'appearance', description: '主题辅助色调', isEditable: true }, @@ -25,7 +25,7 @@ const initDefaultSettings = async () => { { settingKey: 'sidebar_collapsed', settingValue: JSON.stringify(false), settingType: 'boolean', category: 'appearance', description: '侧边栏默认折叠', isEditable: true }, { settingKey: 'table_row_height', settingValue: JSON.stringify('default'), settingType: 'string', category: 'appearance', description: '表格行高: small/default/middle/large', isEditable: true }, { settingKey: 'animation_enabled', settingValue: JSON.stringify(true), settingType: 'boolean', category: 'appearance', description: '启用动画效果', isEditable: true }, - + // 关于页面 { settingKey: 'app_version', settingValue: JSON.stringify('1.0.0'), settingType: 'string', category: 'about', description: '应用版本', isEditable: false }, { settingKey: 'company_name', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '公司/组织名称', isEditable: true }, @@ -36,21 +36,34 @@ const initDefaultSettings = async () => { { settingKey: 'privacy_policy', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '隐私政策URL', isEditable: true }, { settingKey: 'terms_of_service', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '服务条款URL', isEditable: true }, ]; - + + let createdCount = 0; + let updatedCount = 0; + let errorCount = 0; + for (const setting of defaultSettings) { try { const existing = await SystemSetting.findByPk(setting.settingKey); if (!existing) { await SystemSetting.create(setting); + createdCount++; + } else if (existing.description !== setting.description) { + // 更新描述(用于修复英文描述问题) + await existing.update({ description: setting.description }); + updatedCount++; } } catch (error) { - console.error(`初始化设置 ${setting.settingKey} 失败:`, error); + console.error(`初始化设置 ${setting.settingKey} 失败:`, error.message); + errorCount++; } } + + console.log(`系统设置初始化结果: 创建 ${createdCount} 个, 更新 ${updatedCount} 个, 失败 ${errorCount} 个`); + return { createdCount, updatedCount, errorCount }; }; -// 初始化默认设置 -initDefaultSettings(); +// 注意:初始化现在由 server.js 统一调用,避免重复初始化 +// initDefaultSettings(); // 获取所有设置 router.get('/', async (req, res) => { diff --git a/backend/scripts/init-database.js b/backend/scripts/init-database.js index e450e47..0c2a4e3 100644 --- a/backend/scripts/init-database.js +++ b/backend/scripts/init-database.js @@ -1,6 +1,10 @@ /** * 数据库初始化脚本 * 用于生产环境首次部署时创建表结构 + * + * 注意:此脚本为独立运行脚本,与 server.js 的初始化逻辑重复 + * 如果 server.js 已能正常完成初始化,则无需单独运行此脚本 + * 如需单独运行:node backend/scripts/init-database.js */ const { sequelize } = require('../db'); diff --git a/backend/server.js b/backend/server.js index 0cddb89..63e31e3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -44,6 +44,15 @@ sequelize.authenticate() const SystemSetting = require('./models/SystemSetting'); return SystemSetting.sync(); }) + .then(() => { + // 初始化系统设置默认值(关键:确保部署时数据正确初始化) + console.log('开始初始化系统设置默认值...'); + const { initDefaultSettings } = require('./routes/systemSettings'); + return initDefaultSettings(); + }) + .then(() => { + console.log('系统设置初始化完成'); + }) .catch(err => console.error('数据库操作失败:', err)); // 导入路由 diff --git a/install.js b/install.js new file mode 100644 index 0000000..51f072d --- /dev/null +++ b/install.js @@ -0,0 +1,1385 @@ +#!/usr/bin/env node + +/** + * ============================================================================ + * IDC设备管理系统 - 交互式安装部署脚本 + * ============================================================================ + * + * 功能说明: + * - 交互式配置数据库(SQLite/MySQL) + * - 交互式选择前端部署方式(Nginx/PM2 serve) + * - 自动检测并可选安装 Nginx(Windows) + * - 自动安装项目依赖 + * - 自动初始化数据库 + * - 自动构建前端项目 + * - 使用 PM2 启动和管理后端服务 + * + * 使用方法: + * node install.js + * 或 + * npm run deploy + * + * 系统要求: + * - Node.js >= 14.0.0 + * - npm >= 6.0.0 + * - Windows/Linux/macOS + * + * 作者:自动生成 + * 日期:2026-02-05 + * ============================================================================ + */ + +// Node.js 内置模块引入 +const readline = require('readline'); // 读取命令行输入 +const { execSync } = require('child_process'); // 执行系统命令 +const fs = require('fs'); // 文件系统操作 +const path = require('path'); // 路径处理 + +// ============================================================================= +// 工具函数:颜色输出和日志 +// ============================================================================= + +/** + * ANSI 颜色代码配置 + * 用于在终端输出彩色文字,提升可读性 + */ +const colors = { + reset: '\x1b[0m', // 重置颜色 + bright: '\x1b[1m', // 高亮 + green: '\x1b[32m', // 绿色(成功) + yellow: '\x1b[33m', // 黄色(警告) + red: '\x1b[31m', // 红色(错误) + cyan: '\x1b[36m', // 青色(信息) + gray: '\x1b[90m' // 灰色(分隔线) +}; + +/** + * 日志输出工具对象 + * 提供统一格式的日志输出方法 + */ +const log = { + /** 信息日志 - 青色 */ + info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`), + + /** 成功日志 - 绿色带勾选 */ + success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`), + + /** 警告日志 - 黄色带警告符号 */ + warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`), + + /** 错误日志 - 红色带叉号 */ + error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + + /** 步骤日志 - 高亮青色带箭头 */ + step: (msg) => console.log(`\n${colors.bright}${colors.cyan}▶ ${msg}${colors.reset}`), + + /** 分隔线 - 灰色横线 */ + divider: () => console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`) +}; + +// ============================================================================= +// 配置存储对象 +// ============================================================================= + +/** + * 部署配置对象 + * 存储用户交互过程中设置的所有配置参数 + * + * @property {string} dbType - 数据库类型:'sqlite' 或 'mysql' + * @property {Object} dbConfig - MySQL 配置参数(当 dbType='mysql' 时使用) + * @property {number} backendPort - 后端服务监听端口 + * @property {string} frontendDeploy - 前端部署方式:'nginx' 或 'pm2' + * @property {number} frontendPort - 前端服务监听端口 + * @property {string} domain - 域名配置(Nginx 使用) + */ +/** + * 部署配置对象 + * 存储用户交互过程中设置的所有配置参数 + * + * @property {string} dbType - 数据库类型:'sqlite' 或 'mysql' + * @property {Object} dbConfig - MySQL 配置参数(当 dbType='mysql' 时使用) + * @property {number} backendPort - 后端服务监听端口 + * @property {string} nodeEnv - 运行环境:'development' 或 'production' + * @property {string} frontendDeploy - 前端部署方式:'nginx' 或 'pm2' + * @property {number} frontendPort - 前端服务监听端口 + * @property {string} domain - 域名配置(Nginx 使用) + */ +const config = { + dbType: 'sqlite', // 默认使用 SQLite + dbConfig: {}, // MySQL 配置初始为空 + backendPort: 8000, // 默认后端端口 + nodeEnv: 'production', // 默认运行环境 + frontendDeploy: 'nginx', // 默认使用 Nginx + frontendPort: 80, // 默认 Nginx 端口 + domain: 'localhost' // 默认域名 +}; + +// ============================================================================= +// 交互式输入函数 +// ============================================================================= + +/** + * 创建 readline 接口实例 + * 用于在命令行中读取用户输入 + */ +const rl = readline.createInterface({ + input: process.stdin, // 标准输入(键盘) + output: process.stdout // 标准输出(屏幕) +}); + +/** + * 提问函数 - 获取用户输入 + * + * @param {string} question - 提示问题文本 + * @param {string} defaultValue - 默认值(用户直接回车时使用) + * @returns {Promise} 用户输入的内容 + * + * 使用示例: + * const port = await ask('请输入端口', '8000'); + */ +function ask(question, defaultValue = '') { + return new Promise((resolve) => { + // 如果有默认值,在提示中显示 + const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `; + rl.question(prompt, (answer) => { + // 如果用户未输入,使用默认值 + resolve(answer.trim() || defaultValue); + }); + }); +} + +/** + * 选择函数 - 显示选项列表并获取用户选择 + * + * @param {string} question - 提示问题文本 + * @param {Array<{label: string, value: any}>} options - 选项数组 + * @returns {Promise} 用户选择的值 + * + * 使用示例: + * const dbType = await select('选择数据库:', [ + * { label: 'SQLite', value: 'sqlite' }, + * { label: 'MySQL', value: 'mysql' } + * ]); + */ +async function select(question, options) { + // 显示问题和选项列表 + console.log(`\n${question}`); + options.forEach((opt, idx) => { + console.log(` ${colors.cyan}${idx + 1}.${colors.reset} ${opt.label}`); + }); + + // 获取用户输入并转换为索引 + const answer = await ask('请选择', '1'); + const index = parseInt(answer) - 1; + + // 返回选中的值,如果无效则返回第一个选项 + return options[index]?.value || options[0].value; +} + +// ============================================================================= +// 系统命令执行函数 +// ============================================================================= + +/** + * 执行系统命令 + * + * @param {string} command - 要执行的命令 + * @param {Object} options - 执行选项 + * @param {boolean} options.silent - 是否静默执行(不显示输出) + * @param {string} options.cwd - 工作目录 + * @returns {Object} 执行结果 { success: boolean, output?: string, error?: string } + * + * 使用示例: + * const result = runCommand('npm install', { cwd: './backend' }); + * if (result.success) console.log('安装成功'); + */ +function runCommand(command, options = {}) { + try { + // 使用 execSync 同步执行命令 + const result = execSync(command, { + encoding: 'utf8', // 输出编码为 UTF-8 + stdio: options.silent ? 'pipe' : 'inherit', // 是否显示命令输出 + cwd: options.cwd || process.cwd(), // 设置工作目录 + shell: true // 使用系统 shell 执行 + }); + return { success: true, output: result }; + } catch (error) { + // 命令执行失败时返回错误信息 + return { success: false, error: error.message }; + } +} + +/** + * 检查命令是否存在于系统中 + * + * @param {string} command - 命令名称 + * @returns {boolean} 是否存在 + * + * 使用示例: + * if (commandExists('pm2')) console.log('PM2 已安装'); + */ +function commandExists(command) { + try { + // 尝试执行命令的 --version 参数 + execSync(`${command} --version`, { stdio: 'pipe', shell: true }); + return true; + } catch { + return false; + } +} + +// ============================================================================= +// 环境检测函数 +// ============================================================================= + +/** + * 检查 Node.js 版本是否满足要求 + * + * @returns {boolean} 版本是否 >= 14.0.0 + */ +function checkNodeVersion() { + const version = process.version; // 获取当前 Node 版本,如 "v20.10.0" + const major = parseInt(version.slice(1).split('.')[0]); // 提取主版本号 + return major >= 14; +} + +/** + * 检测 Nginx 是否已安装 + * + * 检测逻辑: + * 1. 尝试执行 nginx -v 命令 + * 2. Windows 系统额外检查常见安装路径 + * + * @returns {boolean} 是否已安装 + */ +function isNginxInstalled() { + try { + // 方法1:尝试执行 nginx 命令 + execSync('nginx -v', { stdio: 'pipe', shell: true }); + return true; + } catch { + // 方法2:Windows 下检查常见安装路径 + if (process.platform === 'win32') { + const commonPaths = [ + 'C:\\nginx\\nginx.exe', + 'C:\\Program Files\\nginx\\nginx.exe', + 'C:\\Program Files (x86)\\nginx\\nginx.exe' + ]; + for (const p of commonPaths) { + if (fs.existsSync(p)) return true; + } + } + return false; + } +} + +/** + * 检查 Node.js 和 npm 是否已安装 + * + * @returns {Object} 检查结果 { nodeInstalled: boolean, npmInstalled: boolean, version?: string } + */ +function checkNodeAndNpm() { + let nodeInstalled = false; + let npmInstalled = false; + let version = ''; + + try { + // 检查 Node.js + version = process.version; + nodeInstalled = true; + } catch { + nodeInstalled = false; + } + + // 检查 npm + npmInstalled = commandExists('npm'); + + return { nodeInstalled, npmInstalled, version }; +} + +/** + * 检测 Linux 发行版类型 + * + * @returns {string} 发行版类型:'ubuntu' | 'debian' | 'centos' | 'rhel' | 'fedora' | 'arch' | 'unknown' + */ +function detectLinuxDistro() { + try { + // 尝试读取 /etc/os-release 文件 + const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); + + if (osRelease.includes('Ubuntu')) return 'ubuntu'; + if (osRelease.includes('Debian')) return 'debian'; + if (osRelease.includes('CentOS')) return 'centos'; + if (osRelease.includes('Red Hat')) return 'rhel'; + if (osRelease.includes('Fedora')) return 'fedora'; + if (osRelease.includes('Arch')) return 'arch'; + + return 'unknown'; + } catch { + return 'unknown'; + } +} + +/** + * 显示 Node.js 安装指引 + * + * 以 Linux 为主,同时支持 Windows 和 macOS + * 根据操作系统显示对应的安装方式和下载链接 + */ +function showNodeInstallGuide() { + const platform = process.platform; + const isWindows = platform === 'win32'; + const isMac = platform === 'darwin'; + const isLinux = platform === 'linux'; + + console.log('\n' + colors.bright + 'Node.js 安装指引:' + colors.reset); + + if (isLinux) { + // Linux 为主,优先显示 + const distro = detectLinuxDistro(); + + console.log('\n' + colors.green + '【Linux 推荐安装方式】' + colors.reset); + + // 根据检测到的发行版显示对应命令 + switch (distro) { + case 'ubuntu': + case 'debian': + console.log('\n' + colors.yellow + `Ubuntu/Debian (${distro}):` + colors.reset); + console.log(` ${colors.cyan}# 使用 NodeSource 源安装 Node.js 20.x` + colors.reset); + console.log(` ${colors.cyan}curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -${colors.reset}`); + console.log(` ${colors.cyan}sudo apt-get install -y nodejs${colors.reset}`); + break; + + case 'centos': + case 'rhel': + case 'fedora': + console.log('\n' + colors.yellow + `CentOS/RHEL/Fedora (${distro}):` + colors.reset); + console.log(` ${colors.cyan}# 使用 NodeSource 源安装 Node.js 20.x` + colors.reset); + console.log(` ${colors.cyan}curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -${colors.reset}`); + console.log(` ${colors.cyan}sudo yum install -y nodejs${colors.reset}`); + break; + + case 'arch': + console.log('\n' + colors.yellow + 'Arch Linux:' + colors.reset); + console.log(` ${colors.cyan}sudo pacman -S nodejs npm${colors.reset}`); + break; + + default: + console.log('\n' + colors.yellow + '通用 Linux 安装方式:' + colors.reset); + console.log(` ${colors.cyan}# Ubuntu/Debian${colors.reset}`); + console.log(` curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -`); + console.log(` sudo apt-get install -y nodejs`); + console.log(`\n ${colors.cyan}# CentOS/RHEL/Fedora${colors.reset}`); + console.log(` curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -`); + console.log(` sudo yum install -y nodejs`); + } + + // 所有 Linux 都显示 nvm 方式 + console.log('\n' + colors.yellow + '【开发者推荐】使用 nvm(Node Version Manager):' + colors.reset); + console.log(` ${colors.cyan}# 1. 安装 nvm${colors.reset}`); + console.log(` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`); + console.log(` ${colors.cyan}# 2. 加载 nvm(或重新打开终端)${colors.reset}`); + console.log(` export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`); + console.log(` ${colors.cyan}# 3. 安装并使用 Node.js 20${colors.reset}`); + console.log(` nvm install 20`); + console.log(` nvm use 20`); + console.log(` ${colors.cyan}# 4. 验证安装${colors.reset}`); + console.log(` node -v && npm -v`); + + // Docker 方式 + console.log('\n' + colors.yellow + '【Docker 方式】' + colors.reset); + console.log(` ${colors.cyan}# 使用官方 Node 镜像${colors.reset}`); + console.log(` docker run -it --rm node:20-alpine node -v`); + + } else if (isWindows) { + // Windows + console.log('\n' + colors.green + '【Windows 安装方式】' + colors.reset); + + console.log('\n' + colors.yellow + '方式一:使用官方安装包(推荐新手)' + colors.reset); + console.log(` 1. 访问: ${colors.cyan}https://nodejs.org/${colors.reset}`); + console.log(` 2. 下载 LTS 版本(推荐 ${colors.cyan}v20.x${colors.reset})`); + console.log(` 3. 运行安装包,按向导完成安装`); + console.log(` 4. 安装完成后,${colors.yellow}重新打开终端${colors.reset}并再次运行此脚本`); + + console.log('\n' + colors.yellow + '方式二:使用 nvm-windows(推荐开发者)' + colors.reset); + console.log(` 1. 下载安装 nvm-windows: ${colors.cyan}https://github.com/coreybutler/nvm-windows/releases${colors.reset}`); + console.log(` 2. 打开新的 PowerShell 或 CMD 窗口`); + console.log(` 3. 执行: ${colors.cyan}nvm install 20${colors.reset}`); + console.log(` 4. 执行: ${colors.cyan}nvm use 20${colors.reset}`); + console.log(` 5. 验证: ${colors.cyan}node -v${colors.reset}`); + + console.log('\n' + colors.yellow + '方式三:使用 Winget(Windows 10/11 自带)' + colors.reset); + console.log(` ${colors.cyan}winget install OpenJS.NodeJS.LTS${colors.reset}`); + + } else if (isMac) { + // macOS + console.log('\n' + colors.green + '【macOS 安装方式】' + colors.reset); + + console.log('\n' + colors.yellow + '方式一:使用 Homebrew(推荐)' + colors.reset); + console.log(` ${colors.cyan}brew install node@${colors.reset}`); + + console.log('\n' + colors.yellow + '方式二:使用官方安装包' + colors.reset); + console.log(` 1. 访问: ${colors.cyan}https://nodejs.org/${colors.reset}`); + console.log(` 2. 下载 macOS 安装包(.pkg)`); + console.log(` 3. 双击安装包按向导完成安装`); + + console.log('\n' + colors.yellow + '方式三:使用 nvm(推荐开发者)' + colors.reset); + console.log(` ${colors.cyan}# 1. 安装 nvm${colors.reset}`); + console.log(` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`); + console.log(` ${colors.cyan}# 2. 重新加载 shell 配置${colors.reset}`); + console.log(` source ~/.bashrc # 或 source ~/.zshrc`); + console.log(` ${colors.cyan}# 3. 安装 Node.js 20${colors.reset}`); + console.log(` nvm install 20`); + console.log(` nvm use 20`); + } + + // 通用验证命令 + console.log('\n' + colors.green + '【验证安装】' + colors.reset); + console.log(` 安装完成后,运行以下命令验证:`); + console.log(` ${colors.cyan}node -v${colors.reset} # 应显示 v20.x.x 或更高版本`); + console.log(` ${colors.cyan}npm -v${colors.reset} # 应显示 10.x.x 或更高版本`); + + console.log('\n' + colors.gray + '安装完成后,请重新运行此部署脚本。' + colors.reset); +} + +/** + * Linux 系统自动安装 Node.js + * + * 根据检测到的发行版,使用对应的方式自动安装 Node.js 20.x + * + * @returns {Promise} 安装是否成功 + */ +async function autoInstallNodeLinux() { + const distro = detectLinuxDistro(); + + log.step('自动安装 Node.js'); + log.info(`检测到 Linux 发行版: ${distro}`); + + try { + switch (distro) { + case 'ubuntu': + case 'debian': + log.info('使用 NodeSource 源安装 Node.js 20.x...'); + log.info('执行: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -'); + const debianSetup = runCommand('curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -'); + if (!debianSetup.success) { + log.error('NodeSource 源配置失败'); + return false; + } + + log.info('执行: sudo apt-get install -y nodejs'); + const debianInstall = runCommand('sudo apt-get install -y nodejs'); + if (!debianInstall.success) { + log.error('Node.js 安装失败'); + return false; + } + break; + + case 'centos': + case 'rhel': + case 'fedora': + log.info('使用 NodeSource 源安装 Node.js 20.x...'); + log.info('执行: curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -'); + const rpmSetup = runCommand('curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -'); + if (!rpmSetup.success) { + log.error('NodeSource 源配置失败'); + return false; + } + + log.info('执行: sudo yum install -y nodejs'); + const rpmInstall = runCommand('sudo yum install -y nodejs'); + if (!rpmInstall.success) { + log.error('Node.js 安装失败'); + return false; + } + break; + + case 'arch': + log.info('执行: sudo pacman -S nodejs npm'); + const archInstall = runCommand('sudo pacman -S --noconfirm nodejs npm'); + if (!archInstall.success) { + log.error('Node.js 安装失败'); + return false; + } + break; + + default: + // 未知发行版,尝试使用 nvm 安装 + log.info('未知发行版,尝试使用 nvm 安装...'); + return await installNodeViaNvm(); + } + + // 验证安装 + const checkResult = runCommand('node -v', { silent: true }); + if (checkResult.success) { + log.success(`Node.js 安装成功: ${checkResult.output.trim()}`); + const npmCheck = runCommand('npm -v', { silent: true }); + if (npmCheck.success) { + log.success(`npm 安装成功: ${npmCheck.output.trim()}`); + } + return true; + } else { + log.error('Node.js 安装后验证失败'); + return false; + } + + } catch (error) { + log.error(`自动安装失败: ${error.message}`); + return false; + } +} + +/** + * 使用 nvm 安装 Node.js + * + * 适用于所有 Linux 发行版,作为通用安装方式 + * + * @returns {Promise} 安装是否成功 + */ +async function installNodeViaNvm() { + log.info('使用 nvm 安装 Node.js...'); + + try { + // 1. 检查 nvm 是否已安装 + const nvmCheck = runCommand('source ~/.nvm/nvm.sh && nvm --version', { silent: true }); + + if (!nvmCheck.success) { + // 安装 nvm + log.info('安装 nvm...'); + const nvmInstall = runCommand('curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash'); + if (!nvmInstall.success) { + log.error('nvm 安装失败'); + return false; + } + } + + // 2. 使用 nvm 安装 Node.js 20 + log.info('使用 nvm 安装 Node.js 20...'); + const nodeInstall = runCommand('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm install 20'); + if (!nodeInstall.success) { + log.error('Node.js 安装失败'); + return false; + } + + // 3. 设置默认版本 + log.info('设置 Node.js 20 为默认版本...'); + runCommand('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm alias default 20', { silent: true }); + + log.success('Node.js 安装成功'); + return true; + + } catch (error) { + log.error(`nvm 安装失败: ${error.message}`); + return false; + } +} + +/** + * 处理 Node.js 未安装的情况(Linux 交互式) + * + * @returns {Promise} 是否继续部署 + */ +async function handleNodeNotInstalledLinux() { + log.error('未检测到 Node.js'); + + console.log('\n' + colors.yellow + '您可以选择:' + colors.reset); + console.log(` ${colors.cyan}1.${colors.reset} 自动安装 Node.js 20.x(推荐,需要 sudo 权限)`); + console.log(` ${colors.cyan}2.${colors.reset} 显示手动安装指引,退出脚本`); + + const choice = await ask('请选择', '1'); + + if (choice === '1') { + const installed = await autoInstallNodeLinux(); + if (installed) { + log.success('Node.js 和 npm 安装完成!'); + log.info('请重新运行此脚本以继续部署'); + console.log(colors.cyan + ' node install.js' + colors.reset); + + // 询问是否立即重新运行 + const rerun = await ask('\n是否立即重新运行部署脚本? (Y/n)', 'Y'); + if (rerun.toLowerCase() === 'y') { + // 重新检查环境 + return true; + } + } else { + log.error('自动安装失败,请尝试手动安装'); + showNodeInstallGuide(); + } + process.exit(1); + } else { + showNodeInstallGuide(); + process.exit(1); + } +} + +/** + * 环境检测主函数 + * + * 检测内容: + * - Node.js 是否安装及版本(要求 >= 14.0.0) + * - npm 是否安装 + * - PM2 是否安装(未安装则自动安装) + * - Nginx 是否安装(可选) + * + * Linux 系统支持自动安装 Node.js + */ +async function checkEnvironment() { + log.step('环境检测'); + + // 1. 检测 Node.js 和 npm + const { nodeInstalled, npmInstalled, version } = checkNodeAndNpm(); + const isLinux = process.platform === 'linux'; + + if (!nodeInstalled) { + if (isLinux) { + // Linux 系统:交互式询问是否自动安装 + const shouldContinue = await handleNodeNotInstalledLinux(); + if (shouldContinue) { + // 重新检测 + return await checkEnvironment(); + } + } else { + // 非 Linux 系统:显示安装指引 + log.error('未检测到 Node.js,请先安装'); + showNodeInstallGuide(); + process.exit(1); + } + } + + if (!npmInstalled) { + if (isLinux && nodeInstalled) { + // Linux 下 npm 未安装但 node 已安装,尝试重新安装 node + log.warning('未检测到 npm,尝试修复...'); + const fixed = await autoInstallNodeLinux(); + if (!fixed) { + log.error('npm 修复失败,请手动安装'); + showNodeInstallGuide(); + process.exit(1); + } + } else { + log.error('未检测到 npm,请重新安装 Node.js(npm 会随 Node.js 一起安装)'); + showNodeInstallGuide(); + process.exit(1); + } + } + + // 2. 检查 Node.js 版本 + if (checkNodeVersion()) { + log.success(`Node.js ${version}`); + } else { + log.error(`Node.js 版本过低 (${version}),需要 >= 14.0.0`); + + if (isLinux) { + console.log('\n' + colors.yellow + '您可以选择:' + colors.reset); + console.log(` ${colors.cyan}1.${colors.reset} 自动升级到 Node.js 20.x`); + console.log(` ${colors.cyan}2.${colors.reset} 显示手动升级指引,退出脚本`); + + const upgradeChoice = await ask('请选择', '1'); + if (upgradeChoice === '1') { + const upgraded = await autoInstallNodeLinux(); + if (upgraded) { + log.success('Node.js 升级完成!请重新运行此脚本'); + } else { + log.error('自动升级失败'); + showNodeInstallGuide(); + } + } else { + console.log('\n' + colors.yellow + '建议升级到 Node.js 20 LTS 版本:' + colors.reset); + showNodeInstallGuide(); + } + } else { + console.log('\n' + colors.yellow + '建议升级到 Node.js 20 LTS 版本:' + colors.reset); + showNodeInstallGuide(); + } + process.exit(1); + } + + // 3. 检测 npm 版本 + const npmResult = runCommand('npm --version', { silent: true }); + if (npmResult.success) { + log.success(`npm ${npmResult.output.trim()}`); + } + + // 4. 检测 PM2(进程管理器) + if (commandExists('pm2')) { + log.success('PM2 已安装'); + } else { + log.warning('未检测到 PM2,将自动安装'); + const result = runCommand('npm install -g pm2'); + if (result.success) { + log.success('PM2 安装完成'); + } else { + log.error('PM2 安装失败,请手动执行: npm install -g pm2'); + } + } + + // 5. 检测 Nginx(可选,仅在使用 Nginx 部署时需要) + if (commandExists('nginx') || commandExists('nginx.exe')) { + log.success('Nginx 已安装'); + } else { + log.warning('未检测到 Nginx(可选,仅在使用Nginx部署时需要)'); + } + + log.divider(); +} + +// ============================================================================= +// 配置函数 +// ============================================================================= + +/** + * 数据库配置函数 + * + * 交互流程: + * 1. 选择数据库类型(SQLite / MySQL) + * 2. 如选择 MySQL,提示输入连接参数 + */ +async function configureDatabase() { + log.step('数据库配置'); + + // 选择数据库类型 + config.dbType = await select('选择数据库类型:', [ + { label: 'SQLite(零配置,适合开发/小规模)', value: 'sqlite' }, + { label: 'MySQL(生产环境推荐)', value: 'mysql' } + ]); + + // MySQL 配置 + if (config.dbType === 'mysql') { + console.log('\n' + colors.yellow + '请确保MySQL服务已启动并创建了数据库' + colors.reset); + console.log(colors.gray + '创建数据库命令: CREATE DATABASE idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;' + colors.reset + '\n'); + + // 依次询问 MySQL 连接参数 + config.dbConfig.host = await ask('MySQL 主机地址', 'localhost'); + config.dbConfig.port = await ask('MySQL 端口', '3306'); + config.dbConfig.username = await ask('MySQL 用户名', 'root'); + config.dbConfig.password = await ask('MySQL 密码', ''); + config.dbConfig.database = await ask('数据库名称', 'idc_management'); + } + + log.divider(); +} + +/** + * 服务配置函数 + * + * 交互流程: + * 1. 配置后端端口 + * 2. 选择运行环境(development / production) + * 3. 选择前端部署方式(Nginx / PM2 serve) + * 4. 如选择 Nginx 但未安装,提供安装选项 + * 5. 配置前端端口和域名 + */ +async function configureServices() { + log.step('服务配置'); + + // 1. 后端端口配置 + config.backendPort = await ask('后端服务端口', '8000'); + + // 2. 运行环境选择 + config.nodeEnv = await select('选择运行环境:', [ + { label: 'production(生产模式,性能优化,推荐正式部署)', value: 'production' }, + { label: 'development(开发模式,详细日志,便于调试)', value: 'development' } + ]); + + // 3. 前端部署方式选择 + config.frontendDeploy = await select('前端部署方式:', [ + { label: 'Nginx(性能最优,推荐生产环境)', value: 'nginx' }, + { label: 'PM2 serve(简单快捷,无需额外安装)', value: 'pm2' } + ]); + + // 3. 如果选择 Nginx,检查是否已安装 + if (config.frontendDeploy === 'nginx') { + const nginxInstalled = isNginxInstalled(); + + if (!nginxInstalled) { + log.warning('未检测到 Nginx 安装'); + + if (process.platform === 'win32') { + // Windows 下询问是否自动安装 + const autoInstall = await ask('是否自动下载并安装 Nginx? (Y/n)', 'Y'); + if (autoInstall.toLowerCase() === 'y') { + await autoInstallNginx(); + } else { + log.info('已跳过自动安装,请手动安装 Nginx 后启动服务'); + console.log(` 下载地址: ${colors.cyan}https://nginx.org/en/download.html${colors.reset}`); + console.log(` 安装路径建议: ${colors.cyan}C:/nginx${colors.reset}`); + } + } else { + // Linux/Mac 下提供安装命令 + log.info('请根据您的系统手动安装 Nginx:'); + console.log(` ${colors.cyan}Ubuntu/Debian:${colors.reset} sudo apt update && sudo apt install nginx`); + console.log(` ${colors.cyan}CentOS/RHEL:${colors.reset} sudo yum install nginx`); + console.log(` ${colors.cyan}Mac:${colors.reset} brew install nginx`); + console.log(`\n安装完成后,请重新运行此脚本继续部署。\n`); + + const continueDeploy = await ask('是否继续部署(后端将启动,前端需手动配置 Nginx)? (Y/n)', 'Y'); + if (continueDeploy.toLowerCase() !== 'y') { + log.info('已取消部署,请安装 Nginx 后重新运行'); + process.exit(0); + } + } + } + + // Nginx 端口和域名配置 + config.frontendPort = await ask('Nginx 监听端口', '80'); + config.domain = await ask('域名(没有则填localhost)', 'localhost'); + } else { + // PM2 serve 端口配置 + config.frontendPort = await ask('前端服务端口', '3000'); + } + + log.divider(); +} + +/** + * 配置确认函数 + * + * 显示所有配置参数,等待用户确认后开始部署 + */ +async function confirmConfiguration() { + log.step('配置确认'); + + // 显示配置摘要 + console.log('\n' + colors.bright + '部署配置摘要:' + colors.reset); + console.log(` 数据库类型: ${colors.cyan}${config.dbType}${colors.reset}`); + if (config.dbType === 'mysql') { + console.log(` MySQL主机: ${colors.cyan}${config.dbConfig.host}:${config.dbConfig.port}${colors.reset}`); + console.log(` 数据库名: ${colors.cyan}${config.dbConfig.database}${colors.reset}`); + console.log(` 用户名: ${colors.cyan}${config.dbConfig.username}${colors.reset}`); + } + console.log(` 后端端口: ${colors.cyan}${config.backendPort}${colors.reset}`); + console.log(` 运行环境: ${colors.cyan}${config.nodeEnv}${colors.reset}`); + console.log(` 前端部署: ${colors.cyan}${config.frontendDeploy}${colors.reset}`); + console.log(` 前端端口: ${colors.cyan}${config.frontendPort}${colors.reset}`); + if (config.frontendDeploy === 'nginx') { + console.log(` 域名: ${colors.cyan}${config.domain}${colors.reset}`); + } + + // 等待用户确认 + const confirm = await ask('\n确认以上配置并开始部署? (Y/n)', 'Y'); + if (confirm.toLowerCase() !== 'y') { + log.info('已取消部署'); + process.exit(0); + } + + log.divider(); +} + +// ============================================================================= +// Nginx 自动安装函数(仅 Windows) +// ============================================================================= + +/** + * Windows 系统自动下载并安装 Nginx + * + * 安装流程: + * 1. 从 nginx.org 下载稳定版压缩包 + * 2. 解压到 C:\nginx + * 3. 输出后续配置指引 + */ +async function autoInstallNginx() { + log.step('自动安装 Nginx'); + log.info('正在下载 Nginx...'); + + // 引入所需模块 + const https = require('https'); + + // 配置参数 + const nginxUrl = 'https://nginx.org/download/nginx-1.24.0.zip'; // Nginx 稳定版下载地址 + const downloadPath = path.join(__dirname, 'nginx-1.24.0.zip'); // 临时下载路径 + const installPath = 'C:\\nginx'; // 安装路径 + + try { + // 1. 下载 Nginx 压缩包 + await new Promise((resolve, reject) => { + const file = fs.createWriteStream(downloadPath); + https.get(nginxUrl, (response) => { + response.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }).on('error', reject); + }); + log.success('Nginx 下载完成'); + + // 2. 解压到 C:\ + log.info('正在解压...'); + execSync(`powershell -Command "Expand-Archive -Path '${downloadPath}' -DestinationPath 'C:\\\\' -Force"`, { stdio: 'inherit' }); + + // 3. 重命名文件夹为 nginx + if (fs.existsSync('C:\\nginx-1.24.0')) { + if (fs.existsSync(installPath)) { + fs.rmSync(installPath, { recursive: true }); // 删除旧版本 + } + fs.renameSync('C:\\nginx-1.24.0', installPath); + } + + // 4. 清理临时文件 + fs.unlinkSync(downloadPath); + + // 5. 输出安装成功信息和后续步骤 + log.success(`Nginx 已安装到 ${installPath}`); + log.info('安装后操作:'); + console.log(` 1. 将配置文件复制到: ${colors.cyan}C:/nginx/conf/conf.d/idc.conf${colors.reset}`); + console.log(` 2. 修改主配置: 在 C:/nginx/conf/nginx.conf 的 http 段添加: ${colors.cyan}include conf.d/*.conf;${colors.reset}`); + console.log(` 3. 启动 Nginx: ${colors.cyan}C:/nginx/nginx.exe${colors.reset}`); + + } catch (error) { + log.error('Nginx 自动安装失败: ' + error.message); + log.info('请手动下载安装: https://nginx.org/en/download.html'); + throw error; + } +} + +// ============================================================================= +// 配置文件生成函数 +// ============================================================================= + +/** + * 生成后端环境变量文件 (.env) + * + * 根据用户选择的配置生成后端所需的 .env 文件 + */ +function generateBackendEnv() { + const envPath = path.join(__dirname, 'backend', '.env'); + + // 构建环境变量内容 + let envContent = `# IDC设备管理系统 - 环境配置 +# 生成时间: ${new Date().toISOString()} + +# ============================================== +# 服务器配置 +# ============================================== + +# 服务器端口 +PORT=${config.backendPort} + +# 运行环境: development 或 production +NODE_ENV=${config.nodeEnv} + +# ============================================== +# 数据库配置 +# ============================================== + +# 数据库类型: sqlite 或 mysql +DB_TYPE=${config.dbType} +`; + + // 根据数据库类型添加相应配置 + if (config.dbType === 'sqlite') { + envContent += ` +# SQLite 配置 +DB_PATH=./idc_management.db +`; + } else { + envContent += ` +# MySQL 配置 +MYSQL_HOST=${config.dbConfig.host} +MYSQL_PORT=${config.dbConfig.port} +MYSQL_USERNAME=${config.dbConfig.username} +MYSQL_PASSWORD=${config.dbConfig.password} +MYSQL_DATABASE=${config.dbConfig.database} +`; + } + + // 写入文件 + fs.writeFileSync(envPath, envContent); + log.success('后端环境变量文件已生成 (.env)'); +} + +/** + * 生成 PM2 配置文件 + * + * 生成 ecosystem.config.js 用于 PM2 进程管理 + */ +function generatePM2Config() { + // 创建 deploy 目录 + const deployDir = path.join(__dirname, 'deploy'); + if (!fs.existsSync(deployDir)) { + fs.mkdirSync(deployDir, { recursive: true }); + } + + // 构建 PM2 配置对象 + const pm2Config = { + apps: [{ + name: 'idc-backend', // 应用名称 + script: './server.js', // 启动脚本 + cwd: path.join(__dirname, 'backend'), // 工作目录 + instances: 1, // 实例数 + exec_mode: 'fork', // 执行模式 + env: { + NODE_ENV: 'production', + PORT: config.backendPort + }, + max_memory_restart: '1G', // 内存限制 + log_file: path.join(__dirname, 'backend', 'logs', 'combined.log'), + out_file: path.join(__dirname, 'backend', 'logs', 'out.log'), + error_file: path.join(__dirname, 'backend', 'logs', 'error.log'), + log_date_format: 'YYYY-MM-DD HH:mm:ss Z', + merge_logs: true, + autorestart: true // 自动重启 + }] + }; + + // 如果使用 PM2 serve 部署前端,添加前端应用配置 + if (config.frontendDeploy === 'pm2') { + pm2Config.apps.push({ + name: 'idc-frontend', + script: 'serve', + cwd: path.join(__dirname, 'frontend'), + args: `-s dist -l ${config.frontendPort}`, // -s: 静态文件目录, -l: 监听端口 + instances: 1, + exec_mode: 'fork', + env: { NODE_ENV: 'production' }, + max_memory_restart: '500M', + autorestart: true + }); + } + + // 写入配置文件 + fs.writeFileSync( + path.join(deployDir, 'ecosystem.config.js'), + `module.exports = ${JSON.stringify(pm2Config, null, 2)};` + ); + log.success('PM2 配置文件已生成 (deploy/ecosystem.config.js)'); +} + +/** + * 生成 Nginx 配置文件 + * + * 生成 nginx-idc.conf 用于 Nginx 反向代理和静态文件服务 + */ +function generateNginxConfig() { + if (config.frontendDeploy !== 'nginx') return; + + // 创建 deploy 目录 + const deployDir = path.join(__dirname, 'deploy'); + if (!fs.existsSync(deployDir)) { + fs.mkdirSync(deployDir, { recursive: true }); + } + + const isWindows = process.platform === 'win32'; + // 转换路径分隔符(Windows 反斜杠转斜杠) + const frontendPath = path.join(__dirname, 'frontend', 'dist').replace(/\\/g, '/'); + + // 构建 Nginx 配置内容 + const nginxConfig = `# IDC设备管理系统 - Nginx配置 +# 生成时间: ${new Date().toISOString()} + +server { + listen ${config.frontendPort}; + server_name ${config.domain}; + + # 前端静态文件目录 + root "${frontendPath}"; + index index.html; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # 静态资源缓存(1年) + location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # 前端路由支持(SPA单页应用) + location / { + try_files $uri $uri/ /index.html; + } + + # API代理到后端 + location /api { + proxy_pass http://127.0.0.1:${config.backendPort}; + proxy_http_version 1.1; + 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_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 文件上传代理 + location /uploads { + proxy_pass http://127.0.0.1:${config.backendPort}; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + client_max_body_size 50M; # 最大上传文件大小 + } +} +`; + + // 写入配置文件 + fs.writeFileSync(path.join(deployDir, 'nginx-idc.conf'), nginxConfig); + log.success('Nginx 配置文件已生成 (deploy/nginx-idc.conf)'); + + // 输出平台特定的配置指引 + if (isWindows) { + log.info('Windows Nginx 配置路径示例:'); + console.log(` 将配置文件复制到: ${colors.cyan}C:/nginx/conf/conf.d/idc.conf${colors.reset}`); + console.log(` 或修改主配置 include: ${colors.cyan}C:/nginx/conf/nginx.conf${colors.reset}`); + } else { + console.log(`\n${colors.yellow}Nginx 配置启用命令:${colors.reset}`); + console.log(` sudo cp deploy/nginx-idc.conf /etc/nginx/sites-available/idc`); + console.log(` sudo ln -s /etc/nginx/sites-available/idc /etc/nginx/sites-enabled/`); + console.log(` sudo nginx -t`); + console.log(` sudo systemctl restart nginx`); + } +} + +// ============================================================================= +// 部署执行函数 +// ============================================================================= + +/** + * 安装项目依赖 + * + * 依次安装后端和前端的 npm 依赖 + */ +async function installDependencies() { + log.step('安装依赖'); + + // 安装后端依赖 + log.info('安装后端依赖...'); + const backendResult = runCommand('npm install', { cwd: path.join(__dirname, 'backend') }); + if (backendResult.success) { + log.success('后端依赖安装完成'); + } else { + log.error('后端依赖安装失败'); + throw new Error('Backend install failed'); + } + + // 安装前端依赖 + log.info('安装前端依赖...'); + const frontendResult = runCommand('npm install', { cwd: path.join(__dirname, 'frontend') }); + if (frontendResult.success) { + log.success('前端依赖安装完成'); + } else { + log.error('前端依赖安装失败'); + throw new Error('Frontend install failed'); + } +} + +/** + * 初始化数据库 + * + * 执行 backend/scripts/init-database.js 创建数据库表结构 + */ +async function initDatabase() { + log.step('数据库初始化'); + + const initScript = path.join(__dirname, 'backend', 'scripts', 'init-database.js'); + + // 检查初始化脚本是否存在 + if (!fs.existsSync(initScript)) { + log.warning('未找到数据库初始化脚本,跳过'); + return; + } + + log.info('正在初始化数据库...'); + const result = runCommand('node scripts/init-database.js', { + cwd: path.join(__dirname, 'backend') + }); + + if (result.success) { + log.success('数据库初始化完成'); + } else { + log.error('数据库初始化失败'); + log.info('请检查数据库配置并手动运行: node backend/scripts/init-database.js'); + } +} + +/** + * 构建前端项目 + * + * 执行 npm run build 生成生产环境静态文件 + */ +async function buildFrontend() { + log.step('构建前端'); + + log.info('执行 npm run build...'); + const result = runCommand('npm run build', { cwd: path.join(__dirname, 'frontend') }); + + if (result.success) { + log.success('前端构建完成'); + } else { + log.error('前端构建失败'); + throw new Error('Frontend build failed'); + } +} + +/** + * 启动服务 + * + * 使用 PM2 启动后端和前端(PM2方式)服务 + */ +async function startServices() { + log.step('启动服务'); + + // 确保日志目录存在 + const logDir = path.join(__dirname, 'backend', 'logs'); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + // 停止已有服务(避免冲突) + log.info('停止已有服务...'); + runCommand('pm2 stop idc-backend idc-frontend 2>nul || true', { silent: true }); + + // 启动后端服务 + log.info('启动后端服务...'); + const backendResult = runCommand( + `pm2 start server.js --name "idc-backend" --env ${config.nodeEnv}`, + { cwd: path.join(__dirname, 'backend') } + ); + + if (backendResult.success) { + log.success(`后端服务已启动 (端口: ${config.backendPort})`); + } else { + log.error('后端服务启动失败'); + } + + // 如果使用 PM2 serve,启动前端服务 + if (config.frontendDeploy === 'pm2') { + log.info('安装 serve 包...'); + runCommand('npm install -g serve', { silent: true }); + + log.info('启动前端服务...'); + const frontendResult = runCommand( + `pm2 start serve --name "idc-frontend" -- -s dist -l ${config.frontendPort}`, + { cwd: path.join(__dirname, 'frontend') } + ); + + if (frontendResult.success) { + log.success(`前端服务已启动 (端口: ${config.frontendPort})`); + } else { + log.error('前端服务启动失败'); + } + } + + // 保存 PM2 配置(开机自启) + runCommand('pm2 save', { silent: true }); + + log.divider(); +} + +// ============================================================================= +// 输出函数 +// ============================================================================= + +/** + * 输出部署完成后的管理命令和访问地址 + */ +function printManagementCommands() { + log.step('部署完成!管理命令:'); + + // PM2 管理命令 + console.log('\n' + colors.bright + '服务管理:' + colors.reset); + console.log(` ${colors.cyan}pm2 status${colors.reset} 查看服务状态`); + console.log(` ${colors.cyan}pm2 logs idc-backend${colors.reset} 查看后端日志`); + console.log(` ${colors.cyan}pm2 logs idc-frontend${colors.reset} 查看前端日志(PM2方式)`); + console.log(` ${colors.cyan}pm2 restart idc-backend${colors.reset} 重启后端`); + console.log(` ${colors.cyan}pm2 stop idc-backend${colors.reset} 停止后端`); + console.log(` ${colors.cyan}pm2 delete idc-backend${colors.reset} 删除后端服务`); + + // Nginx 管理命令(如适用) + if (config.frontendDeploy === 'nginx') { + console.log('\n' + colors.bright + 'Nginx 管理:' + colors.reset); + if (process.platform === 'win32') { + console.log(` ${colors.cyan}nginx -t${colors.reset} 测试配置`); + console.log(` ${colors.cyan}nginx -s reload${colors.reset} 重载配置`); + console.log(` ${colors.cyan}nginx -s stop${colors.reset} 停止Nginx`); + } else { + console.log(` ${colors.cyan}sudo nginx -t${colors.reset} 测试配置`); + console.log(` ${colors.cyan}sudo systemctl restart nginx${colors.reset} 重启Nginx`); + } + } + + // 访问地址 + console.log('\n' + colors.bright + '访问地址:' + colors.reset); + console.log(` 后端API: ${colors.cyan}http://localhost:${config.backendPort}/api${colors.reset}`); + if (config.frontendDeploy === 'nginx') { + console.log(` 前端页面: ${colors.cyan}http://${config.domain}:${config.frontendPort}${colors.reset}`); + } else { + console.log(` 前端页面: ${colors.cyan}http://localhost:${config.frontendPort}${colors.reset}`); + } + + // 更新升级命令 + console.log('\n' + colors.bright + '更新升级:' + colors.reset); + console.log(` ${colors.cyan}npm run update${colors.reset} 一键更新(拉取代码+重建+重启)`); + + log.divider(); + log.success('安装部署完成!'); +} + +// ============================================================================= +// 主函数 +// ============================================================================= + +/** + * 主函数 - 脚本入口 + * + * 执行流程: + * 1. 显示欢迎信息 + * 2. 环境检测 + * 3. 交互式配置(数据库、服务) + * 4. 配置确认 + * 5. 生成配置文件 + * 6. 安装依赖 + * 7. 初始化数据库 + * 8. 构建前端 + * 9. 启动服务 + * 10. 输出管理命令 + */ +async function main() { + // 显示欢迎信息 + console.log(` +${colors.bright}${colors.cyan} +╔══════════════════════════════════════════════════════════╗ +║ IDC设备管理系统 - 交互式安装部署脚本 ║ +║ Interactive Installation & Deployment Script ║ +╚══════════════════════════════════════════════════════════╝ +${colors.reset}`); + + try { + // 步骤1:环境检测 + await checkEnvironment(); + + // 步骤2:交互式配置 + await configureDatabase(); + await configureServices(); + await confirmConfiguration(); + + // 步骤3:生成配置文件 + generateBackendEnv(); + generatePM2Config(); + generateNginxConfig(); + + // 步骤4:安装和部署 + await installDependencies(); + await initDatabase(); + await buildFrontend(); + await startServices(); + + // 步骤5:输出管理命令 + printManagementCommands(); + + } catch (error) { + log.error(`部署失败: ${error.message}`); + console.error(error); + process.exit(1); + } finally { + // 关闭 readline 接口 + rl.close(); + } +} + +// 运行主函数 +main(); diff --git a/package.json b/package.json index 956f197..3ed4799 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "start": "concurrently \"npm run start:backend\" \"npm run start:frontend\"", "start:backend": "cd backend && npm run dev", "start:frontend": "cd frontend && npm run dev", - "install:all": "npm install && cd backend && npm install && cd ../frontend && npm install" + "install:all": "npm install && cd backend && npm install && cd ../frontend && npm install", + "deploy": "node install.js", + "update": "node update.js" }, "keywords": [ "idc", diff --git a/update.js b/update.js new file mode 100644 index 0000000..8f064a0 --- /dev/null +++ b/update.js @@ -0,0 +1,143 @@ +#!/usr/bin/env node + +/** + * IDC设备管理系统 - 一键更新脚本 + * 功能:拉取最新代码 → 安装依赖 → 重建前端 → 重启服务 + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +// 颜色输出 +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + green: '\x1b[32m', + yellow: '\x1b[33m', + red: '\x1b[31m', + cyan: '\x1b[36m', + gray: '\x1b[90m' +}; + +const log = { + info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`), + success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`), + warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`), + error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + step: (msg) => console.log(`\n${colors.bright}${colors.cyan}▶ ${msg}${colors.reset}`), + divider: () => console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`) +}; + +function runCommand(command, options = {}) { + try { + const result = execSync(command, { + encoding: 'utf8', + stdio: options.silent ? 'pipe' : 'inherit', + cwd: options.cwd || process.cwd(), + shell: true + }); + return { success: true, output: result }; + } catch (error) { + return { success: false, error: error.message }; + } +} + +async function main() { + console.log(` +${colors.bright}${colors.cyan} +╔══════════════════════════════════════════════════════════╗ +║ IDC设备管理系统 - 一键更新脚本 ║ +║ One-Click Update Script ║ +╚══════════════════════════════════════════════════════════╝ +${colors.reset}`); + + try { + // 1. 备份数据库 + log.step('1. 备份数据'); + const envPath = path.join(__dirname, 'backend', '.env'); + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf8'); + const dbTypeMatch = envContent.match(/DB_TYPE=(\w+)/); + const dbType = dbTypeMatch ? dbTypeMatch[1] : 'sqlite'; + + if (dbType === 'sqlite') { + const dbPath = path.join(__dirname, 'backend', 'idc_management.db'); + const backupDir = path.join(__dirname, 'backup'); + if (!fs.existsSync(backupDir)) { + fs.mkdirSync(backupDir, { recursive: true }); + } + const backupPath = path.join(backupDir, `database_${Date.now()}.db`); + if (fs.existsSync(dbPath)) { + fs.copyFileSync(dbPath, backupPath); + log.success(`数据库已备份: ${backupPath}`); + } + } else { + log.warning('MySQL数据库请手动备份'); + console.log(` ${colors.gray}mysqldump -u username -p idc_management > backup_${Date.now()}.sql${colors.reset}`); + } + } + + // 2. 拉取最新代码 + log.step('2. 拉取最新代码'); + const gitResult = runCommand('git pull'); + if (gitResult.success) { + log.success('代码更新完成'); + } else { + log.warning('代码拉取失败或不是git仓库,跳过'); + } + + // 3. 更新后端依赖 + log.step('3. 更新后端依赖'); + const backendResult = runCommand('npm install', { cwd: path.join(__dirname, 'backend') }); + if (backendResult.success) { + log.success('后端依赖更新完成'); + } else { + log.error('后端依赖更新失败'); + } + + // 4. 更新前端依赖并构建 + log.step('4. 更新前端依赖并构建'); + const frontendInstall = runCommand('npm install', { cwd: path.join(__dirname, 'frontend') }); + if (frontendInstall.success) { + log.success('前端依赖更新完成'); + + const frontendBuild = runCommand('npm run build', { cwd: path.join(__dirname, 'frontend') }); + if (frontendBuild.success) { + log.success('前端构建完成'); + } else { + log.error('前端构建失败'); + } + } else { + log.error('前端依赖更新失败'); + } + + // 5. 重启服务 + log.step('5. 重启服务'); + const restartResult = runCommand('pm2 restart idc-backend'); + if (restartResult.success) { + log.success('后端服务已重启'); + } else { + log.error('后端服务重启失败,请手动执行: pm2 restart idc-backend'); + } + + // 检查是否有前端PM2服务 + const frontendRestart = runCommand('pm2 restart idc-frontend 2>nul || echo "frontend not found"', { silent: true }); + if (frontendRestart.success && !frontendRestart.output.includes('frontend not found')) { + log.success('前端服务已重启'); + } + + log.divider(); + log.success('更新完成!'); + console.log(`\n${colors.cyan}访问地址:${colors.reset}`); + console.log(` 后端API: http://localhost:8000/api`); + console.log(` 前端页面: http://localhost`); + + } catch (error) { + log.error(`更新失败: ${error.message}`); + console.error(error); + process.exit(1); + } +} + +main();