绿色包,mysql处理成即用即启,而非服务的形式,因为注册服务需要管理员身份
This commit is contained in:
@@ -7,7 +7,8 @@
|
|||||||
"Bash(taskkill:*)",
|
"Bash(taskkill:*)",
|
||||||
"Bash(iconv:*)",
|
"Bash(iconv:*)",
|
||||||
"Bash(powershell:*)",
|
"Bash(powershell:*)",
|
||||||
"Bash(del:*)"
|
"Bash(del:*)",
|
||||||
|
"Bash(git -C \"c:\\code\\gitea\\NPQS-9100\\pqs-9100_client\" log -1 --oneline)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ class Lifecycle {
|
|||||||
this.mysqlPort = null;
|
this.mysqlPort = null;
|
||||||
this.javaPort = null;
|
this.javaPort = null;
|
||||||
this.autoRefreshTimer = null;
|
this.autoRefreshTimer = null;
|
||||||
this.isRestartingForAdmin = false; // 权限提升重启标记
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,23 +118,8 @@ class Lifecycle {
|
|||||||
this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort });
|
this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort });
|
||||||
await this.sleep(500);
|
await this.sleep(500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[lifecycle] MySQL service error:', error);
|
logger.error('[lifecycle] MySQL error:', error);
|
||||||
this.logWindowManager.addLog('error', `MySQL 服务错误: ${error.message}`);
|
this.logWindowManager.addLog('error', `MySQL 错误: ${error.message}`);
|
||||||
// 检查是否是权限问题
|
|
||||||
if (error.message && (error.message.includes('administrator') || error.message.includes('Denied'))) {
|
|
||||||
logger.error('[lifecycle] Need administrator privileges');
|
|
||||||
this.logWindowManager.addLog('error', '检测到需要管理员权限来安装 MySQL 服务');
|
|
||||||
this.logWindowManager.addLog('system', '应用将自动请求管理员权限并重启...');
|
|
||||||
|
|
||||||
// 等待用户看清日志
|
|
||||||
await this.sleep(2000);
|
|
||||||
|
|
||||||
// 自动以管理员身份重启应用
|
|
||||||
await this.restartAsAdmin();
|
|
||||||
|
|
||||||
// 退出当前实例
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,12 +334,6 @@ class Lifecycle {
|
|||||||
async cleanup() {
|
async cleanup() {
|
||||||
logger.info('[lifecycle] Starting cleanup...');
|
logger.info('[lifecycle] Starting cleanup...');
|
||||||
|
|
||||||
// 如果是权限提升重启,跳过清理(服务需要继续运行)
|
|
||||||
if (this.isRestartingForAdmin) {
|
|
||||||
logger.info('[lifecycle] Restarting for admin, skip cleanup');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除自动刷新定时器
|
// 清除自动刷新定时器
|
||||||
if (this.autoRefreshTimer) {
|
if (this.autoRefreshTimer) {
|
||||||
clearTimeout(this.autoRefreshTimer);
|
clearTimeout(this.autoRefreshTimer);
|
||||||
@@ -397,10 +375,26 @@ class Lifecycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MySQL 作为Windows服务运行,应用关闭时保持运行
|
// 停止 MySQL 进程(进程模式)
|
||||||
logger.info('[lifecycle] MySQL service keeps running as Windows service');
|
if (this.mysqlServiceManager) {
|
||||||
|
try {
|
||||||
|
logger.info('[lifecycle] Stopping MySQL process...');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('system', '正在停止 MySQL...');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.mysqlServiceManager.stopMySQLProcess();
|
||||||
|
|
||||||
|
logger.info('[lifecycle] MySQL process stopped');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('success', 'MySQL 已停止');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[lifecycle] Failed to stop MySQL:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
this.logWindowManager.addLog('system', 'MySQL 服务保持运行');
|
|
||||||
this.logWindowManager.addLog('system', '清理完成,应用即将退出');
|
this.logWindowManager.addLog('system', '清理完成,应用即将退出');
|
||||||
this.logWindowManager.addLog('system', '='.repeat(60));
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
}
|
}
|
||||||
@@ -485,72 +479,6 @@ class Lifecycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 以管理员身份重启应用
|
|
||||||
*/
|
|
||||||
async restartAsAdmin() {
|
|
||||||
const { app, dialog } = require('electron');
|
|
||||||
const { spawn } = require('child_process');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
logger.info('[lifecycle] Requesting administrator privileges...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 显示提示对话框
|
|
||||||
await dialog.showMessageBox({
|
|
||||||
type: 'warning',
|
|
||||||
title: '告警',
|
|
||||||
message: '需要管理员权限',
|
|
||||||
buttons: ['确定']
|
|
||||||
});
|
|
||||||
|
|
||||||
// 用户点击确定,以管理员身份重启
|
|
||||||
|
|
||||||
// 获取应用可执行文件路径
|
|
||||||
const exePath = app.getPath('exe');
|
|
||||||
logger.info('[lifecycle] Restarting with admin privileges:', exePath);
|
|
||||||
|
|
||||||
// 使用 PowerShell 以管理员身份启动
|
|
||||||
const psCommand = `Start-Process -FilePath "${exePath}" -Verb RunAs`;
|
|
||||||
|
|
||||||
const child = spawn('powershell.exe', ['-Command', psCommand], {
|
|
||||||
detached: true,
|
|
||||||
stdio: 'ignore',
|
|
||||||
windowsHide: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 分离子进程,父进程退出不影响子进程
|
|
||||||
child.unref();
|
|
||||||
|
|
||||||
// 立即退出当前实例,释放单实例锁
|
|
||||||
// 必须立即退出,否则新实例会因为单实例锁无法启动
|
|
||||||
logger.info('[lifecycle] Quitting current instance to release lock...');
|
|
||||||
|
|
||||||
// 设置标记,跳过清理流程(这只是重启,不是真正退出)
|
|
||||||
this.isRestartingForAdmin = true;
|
|
||||||
|
|
||||||
// 关闭所有窗口
|
|
||||||
const BrowserWindow = require('electron').BrowserWindow;
|
|
||||||
BrowserWindow.getAllWindows().forEach(win => {
|
|
||||||
try {
|
|
||||||
win.destroy();
|
|
||||||
} catch (e) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 立即强制退出,释放锁
|
|
||||||
process.exit(0);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[lifecycle] Failed to restart as admin:', error);
|
|
||||||
this.logWindowManager.addLog('error', '自动提升权限失败,请手动以管理员身份运行');
|
|
||||||
|
|
||||||
// 等待5秒后关闭
|
|
||||||
await this.sleep(5000);
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 睡眠函数
|
* 睡眠函数
|
||||||
*/
|
*/
|
||||||
@@ -558,24 +486,6 @@ class Lifecycle {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有管理员权限(仅 Windows)
|
|
||||||
*/
|
|
||||||
checkAdminPrivileges() {
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
return true; // 非 Windows 系统不需要检查
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
// 尝试执行需要管理员权限的命令
|
|
||||||
execSync('net session', { stdio: 'ignore' });
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* electron app ready
|
* electron app ready
|
||||||
*/
|
*/
|
||||||
@@ -589,16 +499,7 @@ class Lifecycle {
|
|||||||
async windowReady() {
|
async windowReady() {
|
||||||
logger.info('[lifecycle] window-ready hook triggered');
|
logger.info('[lifecycle] window-ready hook triggered');
|
||||||
|
|
||||||
// 在创建任何窗口之前,先检查管理员权限
|
// 进程模式不需要管理员权限检查
|
||||||
const hasAdminPrivileges = this.checkAdminPrivileges();
|
|
||||||
logger.info('[lifecycle] Has admin privileges:', hasAdminPrivileges);
|
|
||||||
|
|
||||||
if (!hasAdminPrivileges) {
|
|
||||||
logger.warn('[lifecycle] No admin privileges, requesting elevation');
|
|
||||||
// 调用已有的 restartAsAdmin 方法,避免代码重复
|
|
||||||
await this.restartAsAdmin();
|
|
||||||
return; // 阻止后续代码执行
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建日志管理器(但不显示窗口,仅用于写日志文件)
|
// 创建日志管理器(但不显示窗口,仅用于写日志文件)
|
||||||
logger.info('[lifecycle] Creating log window manager...');
|
logger.info('[lifecycle] Creating log window manager...');
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ const path = require('path');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MySQL Windows 服务管理器
|
* MySQL 进程管理器
|
||||||
* 将MySQL安装为Windows服务 mysql9100,持久运行
|
* 使用进程模式启动 MySQL,无需管理员权限
|
||||||
*/
|
*/
|
||||||
class MySQLServiceManager {
|
class MySQLServiceManager {
|
||||||
constructor(logWindowManager = null) {
|
constructor(logWindowManager = null) {
|
||||||
@@ -16,9 +16,12 @@ class MySQLServiceManager {
|
|||||||
this.mysqlPath = path.join(baseDir, 'mysql');
|
this.mysqlPath = path.join(baseDir, 'mysql');
|
||||||
this.binPath = path.join(this.mysqlPath, 'bin');
|
this.binPath = path.join(this.mysqlPath, 'bin');
|
||||||
this.dataPath = path.join(this.mysqlPath, 'data');
|
this.dataPath = path.join(this.mysqlPath, 'data');
|
||||||
this.serviceName = 'mysql9100';
|
this.configFile = path.join(this.mysqlPath, 'my.ini');
|
||||||
this.configFile = path.join(this.mysqlPath, 'my.ini'); // 使用标准的 my.ini
|
this.logWindowManager = logWindowManager;
|
||||||
this.logWindowManager = logWindowManager; // 用于输出日志到文件
|
|
||||||
|
// 进程模式:保存 MySQL 进程引用
|
||||||
|
this.mysqlProcess = null;
|
||||||
|
this.currentPort = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 睡眠函数
|
// 睡眠函数
|
||||||
@@ -28,7 +31,7 @@ class MySQLServiceManager {
|
|||||||
|
|
||||||
// 日志输出辅助方法
|
// 日志输出辅助方法
|
||||||
log(type, message) {
|
log(type, message) {
|
||||||
console.log(`[MySQL Service] ${message}`);
|
console.log(`[MySQL] ${message}`);
|
||||||
if (this.logWindowManager) {
|
if (this.logWindowManager) {
|
||||||
this.logWindowManager.addLog(type, `[MySQL] ${message}`);
|
this.logWindowManager.addLog(type, `[MySQL] ${message}`);
|
||||||
}
|
}
|
||||||
@@ -41,13 +44,10 @@ class MySQLServiceManager {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(command, { encoding: 'utf8' }, (error, stdout, stderr) => {
|
exec(command, { encoding: 'utf8' }, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
// 创建标准 Error 对象,确保有 message 属性
|
|
||||||
const err = new Error(error.message || stderr || stdout || 'Command execution failed');
|
const err = new Error(error.message || stderr || stdout || 'Command execution failed');
|
||||||
err.code = error.code;
|
err.code = error.code;
|
||||||
err.stdout = stdout;
|
err.stdout = stdout;
|
||||||
err.stderr = stderr;
|
err.stderr = stderr;
|
||||||
err.command = command;
|
|
||||||
err.originalError = error;
|
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
resolve({ stdout, stderr });
|
resolve({ stdout, stderr });
|
||||||
@@ -56,70 +56,6 @@ class MySQLServiceManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查服务是否存在
|
|
||||||
*/
|
|
||||||
async checkServiceExists() {
|
|
||||||
try {
|
|
||||||
const { stdout } = await this.execCommand(`sc query ${this.serviceName}`);
|
|
||||||
return stdout.includes('SERVICE_NAME');
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务状态
|
|
||||||
* @returns {string} 'RUNNING' | 'STOPPED' | 'NOT_EXISTS'
|
|
||||||
*/
|
|
||||||
async getServiceStatus() {
|
|
||||||
try {
|
|
||||||
const { stdout } = await this.execCommand(`sc query ${this.serviceName}`);
|
|
||||||
|
|
||||||
if (stdout.includes('RUNNING')) {
|
|
||||||
return 'RUNNING';
|
|
||||||
} else if (stdout.includes('STOPPED')) {
|
|
||||||
return 'STOPPED';
|
|
||||||
} else {
|
|
||||||
return 'UNKNOWN';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return 'NOT_EXISTS';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查服务路径是否匹配当前环境
|
|
||||||
* @returns {Promise<boolean>} true=路径匹配,false=路径不匹配
|
|
||||||
*/
|
|
||||||
async checkServicePath() {
|
|
||||||
try {
|
|
||||||
const { stdout } = await this.execCommand(`sc qc ${this.serviceName}`);
|
|
||||||
|
|
||||||
// 从输出中提取 BINARY_PATH_NAME
|
|
||||||
const match = stdout.match(/BINARY_PATH_NAME\s*:\s*(.+)/i);
|
|
||||||
if (!match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const binaryPath = match[1].trim();
|
|
||||||
const expectedPath = path.join(this.binPath, 'mysqld.exe');
|
|
||||||
|
|
||||||
// 检查路径是否包含当前的 MySQL 目录
|
|
||||||
const pathMatches = binaryPath.toLowerCase().includes(this.mysqlPath.toLowerCase());
|
|
||||||
|
|
||||||
this.log('system', `服务路径检查:`);
|
|
||||||
this.log('system', ` 当前环境: ${this.mysqlPath}`);
|
|
||||||
this.log('system', ` 服务路径: ${binaryPath}`);
|
|
||||||
this.log('system', ` 路径匹配: ${pathMatches ? '是' : '否'}`);
|
|
||||||
|
|
||||||
return pathMatches;
|
|
||||||
} catch (error) {
|
|
||||||
this.log('warn', `检查服务路径失败: ${error.message}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取配置文件中的端口
|
* 读取配置文件中的端口
|
||||||
*/
|
*/
|
||||||
@@ -131,7 +67,7 @@ class MySQLServiceManager {
|
|||||||
return match ? parseInt(match[1]) : 3306;
|
return match ? parseInt(match[1]) : 3306;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MySQL Service] Failed to read port from config:', error);
|
console.error('[MySQL] Failed to read port from config:', error);
|
||||||
}
|
}
|
||||||
return 3306;
|
return 3306;
|
||||||
}
|
}
|
||||||
@@ -175,7 +111,7 @@ default-character-set=utf8mb4
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
fs.writeFileSync(this.configFile, config, 'utf-8');
|
fs.writeFileSync(this.configFile, config, 'utf-8');
|
||||||
console.log('[MySQL Service] Generated my.ini for port:', port);
|
this.log('system', `生成配置文件,端口: ${port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,7 +119,7 @@ default-character-set=utf8mb4
|
|||||||
*/
|
*/
|
||||||
async initializeDatabase() {
|
async initializeDatabase() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log('[MySQL Service] Initializing MySQL database...');
|
this.log('system', '正在初始化 MySQL 数据库...');
|
||||||
|
|
||||||
const mysqld = path.join(this.binPath, 'mysqld.exe');
|
const mysqld = path.join(this.binPath, 'mysqld.exe');
|
||||||
const initProcess = spawn(mysqld, [
|
const initProcess = spawn(mysqld, [
|
||||||
@@ -191,207 +127,30 @@ default-character-set=utf8mb4
|
|||||||
'--initialize-insecure'
|
'--initialize-insecure'
|
||||||
], {
|
], {
|
||||||
cwd: this.mysqlPath,
|
cwd: this.mysqlPath,
|
||||||
stdio: 'inherit'
|
stdio: 'pipe'
|
||||||
|
});
|
||||||
|
|
||||||
|
initProcess.stdout.on('data', (data) => {
|
||||||
|
this.log('system', data.toString().trim());
|
||||||
|
});
|
||||||
|
|
||||||
|
initProcess.stderr.on('data', (data) => {
|
||||||
|
this.log('system', data.toString().trim());
|
||||||
});
|
});
|
||||||
|
|
||||||
initProcess.on('close', (code) => {
|
initProcess.on('close', (code) => {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
console.log('[MySQL Service] Database initialized successfully');
|
this.log('success', '数据库初始化成功');
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`Database initialization failed with code ${code}`));
|
reject(new Error(`数据库初始化失败,退出码: ${code}`));
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安装MySQL服务
|
|
||||||
*/
|
|
||||||
async installService() {
|
|
||||||
try {
|
|
||||||
const mysqld = path.join(this.binPath, 'mysqld.exe');
|
|
||||||
|
|
||||||
// 使用绝对路径和完整参数,避免环境变量干扰
|
|
||||||
// 关键:使用 --defaults-file 的绝对路径,确保不会读取系统 MySQL 的配置
|
|
||||||
const absoluteConfigPath = path.resolve(this.configFile);
|
|
||||||
const absoluteMysqldPath = path.resolve(mysqld);
|
|
||||||
|
|
||||||
// 构建完全隔离的安装命令
|
|
||||||
const command = `"${absoluteMysqldPath}" --install ${this.serviceName} --defaults-file="${absoluteConfigPath}"`;
|
|
||||||
|
|
||||||
this.log('system', `准备安装服务: ${this.serviceName}`);
|
|
||||||
this.log('system', `MySQL可执行文件: ${absoluteMysqldPath}`);
|
|
||||||
this.log('system', `配置文件: ${absoluteConfigPath}`);
|
|
||||||
|
|
||||||
// 检查文件是否存在
|
|
||||||
if (!fs.existsSync(mysqld)) {
|
|
||||||
throw new Error(`mysqld.exe not found at: ${mysqld}`);
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(this.configFile)) {
|
|
||||||
throw new Error(`Config file not found at: ${this.configFile}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在安装前清理环境变量(避免继承系统 MySQL 的环境变量)
|
|
||||||
const cleanEnv = { ...process.env };
|
|
||||||
delete cleanEnv.MYSQL_HOME;
|
|
||||||
delete cleanEnv.MYSQL_TCP_PORT;
|
|
||||||
|
|
||||||
// 使用 spawn 代替 exec,更好地控制环境
|
|
||||||
const { spawn } = require('child_process');
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const installProcess = spawn(absoluteMysqldPath, [
|
|
||||||
'--install',
|
|
||||||
this.serviceName,
|
|
||||||
`--defaults-file=${absoluteConfigPath}`
|
|
||||||
], {
|
|
||||||
env: cleanEnv,
|
|
||||||
cwd: this.mysqlPath,
|
|
||||||
stdio: 'pipe'
|
|
||||||
});
|
|
||||||
|
|
||||||
let stdout = '';
|
|
||||||
let stderr = '';
|
|
||||||
|
|
||||||
if (installProcess.stdout) {
|
|
||||||
installProcess.stdout.on('data', (data) => {
|
|
||||||
stdout += data.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installProcess.stderr) {
|
|
||||||
installProcess.stderr.on('data', (data) => {
|
|
||||||
stderr += data.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
installProcess.on('close', (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
this.log('system', '服务安装命令执行成功');
|
|
||||||
if (stdout) this.log('system', `输出: ${stdout}`);
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
const errorMsg = stderr || stdout || `Exit code: ${code}`;
|
|
||||||
this.log('error', `安装命令失败: ${errorMsg}`);
|
|
||||||
reject(new Error(errorMsg));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
installProcess.on('error', (err) => {
|
initProcess.on('error', (err) => {
|
||||||
this.log('error', `进程启动失败: ${err.message}`);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[MySQL Service] Failed to install service:', error);
|
|
||||||
this.log('error', `服务安装失败: ${error.message}`);
|
|
||||||
throw new Error(`Failed to install MySQL service: ${error.message}. Please run as administrator.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有其他MySQL服务
|
|
||||||
*/
|
|
||||||
async checkOtherMySQLServices() {
|
|
||||||
try {
|
|
||||||
const { stdout } = await this.execCommand('sc query type= service state= all | findstr /i "mysql"');
|
|
||||||
const services = [];
|
|
||||||
const lines = stdout.split('\n');
|
|
||||||
for (const line of lines) {
|
|
||||||
const match = line.match(/SERVICE_NAME:\s*(\S+)/);
|
|
||||||
if (match && match[1] !== this.serviceName) {
|
|
||||||
services.push(match[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return services;
|
|
||||||
} catch (error) {
|
|
||||||
// 如果没找到任何MySQL服务,命令会失败,这是正常的
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动服务
|
|
||||||
*/
|
|
||||||
async startService() {
|
|
||||||
try {
|
|
||||||
console.log('[MySQL Service] Starting service...');
|
|
||||||
await this.execCommand(`net start ${this.serviceName}`);
|
|
||||||
console.log('[MySQL Service] Service started successfully');
|
|
||||||
|
|
||||||
// 等待MySQL完全启动
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[MySQL Service] Failed to start service:', error);
|
|
||||||
console.error('[MySQL Service] This may be due to invalid service configuration');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止服务
|
|
||||||
*/
|
|
||||||
async stopService() {
|
|
||||||
try {
|
|
||||||
console.log('[MySQL Service] Stopping service...');
|
|
||||||
await this.execCommand(`net stop ${this.serviceName}`);
|
|
||||||
console.log('[MySQL Service] Service stopped successfully');
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[MySQL Service] Failed to stop service:', error);
|
|
||||||
// 停止失败不抛异常,因为可能服务本来就是停止状态
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除服务
|
|
||||||
*/
|
|
||||||
async removeService() {
|
|
||||||
try {
|
|
||||||
// 先尝试停止服务(如果正在运行)
|
|
||||||
try {
|
|
||||||
await this.stopService();
|
|
||||||
} catch (e) {
|
|
||||||
// 服务可能已经停止,忽略错误
|
|
||||||
console.log('[MySQL Service] Service already stopped or not running');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除服务
|
|
||||||
await this.execCommand(`sc delete ${this.serviceName}`);
|
|
||||||
console.log('[MySQL Service] Service removed successfully');
|
|
||||||
|
|
||||||
// 等待服务完全删除
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[MySQL Service] Failed to remove service:', error);
|
|
||||||
// 即使删除失败也返回 true,因为可能服务本来就不存在
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新服务端口
|
|
||||||
* 需要停止服务、修改配置、重启服务
|
|
||||||
*/
|
|
||||||
async updateServicePort(newPort) {
|
|
||||||
console.log('[MySQL Service] Updating service port to:', newPort);
|
|
||||||
|
|
||||||
// 1. 停止服务
|
|
||||||
await this.stopService();
|
|
||||||
|
|
||||||
// 2. 更新配置文件
|
|
||||||
this.generateMyIni(newPort);
|
|
||||||
|
|
||||||
// 3. 启动服务
|
|
||||||
await this.startService();
|
|
||||||
|
|
||||||
console.log('[MySQL Service] Port updated successfully');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -402,279 +161,229 @@ default-character-set=utf8mb4
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空数据库目录(重新初始化时使用)
|
* 检查是否有残留的 mysqld 进程
|
||||||
*/
|
*/
|
||||||
clearDatabase() {
|
async checkAndKillOrphanProcess() {
|
||||||
console.log('[MySQL Service] Clearing database directory...');
|
try {
|
||||||
if (fs.existsSync(this.dataPath)) {
|
// 检查是否有 mysqld.exe 进程在运行
|
||||||
// 递归删除目录
|
const { stdout } = await this.execCommand('tasklist /FI "IMAGENAME eq mysqld.exe" /FO CSV /NH');
|
||||||
const rimraf = (dir) => {
|
|
||||||
if (fs.existsSync(dir)) {
|
if (stdout.includes('mysqld.exe')) {
|
||||||
fs.readdirSync(dir).forEach((file) => {
|
this.log('warn', '检测到残留的 MySQL 进程,正在清理...');
|
||||||
const curPath = path.join(dir, file);
|
|
||||||
if (fs.lstatSync(curPath).isDirectory()) {
|
// 尝试优雅关闭
|
||||||
rimraf(curPath);
|
try {
|
||||||
} else {
|
const mysqladmin = path.join(this.binPath, 'mysqladmin.exe');
|
||||||
fs.unlinkSync(curPath);
|
await this.execCommand(`"${mysqladmin}" -u root shutdown`);
|
||||||
|
await this.sleep(2000);
|
||||||
|
this.log('success', '残留进程已优雅关闭');
|
||||||
|
} catch (e) {
|
||||||
|
// 优雅关闭失败,强制杀死
|
||||||
|
this.log('warn', '优雅关闭失败,强制终止...');
|
||||||
|
await this.execCommand('taskkill /F /IM mysqld.exe');
|
||||||
|
await this.sleep(1000);
|
||||||
|
this.log('success', '残留进程已强制终止');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
fs.rmdirSync(dir);
|
|
||||||
}
|
}
|
||||||
};
|
} catch (error) {
|
||||||
rimraf(this.dataPath);
|
// tasklist 命令失败通常意味着没有找到进程,这是正常的
|
||||||
}
|
}
|
||||||
// 重新创建空目录
|
|
||||||
fs.mkdirSync(this.dataPath, { recursive: true });
|
|
||||||
console.log('[MySQL Service] Database directory cleared');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主流程:确保MySQL服务运行
|
* 进程模式:启动 MySQL 进程
|
||||||
* @param {function} findAvailablePort - 查找可用端口的函数
|
|
||||||
* @param {function} waitForPort - 等待端口就绪的函数
|
|
||||||
* @returns {Promise<number>} 返回MySQL运行的端口
|
|
||||||
*/
|
*/
|
||||||
async ensureServiceRunning(findAvailablePort, waitForPort) {
|
async startMySQLProcess() {
|
||||||
this.log('system', '开始MySQL服务检查流程');
|
return new Promise((resolve, reject) => {
|
||||||
this.log('system', `MySQL路径: ${this.mysqlPath}`);
|
const mysqld = path.join(this.binPath, 'mysqld.exe');
|
||||||
|
|
||||||
|
this.log('system', '正在启动 MySQL 进程...');
|
||||||
|
this.log('system', `可执行文件: ${mysqld}`);
|
||||||
this.log('system', `配置文件: ${this.configFile}`);
|
this.log('system', `配置文件: ${this.configFile}`);
|
||||||
|
|
||||||
const serviceStatus = await this.getServiceStatus();
|
// 检查文件是否存在
|
||||||
this.log('system', `服务状态: ${serviceStatus}`);
|
if (!fs.existsSync(mysqld)) {
|
||||||
|
return reject(new Error(`mysqld.exe 不存在: ${mysqld}`));
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(this.configFile)) {
|
||||||
|
return reject(new Error(`配置文件不存在: ${this.configFile}`));
|
||||||
|
}
|
||||||
|
|
||||||
// 如果服务存在,检查路径是否匹配
|
this.mysqlProcess = spawn(mysqld, [
|
||||||
if (serviceStatus !== 'NOT_EXISTS') {
|
`--defaults-file=${this.configFile}`,
|
||||||
const pathMatches = await this.checkServicePath();
|
'--console'
|
||||||
|
], {
|
||||||
|
cwd: this.mysqlPath,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
windowsHide: true
|
||||||
|
});
|
||||||
|
|
||||||
if (!pathMatches) {
|
let startupComplete = false;
|
||||||
this.log('warn', '检测到服务路径与当前环境不匹配');
|
let startupTimeout = null;
|
||||||
this.log('warn', '这可能是从其他环境(开发/打包)安装的旧服务');
|
|
||||||
this.log('system', '正在删除旧服务并重新安装...');
|
|
||||||
|
|
||||||
// 删除旧服务
|
// 监听 stderr(MySQL 主要输出在 stderr)
|
||||||
await this.removeService();
|
this.mysqlProcess.stderr.on('data', (data) => {
|
||||||
|
const msg = data.toString().trim();
|
||||||
// 重置状态,后续将按新安装流程处理
|
if (msg) {
|
||||||
this.log('system', '旧服务已删除,准备重新安装');
|
// 检查启动成功标志
|
||||||
// 继续执行下面的 NOT_EXISTS 流程
|
if (msg.includes('ready for connections') || msg.includes('port:')) {
|
||||||
|
if (!startupComplete) {
|
||||||
|
startupComplete = true;
|
||||||
|
if (startupTimeout) clearTimeout(startupTimeout);
|
||||||
|
this.log('success', 'MySQL 进程已就绪');
|
||||||
|
resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新获取服务状态(如果删除了旧服务,状态会变为 NOT_EXISTS)
|
// 输出日志(过滤一些不重要的信息)
|
||||||
const currentStatus = await this.getServiceStatus();
|
if (!msg.includes('[Note]') || msg.includes('error') || msg.includes('ready')) {
|
||||||
|
this.log('system', `[mysqld] ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (currentStatus === 'NOT_EXISTS') {
|
this.mysqlProcess.stdout.on('data', (data) => {
|
||||||
// ========== 服务不存在,全新安装 ==========
|
const msg = data.toString().trim();
|
||||||
this.log('system', '服务不存在,开始安装流程');
|
if (msg) {
|
||||||
|
this.log('system', `[mysqld] ${msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 0. 检查是否有其他MySQL服务(仅作提示,不影响安装)
|
this.mysqlProcess.on('error', (err) => {
|
||||||
const otherServices = await this.checkOtherMySQLServices();
|
this.log('error', `MySQL 进程启动失败: ${err.message}`);
|
||||||
if (otherServices.length > 0) {
|
this.mysqlProcess = null;
|
||||||
this.log('system', `检测到系统中有其他MySQL服务: ${otherServices.join(', ')}`);
|
reject(err);
|
||||||
this.log('system', 'mysql9100 将使用独立的配置和端口,不会冲突');
|
});
|
||||||
|
|
||||||
|
this.mysqlProcess.on('exit', (code, signal) => {
|
||||||
|
this.log('system', `MySQL 进程退出,代码: ${code}, 信号: ${signal}`);
|
||||||
|
this.mysqlProcess = null;
|
||||||
|
|
||||||
|
// 如果还没完成启动就退出了,说明启动失败
|
||||||
|
if (!startupComplete) {
|
||||||
|
reject(new Error(`MySQL 进程异常退出,代码: ${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 超时处理(30秒)
|
||||||
|
startupTimeout = setTimeout(() => {
|
||||||
|
if (!startupComplete && this.mysqlProcess) {
|
||||||
|
// 进程还在运行,认为启动成功
|
||||||
|
startupComplete = true;
|
||||||
|
this.log('success', 'MySQL 进程启动完成(超时检测)');
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 查找可用端口
|
/**
|
||||||
|
* 进程模式:停止 MySQL 进程
|
||||||
|
*/
|
||||||
|
async stopMySQLProcess() {
|
||||||
|
if (!this.mysqlProcess) {
|
||||||
|
this.log('system', 'MySQL 进程未运行');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('system', '正在停止 MySQL 进程...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 方式1:通过 mysqladmin 优雅关闭
|
||||||
|
const mysqladmin = path.join(this.binPath, 'mysqladmin.exe');
|
||||||
|
await this.execCommand(`"${mysqladmin}" -u root shutdown`);
|
||||||
|
|
||||||
|
// 等待进程退出
|
||||||
|
await this.sleep(3000);
|
||||||
|
|
||||||
|
if (this.mysqlProcess && !this.mysqlProcess.killed) {
|
||||||
|
// 如果还没退出,强制杀死
|
||||||
|
this.log('warn', '优雅关闭超时,强制终止...');
|
||||||
|
this.mysqlProcess.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('success', 'MySQL 进程已关闭');
|
||||||
|
} catch (error) {
|
||||||
|
// mysqladmin 关闭失败,强制杀死进程
|
||||||
|
this.log('warn', `优雅关闭失败: ${error.message},强制终止...`);
|
||||||
|
|
||||||
|
if (this.mysqlProcess) {
|
||||||
|
this.mysqlProcess.kill('SIGTERM');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保进程被杀死
|
||||||
|
try {
|
||||||
|
await this.execCommand('taskkill /F /IM mysqld.exe');
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mysqlProcess = null;
|
||||||
|
this.currentPort = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 MySQL 进程是否在运行
|
||||||
|
*/
|
||||||
|
isMySQLRunning() {
|
||||||
|
return this.mysqlProcess !== null && !this.mysqlProcess.killed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主流程:确保 MySQL 运行(进程模式)
|
||||||
|
* @param {function} findAvailablePort - 查找可用端口的函数
|
||||||
|
* @param {function} waitForPort - 等待端口就绪的函数
|
||||||
|
* @returns {Promise<number>} 返回 MySQL 运行的端口
|
||||||
|
*/
|
||||||
|
async ensureServiceRunning(findAvailablePort, waitForPort) {
|
||||||
|
this.log('system', '开始 MySQL 进程检查流程(进程模式)');
|
||||||
|
this.log('system', `MySQL 路径: ${this.mysqlPath}`);
|
||||||
|
this.log('system', `配置文件: ${this.configFile}`);
|
||||||
|
|
||||||
|
// 1. 检查是否已有 MySQL 进程在运行
|
||||||
|
if (this.isMySQLRunning()) {
|
||||||
|
const port = this.currentPort || this.readPortFromConfig();
|
||||||
|
this.log('success', `MySQL 进程已在运行,端口: ${port}`);
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查并清理残留进程
|
||||||
|
await this.checkAndKillOrphanProcess();
|
||||||
|
|
||||||
|
// 3. 查找可用端口
|
||||||
const port = await findAvailablePort(3306, 100);
|
const port = await findAvailablePort(3306, 100);
|
||||||
this.log('system', `找到可用端口: ${port}`);
|
this.log('system', `找到可用端口: ${port}`);
|
||||||
|
|
||||||
// 2. 检查数据库是否已初始化
|
// 4. 检查数据库是否已初始化
|
||||||
if (!this.isDatabaseInitialized()) {
|
if (!this.isDatabaseInitialized()) {
|
||||||
// 数据库未初始化,正常初始化流程
|
|
||||||
this.log('system', '数据库未初始化,开始初始化...');
|
this.log('system', '数据库未初始化,开始初始化...');
|
||||||
this.generateMyIni(port);
|
this.generateMyIni(port);
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
} else {
|
} else {
|
||||||
// 数据库已存在(可能是从其他机器拷贝过来的)
|
|
||||||
this.log('system', '检测到已有数据库目录');
|
this.log('system', '检测到已有数据库目录');
|
||||||
|
// 更新配置文件(可能端口变了)
|
||||||
// 无论端口是什么,都使用现有数据库,只修改配置文件
|
|
||||||
if (port !== 3306) {
|
|
||||||
this.log('system', `端口3306被占用,将使用端口${port}`);
|
|
||||||
this.log('system', '保留现有数据库,仅更新端口配置');
|
|
||||||
} else {
|
|
||||||
this.log('system', '端口3306可用,使用现有数据库');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只生成新配置,不删除数据
|
|
||||||
this.generateMyIni(port);
|
this.generateMyIni(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 安装服务
|
// 5. 启动 MySQL 进程
|
||||||
this.log('system', '开始安装MySQL服务...');
|
this.log('system', '启动 MySQL 进程...');
|
||||||
await this.installService();
|
await this.startMySQLProcess();
|
||||||
|
|
||||||
// 3.1 验证服务是否真的安装成功
|
|
||||||
await this.sleep(2000); // 等待2秒让服务注册完成
|
|
||||||
const verifyStatus = await this.getServiceStatus();
|
|
||||||
if (verifyStatus === 'NOT_EXISTS') {
|
|
||||||
this.log('error', '服务安装失败:服务未创建');
|
|
||||||
this.log('error', '可能的原因:');
|
|
||||||
this.log('error', '1. 权限不足(请确保以管理员身份运行)');
|
|
||||||
this.log('error', '2. MySQL可执行文件损坏');
|
|
||||||
this.log('error', '3. 配置文件路径有误');
|
|
||||||
|
|
||||||
// 检查是否有其他MySQL服务
|
|
||||||
const otherServices = await this.checkOtherMySQLServices();
|
|
||||||
if (otherServices.length > 0) {
|
|
||||||
this.log('system', `提示:系统中存在其他MySQL服务 ${otherServices.join(', ')}`);
|
|
||||||
this.log('system', '如果问题持续,请查看文档:doc/MySQL服务冲突处理方案.md');
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('MySQL服务安装失败,请检查管理员权限和日志');
|
|
||||||
}
|
|
||||||
this.log('success', '服务安装成功,已验证');
|
|
||||||
|
|
||||||
// 3.2 设置服务为开机自启
|
|
||||||
this.log('system', '正在配置服务为开机自启...');
|
|
||||||
try {
|
|
||||||
await this.execCommand(`sc config ${this.serviceName} start= auto`);
|
|
||||||
this.log('success', '服务已配置为开机自启');
|
|
||||||
|
|
||||||
// 验证配置是否生效
|
|
||||||
const { stdout: qcOutput } = await this.execCommand(`sc qc ${this.serviceName}`);
|
|
||||||
if (qcOutput.includes('AUTO_START')) {
|
|
||||||
this.log('success', '开机自启配置验证成功');
|
|
||||||
} else {
|
|
||||||
this.log('warn', '开机自启配置可能未生效,请手动检查');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.log('warn', `设置开机自启失败: ${error.message}`);
|
|
||||||
this.log('warn', '服务已安装,但需要手动设置启动类型');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 启动服务
|
|
||||||
this.log('system', '开始启动MySQL服务...');
|
|
||||||
await this.startService();
|
|
||||||
|
|
||||||
const finalPort = this.readPortFromConfig();
|
|
||||||
this.log('success', `MySQL服务安装并启动成功,端口: ${finalPort}`);
|
|
||||||
|
|
||||||
// 4. 等待端口就绪
|
|
||||||
await waitForPort(finalPort, 30000);
|
|
||||||
|
|
||||||
return finalPort;
|
|
||||||
}
|
|
||||||
else if (currentStatus === 'STOPPED') {
|
|
||||||
// ========== 服务存在但停止,尝试启动 ==========
|
|
||||||
this.log('system', '服务已停止,尝试启动...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 先读取配置端口
|
|
||||||
const configPort = this.readPortFromConfig();
|
|
||||||
|
|
||||||
// 重新生成配置文件,确保使用绝对路径(修复相对路径问题)
|
|
||||||
this.log('system', '更新配置文件,确保路径正确...');
|
|
||||||
this.generateMyIni(configPort);
|
|
||||||
|
|
||||||
// 尝试启动服务
|
|
||||||
await this.startService();
|
|
||||||
|
|
||||||
this.log('success', `服务启动成功,端口: ${configPort}`);
|
|
||||||
|
|
||||||
// 等待端口就绪
|
|
||||||
await waitForPort(configPort, 30000);
|
|
||||||
|
|
||||||
return configPort;
|
|
||||||
} catch (startError) {
|
|
||||||
// 启动失败,先诊断问题
|
|
||||||
this.log('error', '服务启动失败');
|
|
||||||
this.log('system', `失败原因: ${startError.message}`);
|
|
||||||
|
|
||||||
// 检查是否是端口被占用导致的启动失败
|
|
||||||
const configPort = this.readPortFromConfig();
|
|
||||||
this.log('system', `配置的端口: ${configPort}`);
|
|
||||||
|
|
||||||
// 检查端口是否被占用
|
|
||||||
const PortChecker = require('./port-checker');
|
|
||||||
const portAvailable = await PortChecker.findAvailablePort(configPort, 1) === configPort;
|
|
||||||
|
|
||||||
if (!portAvailable) {
|
|
||||||
// 端口被占用,需要更换端口并重新安装
|
|
||||||
this.log('warn', `端口${configPort}已被占用,尝试重新配置服务`);
|
|
||||||
|
|
||||||
// 1. 删除旧服务
|
|
||||||
this.log('system', '正在删除旧服务...');
|
|
||||||
await this.removeService();
|
|
||||||
|
|
||||||
// 2. 查找可用端口
|
|
||||||
const port = await findAvailablePort(3306, 100);
|
|
||||||
this.log('system', `找到可用端口: ${port}`);
|
|
||||||
|
|
||||||
// 3. 保留现有数据库,只更新配置
|
|
||||||
if (this.isDatabaseInitialized()) {
|
|
||||||
this.log('system', '保留现有数据库,更新端口配置');
|
|
||||||
this.generateMyIni(port);
|
|
||||||
} else {
|
|
||||||
this.log('system', '数据库未初始化,开始初始化...');
|
|
||||||
this.generateMyIni(port);
|
|
||||||
await this.initializeDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 重新安装服务
|
|
||||||
this.log('system', '开始重新安装MySQL服务...');
|
|
||||||
await this.installService();
|
|
||||||
|
|
||||||
// 4.1 设置开机自启
|
|
||||||
this.log('system', '正在配置服务为开机自启...');
|
|
||||||
try {
|
|
||||||
await this.execCommand(`sc config ${this.serviceName} start= auto`);
|
|
||||||
this.log('success', '服务已配置为开机自启');
|
|
||||||
} catch (error) {
|
|
||||||
this.log('warn', `设置开机自启失败: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 启动服务
|
|
||||||
this.log('system', '开始启动MySQL服务...');
|
|
||||||
await this.startService();
|
|
||||||
|
|
||||||
const finalPort = this.readPortFromConfig();
|
|
||||||
this.log('success', `MySQL服务重新安装并启动成功,端口: ${finalPort}`);
|
|
||||||
|
|
||||||
// 6. 等待端口就绪
|
// 6. 等待端口就绪
|
||||||
await waitForPort(finalPort, 30000);
|
const finalPort = this.readPortFromConfig();
|
||||||
|
this.currentPort = finalPort;
|
||||||
|
|
||||||
return finalPort;
|
this.log('system', `等待端口 ${finalPort} 就绪...`);
|
||||||
} else {
|
const portReady = await waitForPort(finalPort, 30000);
|
||||||
// 端口没被占用,但服务启动失败,可能是配置问题或权限问题
|
|
||||||
this.log('error', '服务启动失败,但端口未被占用');
|
|
||||||
this.log('error', '可能的原因:');
|
|
||||||
this.log('error', '1. 配置文件路径错误或损坏');
|
|
||||||
this.log('error', '2. 数据库文件损坏');
|
|
||||||
this.log('error', '3. 权限不足');
|
|
||||||
this.log('error', '4. MySQL可执行文件问题');
|
|
||||||
this.log('system', '建议:检查日志文件 mysql/mysql_error.log');
|
|
||||||
|
|
||||||
throw new Error(`MySQL服务启动失败: ${startError.message}。请检查配置和日志。`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentStatus === 'RUNNING') {
|
|
||||||
// ========== 服务正在运行 ==========
|
|
||||||
const configPort = this.readPortFromConfig();
|
|
||||||
this.log('success', `MySQL服务已在运行,端口: ${configPort}`);
|
|
||||||
|
|
||||||
// 验证端口是否真的在监听
|
|
||||||
const PortChecker = require('./port-checker');
|
|
||||||
const portReady = await PortChecker.checkPort(configPort);
|
|
||||||
|
|
||||||
if (!portReady) {
|
if (!portReady) {
|
||||||
this.log('warn', '服务运行中但端口未就绪,重新启动...');
|
throw new Error(`MySQL 端口 ${finalPort} 未能在 30 秒内就绪`);
|
||||||
|
|
||||||
// 重新生成配置文件
|
|
||||||
this.generateMyIni(configPort);
|
|
||||||
|
|
||||||
await this.stopService();
|
|
||||||
await this.startService();
|
|
||||||
await waitForPort(configPort, 30000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return configPort;
|
this.log('success', `MySQL 进程启动成功,端口: ${finalPort}`);
|
||||||
}
|
return finalPort;
|
||||||
|
|
||||||
throw new Error('Unknown service status: ' + currentStatus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MySQLServiceManager;
|
module.exports = MySQLServiceManager;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user