From e9cb50f0ff2fab97146e28870654940921537a50 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Wed, 11 Mar 2026 14:28:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(install):=20=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install.js | 103 ++++++++++++++++++++++++++++++++++++++++----------- uninstall.js | 18 ++++----- 2 files changed, 91 insertions(+), 30 deletions(-) diff --git a/install.js b/install.js index dd16906..b862e0d 100644 --- a/install.js +++ b/install.js @@ -81,17 +81,6 @@ const log = { // 配置存储对象 // ============================================================================= -/** - * 部署配置对象 - * 存储用户交互过程中设置的所有配置参数 - * - * @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 使用) - */ /** * 部署配置对象 * 存储用户交互过程中设置的所有配置参数 @@ -139,15 +128,69 @@ const rl = readline.createInterface({ */ 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 - 提示问题文本 + * @returns {Promise} 用户输入的密码 + * + * 使用示例: + * const password = await askPassword('请输入密码'); + */ +function askPassword(question) { + return new Promise((resolve) => { + const prompt = `${question}: `; + process.stdout.write(prompt); + + let password = ''; + const stdin = process.stdin; + const wasRaw = stdin.isRaw; + + if (stdin.isTTY) { + stdin.setRawMode(true); + } + stdin.resume(); + stdin.setEncoding('utf8'); + + const onData = (char) => { + const c = char; + + switch (c) { + case '\n': + case '\r': + case '\u0004': + if (stdin.isTTY) { + stdin.setRawMode(wasRaw || false); + } + stdin.pause(); + stdin.removeListener('data', onData); + console.log(); + resolve(password); + break; + case '\u0003': + process.exit(); + break; + case '\u007F': + case '\b': + password = password.slice(0, -1); + break; + default: + password += c; + break; + } + }; + + stdin.on('data', onData); + }); +} + /** * 选择函数 - 显示选项列表并获取用户选择 * @@ -162,20 +205,28 @@ function ask(question, defaultValue = '') { * ]); */ 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 {number} length - 密钥长度,默认64位 + * @returns {string} 随机密钥 + */ +function generateSecretKey(length = 64) { + const crypto = require('crypto'); + return crypto.randomBytes(length).toString('hex'); +} + // ============================================================================= // 系统命令执行函数 // ============================================================================= @@ -752,7 +803,7 @@ async function configureDatabase() { 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.password = await askPassword('MySQL 密码'); config.dbConfig.database = await ask('数据库名称', 'idc_management'); } @@ -1126,7 +1177,8 @@ async function autoInstallNginx() { function generateBackendEnv() { const envPath = path.join(__dirname, 'backend', '.env'); - // 构建环境变量内容 + const jwtSecret = generateSecretKey(64); + let envContent = `# IDC设备管理系统 - 环境配置 # 生成时间: ${new Date().toISOString()} @@ -1140,6 +1192,13 @@ PORT=${config.backendPort} # 运行环境: development 或 production NODE_ENV=${config.nodeEnv} +# ============================================== +# 安全配置 +# ============================================== + +# JWT 密钥(自动生成,请妥善保管) +JWT_SECRET=${jwtSecret} + # ============================================== # 数据库配置 # ============================================== @@ -1148,7 +1207,6 @@ NODE_ENV=${config.nodeEnv} DB_TYPE=${config.dbType} `; - // 根据数据库类型添加相应配置 if (config.dbType === 'sqlite') { envContent += ` # SQLite 配置 @@ -1165,9 +1223,9 @@ MYSQL_DATABASE=${config.dbConfig.database} `; } - // 写入文件 fs.writeFileSync(envPath, envContent); log.success('后端环境变量文件已生成 (.env)'); + log.info(`JWT_SECRET 已自动生成 (${jwtSecret.length}位)`); } /** @@ -1489,7 +1547,10 @@ async function startServices() { // 停止已有服务(避免冲突) log.info('停止已有服务...'); - runCommand('pm2 stop idc-backend idc-frontend 2>nul || true', { silent: true }); + const stopCmd = process.platform === 'win32' + ? 'pm2 stop idc-backend idc-frontend 2>nul || exit 0' + : 'pm2 stop idc-backend idc-frontend 2>/dev/null || true'; + runCommand(stopCmd, { silent: true }); // 启动后端服务 log.info('启动后端服务...'); diff --git a/uninstall.js b/uninstall.js index dcc258a..54f045c 100644 --- a/uninstall.js +++ b/uninstall.js @@ -111,41 +111,41 @@ function isRootUser() { async function stopAndDeleteServices() { log.step('停止并删除服务'); - // 检查 PM2 是否存在 if (!commandExists('pm2')) { log.warning('未检测到 PM2,跳过服务停止步骤'); return; } - // 停止并删除后端服务 + const isWindows = process.platform === 'win32'; + const nullRedirect = isWindows ? '2>nul' : '2>/dev/null'; + const orTrue = isWindows ? '|| exit 0' : '|| true'; + log.info('停止后端服务 (idc-backend)...'); - const backendStop = runCommand('pm2 stop idc-backend 2>nul || true', { silent: true }); + const backendStop = runCommand(`pm2 stop idc-backend ${nullRedirect} ${orTrue}`, { silent: true }); if (backendStop.success) { log.success('后端服务已停止'); } log.info('删除后端服务 (idc-backend)...'); - const backendDelete = runCommand('pm2 delete idc-backend 2>nul || true', { silent: true }); + const backendDelete = runCommand(`pm2 delete idc-backend ${nullRedirect} ${orTrue}`, { silent: true }); if (backendDelete.success) { log.success('后端服务已删除'); } - // 停止并删除前端服务 log.info('停止前端服务 (idc-frontend)...'); - const frontendStop = runCommand('pm2 stop idc-frontend 2>nul || true', { silent: true }); + const frontendStop = runCommand(`pm2 stop idc-frontend ${nullRedirect} ${orTrue}`, { silent: true }); if (frontendStop.success) { log.success('前端服务已停止'); } log.info('删除前端服务 (idc-frontend)...'); - const frontendDelete = runCommand('pm2 delete idc-frontend 2>nul || true', { silent: true }); + const frontendDelete = runCommand(`pm2 delete idc-frontend ${nullRedirect} ${orTrue}`, { silent: true }); if (frontendDelete.success) { log.success('前端服务已删除'); } - // 保存 PM2 配置 log.info('保存 PM2 配置...'); - runCommand('pm2 save 2>nul || true', { silent: true }); + runCommand(`pm2 save ${nullRedirect} ${orTrue}`, { silent: true }); log.divider(); }