feat:添加了mysql运行测试的功能
This commit is contained in:
parent
8888b96992
commit
a2c0076191
2
package-lock.json
generated
2
package-lock.json
generated
@ -1906,7 +1906,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/bun": {
|
"node_modules/bun": {
|
||||||
"version": "1.2.2",
|
"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==",
|
"integrity": "sha512-RUc8uVVTw8WoASUzXaEQJR1s7mnwoHm3P871qBUIqSaoOpuwcU+bSVX151/xoqDwnyv38SjOX7yQ3oO0IeT73g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64",
|
"arm64",
|
||||||
|
@ -3,65 +3,181 @@
|
|||||||
import Docker from 'dockerode';
|
import Docker from 'dockerode';
|
||||||
|
|
||||||
const docker = new Docker();
|
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 dockerImage: string;
|
||||||
let command: string;
|
let command: string;
|
||||||
let env: string[] = [];
|
let env: string[] = [];
|
||||||
|
let container: Docker.Container;
|
||||||
|
|
||||||
|
// 配置数据库参数
|
||||||
switch (databaseType) {
|
switch (databaseType) {
|
||||||
|
|
||||||
case 'sqlserver':
|
case 'sqlserver':
|
||||||
|
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 = `sqlcmd -S localhost -U SA -P YourStrong!Passw0rd -Q "${sql}"`;
|
command = `sqlcmd -S localhost -U SA -P ${process.env.SQL_SERVER_SA_PASSWORD} -Q "${sql}"`;
|
||||||
env = ['ACCEPT_EULA=Y', 'SA_PASSWORD=YourStrong!Passw0rd'];
|
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';
|
||||||
dockerImage = 'mysql:latest';
|
dockerImage = 'mysql:latest';
|
||||||
command = `mysql -u root -pYourStrong!Passw0rd -e "${sql}"`;
|
command = `mysql -u root -p${process.env.MYSQL_ROOT_PASSWORD} -e "${sql}"`;
|
||||||
env = ['MYSQL_ROOT_PASSWORD=YourStrong!Passw0rd'];
|
env = [
|
||||||
|
`MYSQL_ROOT_PASSWORD=${process.env.MYSQL_ROOT_PASSWORD}`,
|
||||||
|
'MYSQL_TCP_PORT=3306',
|
||||||
|
'MYSQL_ROOT_HOST=%', // 允许远程连接
|
||||||
|
];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Unsupported database type');
|
throw new Error('Unsupported database type');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start Docker container
|
// 创建并启动容器
|
||||||
const container = await docker.run(dockerImage, [], process.stdout, {
|
container = await docker.createContainer({
|
||||||
Tty: false,
|
Image: dockerImage,
|
||||||
Env: env,
|
Env: env,
|
||||||
HostConfig: {
|
HostConfig: {
|
||||||
|
AutoRemove: true,
|
||||||
PortBindings: {
|
PortBindings: {
|
||||||
'3306/tcp': [{ HostPort: '3306' }], // MySQL port binding
|
[databaseType === 'sqlserver' ? '1433/tcp' : '3306/tcp']: [
|
||||||
'1433/tcp': [{ HostPort: '1433' }], // SQL Server port binding
|
{ HostPort: databaseType === 'sqlserver' ? '1433' : '3306' }
|
||||||
},
|
]
|
||||||
},
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for the database to start (this is a simple sleep, you might want a more robust solution)
|
// 对于 SQL Server,安装 mssql-tools
|
||||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
if (databaseType === 'sqlserver') {
|
||||||
|
await container.start();
|
||||||
// Execute SQL command
|
await new Promise((resolve, reject) => {
|
||||||
const exec = await container.exec({
|
const exec = container.exec({
|
||||||
Cmd: ['/bin/sh', '-c', command],
|
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,
|
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 = '';
|
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();
|
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
|
stream?.resume();
|
||||||
await container.stop();
|
});
|
||||||
await container.remove();
|
});
|
||||||
|
|
||||||
return output;
|
|
||||||
} catch (error) {
|
} 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');
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user