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": {
|
"dependencies": {
|
||||||
"@types/pg": "^8.11.10",
|
"@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",
|
"next": "15.1.4",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"tls": "^0.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
@ -1,52 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import serverAction from '../utils/dockerUtils'; // 更新引用
|
||||||
|
|
||||||
export default function Home() {
|
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 (
|
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)]">
|
<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">
|
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||||
<Image
|
<textarea
|
||||||
className="dark:invert"
|
value={sqlCode}
|
||||||
src="/next.svg"
|
onChange={(e) => setSqlCode(e.target.value)}
|
||||||
alt="Next.js logo"
|
style={{ width: '100%', height: '500px' }}
|
||||||
width={180}
|
className="border border-gray-300 p-4 rounded"
|
||||||
height={38}
|
></textarea>
|
||||||
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>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||||
<a
|
<select value={databaseType} onChange={(e) => setDatabaseType(e.target.value)}>
|
||||||
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"
|
<option value="sqlserver">SQL Server</option>
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<option value="mysql">MySQL</option>
|
||||||
target="_blank"
|
{/* 可以在这里添加其他数据库选项 */}
|
||||||
rel="noopener noreferrer"
|
</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">
|
||||||
<Image
|
Execute SQL
|
||||||
className="dark:invert"
|
</button>
|
||||||
src="/vercel.svg"
|
</div>
|
||||||
alt="Vercel logomark"
|
<div>
|
||||||
width={20}
|
<h3>Result:</h3>
|
||||||
height={20}
|
<pre>{result}</pre>
|
||||||
/>
|
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
<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