diff --git a/package-lock.json b/package-lock.json index b623f4d..f6abd48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/dockerode": "^3.3.34", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1168,6 +1169,29 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.34", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.34.tgz", + "integrity": "sha512-mH9SuIb8NuTDsMus5epcbTzSbEo52fKLBMo0zapzYIAIyfDqoIFn7L3trekHLKC8qmxGV++pPUP4YqQ9n5v2Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", @@ -1229,6 +1253,33 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/ssh2": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.4.tgz", + "integrity": "sha512-9JTQgVBWSgq6mAen6PVnrAmty1lqgCMvpfN+1Ck5WRUsyMYPa6qd50/vMJ0y1zkGpOEgLzm8m8Dx/Y5vRouLaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.76", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", + "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.19.1", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", diff --git a/package.json b/package.json index 4909d16..1cee5d0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/dockerode": "^3.3.34", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/src/utils/dockerUtils.ts b/src/utils/dockerUtils.ts index b0bc972..f3e1ea0 100644 --- a/src/utils/dockerUtils.ts +++ b/src/utils/dockerUtils.ts @@ -6,8 +6,8 @@ const docker = new Docker(); const CONTAINER_TIMEOUT = 60; // 容器超时时间(秒) export default async function serverAction( - sql: string, - databaseType: string + sql: string, + databaseType: string ): Promise { let dockerImage: string; let command: string; @@ -16,11 +16,10 @@ export default async function serverAction( // 配置数据库参数 switch (databaseType) { - case 'sqlserver': process.env.SQL_SERVER_SA_PASSWORD='YourStrong!Passw0rd'; dockerImage = 'mcr.microsoft.com/mssql/server:2019-latest'; - command = `sqlcmd -S localhost -U SA -P ${process.env.SQL_SERVER_SA_PASSWORD} -Q "${sql}"`; + command = `/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P ${process.env.SQL_SERVER_SA_PASSWORD} -C -Q "${sql}"`; // 添加 -C 参数 env = ['ACCEPT_EULA=Y', `SA_PASSWORD=${process.env.SQL_SERVER_SA_PASSWORD}`]; break; case 'mysql': @@ -30,7 +29,7 @@ export default async function serverAction( env = [ `MYSQL_ROOT_PASSWORD=${process.env.MYSQL_ROOT_PASSWORD}`, 'MYSQL_TCP_PORT=3306', - 'MYSQL_ROOT_HOST=%', // 允许远程连接 + 'MYSQL_ROOT_HOST=%', ]; break; default: @@ -38,29 +37,36 @@ export default async function serverAction( } try { - // 创建并启动容器 + // 创建并启动容器(SQL Server专用配置) container = await docker.createContainer({ Image: dockerImage, - Env: env, + Env: databaseType === 'sqlserver' + ? [...env, 'MSSQL_AGENT_ENABLED=True', 'MSSQL_TCP_PROTOCOL=1'] + : env, HostConfig: { AutoRemove: true, PortBindings: { [databaseType === 'sqlserver' ? '1433/tcp' : '3306/tcp']: [ { HostPort: databaseType === 'sqlserver' ? '1433' : '3306' } ] - } + }, + ...(databaseType === 'sqlserver' && { + Memory: 2147483648 // 2GB内存限制 + }) } }); // 对于 SQL Server,安装 mssql-tools if (databaseType === 'sqlserver') { await container.start(); + + // 安装依赖工具链 await new Promise((resolve, reject) => { const exec = container.exec({ Cmd: [ - '/bin/bash', - '-c', - 'echo "YourStrong!Passw0rd" | su -c "apt-get update && apt-get install -y curl && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list"' + '/bin/bash', + '-c', + 'echo "YourStrong!Passw0rd" | su -c "apt-get update && apt-get install -y curl && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list"' ], AttachStdout: true, AttachStderr: true @@ -74,6 +80,7 @@ export default async function serverAction( }); }); + // 安装核心组件 await new Promise((resolve, reject) => { const exec = container.exec({ Cmd: [ @@ -92,6 +99,8 @@ export default async function serverAction( }); }); }); + + await new Promise(resolve => setTimeout(resolve, 5000)); // 新增等待时间 } else { await container.start(); } @@ -102,12 +111,19 @@ export default async function serverAction( while (Date.now() - startTime < CONTAINER_TIMEOUT * 1000) { try { const healthCheckCmd = databaseType === 'sqlserver' - ? ['/opt/mssql-tools/bin/sqlcmd', '-S', 'localhost', '-U', 'SA', '-P', process.env.SQL_SERVER_SA_PASSWORD, '-Q', 'SELECT 1'] + ? [ + '/opt/mssql-tools18/bin/sqlcmd', + '-S', 'localhost', + '-U', 'SA', + '-P', process.env.SQL_SERVER_SA_PASSWORD, + '-C', // 添加信任证书参数 + '-Q', 'SELECT 1' + ] : [ - 'sh', - '-c', - `mysql -h 127.0.0.1 -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "SELECT 1" 2>&1 | grep -v "Using a password"` - ]; // 过滤警告信息 + 'sh', + '-c', + `mysql -h 127.0.0.1 -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "SELECT 1" 2>&1 | grep -v "Using a password"` + ]; const exec = await container.exec({ Cmd: healthCheckCmd, @@ -158,7 +174,6 @@ export default async function serverAction( }); stream?.on('end', () => { - // 过滤MySQL警告信息 if (databaseType === 'mysql') { output = output.replace(/Warning: Using a password on the command line interface can be insecure.\n/g, ''); } @@ -175,9 +190,9 @@ export default async function serverAction( await container.remove().catch(() => {}); } throw typeof error === 'string' - ? new Error(error) - : error instanceof Error - ? error - : new Error('Unknown error'); + ? new Error(error) + : error instanceof Error + ? error + : new Error('Unknown error'); } }