在线代码编辑器

在线代码编辑器

文章说明

采用Java结合vue3设计实现的在线代码编辑功能,支持在线编辑代码、运行代码,同时支持导入文件,支持图片识别,支持复制代码,可将代码导出为图片
暂时还未添加图片代码识别导入的功能,然后目前Linux版本下安装C#的编译器我还没找到好的方案,这个C#代码运行在Linux环境下暂时有点问题
目前对于代码安全方面还未添加校验,只作为简单的学习使用,存在着不小的风险,后续会考虑新开坑,书写更完善的在线编辑器
目前的这个结果展示的编码方面还有一些问题,主要是控制台输入、命令行以及文件本身的编码会有不一致的情况,有些难整,后续考虑采用新方案给这个问题解决一下

前台核心代码

前台主要采用 md-editor-v3 编辑器作为代码编辑区域,虽然没有提示功能,但是展示效果上会好一些,然后运行的时候调用后台接口,采用Java调用cmd命令行来实现代码的运行
其中代码下载为图片采用的是对dom对象进行下载,采用html2canvas来实现,会有一些小bug,不过效果感觉还可以
前台核心代码

html 复制代码
<script setup>
import {MdEditor, MdPreview} from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import 'md-editor-v3/lib/preview.css';
import {computed, onBeforeMount, reactive, ref, watch} from "vue";
import html2canvas from "html2canvas";
import {
  confirm,
  downloadFileToLocalByUrl,
  exportCommon,
  getRequest,
  loading,
  message,
  postRequest,
  prompt
} from "@/util";

const data = reactive({
  language: "",
  codeText: "",
  inputText: "",
  displayText: "",
  exportDialogVisible: false,
  exportImgUrl: "",
  uploadExist: true,
});

const languageList = [
  "Java",
  "Python",
  "Javascript",
  "C",
  "C++",
  "C#",
];

const codeText = computed(() => {
  return "```" + data.language + "\n" + data.codeText + "\n```"
});

function exportCodeToImg() {
  const displayContent = document.getElementsByClassName("display-content")[0];
  data.exportDialogVisible = true;
  html2canvas(displayContent, {}).then((canvas) => {
    data.exportImgUrl = canvas.toDataURL('image/png');
  });
}

function exportCode() {
  prompt("导出代码文件", "导出代码文件名称", data.language + "_" + new Date().toLocaleString() + ".txt", (value) => {
    exportCommon(data.codeText, value);
  });
}

function exportCodeToImgConfirm() {
  prompt("导出代码图片", "导出代码图片名称", data.language + "_" + new Date().toLocaleString() + ".png", (value) => {
    downloadFileToLocalByUrl(data.exportImgUrl, value);
    data.exportDialogVisible = false;
  });
}

function reset() {
  confirm("确认清空吗,包括代码区域、控制台文件输入以及结果展示区域?", () => {
    data.codeText = "";
    data.inputText = "";
    data.displayText = "";
  });
}

function run() {
  if (!data.language) {
    message("请先选择编程语言", "warning");
    return;
  }
  const loadingInstance = loading("运行中...");
  postRequest("/codeRunner/run", null, {
    language: data.language,
    codeText: data.codeText,
    inputText: data.inputText,
  }).then((res) => {
    loadingInstance.close();
    if (res.data.code === 200) {
      data.displayText = res.data.data;
      message("代码运行成功", "success");
    } else if (res.data.code === 500) {
      message("代码运行失败", "error");
    }
  })
}

const languageText = {};

onBeforeMount(async () => {
  for (let i = 0; i < languageList.length; i++) {
    languageText[languageList[i]] = await getLanguageDefaultText(languageList[i]);
  }
});

async function getLanguageDefaultText(language) {
  const res = await getRequest("/codeRunner/getDefaultText", {
    language
  });
  if (res.data.code === 200) {
    return res.data.data;
  } else if (res.data.code === 500) {
    message(language + "语言默认代码获取失败", "error");
  }
}

watch(() => data.language, (newValue, oldValue) => {
  if (data.codeText) {
    languageText[oldValue] = data.codeText;
  }
  data.codeText = languageText[data.language];
});

const uploadFileRef = ref();

function uploadFile(event) {
  const loadingInstance = loading("导入中...");
  const file = event.target.files[0];
  const reader = new FileReader();
  reader.readAsText(file, 'utf8');
  data.uploadExist = false;
  reader.onload = () => {
    loadingInstance.close();
    const codeText = reader.result.toString();
    data.uploadExist = true;
    if (!codeText) {
      message("导出文件内容为空", "warning");
      return;
    }
    data.codeText = codeText;
  }
}

function singleImport() {
  if (!data.language) {
    message("请先选择编程语言", "warning");
    return;
  }
  if (!data.uploadExist) {
    return;
  }
  uploadFileRef.value.click();
}
</script>

<template>
  <div class="container">
    <div class="tool-bar">
      <div class="row">
        <el-select v-model="data.language" placeholder="请选择编程语言" style="width: 80%">
          <template v-for="item in languageList" :key="item">
            <el-option :label="item" :value="item"/>
          </template>
        </el-select>
      </div>
      <div class="row" style="margin-top: -0.5rem">
        <el-button type="primary" @click="run">运行</el-button>
        <el-button type="danger" @click="reset">清空</el-button>
        <el-button type="danger" @click="singleImport">导入代码</el-button>
        <input v-if="data.uploadExist" ref="uploadFileRef" accept=".txt,.java,.py,.js,.c,.cpp,.cs" style="display: none"
               type="file" @change="uploadFile($event)">
      </div>
      <div class="row" style="margin-top: -0.5rem">
        <el-button type="primary" @click="exportCode">导出代码</el-button>
        <el-button type="danger" @click="exportCodeToImg">下载为图片</el-button>
      </div>
    </div>
    <div class="content-container">
      <div class="editor-content">
        <MdEditor v-model="data.codeText" :preview="false" :toolbars="[]" placeholder="代码输入区域"
                  style="height: 100%; width: 100%" theme="dark"/>
      </div>
      <div class="display-content">
        <MdPreview v-model="codeText" :show-code-row-number="false" style="height: 100%; width: 100%"/>
      </div>
      <div class="input-content">
        <MdEditor v-model="data.inputText" :preview="false" :toolbars="[]" placeholder="控制台文件输入区域"
                  style="height: 100%; width: 100%" theme="dark"/>
      </div>
      <div class="result-content">
        <MdEditor v-model="data.displayText" :preview="false" :toolbars="[]" placeholder="结果展示区域" read-only
                  style="height: 100%; width: 100%" theme="dark"/>
      </div>
    </div>
  </div>

  <el-dialog v-model="data.exportDialogVisible" title="代码下载为图片" width="90%">
    <img :src="data.exportImgUrl" alt="" style="width: 100%; height: auto"/>
    <template #footer>
      <el-button type="primary" @click="data.exportDialogVisible = false">取消</el-button>
      <el-button type="danger" @click="exportCodeToImgConfirm">确认</el-button>
    </template>
  </el-dialog>
</template>

<style lang="scss">
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

.container {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;

  .tool-bar {
    height: 9rem;
    background-color: #282c34;
    border-bottom: 0.1rem solid gray;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    user-select: none;

    .row {
      width: 100%;
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }

  .content-container {
    flex: 1;
    overflow: auto;
    display: flex;
    flex-direction: column;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }

    .md-editor-footer {
      display: none;
    }

    .cm-scroller {
      background-color: #282c34;
    }

    .editor-content {
      height: fit-content;
      border-bottom: 0.1rem solid gray;
    }

    .display-content {
      height: fit-content;
      border-bottom: 0.1rem solid gray;

      .md-editor-preview-wrapper {
        padding: 0;
      }

      .md-editor-code {
        margin: 0;
      }

      .md-editor-code-head {
        border-radius: 0 !important;
      }

      code {
        border-radius: 0 !important;
        white-space: pre-wrap;
      }
    }

    .input-content {
      flex: 1;
      background-color: #282c34;
      border-bottom: 0.1rem solid gray;
    }

    .result-content {
      flex: 1;
      background-color: #282c34;
    }
  }
}
</style>

后台核心代码

后台采用命令行运行代码,在Java、C、C++、C#,代码运行时会需要多步骤进行,目前采用的都是将代码写入到指定文件,然后采用指定命令运行绝对路径的文件,限制较大,后续考虑运行项目的形式,可以更加方便的解决文件编码问题以及代码运行问题
Java调用cmd运行代码工具类

java 复制代码
package com.boot.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;

import static com.boot.util.DefaultConfig.*;
import static com.boot.util.LanguageType.*;

/**
 * @author bbyh
 * @date 2023/2/27 0027 15:10
 */
@Slf4j
public class ExecUtil {
    public static String exec(String language, String codeText, String inputText) throws Exception {
        File codeTextInputFile = new File(LANGUAGE_INPUT_FILE_MAP.get(language));
        if (codeTextInputFile.exists()) {
            try (FileOutputStream outputStream = new FileOutputStream(codeTextInputFile)) {
                outputStream.write(codeText.getBytes(StandardCharsets.UTF_8));
            }
        }
        File inputTextInputFile = new File(INPUT_TEXT_PUT_FILE);
        if (inputTextInputFile.exists()) {
            try (FileOutputStream outputStream = new FileOutputStream(inputTextInputFile)) {
                outputStream.write(inputText.getBytes(StandardCharsets.UTF_8));
            }
        }

        if (judgeLinux()) {
            int exeCode = execLinux(language);
            logCodeTextRunLog(language, codeText, inputText, exeCode);
        } else if (judgeWindows()) {
            int exeCode = execWindows(language);
            logCodeTextRunLog(language, codeText, inputText, exeCode);
        }
        String[] outputFileNames = {ERROR_OUTPUT_FILE, OUTPUT_FILE};
        for (String outputFileName : outputFileNames) {
            File outputFile = new File(outputFileName);
            if (outputFile.exists()) {
                try (FileInputStream inputStream = new FileInputStream(outputFile)) {
                    byte[] buf = new byte[1024 * 1024];
                    int read = inputStream.read(buf);
                    if (read >= 0) {
                        String result = new String(buf, 0, read);
                        String encoding = getEncoding(result);
                        if (!"".equals(encoding)) {
                            return new String(buf, 0, read, encoding);
                        }
                    }
                }
            }
        }
        return "";
    }

    private static void logCodeTextRunLog(String language, String codeText, String inputText, int exeCode) {
        log.info("运行时间:{}", new Date());
        log.info("编程语言:{}", language);
        log.info("运行代码:{}", codeText);
        log.info("运行输入:{}", inputText);
        log.info("运行结果:{}", exeCode);
    }

    private static int execLinux(String language) throws Exception {
        switch (language) {
            case JAVA:
                if (execLinuxCommand("javac " + JAVA_INPUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    int start = JAVA_INPUT_FILE.lastIndexOf("/") + 1;
                    int end = JAVA_INPUT_FILE.lastIndexOf(".");
                    String javaClassFileName = JAVA_INPUT_FILE.substring(start, end);
                    return execLinuxCommand("java -classpath " + LINUX_PREFIX + " " + javaClassFileName + " < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
            case PYTHON:
                return execLinuxCommand("python " + PYTHON_INPUT_FILE + " < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
            case JAVASCRIPT:
                return execLinuxCommand("node " + JAVASCRIPT_INPUT_FILE + " < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
            case C:
                if (execLinuxCommand("gcc " + C_INPUT_FILE + " -o " + LINUX_PREFIX + "a.exe > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    return execLinuxCommand(LINUX_PREFIX + "a.exe < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
            case C_PLUS_PLUS:
                if (execLinuxCommand("g++ " + C_PLUS_PLUS_INPUT_FILE + " -o " + LINUX_PREFIX + "a.exe > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    return execLinuxCommand(LINUX_PREFIX + "a.exe < " + INPUT_TEXT_PUT_FILE + "> " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
            case C_SHARP:
                if (execLinuxCommand("csc /out:" + LINUX_PREFIX.replaceAll("/", "\\\\") + "a.exe "
                        + C_SHARP_INPUT_FILE.replaceAll("/", "\\\\") + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    return execLinuxCommand(LINUX_PREFIX + "a.exe < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
        }
        return 0;
    }

    private static int execLinuxCommand(String command) throws Exception {
        ArrayList<String> commandList = new ArrayList<>();
        commandList.add("/bin/sh");
        commandList.add("-c");
        commandList.add(command);
        Process exec = Runtime.getRuntime().exec(commandList.toArray(new String[0]));
        return exec.waitFor();
    }

    private static int execWindows(String language) throws Exception {
        switch (language) {
            case JAVA:
                if (execWindowsCommand("javac " + JAVA_INPUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    int start = JAVA_INPUT_FILE.lastIndexOf("/") + 1;
                    int end = JAVA_INPUT_FILE.lastIndexOf(".");
                    String javaClassFileName = JAVA_INPUT_FILE.substring(start, end);
                    return execWindowsCommand("java -classpath " + WINDOWS_PREFIX + " " + javaClassFileName + " < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                break;
            case PYTHON:
                return execWindowsCommand("python " + PYTHON_INPUT_FILE + " < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
            case JAVASCRIPT:
                return execWindowsCommand("node " + JAVASCRIPT_INPUT_FILE + " < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
            case C:
                if (execWindowsCommand("gcc " + C_INPUT_FILE + " -o " + WINDOWS_PREFIX + "a.exe > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    return execWindowsCommand(WINDOWS_PREFIX + "a.exe < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
            case C_PLUS_PLUS:
                if (execWindowsCommand("g++ " + C_PLUS_PLUS_INPUT_FILE + " -o " + WINDOWS_PREFIX + "a.exe > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    return execWindowsCommand(WINDOWS_PREFIX + "a.exe < " + INPUT_TEXT_PUT_FILE + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
            case C_SHARP:
                if (execWindowsCommand("csc /out:" + WINDOWS_PREFIX.replaceAll("/", "\\\\") + "a.exe "
                        + C_SHARP_INPUT_FILE.replaceAll("/", "\\\\") + " > " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE) == 0) {
                    return execWindowsCommand(WINDOWS_PREFIX + "a.exe < " + INPUT_TEXT_PUT_FILE + "> " + OUTPUT_FILE + " 2> " + ERROR_OUTPUT_FILE);
                }
                return 0;
        }
        return 0;
    }

    private static int execWindowsCommand(String command) throws Exception {
        Process exec = Runtime.getRuntime().exec("cmd /c " + command);
        return exec.waitFor();
    }

    public static Boolean judgeWindows() {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }

    public static Boolean judgeLinux() {
        return System.getProperty("os.name").toLowerCase().contains("linux");
    }

    public static String getEncoding(String str) throws Exception {
        String[] encodes = {"UTF-8", "GBK"};
        for (String encode : encodes) {
            if (str.equals(new String(str.getBytes(encode), encode))) {
                return encode;
            }
        }
        return "";
    }

}

效果展示

运行Java代码

运行Python代码

运行JavaScript代码

运行C语言代码

运行C++代码

运行C#代码

将代码下载为图片

源码下载

在线代码编辑器

相关推荐
电子云与长程纠缠2 小时前
UE5.3中通过编辑器工具创建大纲菜单文件夹
java·ue5·编辑器
lucky九年3 小时前
vscode翻译插件
ide·vscode·编辑器
真·Wild·攻城狮4 小时前
【码农日常】Vscode Clangd初始化失败(Win10)
ide·vscode·编辑器
七灵微4 小时前
【测试】【Debug】vscode中同一个测试用例出现重复
ide·vscode·编辑器
4U24719 小时前
Linux入门之vim
linux·编辑器·vim·命令模式·底行模式
Liquor14191 天前
vim 编辑器
java·linux·c语言·开发语言·python·编辑器·vim
skywalk81631 天前
三周精通FastAPI:33 在编辑器中调试
python·编辑器·fastapi
188_djh2 天前
# vim那些事...... vim删除文件全部内容
linux·ubuntu·centos·编辑器·vim·vi·vim删除文件全部内容
虞书欣的62 天前
Python小游戏22——吃豆豆小游戏
python·算法·游戏·编辑器·pygame
shujuwa662 天前
什么是开源软件(OSS)?
pdf·编辑器·电脑·word·开源软件