基于SpringBoot實(shí)現(xiàn)代碼在線運(yùn)行工具
說(shuō)明
由于沒(méi)有實(shí)現(xiàn)沙盒,所以這個(gè)運(yùn)行只適合提交自己寫(xiě)的代碼到服務(wù)器,不適合像 菜鳥(niǎo)工具 那樣可以讓人公開(kāi)提交代碼并訪問(wèn)。
基本思路
前端提交代碼,后端運(yùn)行并返回結(jié)果。
后端實(shí)現(xiàn)
為了方便實(shí)現(xiàn)后端采用到了SpringBoot
我們需要先完成代碼運(yùn)行所需要的配置
@ConfigurationProperties(prefix = "run.script") @Component public class Config { private String cpp; private String c; private String python; public void setCpp(String cpp) { this.cpp = cpp; } public void setC(String c) { this.c = c; } public void setPython(String python) { this.python = python; } public String getCpp() { return cpp; } public String getC() { return c; } public String getPython() { return python; } }
配置yml文件
此處的cpp和c應(yīng)為需要編譯運(yùn)行,所以需要根據(jù)不同的操作系統(tǒng)寫(xiě)運(yùn)行腳本
所有的路徑都必須是絕對(duì)路徑
run: script: cpp: F:\Spring\runCode\src\main\resources\runCpp.bat c: F:\Spring\runCode\src\main\resources\runC.bat python: C:\Users\puzhiwei\AppData\Local\Programs\Python\Python38\python.exe
然后我們需要將前端提交的代碼保存到文件
// 獲取系統(tǒng)緩存文件的位置 String tmpDir = System.getProperty("java.io.tmpdir"); // 隨機(jī)文件夾的名字 File pwd = Paths.get(tmpDir, String.format("%016x", nextLong.incrementAndGet())).toFile(); // 新建文件夾 pwd.mkdirs(); ProcessBuilder pb = null; switch (type) { case "C": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.c"), Charset.defaultCharset()))) { writer.write(code); } pb = new ProcessBuilder().command(config.getC()).directory(pwd); break; case "CPP": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.cpp"), Charset.defaultCharset()))) { writer.write(code); } pb = new ProcessBuilder().command(config.getCpp()).directory(pwd); break; case "JAVA": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.java"), Charset.defaultCharset()))) { writer.write(code); } String[] command = new String[]{getJavaExecutePath(), "-Dfile.encoding=" + Charset.defaultCharset(), "--source", "11", "--enable-preview", "Main.java"}; pb = new ProcessBuilder().command(command).directory(pwd); break; case "PYTHON": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.py"), Charset.defaultCharset()))) { writer.write(code); } pb = new ProcessBuilder().command(config.getPython(), "Main.py").directory(pwd); break; default: break; }
這段代碼主要實(shí)現(xiàn)了將代碼保存到系統(tǒng)的緩存文件夾中,
pb為要在終端中執(zhí)行的編譯運(yùn)行命令
由于C和C++需要編譯才能執(zhí)行,所以執(zhí)行的是運(yùn)行腳本,需要根據(jù)自己的系統(tǒng)進(jìn)行修改
在windows下如下
@echo off clang -std=c11 main.c && a.exe
@echo off clang++ -std=c++17 main.cpp && a.exe
獲取Java執(zhí)行路徑的的代碼如下
private String getJavaExecutePath() { if (javaExec == null) { String javaHome = System.getProperty("java.home"); String os = System.getProperty("os.name"); boolean isWindows = os.toLowerCase().startsWith("windows"); Path javaPath = Paths.get(javaHome, "bin", isWindows ? "java.exe" : "java"); javaExec = javaPath.toString(); } return javaExec; }
之后就是使用 ProcessBuilder 執(zhí)行腳本,并讀取運(yùn)行結(jié)果了
pb.redirectErrorStream(true); Process p = pb.start(); if (p.waitFor(5, TimeUnit.SECONDS)) { String result = null; try (InputStream input = p.getInputStream()) { result = readAsString(input, Charset.defaultCharset()); } return new ProcessResult(p.exitValue(), result); } else { System.err.println(String.format("Error: process %s timeout. destroy forcibly.", p.pid())); p.destroyForcibly(); return new ProcessResult(p.exitValue(), "運(yùn)行超時(shí)"); }
最后,這個(gè)類(lèi)的完整代碼如下
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.*; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * @author Pu Zhiwei {@literal puzhiweipuzhiwei@foxmail.com} * create 2020-03-13 18:22 */ @Component public class RunCode { private final Config config; private static String javaExec = null; private static AtomicLong nextLong = new AtomicLong(System.currentTimeMillis()); @Autowired public RunCode(Config config) { this.config = config; } public ProcessResult runCode(String type, String code) throws IOException, InterruptedException { // 獲取系統(tǒng)緩存文件的位置 String tmpDir = System.getProperty("java.io.tmpdir"); // 隨機(jī)文件夾的名字 File pwd = Paths.get(tmpDir, String.format("%016x", nextLong.incrementAndGet())).toFile(); // 新建文件夾 pwd.mkdirs(); ProcessBuilder pb = null; switch (type) { case "C": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.c"), Charset.defaultCharset()))) { writer.write(code); } pb = new ProcessBuilder().command(config.getC()).directory(pwd); break; case "CPP": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.cpp"), Charset.defaultCharset()))) { writer.write(code); } pb = new ProcessBuilder().command(config.getCpp()).directory(pwd); break; case "JAVA": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.java"), Charset.defaultCharset()))) { writer.write(code); } String[] command = new String[]{getJavaExecutePath(), "-Dfile.encoding=" + Charset.defaultCharset(), "--source", "11", "--enable-preview", "Main.java"}; pb = new ProcessBuilder().command(command).directory(pwd); break; case "PYTHON": try (Writer writer = new BufferedWriter(new FileWriter(new File(pwd, "Main.py"), Charset.defaultCharset()))) { writer.write(code); } pb = new ProcessBuilder().command(config.getPython(), "Main.py").directory(pwd); break; default: break; } pb.redirectErrorStream(true); Process p = pb.start(); if (p.waitFor(5, TimeUnit.SECONDS)) { String result = null; try (InputStream input = p.getInputStream()) { result = readAsString(input, Charset.defaultCharset()); } return new ProcessResult(p.exitValue(), result); } else { System.err.println(String.format("Error: process %s timeout. destroy forcibly.", p.pid())); p.destroyForcibly(); return new ProcessResult(p.exitValue(), "運(yùn)行超時(shí)"); } } private String getJavaExecutePath() { if (javaExec == null) { String javaHome = System.getProperty("java.home"); String os = System.getProperty("os.name"); boolean isWindows = os.toLowerCase().startsWith("windows"); Path javaPath = Paths.get(javaHome, "bin", isWindows ? "java.exe" : "java"); javaExec = javaPath.toString(); } return javaExec; } public String readAsString(InputStream input, Charset charset) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[102400]; for (; ; ) { int n = input.read(buffer); if (n == (-1)) { break; } output.write(buffer, 0, n); } return output.toString(charset); } }
寫(xiě)完這些,我們就基本完成了代碼在后端的運(yùn)行并返回結(jié)果
接下來(lái)可以寫(xiě)一個(gè)測(cè)試方法測(cè)試一下結(jié)果的運(yùn)行
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class RunApplicationTests { @Autowired private RunCode runCode; @Test void contextLoads() throws Exception { String code = "#include <stdio.h>\n" + "\n" + "int main()\n" + "{\n" + " printf(\"Hello, World! \\n\");\n" + " \n" + " return 0;\n" + "}"; System.out.println(runCode.runCode("C", code).getOutput()); } }
如果沒(méi)有異常,應(yīng)該可以看到如下內(nèi)容
最后,寫(xiě)一個(gè)controller,用來(lái)接收前端提交的代碼
@RestController @CrossOrigin("*") public class WebController { public final RunCode runCode; @Autowired public WebController(RunCode runCode) { this.runCode = runCode; } @PostMapping("/run") public ProcessResult runCode(@RequestBody CodeModel codeModel) throws Exception { return runCode.runCode(codeModel.getType(), codeModel.getCode()); } }
public class CodeModel { private String type; private String code; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
/** * @author Pu Zhiwei {@literal puzhiweipuzhiwei@foxmail.com} * create 2020-03-13 18:26 */ public class ProcessResult { private int exitCode; private String output; public ProcessResult(int exitCode, String output) { this.exitCode = exitCode; this.output = output; } public int getExitCode() { return exitCode; } public String getOutput() { return output; } }
至此,我們的后端就基本完成了。
前端
我們先寫(xiě)一個(gè)簡(jiǎn)單的html頁(yè)面來(lái)進(jìn)行測(cè)試
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <select> <option selected>Java</option> <option>C</option> </select> <br/> <textarea id="code" style="height: 500px; width: 600px"></textarea> <button id="sub-btn" onclick="submit()">提交</button> <br/> <textarea id="output"></textarea> <script> function submit() { let data = document.querySelector("#code").value; fetch("http://127.0.0.1:8848/run", { method: "POST", headers: { "Content-Type": "application/json; charset=UTF-8" }, body: JSON.stringify({ code: data, type: "JAVA" }) }).then(response => response.json()) .then(json => { console.log(json) document.querySelector("#output").value = json.output; }); } </script> </body> </html>
如果沒(méi)有問(wèn)題,我們就能看到如下結(jié)果了
最后,完善一下頁(yè)面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>代碼在線運(yùn)行工具</title> <link rel="stylesheet" rel="external nofollow" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <style> #editor { position: absolute; width: 100%; height: 100%; } </style> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container"> <a class="navbar-brand" href="/" rel="external nofollow" >代碼在線運(yùn)行工具</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> </div> </nav> <div style="height: 30px"></div> <div class="container shadow p-3 mb-5 bg-white rounded"> <div class="container-fluid"> <div class="row"> <div class="col-2"> <button id="sub-btn" class="btn btn-success " onclick="submit()">點(diǎn)擊運(yùn)行!</button> </div> <div class="col-3"> <select onchange="selectLanguage(this)" id="language-type" class="form-control"> <option selected>Java</option> <option>C</option> <option>CPP</option> <option>Python</option> </select> </div> <div class="col-3"> <button type="button" class="btn btn-secondary" onclick="clean()">清空</button> </div> </div> </div> <div style="height: 20px"></div> <div class="row"> <div class="col-7 border border-light"> <div id="editor"></div> </div> <div class="col-1 border-left"></div> <div class="col text-center"> <textarea id="output" class="form-control" rows="15"></textarea> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.8/ace.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.8/ext-language_tools.min.js" type="text/javascript"></script> <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.8/mode-java.min.js" type="text/javascript"></script>--> <script> ace.require("ace/ext/language_tools"); const editor = ace.edit("editor"); editor.session.setMode("ace/mode/java"); editor.setTheme("ace/theme/github"); // enable autocompletion and snippets editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true, enableLiveAutocompletion: true }); function submit() { document.querySelector("#output").value = "代碼運(yùn)行中!"; let data = editor.getValue(); fetch("http://127.0.0.1:8848/run", { method: "POST", headers: { "Content-Type": "application/json; charset=UTF-8" }, body: JSON.stringify({ code: data, type: document.querySelector("#language-type").value.toUpperCase() }) }).then(response => response.json()) .then(json => { console.log(json) document.querySelector("#output").value = json.output; }); } function clean() { editor.setValue(""); } function selectLanguage(e) { let mode = "ace/mode/" + e.value.toLowerCase(); if (e.value.toLowerCase() === "c" || e.value.toLowerCase() === "cpp") { mode = "ace/mode/c_cpp" } editor.session.setMode(mode); } </script> </body> </html>
效果如下
以上就是基于SpringBoot實(shí)現(xiàn)代碼在線運(yùn)行工具的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot代碼在線運(yùn)行工具的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot項(xiàng)目解決跨域的四種方案分享
在用SpringBoot開(kāi)發(fā)后端服務(wù)時(shí),我們一般是提供接口給前端使用,但前端通過(guò)瀏覽器調(diào)我們接口時(shí),瀏覽器會(huì)有個(gè)同源策略的限制,即協(xié)議,域名,端口任一不一樣時(shí)都會(huì)導(dǎo)致跨域,這篇文章主要介紹跨域的幾種常用解決方案,希望對(duì)大家有所幫助2023-05-05Spring boot GC實(shí)現(xiàn)過(guò)程原理解析
這篇文章主要介紹了Spring boot GC實(shí)現(xiàn)過(guò)程原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Mybatis工具類(lèi)JdbcTypeInterceptor運(yùn)行時(shí)自動(dòng)添加jdbcType屬性
今天小編就為大家分享一篇關(guān)于Mybatis工具類(lèi)JdbcTypeInterceptor運(yùn)行時(shí)自動(dòng)添加jdbcType屬性,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12Java面試突擊為什么要用HTTPS及它的優(yōu)點(diǎn)
這篇文章主要介紹了Java面試突擊為什么要用HTTPS及它的優(yōu)點(diǎn),文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07Java實(shí)現(xiàn)將文件或者文件夾壓縮成zip的詳細(xì)代碼
這篇文章主要介紹了Java實(shí)現(xiàn)將文件或者文件夾壓縮成zip的詳細(xì)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11idea 修改項(xiàng)目名和module名稱(chēng)的操作
這篇文章主要介紹了idea 修改項(xiàng)目名和module名稱(chēng)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02深度deepin安裝以及jdk、tomcat、Nginx安裝教程
這篇文章主要給大家介紹了關(guān)于深度deepin安裝以及jdk、tomcat、Nginx安裝的相關(guān)資料,按照文中介紹的方法可以輕松的實(shí)現(xiàn)安裝,對(duì)大家的工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01