docs: 删除过时的文档文件

refactor(uninstall): 改进 Nginx 清理逻辑
This commit is contained in:
zhang1106
2026-05-09 13:19:30 +08:00
parent 3d1d10d142
commit d4e4e0f143
3 changed files with 225 additions and 877 deletions
-186
View File
@@ -1,186 +0,0 @@
# 向导式接线创建功能使用指南
## 功能概述
新的向导式接线创建功能提供了一种直观、交互式的接线创建流程,帮助用户更高效地管理设备间的物理连接。
## 核心特性
### 1. 四步向导流程
#### 步骤 1: 选择源设备
- 搜索并选择接线的起点设备
- 支持设备类型过滤(服务器、交换机、存储设备)
- 点击设备卡片快速选择
#### 步骤 2: 选择目标设备
- 搜索并选择接线的终点设备
- 实时端口冲突检测
- 高亮显示已占用端口
#### 步骤 3: 线缆配置
- **线缆类型选择**:
- 🌐 以太网线(适用于1G/10G短距离连接)
- 🔦 光纤(适用于长距离或高带宽需求)
- 🔌 铜缆(适用于电源或特殊设备连接)
- **线缆长度选择**:
- 提供常用长度选项(1m, 2m, 3m, 5m, 7m, 10m, 15m, 20m, 30m, 50m
- **自动计算建议长度**:根据源/目标设备的机柜位置自动估算所需长度
- **线缆属性设置**:
- 自定义线缆标签(支持自动生成或手动输入)
- 添加备注说明
#### 步骤 4: 预览确认
- 全面展示接线信息
- 可视化连接预览
- 确认创建接线
### 2. 端口可视化面板
- **端口状态颜色编码**:
- 🟦 灰色:空闲端口
- 🟩 绿色:已连接端口
- 🟥 红色:故障端口
- **端口信息提示**:
- 悬停显示端口详细信息
- 显示端口类型、速率、VLAN
- 显示已连接的对端设备信息
- **智能端口选择**:
- 仅显示可用端口
- 自动过滤已占用端口
- 冲突端口高亮显示
### 3. 冲突检测
- 实时检测端口占用情况
- 显示冲突的详细信息
- 提供解决方案建议
## 使用方法
### 从设备管理页面创建接线
1. 进入**设备管理**页面
2. 在交换机设备卡片上找到 **"添加接线"** 按钮
3. 点击按钮,打开向导式创建界面
4. 按照四步向导完成接线创建
### 从机柜3D视图创建接线
1. 进入**机柜3D可视化**页面
2. 在3D场景中选择源设备
3. 点击设备上的端口
4. 选择目标设备的端口
5. 确认接线配置
## 新增字段说明
### Cable 模型新增字段
| 字段名 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| `cableLabel` | VARCHAR(255) | 线缆标签/编号 | CABLE-001-002-1234 |
| `cableColor` | VARCHAR(50) | 线缆颜色 | "红色", "蓝色", "黄色" |
| `installedBy` | VARCHAR(100) | 安装人 | "张三" |
| `installedAt` | DATETIME | 安装时间 | "2024-01-15 10:30:00" |
| `lastTestedAt` | DATETIME | 上次测试时间 | "2024-01-15 11:00:00" |
## 技术实现
### 前端组件
- **CableWizardModal**: 向导式接线创建主组件
- **PortPanel**: 端口可视化面板
- **framer-motion**: 动画效果
### 后端 API
- `POST /api/cables`: 创建接线(支持新字段)
- `POST /api/cables/check-conflict`: 检查端口冲突
- `GET /api/device-ports/device/:deviceId`: 获取设备端口列表
### 数据库迁移
运行以下命令添加新字段:
```bash
cd backend
node scripts/migrate-cable-fields.js
```
## 最佳实践
### 1. 线缆标签规范
建议使用以下格式生成线缆标签:
```
CABLE-{源设备ID后4位}-{目标设备ID后4位}-{时间戳后4位}
```
示例:
- `CABLE-0001-0002-1234`
- `CABLE-SRV01-SW01-5678`
### 2. 线缆长度估算
系统会根据设备所在机柜的位置自动估算所需长度:
```
建议长度 = |源机柜ID - 目标机柜ID| × 0.5 + 3
```
最小长度:3m
最大长度:50m
### 3. 端口选择建议
- 优先选择相邻的端口(便于管理)
- 同一设备的端口尽量连续分配
- 考虑线缆走线路径,避免交叉
## 常见问题
### Q1: 如何查看已创建的接线?
**A**: 在接线管理页面可以查看所有接线,支持按状态、类型、设备等条件筛选。
### Q2: 如何编辑已创建的接线?
**A**: 点击接线记录的"编辑"按钮,修改后保存即可。
### Q3: 如何删除接线?
**A**: 点击接线记录的"删除"按钮,确认后删除。
### Q4: 端口冲突如何解决?
**A**: 系统会高亮显示冲突端口,建议选择其他空闲端口或先删除原有连接。
## 未来扩展
### 计划功能
1. **批量接线创建**
- 支持 Excel 批量导入
- 支持模板下载
2. **3D 线缆渲染**
- 在 3D 场景中显示线缆走向
- 支持线缆路径追踪
3. **接线统计报表**
- 按类型/长度/状态统计
- 导出 Excel 报表
4. **工单联动**
- 接线操作与工单系统集成
- 支持变更审批流程
## 技术支持
如有问题,请联系系统管理员或查看项目文档。
-661
View File
@@ -1,661 +0,0 @@
# install.js 安装脚本改进计划
## 概述
本文档记录 `install.js` 安装脚本的改进计划,涵盖 **15 项改进**,按优先级分为高/中/低三档。
---
## 一、高优先级改进
### 1. npm 依赖安装网络问题处理
**问题描述**
- 直接执行 `npm install`,无重试机制
- 国内访问 npmjs.org 超时/速度慢
- 网络波动导致安装中断无反馈
**改进方案**
#### 1.1 自动检测并切换镜像源
```javascript
async function detectAndSetRegistry() {
const registries = [
{ name: '淘宝镜像', url: 'https://registry.npmmirror.com' },
{ name: '腾讯镜像', url: 'https://mirrors.cloud.tencent.com/npm/' },
{ name: '华为镜像', url: 'https://repo.huaweicloud.com/repository/npm/' },
{ name: '官方源', url: 'https://registry.npmjs.org' },
];
for (const registry of registries.slice(0, 3)) {
const pingResult = await pingRegistry(registry.url);
if (pingResult.success && pingResult.latency < 3000) {
log.success(`使用 ${registry.name} (${pingResult.latency}ms)`);
return registry.url;
}
}
return null;
}
async function pingRegistry(url) {
const start = Date.now();
try {
await new Promise((resolve, reject) => {
const req = require('https').get(`${url}/-/ping`, (res) => {
if (res.statusCode === 200) resolve();
else reject(new Error('Not 200'));
});
req.on('error', reject);
req.setTimeout(3000, () => { req.destroy(); reject(new Error('Timeout')); });
});
return { success: true, latency: Date.now() - start };
} catch {
return { success: false, latency: Infinity };
}
}
```
#### 1.2 重试机制与指数退避
```javascript
async function installWithRetry(cwd, name, maxRetries = 3) {
const baseDelay = 2000;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
log.info(`安装 ${name} 依赖 (第 ${attempt}/${maxRetries} 次)...`);
const result = await runNpmInstall(cwd);
if (result.success) {
return { success: true };
}
const errorType = analyzeNpmError(result.error);
if (errorType === 'NETWORK_ERROR' && attempt < maxRetries) {
const delay = baseDelay * Math.pow(2, attempt - 1);
log.warning(`网络错误,${delay/1000} 秒后重试...`);
await new Promise(r => setTimeout(r, delay));
runCommand('npm cache clean --force', { cwd, silent: true });
} else if (attempt < maxRetries) {
await new Promise(r => setTimeout(r, baseDelay));
}
}
return { success: false };
}
function analyzeNpmError(error) {
const errorLower = (error || '').toLowerCase();
if (errorLower.includes('econnrefused') ||
errorLower.includes('econnreset') ||
errorLower.includes('etimedout') ||
errorLower.includes('network')) {
return 'NETWORK_ERROR';
}
return 'UNKNOWN_ERROR';
}
```
#### 1.3 安装进度显示
```javascript
function runNpmInstall(cwd) {
return new Promise((resolve) => {
const spawn = require('child_process').spawn;
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const child = spawn(npm, ['install', '--loglevel=warn'], {
cwd, shell: true, stdio: ['ignore', 'pipe', 'pipe']
});
let stdout = '';
let lastProgress = '';
child.stdout.on('data', (data) => {
stdout += data;
const progressMatch = data.toString().match(/(\d+\/\d+|\d+%)/);
if (progressMatch) {
const progress = progressMatch[1];
if (progress !== lastProgress) {
process.stdout.write(`\r 安装进度: ${progress} `);
lastProgress = progress;
}
}
});
child.on('close', (code) => {
if (lastProgress) process.stdout.write('\n');
resolve({ success: code === 0, output: stdout });
});
setTimeout(() => { child.kill(); resolve({ success: false, error: '安装超时' }); }, 10 * 60 * 1000);
});
}
```
#### 1.4 安装后验证
```javascript
async function verifyDependencies(cwd, name) {
log.subStep(`验证 ${name} 依赖完整性...`);
const lsResult = runCommand('npm ls --depth=0 --json', { cwd, silent: true });
if (!lsResult.success) {
return { success: false, error: '依赖检查失败' };
}
try {
const deps = JSON.parse(lsResult.output);
if (deps.problems && deps.problems.length > 0) {
log.warning(`发现 ${deps.problems.length} 个依赖问题`);
return { success: false, problems: deps.problems };
}
return { success: true };
} catch {
return { success: false, error: '验证解析失败' };
}
}
```
**实施位置**`install.js` 第 1782-1804 行 `installDependencies()` 函数
---
### 2. 健康检查跨平台兼容性
**问题描述**
```javascript
// 当前代码(Windows 无 curl
execSync('curl -s -o /dev/null -w "%{http_code}" ...');
```
**改进方案**
```javascript
async function healthCheck() {
const http = require('http');
return new Promise((resolve) => {
const req = http.get(`http://localhost:${config.backendPort}/health`, (res) => {
resolve({ success: res.statusCode === 200, statusCode: res.statusCode });
});
req.on('error', () => resolve({ success: false }));
req.setTimeout(5000, () => { req.destroy(); resolve({ success: false }); });
});
}
```
**实施位置**`install.js` 第 2039-2073 行 `healthCheck()` 函数
---
### 3. MySQL 密码安全传递
**问题描述**
```javascript
// 当前代码 - 密码暴露在命令行
execSync(`mysql -u root -p'${rootPassword}' -e "${sql}"`);
```
**改进方案**
```javascript
async function createMySQLDatabase(rootPassword) {
const createDbSql = 'CREATE DATABASE IF NOT EXISTS idc_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;';
try {
execSync(`mysql -u root -e "${createDbSql}"`, {
env: { ...process.env, MYSQL_PWD: rootPassword },
stdio: ['pipe', 'pipe', 'pipe']
});
log.success('数据库创建成功');
} catch (error) {
// 错误处理...
}
}
```
**实施位置**`install.js` 第 1052-1086 行 `createMySQLDatabase()` 函数
---
### 4. 错误处理完善
**问题描述**
- 错误后继续执行,导致级联失败
- 无重试选项
**改进方案**
```javascript
async function initDatabase() {
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) {
const retry = await ask('数据库初始化失败,是否重试? (Y/n)', 'Y');
if (retry.toLowerCase() === 'y') {
return await initDatabase();
}
throw new Error('Database initialization failed');
}
log.success('数据库初始化完成');
}
```
**实施位置**`install.js` 第 1811-1833 行 `initDatabase()` 函数
---
### 5. 回滚机制
**问题描述**
- 安装失败后无清理
- 残留半成品状态
**改进方案**
```javascript
const rollbackSteps = [];
async function installDependencies() {
try {
log.step('安装后端依赖...');
await installBackend();
rollbackSteps.push(() => runCommand('rmdir /s /q node_modules', { cwd: backendDir, shell: true }));
log.step('安装前端依赖...');
await installFrontend();
rollbackSteps.push(() => runCommand('rmdir /s /q node_modules', { cwd: frontendDir, shell: true }));
} catch (error) {
await rollback();
throw error;
}
}
async function rollback() {
log.warning('安装失败,正在回滚...');
for (const step of rollbackSteps.reverse()) {
try { await step(); } catch {}
}
}
```
**实施位置**:新增 `rollbackSteps` 数组和 `rollback()` 函数,在 `main()` 中调用
---
### 6. SQLite 编译依赖替换
**已完成 ✅**
| 文件 | 修改内容 |
|------|---------|
| `backend/package.json` | `sqlite3``better-sqlite3` |
| `backend/db.js` | 添加 `dialectModule` 配置 |
---
## 二、中优先级改进
### 7. 日志管理优化
**问题描述**
- 每次安装生成新日志,无清理
- Windows 文件名不兼容冒号
**改进方案**
```javascript
function initLogFile() {
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR, { recursive: true });
}
// 清理 7 天前的旧日志
const oldLogs = fs.readdirSync(LOG_DIR)
.filter(f => f.startsWith('install_'))
.filter(f => {
const stat = fs.statSync(path.join(LOG_DIR, f));
return Date.now() - stat.mtimeMs > 7 * 24 * 60 * 60 * 1000;
});
oldLogs.forEach(f => fs.unlinkSync(path.join(LOG_DIR, f)));
// 使用安全的时间格式
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const logFileName = `install_${timestamp}.log`;
logFileStream = fs.createWriteStream(path.join(LOG_DIR, logFileName), { flags: 'a' });
// 重写 console.log 同时输出到文件和终端
const originalLog = console.log;
console.log = (...args) => {
const message = args.map(arg => String(arg)).join(' ');
logFileStream.write(`[${new Date().toISOString()}] ${message}\n`);
originalLog.apply(console, args);
};
}
```
**实施位置**`install.js` 第 100-123 行 `initLogFile()` 函数
---
### 8. 配置持久化与恢复
**问题描述**
- 每次安装需重新配置
**改进方案**
```javascript
const CONFIG_FILE = path.join(__dirname, '.install-config.json');
async function loadSavedConfig() {
if (!fs.existsSync(CONFIG_FILE)) return false;
const saved = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
const useSaved = await ask('检测到上次配置,是否使用? (Y/n)', 'Y');
if (useSaved.toLowerCase() === 'y') {
Object.assign(config, saved);
return true;
}
return false;
}
function saveConfig() {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
log.subStep('配置已保存');
}
// 在 confirmConfiguration() 成功后调用 saveConfig()
```
**实施位置**:新增 `CONFIG_FILE` 常量、`loadSavedConfig()``saveConfig()` 函数
---
### 9. 进度显示优化
**问题描述**
- 只有文字输出,无进度条
**改进方案**
```javascript
const steps = [
{ name: '环境检测', fn: checkEnvironment },
{ name: '数据库配置', fn: configureDatabase },
{ name: '服务配置', fn: configureServices },
{ name: '安装依赖', fn: installDependencies },
{ name: '初始化数据库', fn: initDatabase },
{ name: '构建前端', fn: buildFrontend },
{ name: '启动服务', fn: startServices },
];
async function runWithProgress() {
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
const progress = `[${i + 1}/${steps.length}]`;
log.step(`${progress} ${step.name}`);
const startTime = Date.now();
await step.fn();
log.subStep(`耗时 ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
}
}
```
**实施位置**:新增 `steps` 数组和 `runWithProgress()` 函数
---
### 10. Nginx 版本动态获取
**问题描述**
```javascript
// 固定版本
const nginxUrl = 'https://nginx.org/download/nginx-1.24.0.zip';
```
**改进方案**
```javascript
async function getLatestNginxVersion() {
const https = require('https');
return new Promise((resolve) => {
https.get('https://nginx.org/en/download.html', (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
const match = data.match(/nginx-(\d+\.\d+\.\d+)\.zip/);
resolve(match ? match[1] : '1.24.0');
});
}).on('error', () => resolve('1.24.0'));
});
}
async function autoInstallNginx() {
const version = await getLatestNginxVersion();
const nginxUrl = `https://nginx.org/download/nginx-${version}.zip`;
// 后续下载逻辑...
}
```
**实施位置**`install.js` 第 1430-1483 行 `autoInstallNginx()` 函数
---
### 11. PM2 serve 本地化
**问题描述**
```javascript
// 全局安装
runCommand('npm install -g serve');
```
**改进方案**
```javascript
// 方案1:使用 npx(推荐)
const frontendResult = runCommand(
`npx serve -s dist -l ${config.frontendPort}`,
{ cwd: path.join(__dirname, 'frontend') }
);
// 方案2:本地安装
runCommand('npm install serve', { cwd: path.join(__dirname, 'frontend') });
```
**实施位置**`install.js` 第 1889-1903 行 `startServices()` 函数
---
## 三、低优先级改进
### 12. 代码模块化重构
**问题描述**
- 2100+ 行单文件,难以维护
**改进方案**
```
scripts/
├── install.js # 入口
├── lib/
│ ├── logger.js # 日志模块
│ ├── prompt.js # 交互输入模块
│ ├── executor.js # 命令执行模块
│ ├── database.js # 数据库配置模块
│ ├── nginx.js # Nginx 配置模块
│ ├── pm2.js # PM2 配置模块
│ └── health-check.js # 健康检查模块
└── templates/
├── env.template # .env 模板
├── pm2.template.js # PM2 配置模板
└── nginx.template # Nginx 配置模板
```
---
### 13. 卸载脚本
**改进方案**
```javascript
// uninstall.js
async function uninstall() {
log.step('停止服务');
runCommand('pm2 stop idc-backend idc-frontend');
runCommand('pm2 delete idc-backend idc-frontend');
log.step('清理文件');
['node_modules', 'dist', 'logs'].forEach(dir => {
const cmd = process.platform === 'win32'
? `rmdir /s /q ${dir}`
: `rm -rf ${dir}`;
runCommand(cmd, { cwd: backendDir });
runCommand(cmd, { cwd: frontendDir });
});
log.step('清理配置');
['.env', '.install-config.json'].forEach(file => {
const path = `${backendDir}/${file}`;
if (fs.existsSync(path)) fs.unlinkSync(path);
});
log.success('卸载完成');
}
```
---
### 14. 配置验证
**改进方案**
```javascript
async function validateConfig() {
const errors = [];
// 端口冲突检查
const usedPorts = await checkUsedPorts([config.backendPort, config.frontendPort]);
if (usedPorts.length > 0) {
errors.push(`端口已被占用: ${usedPorts.join(', ')}`);
}
// 磁盘空间检查
const freeSpace = await checkDiskSpace(__dirname);
if (freeSpace < 500 * 1024 * 1024) {
errors.push('磁盘空间不足 500MB');
}
// 权限检查
if (config.frontendPort < 1024 && !isRootUser()) {
errors.push(`端口 ${config.frontendPort} 需要 root 权限`);
}
return errors;
}
```
---
### 15. 配置导入导出
**改进方案**
```javascript
// 导入配置
// node install.js --import=config.json
async function importConfig(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const loaded = JSON.parse(content);
Object.assign(config, loaded);
log.success(`配置已导入: ${filePath}`);
}
// 导出配置(在 saveConfig 时可选择)
// node install.js --export=config.json
```
---
## 四、实施优先级
| 优先级 | 编号 | 改进项 | 复杂度 | 状态 |
|--------|------|--------|--------|------|
| 🔴高 | 1 | npm 安装网络处理 | 中 | 待实施 |
| 🔴高 | 2 | 健康检查跨平台 | 低 | 待实施 |
| 🔴高 | 3 | MySQL 密码安全 | 低 | 待实施 |
| 🔴高 | 4 | 错误处理完善 | 中 | 待实施 |
| 🔴高 | 5 | 回滚机制 | 高 | 待实施 |
| 🔴高 | 6 | SQLite 替换 | 中 | ✅ 已完成 |
| 🟡中 | 7 | 日志管理 | 低 | 待实施 |
| 🟡中 | 8 | 配置持久化 | 中 | 待实施 |
| 🟡中 | 9 | 进度显示 | 低 | 待实施 |
| 🟡中 | 10 | Nginx 版本动态 | 低 | 待实施 |
| 🟡中 | 11 | PM2 serve 本地化 | 低 | 待实施 |
| 🟢低 | 12 | 代码模块化 | 高 | 待实施 |
| 🟢低 | 13 | 卸载脚本 | 中 | 待实施 |
| 🟢低 | 14 | 配置验证 | 中 | 待实施 |
| 🟢低 | 15 | 配置导入导出 | 低 | 待实施 |
---
## 五、建议实施顺序
### 第一阶段(快速见效)
1. **Nginx 版本动态** (10) - 改动小,立即生效
2. **日志管理优化** (7) - 改动小,提升调试体验
3. **PM2 serve 本地化** (11) - 改动小,避免全局污染
### 第二阶段(核心增强)
4. **npm 安装网络处理** (1) - 提升安装成功率
5. **健康检查跨平台** (2) - Windows 兼容性
6. **MySQL 密码安全** (3) - 安全性
### 第三阶段(健壮性)
7. **错误处理完善** (4) - 提升容错能力
8. **回滚机制** (5) - 失败恢复
9. **配置持久化** (8) - 复用配置
10. **进度显示** (9) - 用户体验
### 第四阶段(长期)
11. **代码模块化** (12) - 提升可维护性
12. **卸载脚本** (13) - 功能完整性
13. **配置验证** (14) - 提前发现问题
14. **配置导入导出** (15) - 扩展性
---
## 六、测试清单
每项改进实施后需验证:
- [ ] Windows 环境
- [ ] Linux 环境(Ubuntu/Debian
- [ ] Linux 环境(CentOS/RHEL
- [ ] macOS 环境
- [ ] 非交互模式 (`-y`)
- [ ] 配置恢复功能
- [ ] 日志文件生成
- [ ] 回滚功能(模拟失败)
+225 -30
View File
@@ -346,16 +346,150 @@ async function cleanupNginxConfig(cmdArgs) {
const isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';
const isMac = process.platform === 'darwin';
/**
* 检测 Nginx 是否已安装
*
* @returns {boolean} Nginx 是否可用
*/
function nginxInstalled() {
return commandExists('nginx');
}
/**
* 检测 Nginx 服务进程是否在运行
*
* @returns {boolean} Nginx 是否在运行
*/
function nginxRunning() {
if (isWindows) {
const result = runCommand('tasklist /FI "IMAGENAME eq nginx.exe" 2>nul', { silent: true });
return result.success && result.output.includes('nginx.exe');
}
const result = runCommand('pgrep -x nginx 2>/dev/null', { silent: true });
return result.success && result.output.trim().length > 0;
}
/**
* 停止 Nginx 服务
*
* @returns {boolean} 是否成功停止
*/
function stopNginx() {
if (isLinux) {
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
// 优先使用 systemctl
if (commandExists('systemctl')) {
const result = runCommand(`${sudoPrefix}systemctl stop nginx`, { silent: true });
if (result.success) {
log.success('Nginx 服务已停止 (systemctl)');
return true;
}
}
// 回退到 nginx -s stop
const result = runCommand(`${sudoPrefix}nginx -s stop`, { silent: true });
if (result.success) {
log.success('Nginx 服务已停止');
return true;
}
log.warning('Nginx 停止失败,可能需要手动处理');
return false;
}
if (isWindows) {
if (!nginxRunning()) {
log.info('Nginx 未在运行');
return true;
}
const result = runCommand('nginx -s stop', { silent: true });
if (result.success) {
log.success('Nginx 已停止');
return true;
}
log.warning('Nginx 停止失败,请手动执行: nginx -s stop');
return false;
}
return false;
}
/**
* 禁用 Nginx 开机自启(Linux
*/
function disableNginxStartup() {
if (!isLinux || !commandExists('systemctl')) return;
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
const result = runCommand(`${sudoPrefix}systemctl disable nginx`, { silent: true });
if (result.success) {
log.success('Nginx 开机自启已禁用');
}
}
/**
* 清理 Linux 前端部署目录
*/
function cleanupFrontendDeployDir() {
if (!isLinux) return;
const webRoot = '/var/www/idc';
if (!fs.existsSync(webRoot)) return;
log.info('清理前端部署目录...');
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
const result = runCommand(`${sudoPrefix}rm -rf "${webRoot}"`, { silent: true });
if (result.success) {
log.success('前端部署目录已删除 (/var/www/idc)');
deletedItems.push({ type: '目录', name: '/var/www/idc' });
} else {
log.warning('前端部署目录删除失败,请手动清理');
}
}
if (isWindows) {
// Windows 下提示手动清理
log.info('Windows 系统请手动清理 Nginx 配置:');
log.info('Windows Nginx 清理:');
if (nginxInstalled() && nginxRunning()) {
log.info('检测到 Nginx 正在运行');
if (!cmdArgs?.force) {
const stopConfirm = await ask('是否停止 Nginx 服务? (Y/n)', 'Y');
if (stopConfirm.toLowerCase() === 'y') {
stopNginx();
}
} else {
stopNginx();
}
}
console.log(`\n 请手动完成以下操作:`);
console.log(` 1. 删除配置文件: ${colors.cyan}C:/nginx/conf/conf.d/idc.conf${colors.reset}`);
console.log(` 2. 编辑主配置: 从 ${colors.cyan}C:/nginx/conf/nginx.conf${colors.reset} 中移除 include conf.d/*.conf;`);
console.log(` 2. 编辑主配置: 从 ${colors.cyan}C:/nginx/conf/nginx.conf${colors.reset} 中移除 include 语句`);
console.log(` 3. 停止 Nginx: ${colors.cyan}nginx -s stop${colors.reset}`);
// 检测 Nginx 安装方式,提供卸载指引
log.info('检测 Nginx 安装方式...');
const chocoCheck = runCommand('choco list nginx --local-only 2>nul', { silent: true });
if (chocoCheck.success && chocoCheck.output.includes('nginx')) {
console.log(`\n 检测到 Chocolatey 安装的 Nginx,卸载命令:`);
console.log(` ${colors.cyan}choco uninstall nginx${colors.reset}`);
} else {
const scoopCheck = runCommand('scoop list nginx 2>nul', { silent: true });
if (scoopCheck.success && scoopCheck.output.includes('nginx')) {
console.log(`\n 检测到 Scoop 安装的 Nginx,卸载命令:`);
console.log(` ${colors.cyan}scoop uninstall nginx${colors.reset}`);
} else if (fs.existsSync('C:/nginx')) {
console.log(`\n 检测到 Nginx 安装在 ${colors.cyan}C:/nginx${colors.reset}`);
console.log(` 如需完整卸载,请手动删除该目录`);
}
}
if (!cmdArgs?.force) {
const confirm = await ask('是否已手动清理 Nginx 配置? (Y/n)', 'Y');
const confirm = await ask('\n是否已完成以上清理操作? (Y/n)', 'Y');
if (confirm.toLowerCase() !== 'y') {
log.warning('请记得手动清理 Nginx 配置');
}
@@ -363,11 +497,26 @@ async function cleanupNginxConfig(cmdArgs) {
log.info('强制模式:跳过 Nginx 清理确认');
}
} else if (isLinux) {
// Linux 下自动清理
const root = isRootUser();
const sudoPrefix = root ? '' : 'sudo ';
// 检测配置文件位置
// 1. 停止 Nginx 服务
if (nginxInstalled()) {
log.info('停止 Nginx 服务...');
if (nginxRunning()) {
stopNginx();
} else {
log.info('Nginx 未在运行');
}
}
// 2. 禁用开机自启
disableNginxStartup();
// 3. 清理前端部署目录
cleanupFrontendDeployDir();
// 4. 检测并清理 Nginx 配置文件
const sitesAvailablePath = '/etc/nginx/sites-available/idc';
const sitesEnabledPath = '/etc/nginx/sites-enabled/idc';
const confDPath = '/etc/nginx/conf.d/idc';
@@ -375,7 +524,6 @@ async function cleanupNginxConfig(cmdArgs) {
let configExists = false;
// 删除 sites-available 中的配置
if (fs.existsSync(sitesAvailablePath)) {
configExists = true;
log.info('删除 sites-available 配置...');
@@ -387,7 +535,6 @@ async function cleanupNginxConfig(cmdArgs) {
}
}
// 删除 sites-enabled 中的软链接
if (fs.existsSync(sitesEnabledPath)) {
configExists = true;
log.info('删除 sites-enabled 软链接...');
@@ -399,7 +546,6 @@ async function cleanupNginxConfig(cmdArgs) {
}
}
// 删除 conf.d 中的配置(无扩展名)
if (fs.existsSync(confDPath)) {
configExists = true;
log.info('删除 conf.d 配置...');
@@ -411,7 +557,6 @@ async function cleanupNginxConfig(cmdArgs) {
}
}
// 删除 conf.d 中的配置(带 .conf 扩展名)
if (fs.existsSync(confDWithExtPath)) {
configExists = true;
log.info('删除 conf.d 配置(带扩展名)...');
@@ -425,29 +570,60 @@ async function cleanupNginxConfig(cmdArgs) {
if (!configExists) {
log.info('未找到系统 Nginx 配置文件');
} else {
// 测试并重载 Nginx
} else if (nginxInstalled()) {
log.info('测试 Nginx 配置...');
const testResult = runCommand(`${sudoPrefix}nginx -t`, { silent: true });
if (testResult.success) {
log.success('Nginx 配置测试通过');
log.info('重载 Nginx 服务...');
const reloadResult = runCommand(`${sudoPrefix}nginx -s reload`, { silent: true });
if (reloadResult.success) {
log.success('Nginx 已重载');
} else {
log.warning('Nginx 重载失败,可能需要手动重启');
}
log.success('Nginx 配置测试通过(已无残留配置)');
} else {
log.warning('Nginx 配置测试失败,请手动检查');
log.success('Nginx 配置已清理完成');
}
}
} else {
// macOS 或其他系统
log.info('请手动清理 Nginx 配置');
} else if (isMac) {
log.info('macOS Nginx 清理:');
if (nginxInstalled() && nginxRunning()) {
log.info('检测到 Nginx 正在运行');
if (!cmdArgs?.force) {
const stopConfirm = await ask('是否停止 Nginx 服务? (Y/n)', 'Y');
if (stopConfirm.toLowerCase() === 'y') {
// 优先使用 brew
if (commandExists('brew')) {
const brewResult = runCommand('brew services stop nginx', { silent: true });
if (brewResult.success) {
log.success('Nginx 已停止 (brew)');
} else {
runCommand('nginx -s stop', { silent: true });
}
} else {
runCommand('nginx -s stop', { silent: true });
}
}
} else {
if (commandExists('brew')) {
runCommand('brew services stop nginx', { silent: true });
} else {
runCommand('nginx -s stop', { silent: true });
}
}
}
console.log(`\n 请手动完成以下操作:`);
console.log(` 配置文件可能位于: ${colors.cyan}/usr/local/etc/nginx/servers/${colors.reset}`);
console.log(` 或使用: ${colors.cyan}brew services stop nginx${colors.reset}`);
if (commandExists('brew')) {
console.log(` 如需卸载 Nginx${colors.cyan}brew uninstall nginx${colors.reset}`);
}
if (!cmdArgs?.force) {
const confirm = await ask('\n是否已完成以上清理操作? (Y/n)', 'Y');
if (confirm.toLowerCase() !== 'y') {
log.warning('请记得手动清理 Nginx 配置');
}
} else {
log.info('强制模式:跳过 Nginx 清理确认');
}
}
log.divider();
@@ -787,16 +963,35 @@ function showDryRunPreview(cmdArgs) {
// Nginx 配置
console.log(`\n ${colors.bright}2. 清理 Nginx 配置${colors.reset}`);
// 停止 Nginx 服务
if (nginxRunning()) {
console.log(` - 停止 Nginx 服务`);
}
// Linux 下额外操作
if (process.platform === 'linux') {
const paths = ['/etc/nginx/sites-available/idc', '/etc/nginx/sites-enabled/idc',
console.log(` - 禁用 Nginx 开机自启 (systemctl disable nginx)`);
if (fs.existsSync('/var/www/idc')) {
console.log(` - 删除前端部署目录 (/var/www/idc)`);
}
const nginxConfigPaths = ['/etc/nginx/sites-available/idc', '/etc/nginx/sites-enabled/idc',
'/etc/nginx/conf.d/idc', '/etc/nginx/conf.d/idc.conf'];
paths.forEach(p => {
nginxConfigPaths.forEach(p => {
if (fs.existsSync(p)) {
console.log(` - 删除: ${p}`);
console.log(` - 删除 Nginx 配置: ${p}`);
}
});
} else {
console.log(` - 提示手动清理(${process.platform === 'win32' ? 'Windows' : 'macOS'}`);
} else if (process.platform === 'win32') {
console.log(` - 删除 Nginx 配置文件 (C:/nginx/conf/conf.d/idc.conf)`);
console.log(` - 提供 Chocolatey/Scoop 卸载指引`);
} else if (process.platform === 'darwin') {
if (commandExists('brew')) {
console.log(` - 停止 Nginx (brew services)`);
}
console.log(` - 删除 Nginx 配置 (/usr/local/etc/nginx/servers/)`);
}
// 配置文件