feat(utils): 添加 Docker 方式执行 SQL 语句的功能
- 新增 dockerUtils.ts 文件,实现通过 Docker 容器执行 SQL 语句 - 支持 SQL Server 和 MySQL 数据库类型 - 使用 Dockerode 库与 Docker 引擎交互 - 功能包括启动容器、执行 SQL 命令、停止并移除容器
This commit is contained in:
parent
9f09737b72
commit
8888b96992
889
package-lock.json
generated
889
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -10,10 +10,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/pg": "^8.11.10",
|
||||
"bun": "^1.2.2",
|
||||
"child_process": "^1.0.2",
|
||||
"dockerode": "^4.0.4",
|
||||
"fs": "^0.0.1-security",
|
||||
"http2": "^3.3.6",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"net": "^1.0.2",
|
||||
"next": "15.1.4",
|
||||
"pg": "^8.13.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"tls": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
|
@ -1,52 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from 'react';
|
||||
import serverAction from '../utils/dockerUtils'; // 更新引用
|
||||
|
||||
export default function Home() {
|
||||
const [sqlCode, setSqlCode] = useState('SELECT * FROM your_table;');
|
||||
const [databaseType, setDatabaseType] = useState('sqlserver');
|
||||
const [result, setResult] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化逻辑可以放在这里
|
||||
}, []);
|
||||
|
||||
const handleExecute = async () => {
|
||||
try {
|
||||
const res = await serverAction(sqlCode, databaseType); // 更新函数调用
|
||||
setResult(res);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setResult(`Error: ${error.message}`);
|
||||
} else {
|
||||
setResult('An unknown error occurred.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<textarea
|
||||
value={sqlCode}
|
||||
onChange={(e) => setSqlCode(e.target.value)}
|
||||
style={{ width: '100%', height: '500px' }}
|
||||
className="border border-gray-300 p-4 rounded"
|
||||
></textarea>
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
<select value={databaseType} onChange={(e) => setDatabaseType(e.target.value)}>
|
||||
<option value="sqlserver">SQL Server</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
{/* 可以在这里添加其他数据库选项 */}
|
||||
</select>
|
||||
<button onClick={handleExecute} className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5">
|
||||
Execute SQL
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Result:</h3>
|
||||
<pre>{result}</pre>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
|
67
src/utils/dockerUtils.ts
Normal file
67
src/utils/dockerUtils.ts
Normal file
@ -0,0 +1,67 @@
|
||||
"use server";
|
||||
|
||||
import Docker from 'dockerode';
|
||||
|
||||
const docker = new Docker();
|
||||
|
||||
export default async function serverAction(sql: string, databaseType: string): Promise<string> {
|
||||
let dockerImage: string;
|
||||
let command: string;
|
||||
let env: string[] = [];
|
||||
|
||||
switch (databaseType) {
|
||||
case 'sqlserver':
|
||||
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'];
|
||||
break;
|
||||
case 'mysql':
|
||||
dockerImage = 'mysql:latest';
|
||||
command = `mysql -u root -pYourStrong!Passw0rd -e "${sql}"`;
|
||||
env = ['MYSQL_ROOT_PASSWORD=YourStrong!Passw0rd'];
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported database type');
|
||||
}
|
||||
|
||||
try {
|
||||
// Start Docker container
|
||||
const container = await docker.run(dockerImage, [], process.stdout, {
|
||||
Tty: false,
|
||||
Env: env,
|
||||
HostConfig: {
|
||||
PortBindings: {
|
||||
'3306/tcp': [{ HostPort: '3306' }], // MySQL port binding
|
||||
'1433/tcp': [{ HostPort: '1433' }], // SQL Server port binding
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 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],
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
});
|
||||
|
||||
const stream = await exec.start({});
|
||||
|
||||
let output = '';
|
||||
stream.on('data', (chunk: { toString: () => string; }) => {
|
||||
output += chunk.toString();
|
||||
});
|
||||
|
||||
await new Promise((resolve) => stream.on('end', resolve));
|
||||
|
||||
// Stop and remove Docker container
|
||||
await container.stop();
|
||||
await container.remove();
|
||||
|
||||
return output;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user