From f4099a7805ddb77b4d23eead84ec4a4f090db377 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Thu, 27 Feb 2025 10:31:29 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9Afix=20no=20console=20output=20becau?= =?UTF-8?q?se=20of=20programs=20don't=20write=20ResultOutputFile=20feat:?= =?UTF-8?q?=20add=20json=20config=20and=20seperate=20giteaManager.ts=20out?= =?UTF-8?q?=20of=20enhancedmultiFileRunner.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/dockerConfig.json | 8 ++ src/actions/enhancedmultiFileRunner.ts | 130 +++---------------------- src/actions/giteaManager.ts | 100 +++++++++++++++++++ src/app/page.tsx | 23 +---- 4 files changed, 120 insertions(+), 141 deletions(-) create mode 100644 src/actions/dockerConfig.json create mode 100644 src/actions/giteaManager.ts diff --git a/src/actions/dockerConfig.json b/src/actions/dockerConfig.json new file mode 100644 index 0000000..441a523 --- /dev/null +++ b/src/actions/dockerConfig.json @@ -0,0 +1,8 @@ +{ + "Image": "fly6516.synology.me:8080/multilang:latest", + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "WorkingDir": "/workspace", + "ResultOutputFile": "result.txt" +} diff --git a/src/actions/enhancedmultiFileRunner.ts b/src/actions/enhancedmultiFileRunner.ts index 0d2ce97..b6a7afe 100644 --- a/src/actions/enhancedmultiFileRunner.ts +++ b/src/actions/enhancedmultiFileRunner.ts @@ -4,104 +4,9 @@ import Docker from 'dockerode'; import fs from 'fs/promises'; // 使用异步版本的 fs 模块 import path from 'path'; import axios from 'axios'; +import dockerConfig from './dockerConfig.json'; const docker = new Docker(); -const DOCKER_IMAGE_URL = process.env.DOCKER_IMAGE_URL || 'fly6516.synology.me:8080/multilang:latest'; // 使用环境变量 - -let giteaRepoUrl: string | undefined; -let giteaToken: string | undefined; -let testDataPath: string = 'testdata'; // 默认测试数据目录 - -/** - * 设置 Gitea 仓库 URL - * @param url 仓库 URL - */ -export const setGiteaRepoUrl = async (url: string) => { - if (!url || !url.startsWith('http')) { - throw new Error('Invalid repository URL. Please provide a valid URL starting with "http".'); - } - giteaRepoUrl = url; -}; - -/** - * 设置 Gitea Token - * @param token 仓库 API 访问令牌 - */ -export const setGiteaToken = async (token: string) => { - if (!token || token.trim().length === 0) { - throw new Error('Invalid token. Please provide a non-empty token.'); - } - giteaToken = token; -}; - -/** - * 设置测试数据在仓库中的路径 - * @param path 测试数据目录路径 - */ -export const setTestDataPath = async (path: string) => { - if (!path || path.trim().length === 0) { - throw new Error('Invalid test data path. Please provide a non-empty path.'); - } - testDataPath = path; -}; - -/** - * 从 Gitea 仓库中下载指定目录的文件 - * @param dir 目录路径 - * @param tempDir 本地存储的临时目录 - */ -const fetchFilesFromGitea = async (dir: string, tempDir: string) => { - if (!giteaRepoUrl || !giteaToken) { - throw new Error('Gitea repository URL or token is not set. Please configure them using setGiteaRepoUrl and setGiteaToken.'); - } - - const targetDir = path.join(tempDir, dir); - await fs.mkdir(targetDir, { recursive: true }); - - try { - const response = await axios.get(`${giteaRepoUrl}/contents/${dir}`, { - headers: { - Authorization: `token ${giteaToken}`, - }, - }); - - const files = response.data; - if (!Array.isArray(files)) { - throw new Error(`Invalid directory structure for ${dir} in repository.`); - } - - // 并行下载文件 - await Promise.all(files.filter(file => file.type === 'file').map(async (file) => { - const fileResponse = await axios.get(file.download_url, { - headers: { - Authorization: `token ${giteaToken}`, - }, - responseType: 'arraybuffer', - }); - - const filePath = path.join(targetDir, file.name); - await fs.writeFile(filePath, fileResponse.data); - })); - } catch (error) { - throw new Error(`Failed to fetch files from '${dir}': ${(error as Error).message}`); - } -}; - -/** - * 比较运行结果与正确答案文件 - * @param resultFilePath 运行结果文件路径 - * @param expectedFilePath 正确答案文件路径 - * @returns 是否一致的比较结果 - */ -const compareResults = async (resultFilePath: string, expectedFilePath: string): Promise => { - try { - const result = (await fs.readFile(resultFilePath, 'utf-8')).trim(); - const expected = (await fs.readFile(expectedFilePath, 'utf-8')).trim(); - return result === expected; - } catch (error) { - throw new Error(`Failed to read files for comparison: ${(error as Error).message}`); - } -}; /** * 执行多文件代码并测试结果 @@ -113,9 +18,8 @@ export const runMultiFileCodeWithOptionalTestData = async (params: { language: string; testDataFiles?: string[]; expectedAnswerFiles?: string[]; - resultOutputFile?: string; }) => { - const { files, language, testDataFiles, expectedAnswerFiles, resultOutputFile } = params; + const { files, language, testDataFiles, expectedAnswerFiles } = params; if (!files || files.length === 0 || !language) { throw new Error('Files and language are required.'); @@ -143,28 +47,13 @@ export const runMultiFileCodeWithOptionalTestData = async (params: { await fs.writeFile(filePath, file.content); })); - // 检查是否设置了 Gitea 仓库信息 - if (giteaRepoUrl && giteaToken) { - try { - await fetchFilesFromGitea(testDataPath, tempDir); - } catch (error) { - throw new Error(`Failed to fetch test data from '${testDataPath}': ${(error as Error).message}`); - } - } else { - console.info('No Gitea repository connection set. Proceeding without testdata.'); - } - // 创建 Docker 容器 const container = await docker.createContainer({ - Image: DOCKER_IMAGE_URL, + ...dockerConfig, Cmd: ['sh', '-c', compileAndRunCmd], - AttachStdout: true, - AttachStderr: true, - Tty: false, HostConfig: { Binds: [`${tempDir}:/workspace`], // 挂载临时目录到容器 }, - WorkingDir: '/workspace', }); await container.start(); @@ -183,14 +72,17 @@ export const runMultiFileCodeWithOptionalTestData = async (params: { return { error: `Execution failed with exit code ${exitCode.StatusCode}. Output: ${output}` }; } - // 如果指定了结果输出文件,读取其内容 - if (resultOutputFile) { - const resultFilePath = path.join(tempDir, resultOutputFile); + // 检查结果输出文件是否存在 + const resultOutputFile = dockerConfig.ResultOutputFile; + const resultFilePath = path.join(tempDir, resultOutputFile); + + try { const resultFileContent = await fs.readFile(resultFilePath, 'utf-8'); return { output, resultFileContent }; + } catch (error) { + // 如果结果文件不存在,返回控制台输出 + return { output }; } - - return { output }; } catch (error) { return { error: `An error occurred: ${(error as Error).message}` }; } finally { diff --git a/src/actions/giteaManager.ts b/src/actions/giteaManager.ts new file mode 100644 index 0000000..6f50a69 --- /dev/null +++ b/src/actions/giteaManager.ts @@ -0,0 +1,100 @@ +'use server'; + +import fs from 'fs/promises'; // 使用异步版本的 fs 模块 +import path from 'path'; +import axios from 'axios'; + +let giteaRepoUrl: string | undefined; +let giteaToken: string | undefined; +let testDataPath: string = 'testdata'; // 默认测试数据目录 + +/** + * 设置 Gitea 仓库 URL + * @param url 仓库 URL + */ +export const setGiteaRepoUrl = async (url: string) => { + if (!url || !url.startsWith('http')) { + throw new Error('Invalid repository URL. Please provide a valid URL starting with "http".'); + } + giteaRepoUrl = url; +}; + +/** + * 设置 Gitea Token + * @param token 仓库 API 访问令牌 + */ +export const setGiteaToken = async (token: string) => { + if (!token || token.trim().length === 0) { + throw new Error('Invalid token. Please provide a non-empty token.'); + } + giteaToken = token; +}; + +/** + * 设置测试数据在仓库中的路径 + * @param path 测试数据目录路径 + */ +export const setTestDataPath = async (path: string) => { + if (!path || path.trim().length === 0) { + throw new Error('Invalid test data path. Please provide a non-empty path.'); + } + testDataPath = path; +}; + +/** + * 从 Gitea 仓库中下载指定目录的文件 + * @param dir 目录路径 + * @param tempDir 本地存储的临时目录 + */ +export const fetchFilesFromGitea = async (dir: string, tempDir: string) => { + if (!giteaRepoUrl || !giteaToken) { + throw new Error('Gitea repository URL or token is not set. Please configure them using setGiteaRepoUrl and setGiteaToken.'); + } + + const targetDir = path.join(tempDir, dir); + await fs.mkdir(targetDir, { recursive: true }); + + try { + const response = await axios.get(`${giteaRepoUrl}/contents/${dir}`, { + headers: { + Authorization: `token ${giteaToken}`, + }, + }); + + const files = response.data; + if (!Array.isArray(files)) { + throw new Error(`Invalid directory structure for ${dir} in repository.`); + } + + // 并行下载文件 + await Promise.all(files.filter(file => file.type === 'file').map(async (file) => { + const fileResponse = await axios.get(file.download_url, { + headers: { + Authorization: `token ${giteaToken}`, + }, + responseType: 'arraybuffer', + }); + + const filePath = path.join(targetDir, file.name); + await fs.writeFile(filePath, fileResponse.data); + })); + } catch (error) { + throw new Error(`Failed to fetch files from '${dir}': ${(error as Error).message}`); + } +}; + +/** + * 比较运行结果与正确答案文件 + * @param resultFilePath 运行结果文件路径 + * @param expectedFilePath 正确答案文件路径 + * @returns 是否一致的比较结果 + */ +export const compareResults = async (resultFilePath: string, expectedFilePath: string): Promise => { + try { + const result = (await fs.readFile(resultFilePath, 'utf-8')).trim(); + const expected = (await fs.readFile(expectedFilePath, 'utf-8')).trim(); + return result === expected; + } catch (error) { + throw new Error(`Failed to read files for comparison: ${(error as Error).message}`); + } +}; diff --git a/src/app/page.tsx b/src/app/page.tsx index 4a89d62..bb45ce0 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,14 +6,12 @@ import { runMultiFileCodeWithOptionalTestData } from '@/actions/enhancedmultiFil const TestPage = () => { const [code, setCode] = useState(''); const [language, setLanguage] = useState('c'); - const [result, setResult] = useState(''); const [consoleOutput, setConsoleOutput] = useState(''); - const [loading, setLoading] = useState(false); const [resultFileContent, setResultFileContent] = useState(''); + const [loading, setLoading] = useState(false); const handleRun = async () => { setLoading(true); - setResult(''); setConsoleOutput(''); setResultFileContent(''); @@ -45,10 +43,6 @@ const TestPage = () => { setConsoleOutput(result.error); } else { setConsoleOutput(result.output); - if (result.comparisonResults) { - setResult(JSON.stringify(result.comparisonResults, null, 2)); - } - // 设置 result.txt 的内容 if (result.resultFileContent) { setResultFileContent(result.resultFileContent); @@ -116,21 +110,6 @@ const TestPage = () => { {consoleOutput || 'No console output yet.'} - {result && ( -
-

Result

-
-                        {result || 'No results yet.'}
-                    
-
- )} {resultFileContent && (

Result File Content (result.txt)