feat:优化了自动删除容器的功能
This commit is contained in:
parent
3af5d48344
commit
05a7d85dfc
@ -5,6 +5,27 @@ import Docker from 'dockerode';
|
|||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
const CONTAINER_TIMEOUT = 60; // 容器超时时间(秒)
|
const CONTAINER_TIMEOUT = 60; // 容器超时时间(秒)
|
||||||
|
|
||||||
|
// 安全容器操作函数
|
||||||
|
async function safeContainerStop(container: Docker.Container) {
|
||||||
|
try {
|
||||||
|
await container.stop();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (![409, 404].includes(error.statusCode)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function safeContainerRemove(container: Docker.Container) {
|
||||||
|
try {
|
||||||
|
await container.remove();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.statusCode !== 404) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function serverAction(
|
export default async function serverAction(
|
||||||
sql: string,
|
sql: string,
|
||||||
databaseType: string
|
databaseType: string
|
||||||
@ -17,13 +38,13 @@ export default async function serverAction(
|
|||||||
// 配置数据库参数
|
// 配置数据库参数
|
||||||
switch (databaseType) {
|
switch (databaseType) {
|
||||||
case 'sqlserver':
|
case 'sqlserver':
|
||||||
process.env.SQL_SERVER_SA_PASSWORD='YourStrong!Passw0rd';
|
process.env.SQL_SERVER_SA_PASSWORD = 'YourStrong!Passw0rd';
|
||||||
dockerImage = 'mcr.microsoft.com/mssql/server:2019-latest';
|
dockerImage = 'mcr.microsoft.com/mssql/server:2019-latest';
|
||||||
command = `/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${process.env.SQL_SERVER_SA_PASSWORD} -C -Q "${sql}"`; // 添加 -C 参数
|
command = `/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${process.env.SQL_SERVER_SA_PASSWORD} -C -Q "${sql}"`;
|
||||||
env = ['ACCEPT_EULA=Y', `SA_PASSWORD=${process.env.SQL_SERVER_SA_PASSWORD}`];
|
env = ['ACCEPT_EULA=Y', `SA_PASSWORD=${process.env.SQL_SERVER_SA_PASSWORD}`];
|
||||||
break;
|
break;
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
process.env.MYSQL_ROOT_PASSWORD='YourStrong!Passw0rd';
|
process.env.MYSQL_ROOT_PASSWORD = 'YourStrong!Passw0rd';
|
||||||
dockerImage = 'mysql:latest';
|
dockerImage = 'mysql:latest';
|
||||||
command = `mysql -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "${sql}"`;
|
command = `mysql -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "${sql}"`;
|
||||||
env = [
|
env = [
|
||||||
@ -37,7 +58,7 @@ export default async function serverAction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建并启动容器(SQL Server专用配置)
|
// 创建并启动容器
|
||||||
container = await docker.createContainer({
|
container = await docker.createContainer({
|
||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: databaseType === 'sqlserver'
|
Env: databaseType === 'sqlserver'
|
||||||
@ -51,12 +72,11 @@ export default async function serverAction(
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
...(databaseType === 'sqlserver' && {
|
...(databaseType === 'sqlserver' && {
|
||||||
Memory: 2147483648 // 2GB内存限制
|
Memory: 2147483648
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 对于 SQL Server,安装 mssql-tools
|
|
||||||
if (databaseType === 'sqlserver') {
|
if (databaseType === 'sqlserver') {
|
||||||
await container.start();
|
await container.start();
|
||||||
|
|
||||||
@ -72,7 +92,7 @@ export default async function serverAction(
|
|||||||
AttachStderr: true
|
AttachStderr: true
|
||||||
}, (err, exec) => {
|
}, (err, exec) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
exec.start({}, (err, stream) => {
|
exec?.start({}, (err, stream) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
stream?.on('end', () => resolve(null));
|
stream?.on('end', () => resolve(null));
|
||||||
stream?.resume();
|
stream?.resume();
|
||||||
@ -92,7 +112,7 @@ export default async function serverAction(
|
|||||||
AttachStderr: true
|
AttachStderr: true
|
||||||
}, (err, exec) => {
|
}, (err, exec) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
exec.start({}, (err, stream) => {
|
exec?.start({}, (err, stream) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
stream?.on('end', () => resolve(null));
|
stream?.on('end', () => resolve(null));
|
||||||
stream?.resume();
|
stream?.resume();
|
||||||
@ -100,12 +120,12 @@ export default async function serverAction(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000)); // 新增等待时间
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
} else {
|
} else {
|
||||||
await container.start();
|
await container.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 改进的健康检查逻辑
|
// 健康检查逻辑
|
||||||
let isReady = false;
|
let isReady = false;
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
while (Date.now() - startTime < CONTAINER_TIMEOUT * 1000) {
|
while (Date.now() - startTime < CONTAINER_TIMEOUT * 1000) {
|
||||||
@ -116,7 +136,7 @@ export default async function serverAction(
|
|||||||
'-S', 'localhost',
|
'-S', 'localhost',
|
||||||
'-U', 'SA',
|
'-U', 'SA',
|
||||||
'-P', process.env.SQL_SERVER_SA_PASSWORD,
|
'-P', process.env.SQL_SERVER_SA_PASSWORD,
|
||||||
'-C', // 添加信任证书参数
|
'-C',
|
||||||
'-Q', 'SELECT 1'
|
'-Q', 'SELECT 1'
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
@ -131,7 +151,6 @@ export default async function serverAction(
|
|||||||
AttachStderr: true
|
AttachStderr: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// 验证健康检查结果
|
|
||||||
const output = await new Promise<string>((resolve, reject) => {
|
const output = await new Promise<string>((resolve, reject) => {
|
||||||
exec.start({}, (err, stream) => {
|
exec.start({}, (err, stream) => {
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
@ -153,7 +172,8 @@ export default async function serverAction(
|
|||||||
if (!isReady) throw new Error(`Database not ready within ${CONTAINER_TIMEOUT} seconds`);
|
if (!isReady) throw new Error(`Database not ready within ${CONTAINER_TIMEOUT} seconds`);
|
||||||
|
|
||||||
// 执行SQL命令
|
// 执行SQL命令
|
||||||
const exec = await container.exec({
|
const execResult = await new Promise<string>((resolve, reject) => {
|
||||||
|
container.exec({
|
||||||
Cmd: [
|
Cmd: [
|
||||||
databaseType === 'sqlserver' ? '/bin/bash' : '/bin/sh',
|
databaseType === 'sqlserver' ? '/bin/bash' : '/bin/sh',
|
||||||
'-c',
|
'-c',
|
||||||
@ -161,14 +181,13 @@ export default async function serverAction(
|
|||||||
],
|
],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true
|
AttachStderr: true
|
||||||
});
|
}, (err, exec) => {
|
||||||
|
|
||||||
// 捕获执行结果
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
let output = '';
|
|
||||||
exec.start({}, (err, stream) => {
|
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
|
|
||||||
|
exec?.start({}, (err, stream) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
let output = '';
|
||||||
|
|
||||||
stream?.on('data', (chunk: Buffer) => {
|
stream?.on('data', (chunk: Buffer) => {
|
||||||
output += chunk.toString();
|
output += chunk.toString();
|
||||||
});
|
});
|
||||||
@ -183,16 +202,32 @@ export default async function serverAction(
|
|||||||
stream?.resume();
|
stream?.resume();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (error) {
|
});
|
||||||
// 异常清理
|
|
||||||
if (container) {
|
// 安全停止容器
|
||||||
await container.stop().catch(() => {});
|
await safeContainerStop(container);
|
||||||
await container.remove().catch(() => {});
|
|
||||||
|
// 兜底状态检查
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const info = await container.inspect();
|
||||||
|
if (info.State.Running) {
|
||||||
|
await safeContainerStop(container);
|
||||||
}
|
}
|
||||||
throw typeof error === 'string'
|
} catch (error: any) {
|
||||||
? new Error(error)
|
if (error.statusCode !== 404) {
|
||||||
: error instanceof Error
|
console.error('Post-execution cleanup check failed:', error.message);
|
||||||
? error
|
}
|
||||||
: new Error('Unknown error');
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return execResult;
|
||||||
|
} catch (error) {
|
||||||
|
// 错误处理流程
|
||||||
|
if (container) {
|
||||||
|
await safeContainerStop(container);
|
||||||
|
await safeContainerRemove(container);
|
||||||
|
}
|
||||||
|
throw error instanceof Error ? error : new Error('Unknown error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user