From 05a7d85dfc690e9e485d4b69a6c6d3489a84ec45 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Mon, 24 Feb 2025 16:29:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BC=98=E5=8C=96=E4=BA=86=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=88=A0=E9=99=A4=E5=AE=B9=E5=99=A8=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dockerUtils.ts | 127 +++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 46 deletions(-) diff --git a/src/utils/dockerUtils.ts b/src/utils/dockerUtils.ts index f3e1ea0..f00901c 100644 --- a/src/utils/dockerUtils.ts +++ b/src/utils/dockerUtils.ts @@ -5,9 +5,30 @@ import Docker from 'dockerode'; const docker = new Docker(); 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( - sql: string, - databaseType: string + sql: string, + databaseType: string ): Promise { let dockerImage: string; let command: string; @@ -17,13 +38,13 @@ export default async function serverAction( // 配置数据库参数 switch (databaseType) { 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'; - 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}`]; break; case 'mysql': - process.env.MYSQL_ROOT_PASSWORD='YourStrong!Passw0rd'; + process.env.MYSQL_ROOT_PASSWORD = 'YourStrong!Passw0rd'; dockerImage = 'mysql:latest'; command = `mysql -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "${sql}"`; env = [ @@ -37,7 +58,7 @@ export default async function serverAction( } try { - // 创建并启动容器(SQL Server专用配置) + // 创建并启动容器 container = await docker.createContainer({ Image: dockerImage, Env: databaseType === 'sqlserver' @@ -51,12 +72,11 @@ export default async function serverAction( ] }, ...(databaseType === 'sqlserver' && { - Memory: 2147483648 // 2GB内存限制 + Memory: 2147483648 }) } }); - // 对于 SQL Server,安装 mssql-tools if (databaseType === 'sqlserver') { await container.start(); @@ -72,7 +92,7 @@ export default async function serverAction( AttachStderr: true }, (err, exec) => { if (err) return reject(err); - exec.start({}, (err, stream) => { + exec?.start({}, (err, stream) => { if (err) return reject(err); stream?.on('end', () => resolve(null)); stream?.resume(); @@ -92,7 +112,7 @@ export default async function serverAction( AttachStderr: true }, (err, exec) => { if (err) return reject(err); - exec.start({}, (err, stream) => { + exec?.start({}, (err, stream) => { if (err) return reject(err); stream?.on('end', () => resolve(null)); 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 { await container.start(); } - // 改进的健康检查逻辑 + // 健康检查逻辑 let isReady = false; const startTime = Date.now(); while (Date.now() - startTime < CONTAINER_TIMEOUT * 1000) { @@ -116,7 +136,7 @@ export default async function serverAction( '-S', 'localhost', '-U', 'SA', '-P', process.env.SQL_SERVER_SA_PASSWORD, - '-C', // 添加信任证书参数 + '-C', '-Q', 'SELECT 1' ] : [ @@ -131,7 +151,6 @@ export default async function serverAction( AttachStderr: true }); - // 验证健康检查结果 const output = await new Promise((resolve, reject) => { exec.start({}, (err, stream) => { if (err) return reject(err); @@ -153,46 +172,62 @@ export default async function serverAction( if (!isReady) throw new Error(`Database not ready within ${CONTAINER_TIMEOUT} seconds`); // 执行SQL命令 - const exec = await container.exec({ - Cmd: [ - databaseType === 'sqlserver' ? '/bin/bash' : '/bin/sh', - '-c', - command - ], - AttachStdout: true, - AttachStderr: true - }); - - // 捕获执行结果 - return await new Promise((resolve, reject) => { - let output = ''; - exec.start({}, (err, stream) => { + const execResult = await new Promise((resolve, reject) => { + container.exec({ + Cmd: [ + databaseType === 'sqlserver' ? '/bin/bash' : '/bin/sh', + '-c', + command + ], + AttachStdout: true, + AttachStderr: true + }, (err, exec) => { if (err) return reject(err); - stream?.on('data', (chunk: Buffer) => { - output += chunk.toString(); - }); + exec?.start({}, (err, stream) => { + if (err) return reject(err); + let output = ''; - stream?.on('end', () => { - if (databaseType === 'mysql') { - output = output.replace(/Warning: Using a password on the command line interface can be insecure.\n/g, ''); - } - resolve(output); - }); + stream?.on('data', (chunk: Buffer) => { + output += chunk.toString(); + }); - stream?.resume(); + stream?.on('end', () => { + if (databaseType === 'mysql') { + output = output.replace(/Warning: Using a password on the command line interface can be insecure.\n/g, ''); + } + resolve(output); + }); + + 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) { - // 异常清理 + // 错误处理流程 if (container) { - await container.stop().catch(() => {}); - await container.remove().catch(() => {}); + await safeContainerStop(container); + await safeContainerRemove(container); } - throw typeof error === 'string' - ? new Error(error) - : error instanceof Error - ? error - : new Error('Unknown error'); + throw error instanceof Error ? error : new Error('Unknown error'); } }