侧边栏壁纸
博主头像
昂洋编程 博主等级

鸟随鸾凤飞腾远,人伴贤良品自高

  • 累计撰写 71 篇文章
  • 累计创建 79 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

在线运行Java代码小工具

Administrator
2023-02-16 / 0 评论 / 0 点赞 / 26 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2024-06-14,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

在线体验地址: https://zhouyi.ink/#/runjava

image-1676525872480

实现思路

  1. web前端提供一个代码输入框,一个结果显示框;
  2. 前端携带用户输入的代码请求后端java程序执行运行代码的命令,并将执行结果返回给前端
  3. 前端展示后端返回的代码执行结果

前端实现

前端采用的vue3 + Element plus +typescript + vite实现的,主要代码如下

<template>
    <div>
        <el-container>
            <!-- 头部区域 -->
            <el-header class="header">在线运行Java代码(基于Oracle JDK1.8)</el-header>
            <!-- 下方主体区域 -->
            <el-main class="main">
                <!-- 下方左边区域 -->
                <div class="main-left">
                    <el-header class="left-header">
                        <el-button type="info" @click="examples(1)">九九乘法表</el-button>
                        <el-button type="info" @click="examples(2)">字符串反转</el-button>
                    </el-header>
                    <el-main>
                        <el-input type="textarea" rows="30" resize="none" v-model="inputCode" />
                    </el-main>
                </div>
                <!-- 下方右边区域 -->
                <div class="main-right">
                    <el-header class="right-header">
                        <el-button type="success" @click="runCode">运行</el-button>
                    </el-header>
                    <el-main>
                        <el-input type="textarea" :readonly="true" resize="none" rows="30" v-model="codeResult" />
                    </el-main>
                </div>
            </el-main>
            </el-container>

    </div>
</template>

<script lang="ts" setup>
import axios from 'axios'
import { ref } from 'vue'

const inputCode = ref('public class Main {\n\t public static void main(String[] args) {\n\t\t System.out.println("Hello, World");\n\t } \n }')
const codeResult = ref('运行结果')

const examples = (implType) => {
    if (implType == 1) {
        inputCode.value = "public class Main {\n\t public static void main(String[] args) {\n                // 外层循环 也就是乘法的第一个数\n\t\tfor (int j = 0; j <= 9; j++) {\n                        // 内层循环,乘法的第二个数\n\t\t\tfor (int i = 1; i <= j; i++) {\n\t\t\t\tSystem.out.print(j + \"*\" + i + \"=\" + (j * i) + \"\\t\");\n\t\t\t}\n\t\t\tSystem.out.println();\n\t\t}\n\t } \n }"
    } else if (implType == 2) {
        inputCode.value = "public class Main {\n\t public static void main(String[] args) {\n                String string=\"zhouyi\";\n                String reverse = new StringBuffer(string).reverse().toString();\n                System.out.println(\"字符串反转前:\"+string);\n                System.out.println(\"字符串反转后:\"+reverse);\n\t } \n }"
    }
}

const runCode = () => {
    axios({
        method: "POST",
        url: "https://www.angyum.com:10040/runJava",
        headers: {'Content-Type':'application/json'},
        data: {
            "javaCode": inputCode.value
        }
    }).then(response => {
        const responseData = response.data
        var errCode = responseData.error
        codeResult.value = errCode==0 ? responseData.stdout : responseData.stderr
    })
}
</script>

<style lang="scss" scoped>
.header {
    text-align: center;
    border: 1px;
    background-color: #F7F7F7;
    /* line-height 和 height相等即可实现文字上下居中 */
    line-height: 60px;
    height: 60px;
}
.main {
    margin-top: 2px;
    height: 800px;
    width: 100%;
}
.main-left {
    width: 50%;
    float: left;
    height: 100%;
    border: 1px;
}
.main-right {
    width: 50%;
    float: right;
    height: 100%;
    border: 1px;
}
.left-header {
    margin-left: 1px;
}
.right-header {
    margin-left: 1px;
}
</style>

后端实现

实时运行java代码原理是调用Runtime.getRuntime()的exec(cmd)执行命令

  1. 将用户录入的java代码保存为临时的java文件
  2. 执行javac命令将临时java文件编译成class文件
  3. 执行java命令调用class文件

执行命令的工具类

package com.angyum.zhouyi_online.util;

import com.angyum.zhouyi_online.entity.Answer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author zhangmy
 * @date 2023/2/15 17:10
 * @description 编译工具
 */
public class CompileUtil {

    //1.通过Runtime类得到实例,执行exec方法
    //2.希望获取到标准输出,并写入到指定的文件中
    //3.获取到标准的错误,并写入到指定文件中
    //4.等待子进程结束,拿到状态码,并返回。

    //防止不小心实例化工具类
    private CompileUtil() {
    }

    public static Answer run(String cmd, Boolean stdoutB, Boolean stderrB) {
        //System.out.println(cmd);
        Answer answer = new Answer();
        try {
            //1.通过Runtime类得到实例,执行exec方法
            Process process = Runtime.getRuntime().exec(cmd);
            //2.获取到标准输出,并写入到指定文件中
            String line = "", re = "", stdout = "";

            // 判断是windows还是linux
            String os = System.getProperty("os.name");
            String charsetName = "UTF-8";
            if(os.toLowerCase().startsWith("windows")){
                charsetName = "GBK";
            }else if(os.toLowerCase().startsWith("linux")){
                charsetName = "UTF-8";
            }
            if (stdoutB) {
                //windows上指定GBK中文才能正常显示,Linux还没试明先不管
                //也会自动阻塞
                BufferedReader stdoutFrom = new BufferedReader(new InputStreamReader(process.getInputStream(), charsetName));
                while ((line = stdoutFrom.readLine()) != null)
                    stdout += line + "\n";
                stdoutFrom.close();
                ;
            }
            if (stderrB) {
                BufferedReader stdoutFrom = new BufferedReader(new InputStreamReader(process.getErrorStream(), charsetName));
                while ((line = stdoutFrom.readLine()) != null) {
                    re += line + "\n";
                }
                stdoutFrom.close();
            }
            //exec 执行过程是异步的,waitfor会阻塞主进程,直到子进程完毕
            int exitCode = process.waitFor();
            answer.setError(exitCode);
            answer.setStderr(re);
            answer.setStdout(stdout);
            return answer;
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
        answer.setError(2);
        return answer;
    }
}

业务逻辑实现

package com.angyum.zhouyi_online.service.impl;

import com.angyum.zhouyi_online.entity.Answer;
import com.angyum.zhouyi_online.service.RunJavaService;
import com.angyum.zhouyi_online.util.CompileUtil;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;

/**
 * @author zhangmy
 * @date 2023/2/15 17:53
 * @description
 */
@Service
public class RunJavaServiceImpl implements RunJavaService {

    @Override
    public synchronized Answer runJavaCode(Map<String, Object> map) throws IOException {
        String DIR = "~/javaTest/";
        File dirFile = new File(DIR);
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
        String javaFile = DIR + "Main.java";
        String javaClass = "Main";
        String compileCmd = String.format("javac -encoding utf8 %s -d %s", javaFile, DIR);
        String runningCmd = String.format("java -classpath %s %s", DIR, javaClass);
        //将代码写入到定义路径下特定的java源文件中
        FileWriter writer = new FileWriter(javaFile);
        Object javaCodeObj = map.get("javaCode");
        writer.write(javaCodeObj.toString());
        writer.close();
        //编译源文件为class文件
        Answer answer = CompileUtil.run(compileCmd, false, true);
        System.out.println(answer.getStderr());
        //若编译成功即可开始运行
        if (answer.getError() == 0) {
            answer = CompileUtil.run(runningCmd, true, true);
            if (answer.getError() != 0)
                answer.setReason(Answer.Success);
            else
                answer.setReason(Answer.RuntimeError);
            System.out.println(answer.getStdout());
        } else {
            answer.setReason(Answer.Error);
        }
        File sourceFile = new File(javaFile);
        sourceFile.deleteOnExit();

        File classFile = new File(DIR + "Main.class");
        classFile.deleteOnExit();
        return answer;
    }
}

执行结果响应类

package com.angyum.zhouyi_online.entity;

/**
 * @author zhangmy
 * @date 2023/2/15 17:11
 * @description
 */
public class Answer {

    //错误码如果error为0表示运行ok,1为编译错误,2为命令运行出错…………很多很多状态
    private int error;
    //出错的提示信息-运行成功-编译错误-运行时错误。
    private String reason;
    public static String Success="运行成功";
    public static String Error="编译错误";
    public static String RuntimeError="运行时错误";
    //运行程序得到的标准输出
    private String stdout;
    //运行程序得到的标准错误
    private String stderr;
    public int getError() {
        return error;
    }

    public void setError(int error) {
        this.error = error;
    }
    public String getReason() {
        return reason;
    }
    public void setReason(String reason) {
        this.reason = reason;
    }
    public String getStdout() {
        return stdout;
    }
    public void setStdout(String stdout) {
        this.stdout = stdout;
    }
    public String getStderr() {
        return stderr;
    }
    public void setStderr(String stderr) {
        this.stderr = stderr;
    }
}

一些问题

记录开发过程中遇到的一些问题

  1. 首先要明确实现的思路以及原理

就像文中提到的调用Runtime.getRuntime()的exec(cmd)执行命令。一开始我想着直接在js前端直接保存文件,然后再调用系统命令编译文件和执行结果。多番实验之后中间有许多解决不了的问题,所以我选择了一种简单的方式实现

  1. 跨域问题的解决

因为后端是springboot项目,所以跨域只需要简单配置下即可

package com.angyum.zhouyi_online.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhangmy
 * @date 2023/2/16 9:45
 * @description
 */
@Configuration
@EnableWebMvc
public class ConfigurerAdapter implements WebMvcConfigurer  {

    // 导入Cors的过滤器,在配置文件中进行配置
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 允许访问的客户端域名
        List<String> allowedOriginPatterns = new ArrayList<>();
        allowedOriginPatterns.add("*");
        config.setAllowedOriginPatterns(allowedOriginPatterns);
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

  1. 包含一些前端的知识

因为前端我不是太懂,都是慢慢摸索来的,页面的布局,布局的实现,axios发送请求,样式调整等等,这块花费了大头的时间

  1. 阿里云服务器申请和部署ssl证书

因为前端代码是在另一个服务器上的,域名是加了https证书的,所以在单独请求运行java代码的服务器上也得加上SSL证书,刚好我这是阿里云的服务器,陪着nginx即可添加https安全证书

①:登录阿里云控制,访问SLL证书免费证书页
②:先购买,购买之后创建证书,因为我已经申请了,显示证书已有,阿里云每个账号每年可以申请20个免费的证书
③:创建之后即可通过nginx部署到服务器了
image-1676527600514
image-1676527617010
下载好之后根据一下的nginx配置即可

server {
    listen       10040 ssl;
    server_name  www.angyum.com;
  
    ssl_certificate /etc/nginx/cert/9305602_www.angyum.com.pem;
    ssl_certificate_key /etc/nginx/cert/9305602_www.angyum.com.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;

     location / {
            # 代理的后端真实访问IP+端口
            proxy_pass http://127.0.0.1:10041;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
    }
}

这里的10040是前端访问后端的入口,10041是后端真实的的端口,写好配置之后重启nginx就可以了

到这里一个简单的在线运行java代码的工具就实现了,还有很多优化的地方,比如界面布局,添加可录入参数,如何防止恶意请求等等,这些都等会后面有时间再补上吧

0

评论区