提交额外资源
This commit is contained in:
373
scripts/start-mysql.js
Normal file
373
scripts/start-mysql.js
Normal file
@@ -0,0 +1,373 @@
|
||||
const { spawn, exec } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
class MySQLManager {
|
||||
constructor() {
|
||||
// 在开发与打包后均可解析到应用根目录下的 mysql 目录
|
||||
// 开发环境:项目根目录
|
||||
// 打包后:应用根目录(win-unpacked)
|
||||
const isDev = !process.resourcesPath;
|
||||
const baseDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
: path.dirname(process.resourcesPath);
|
||||
this.mysqlPath = path.join(baseDir, 'mysql');
|
||||
this.binPath = path.join(this.mysqlPath, 'bin');
|
||||
this.dataPath = path.join(this.mysqlPath, 'data');
|
||||
this.process = null;
|
||||
this.currentPort = null;
|
||||
}
|
||||
|
||||
// 检查MySQL是否已初始化
|
||||
isInitialized() {
|
||||
return fs.existsSync(this.dataPath) && fs.readdirSync(this.dataPath).length > 0;
|
||||
}
|
||||
|
||||
// 初始化MySQL数据库
|
||||
async initialize() {
|
||||
if (this.isInitialized()) {
|
||||
console.log('MySQL already initialized');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mysqld = path.join(this.binPath, 'mysqld.exe');
|
||||
|
||||
// 创建初始化SQL文件(授权127.0.0.1和所有主机)
|
||||
const initSqlPath = path.join(this.mysqlPath, 'init_grant.sql');
|
||||
const initSql = `
|
||||
CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION;
|
||||
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(initSqlPath, initSql, 'utf-8');
|
||||
console.log('[MySQL] Created init SQL file for granting permissions');
|
||||
} catch (error) {
|
||||
console.error('[MySQL] Failed to create init SQL file:', error);
|
||||
}
|
||||
|
||||
// 使用 --init-file 参数初始化并授权
|
||||
const initProcess = spawn(mysqld, [
|
||||
'--initialize-insecure',
|
||||
`--init-file=${initSqlPath}`
|
||||
], {
|
||||
cwd: this.mysqlPath,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
initProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('[MySQL] Initialized successfully with permissions granted');
|
||||
// 删除临时SQL文件
|
||||
try {
|
||||
if (fs.existsSync(initSqlPath)) {
|
||||
fs.unlinkSync(initSqlPath);
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略删除失败
|
||||
}
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`MySQL initialization failed with code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 启动MySQL服务
|
||||
start(port = 3306) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// 确保数据库已初始化
|
||||
await this.initialize();
|
||||
|
||||
const mysqld = path.join(this.binPath, 'mysqld.exe');
|
||||
|
||||
// 启动MySQL,指定端口
|
||||
this.process = spawn(mysqld, [
|
||||
'--console',
|
||||
`--port=${port}`
|
||||
], {
|
||||
cwd: this.mysqlPath,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
this.currentPort = port;
|
||||
|
||||
// 将当前端口写入文件,供停止脚本使用
|
||||
try {
|
||||
const portFilePath = path.join(this.mysqlPath, '.running-port');
|
||||
fs.writeFileSync(portFilePath, port.toString(), 'utf-8');
|
||||
console.log(`[MySQL] Port ${port} recorded to ${portFilePath}`);
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] Failed to record port:', error);
|
||||
}
|
||||
|
||||
let output = '';
|
||||
this.process.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
console.log('MySQL:', data.toString());
|
||||
|
||||
// MySQL启动完成的标志
|
||||
if (output.includes('ready for connections') || output.includes('MySQL Community Server')) {
|
||||
console.log(`MySQL started successfully on port ${port}`);
|
||||
|
||||
// 自动授权 root 用户从任何主机连接
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log('[MySQL] Waiting 3 seconds before granting permissions...');
|
||||
await this.grantRootAccess();
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] Failed to grant root access, but MySQL is running:', error.message);
|
||||
}
|
||||
resolve(port);
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
|
||||
this.process.stderr.on('data', (data) => {
|
||||
console.error('MySQL Error:', data.toString());
|
||||
});
|
||||
|
||||
this.process.on('close', (code) => {
|
||||
console.log(`MySQL process exited with code ${code}`);
|
||||
this.process = null;
|
||||
this.currentPort = null;
|
||||
|
||||
// 删除端口记录文件
|
||||
try {
|
||||
const portFilePath = path.join(this.mysqlPath, '.running-port');
|
||||
if (fs.existsSync(portFilePath)) {
|
||||
fs.unlinkSync(portFilePath);
|
||||
console.log('[MySQL] Port record file removed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] Failed to remove port record:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// 超时处理
|
||||
setTimeout(async () => {
|
||||
if (this.process && !this.process.killed) {
|
||||
console.log(`MySQL started on port ${port} (timeout reached, assuming success)`);
|
||||
|
||||
// 自动授权 root 用户从任何主机连接
|
||||
try {
|
||||
console.log('[MySQL] Granting permissions...');
|
||||
await this.grantRootAccess();
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] Failed to grant root access, but MySQL is running:', error.message);
|
||||
}
|
||||
|
||||
resolve(port);
|
||||
}
|
||||
}, 18000);
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 授权 root 用户从 127.0.0.1 访问
|
||||
grantRootAccess() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建 SQL 文件
|
||||
const sqlFilePath = path.join(this.mysqlPath, 'grant_root.sql');
|
||||
const sqlContent = `
|
||||
CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION;
|
||||
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
||||
FLUSH PRIVILEGES;
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(sqlFilePath, sqlContent, 'utf-8');
|
||||
} catch (error) {
|
||||
console.error('[MySQL] Failed to create grant SQL file:', error);
|
||||
return resolve(); // 继续启动
|
||||
}
|
||||
|
||||
const mysqlExe = path.join(this.binPath, 'mysql.exe');
|
||||
const grantProcess = spawn(mysqlExe, [
|
||||
'--host=localhost',
|
||||
`--port=${this.currentPort}`,
|
||||
'--user=root'
|
||||
], {
|
||||
cwd: this.mysqlPath,
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// 通过 stdin 输入 SQL
|
||||
grantProcess.stdin.write(sqlContent);
|
||||
grantProcess.stdin.end();
|
||||
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
grantProcess.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
grantProcess.stderr.on('data', (data) => {
|
||||
errorOutput += data.toString();
|
||||
});
|
||||
|
||||
grantProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('[MySQL] Root user granted access from 127.0.0.1 and all hosts');
|
||||
resolve();
|
||||
} else {
|
||||
console.error('[MySQL] Grant access failed (code:', code, ')');
|
||||
console.error('[MySQL] Error output:', errorOutput);
|
||||
console.error('[MySQL] Standard output:', output);
|
||||
// 即使失败也 resolve,让应用继续启动
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 获取当前MySQL端口
|
||||
getCurrentPort() {
|
||||
return this.currentPort || 3306;
|
||||
}
|
||||
|
||||
// 停止MySQL服务
|
||||
stop() {
|
||||
return new Promise(async (resolve) => {
|
||||
if (this.process && !this.process.killed) {
|
||||
console.log('[MySQL] Stopping MySQL...');
|
||||
|
||||
// 方法1: 尝试使用 mysqladmin shutdown 优雅关闭
|
||||
try {
|
||||
console.log('[MySQL] Trying mysqladmin shutdown...');
|
||||
const mysqladmin = path.join(this.binPath, 'mysqladmin.exe');
|
||||
|
||||
if (fs.existsSync(mysqladmin)) {
|
||||
const shutdownProcess = spawn(mysqladmin, [
|
||||
'-u', 'root',
|
||||
'-pnjcnpqs',
|
||||
'--port=' + this.currentPort,
|
||||
'shutdown'
|
||||
], {
|
||||
cwd: this.mysqlPath,
|
||||
stdio: 'ignore'
|
||||
});
|
||||
|
||||
// 等待 mysqladmin 执行完成(最多5秒)
|
||||
const shutdownPromise = new Promise((res) => {
|
||||
shutdownProcess.on('close', (code) => {
|
||||
console.log(`[MySQL] mysqladmin shutdown exited with code ${code}`);
|
||||
res(code === 0);
|
||||
});
|
||||
});
|
||||
|
||||
const timeoutPromise = new Promise((res) => setTimeout(() => res(false), 5000));
|
||||
const shutdownSuccess = await Promise.race([shutdownPromise, timeoutPromise]);
|
||||
|
||||
if (shutdownSuccess) {
|
||||
console.log('[MySQL] Shutdown successful via mysqladmin');
|
||||
// 等待进程真正退出
|
||||
await new Promise((res) => {
|
||||
if (this.process && !this.process.killed) {
|
||||
this.process.on('close', res);
|
||||
setTimeout(res, 2000); // 最多等2秒
|
||||
} else {
|
||||
res();
|
||||
}
|
||||
});
|
||||
this.cleanupPortFile();
|
||||
return resolve();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] mysqladmin shutdown failed:', error.message);
|
||||
}
|
||||
|
||||
// 方法2: 如果 mysqladmin 失败,尝试 SIGTERM
|
||||
console.log('[MySQL] Trying SIGTERM...');
|
||||
const killTimeout = setTimeout(() => {
|
||||
// 方法3: 5秒后强制 SIGKILL
|
||||
console.log('[MySQL] Force killing with SIGKILL');
|
||||
try {
|
||||
if (this.process && !this.process.killed) {
|
||||
this.process.kill('SIGKILL');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[MySQL] Error force killing:', e);
|
||||
}
|
||||
this.cleanupPortFile();
|
||||
resolve();
|
||||
}, 5000);
|
||||
|
||||
this.process.on('close', () => {
|
||||
clearTimeout(killTimeout);
|
||||
console.log('[MySQL] Process closed');
|
||||
this.cleanupPortFile();
|
||||
resolve();
|
||||
});
|
||||
|
||||
try {
|
||||
this.process.kill('SIGTERM');
|
||||
} catch (e) {
|
||||
console.error('[MySQL] Error sending SIGTERM:', e);
|
||||
clearTimeout(killTimeout);
|
||||
this.cleanupPortFile();
|
||||
resolve();
|
||||
}
|
||||
} else {
|
||||
// 没有进程引用,说明MySQL已经停止或不在我们控制下
|
||||
console.log('[MySQL] No process reference, MySQL may already be stopped');
|
||||
console.log('[MySQL] If MySQL is still running, please use kill-running-port.bat to clean up');
|
||||
this.cleanupPortFile();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 清理端口记录文件
|
||||
cleanupPortFile() {
|
||||
try {
|
||||
const portFilePath = path.join(this.mysqlPath, '.running-port');
|
||||
if (fs.existsSync(portFilePath)) {
|
||||
fs.unlinkSync(portFilePath);
|
||||
console.log('[MySQL] Port record file cleaned up');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] Failed to cleanup port record:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取记录的运行端口
|
||||
getRecordedPort() {
|
||||
try {
|
||||
const portFilePath = path.join(this.mysqlPath, '.running-port');
|
||||
if (fs.existsSync(portFilePath)) {
|
||||
const port = fs.readFileSync(portFilePath, 'utf-8').trim();
|
||||
return parseInt(port);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[MySQL] Failed to read port record:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取MySQL连接配置
|
||||
getConnectionConfig() {
|
||||
return {
|
||||
host: 'localhost',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: '',
|
||||
database: 'app_db'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MySQLManager;
|
||||
Reference in New Issue
Block a user