基于SpringBoot实现代码在线运行工具

目录
  • 说明
  • 基本思路
  • 后端实现
  • 前端

说明

由于没有实现沙盒,所以这个运行只适合提交自己写的代码到服务器,不适合像 菜鸟工具 那样可以让人公开提交代码并访问。

基本思路

前端提交代码,后端运行并返回结果。

后端实现

为了方便实现后端采用到了SpringBoot

我们需要先完成代码运行所需要的配置

@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应为需要编译运行,所以需要根据不同的操作系统写运行脚本

所有的路径都必须是绝对路径

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

然后我们需要将前端提交的代码保存到文件

// 获取系统缓存文件的位置
        String tmpDir = System.getProperty("java.io.tmpdir");
        // 随机文件夹的名字
        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为要在终端中执行的编译运行命令

由于C和C++需要编译才能执行,所以执行的是运行脚本,需要根据自己的系统进行修改

在windows下如下

@echo off
clang -std=c11 main.c && a.exe
@echo off
clang++ -std=c++17 main.cpp && a.exe

获取Java执行路径的的代码如下

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 执行脚本,并读取运行结果了

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(), "运行超时");
        }

最后,这个类的完整代码如下

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 {
        // 获取系统缓存文件的位置
        String tmpDir = System.getProperty("java.io.tmpdir");
        // 随机文件夹的名字
        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(), "运行超时");
        }
    }

    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);
    }
}

写完这些,我们就基本完成了代码在后端的运行并返回结果

接下来可以写一个测试方法测试一下结果的运行

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());
    }

}

如果没有异常,应该可以看到如下内容

最后,写一个controller,用来接收前端提交的代码

@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;
    }
}

至此,我们的后端就基本完成了。

前端

我们先写一个简单的html页面来进行测试

<!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>

如果没有问题,我们就能看到如下结果了

最后,完善一下页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>代码在线运行工具</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" 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" >代码在线运行工具</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()">点击运行!</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 = "代码运行中!";
        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实现代码在线运行工具的详细内容,更多关于SpringBoot代码在线运行工具的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot+Tess4j实现牛逼的OCR识别工具的示例代码

    前言 " 等不到风中你的脸颊 眼泪都美到很融洽 等不到掩饰的雨落下 我的眼泪被你察觉 " 听着循环的歌曲,写着久违的bug.好吧,还是一天.正好一个小伙伴说,要不要做个工具站玩一下.我就随意的找了个工具站,看了下,发现很多都有文字的OCR识别功能.因此,我想起来之前了解的非常流行的开源的OCR大神级别的项目,Tesseract OCR. 简单介绍 官网如下所示 tesseract-ocr.github.io/ 简洁明了,挂在github上的网站. 详细的不再介绍,感兴趣的,可以进入同志

  • springboot实现后台上传图片(工具类)

    本文实例为大家分享了springboot实现后台上传图片的具体代码,供大家参考,具体内容如下 1.先配置启动类 继承WebMvcConfigurer 重写方法 @SpringBootApplication //@MapperScan("com.example.demo.Mapper") public class DemoApplication implements WebMvcConfigurer { public static void main(String[] args) { S

  • 基于SpringBoot实现用户身份验证工具

    session失效时间 在Tomcat上,session的默认有效时间是30分钟.也可以通过配置文件修改session的有效时间. 1)修改web.xml <!-- 设置session失效,单位分 --> <session-config> <session-timeout>1</session-timeout> </session-config> 2).yml文件 server.session.cookie.http-only= #是否开启Htt

  • 基于SpringBoot实现代码在线运行工具

    目录 说明 基本思路 后端实现 前端 说明 由于没有实现沙盒,所以这个运行只适合提交自己写的代码到服务器,不适合像 菜鸟工具 那样可以让人公开提交代码并访问. 基本思路 前端提交代码,后端运行并返回结果. 后端实现 为了方便实现后端采用到了SpringBoot 我们需要先完成代码运行所需要的配置 @ConfigurationProperties(prefix = "run.script") @Component public class Config { private String

  • Shell脚本实现的基于SVN的代码提交量统计工具

    最近没啥事,就用bash写了一个基于svn的代码统计小工具. 可以指定统计的目录,默认递归统计子目录. 目前还没有屏蔽指定目录的功能.哈 代码比较粗糙.不过先晒出来. #!/bin/bash - #""""""""""""""""""""""""""

  • 基于SpringBoot构建电商秒杀项目代码实例

    一.项目功能概述 电商秒杀需要完成的3个功能: 1.展示一个商品列表页面,我们可以从中看到可秒杀的商品列表 2.点击进入商品详情页,获取该商品的详细信息 3.秒杀时间开始后,点击进入下单确认页面,并支付成功 二.基于SpringBoot进行项目环境搭建 步骤1:创建一个maven工程,使用quickStart骨架. 步骤2:在pom.xml导入SpringBoot相关依赖. <?xml version="1.0" encoding="UTF-8"?> &

  • Springboot Mybatis Plus自动生成工具类详解代码

    前言 代码生成器,也叫逆向工程,是根据数据库里的表结构,自动生成对应的实体类.映射文件和接口. 看到很多小伙伴在为数据库生成实体类发愁,现分享给大家,提高开发效率. 一.pom依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> &

  • 基于Springboot实现JWT认证的示例代码

    目录 一.了解JWT 概念 作用 1.1 为什么授权要使用jwt 二.JWT结构 2.1 header 2.2 payload 2.3 signature 三.使用JWT 3.1 上手 3.2 封装工具类 3.3 整合springboot 最近一直想写一个类似于待办的东西,由于不想用传统的session,就卡住了,后来在各种群里扯皮,发现除了用缓存之外,还可以通过 JWT 来实现. 一.了解JWT 概念 json web token 用于在各方之间以 json 对象安全地传输信息,比如在前端和后

  • 基于SpringBoot的Dubbo泛化调用的实现代码

    目录 1.服务端定义 1.1 服务定义及实现 1.2 服务提供者配置 1.3 启动类 1.4 pom文件 2.消费端定义 2.1 Dubbo配置类 2.2 启动类 2.3 pom文件 3. 运行结果 4 .结论 5.改进 5.1 关于服务的实现 5.2 在服务端配置中增加代码 Dubbo的泛化调用不需要引入调用方的接口,只需要指定接口的全类名,就可以调用服务,一般用于框架集成.接下来就基于SpringBoot实现了Dubbo的泛化调用. 1.服务端定义 1.1 服务定义及实现 package c

  • 基于Next.js实现在线Excel的详细代码

    目录 认识 Next.js 实战之旅 如果要从头开始使用 React 构建一个完整的 Web 应用程序,需要哪些步骤?这当然不像把大象装进冰箱那么简单,只需要分成三步:打开冰箱,拿起大象,塞进冰箱就好. 我们需要考虑细节有很多,比如: 必须使用打包程序(例如 webpack)打包代码,并使用 Babel 等编译器进行代码转换. 需要针对生产环境进行优化,例如代码拆分.需要对一些页面进行预先渲染以提高页面性能和 SEO,可能还希望使用服务器端渲染或客户端渲染. 必须编写一些服务器端代码才能将 Re

  • JS、HTML代码运行工具

    JS.HTML代码运行工具 function runCode(obj) { if(obj.value==""){ alert("请输入要运行的代码内容"); return false;} var winname = window.open('', "_blank", ''); winname.document.open('text/html', 'replace'); winname.document.writeln(obj.value); wi

  • springboot利用@Aspect实现日志工具类的详细代码

    目录 一.导包 二.在启动类上进行注解自动扫描 三.工具类 四.结果 一.导包 <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.12</version> </dependency> <dependency> <groupId>org.aspectj<

随机推荐