feat:添加了mysql运行测试的功能

This commit is contained in:
fly6516 2025-02-21 16:35:19 +08:00
parent 8888b96992
commit a2c0076191
2 changed files with 148 additions and 32 deletions

2
package-lock.json generated
View File

@ -1906,7 +1906,7 @@
},
"node_modules/bun": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/bun/-/bun-1.2.2.tgz",
"resolved": "https://registry.npmjs.org/bun/-/bun-1.2.2.tgz",
"integrity": "sha512-RUc8uVVTw8WoASUzXaEQJR1s7mnwoHm3P871qBUIqSaoOpuwcU+bSVX151/xoqDwnyv38SjOX7yQ3oO0IeT73g==",
"cpu": [
"arm64",

View File

@ -3,65 +3,181 @@
import Docker from 'dockerode';
const docker = new Docker();
const CONTAINER_TIMEOUT = 60; // 容器超时时间(秒)
export default async function serverAction(sql: string, databaseType: string): Promise<string> {
export default async function serverAction(
sql: string,
databaseType: string
): Promise<string> {
let dockerImage: string;
let command: string;
let env: string[] = [];
let container: Docker.Container;
// 配置数据库参数
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 YourStrong!Passw0rd -Q "${sql}"`;
env = ['ACCEPT_EULA=Y', 'SA_PASSWORD=YourStrong!Passw0rd'];
command = `sqlcmd -S localhost -U SA -P ${process.env.SQL_SERVER_SA_PASSWORD} -Q "${sql}"`;
env = ['ACCEPT_EULA=Y', `SA_PASSWORD=${process.env.SQL_SERVER_SA_PASSWORD}`];
break;
case 'mysql':
process.env.MYSQL_ROOT_PASSWORD='YourStrong!Passw0rd';
dockerImage = 'mysql:latest';
command = `mysql -u root -pYourStrong!Passw0rd -e "${sql}"`;
env = ['MYSQL_ROOT_PASSWORD=YourStrong!Passw0rd'];
command = `mysql -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "${sql}"`;
env = [
`MYSQL_ROOT_PASSWORD=${process.env.MYSQL_ROOT_PASSWORD}`,
'MYSQL_TCP_PORT=3306',
'MYSQL_ROOT_HOST=%', // 允许远程连接
];
break;
default:
throw new Error('Unsupported database type');
}
try {
// Start Docker container
const container = await docker.run(dockerImage, [], process.stdout, {
Tty: false,
// 创建并启动容器
container = await docker.createContainer({
Image: dockerImage,
Env: env,
HostConfig: {
AutoRemove: true,
PortBindings: {
'3306/tcp': [{ HostPort: '3306' }], // MySQL port binding
'1433/tcp': [{ HostPort: '1433' }], // SQL Server port binding
},
},
[databaseType === 'sqlserver' ? '1433/tcp' : '3306/tcp']: [
{ HostPort: databaseType === 'sqlserver' ? '1433' : '3306' }
]
}
}
});
// Wait for the database to start (this is a simple sleep, you might want a more robust solution)
await new Promise(resolve => setTimeout(resolve, 10000));
// Execute SQL command
const exec = await container.exec({
Cmd: ['/bin/sh', '-c', command],
// 对于 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"'
],
AttachStdout: true,
AttachStderr: true,
AttachStderr: true
}, (err, exec) => {
if (err) return reject(err);
exec.start({}, (err, stream) => {
if (err) return reject(err);
stream?.on('end', () => resolve(null));
stream?.resume();
});
});
});
const stream = await exec.start({});
await new Promise((resolve, reject) => {
const exec = container.exec({
Cmd: [
'/bin/bash',
'-c',
'ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools && echo "export PATH=$PATH:/opt/mssql-tools/bin" >> ~/.bashrc && source ~/.bashrc'
],
AttachStdout: true,
AttachStderr: true
}, (err, exec) => {
if (err) return reject(err);
exec.start({}, (err, stream) => {
if (err) return reject(err);
stream?.on('end', () => resolve(null));
stream?.resume();
});
});
});
} else {
await container.start();
}
// 改进的健康检查逻辑
let isReady = false;
const startTime = Date.now();
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']
: [
'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,
AttachStdout: true,
AttachStderr: true
});
// 验证健康检查结果
const output = await new Promise<string>((resolve, reject) => {
exec.start({}, (err, stream) => {
if (err) return reject(err);
let data = '';
stream?.on('data', (chunk: Buffer) => data += chunk.toString());
stream?.on('end', () => resolve(data));
stream?.resume();
});
});
if (output.includes("ERROR")) throw new Error(output);
isReady = true;
break;
} catch {
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
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 = '';
stream.on('data', (chunk: { toString: () => string; }) => {
exec.start({}, (err, stream) => {
if (err) return reject(err);
stream?.on('data', (chunk: Buffer) => {
output += chunk.toString();
});
await new Promise((resolve) => stream.on('end', resolve));
stream?.on('end', () => {
// 过滤MySQL警告信息
if (databaseType === 'mysql') {
output = output.replace(/Warning: Using a password on the command line interface can be insecure.\n/g, '');
}
resolve(output);
});
// Stop and remove Docker container
await container.stop();
await container.remove();
return output;
stream?.resume();
});
});
} catch (error) {
throw error;
// 异常清理
if (container) {
await container.stop().catch(() => {});
await container.remove().catch(() => {});
}
throw typeof error === 'string'
? new Error(error)
: error instanceof Error
? error
: new Error('Unknown error');
}
}