docs: 删除过时的文档文件
refactor(uninstall): 改进 Nginx 清理逻辑
This commit is contained in:
@@ -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. **工单联动**
|
||||
- 接线操作与工单系统集成
|
||||
- 支持变更审批流程
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题,请联系系统管理员或查看项目文档。
|
||||
@@ -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`)
|
||||
- [ ] 配置恢复功能
|
||||
- [ ] 日志文件生成
|
||||
- [ ] 回滚功能(模拟失败)
|
||||
Reference in New Issue
Block a user