feat:优化了自动删除容器的功能

This commit is contained in:
fly6516 2025-02-24 16:29:19 +08:00
parent 3af5d48344
commit 05a7d85dfc

View File

@ -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
@ -19,7 +40,7 @@ export default async function serverAction(
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':
@ -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();
}); });
}); });
});
// 安全停止容器
await safeContainerStop(container);
// 兜底状态检查
setTimeout(async () => {
try {
const info = await container.inspect();
if (info.State.Running) {
await safeContainerStop(container);
}
} catch (error: any) {
if (error.statusCode !== 404) {
console.error('Post-execution cleanup check failed:', error.message);
}
}
}, 2000);
return execResult;
} catch (error) { } catch (error) {
// 异常清理 // 错误处理流程
if (container) { if (container) {
await container.stop().catch(() => {}); await safeContainerStop(container);
await container.remove().catch(() => {}); await safeContainerRemove(container);
} }
throw typeof error === 'string' throw error instanceof Error ? error : new Error('Unknown error');
? new Error(error)
: error instanceof Error
? error
: new Error('Unknown error');
} }
} }