微服务的编程测评系统17-判题功能-代码沙箱
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 1. 判题功能
- 1.1 初版代码-friend-api
- 1.2 初版代码-judge-无执行代码
- 1.3 初版代码-judge-执行代码
- 1.4 创建目录
- 1.5 拉取镜像-创建容器
- 1.6 编译代码
- 总结
前言
1. 判题功能
1.1 初版代码-friend-api
在friend中
@PostMapping("/submitQuestion")public R<SubmitQuestionVO> submitQuestion(@RequestBody SubmitQuestionDTO submitQuestionDTO){log.info("用户提交题目代码,submitQuestionDTO:{}",submitQuestionDTO);return R.ok(userQuestionService.submitQuestion(submitQuestionDTO));}
@Service
public class UserQuestionService implements IUserQuestionService {@Overridepublic SubmitQuestionVO submitQuestion(SubmitQuestionDTO submitQuestionDTO) {Integer programType = submitQuestionDTO.getProgramType();if(ProgramType.JAVA.getValue().equals(programType)){}throw new ServiceException(ResultCode.PROGRAM_TYPE_ERR);}
}
然后submitQuestion要调用judge服务,就去调用api
把friend的参数传给judge,调用judge服务
在api中
创建一个模块oj-api
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>com.bite</groupId><artifactId>oj-common-core</artifactId><version>1.0-SNAPSHOT</version></dependency>
@FeignClient(contextId = "RemoteJudgeService", value = Constants.JUDGE_SERVICE)
public interface RemoteJudgeService {@PostMapping("/judge/doJudgeJavaCode")R<UserQuestionResultVO> doJudgeJavaCode(@RequestBody JudgeSubmitDTO judgeSubmitDTO);
}
创建一个service来调用judge
然后vo和dto都定义在api中
@Getter
@Setter
public class JudgeSubmitDTO {private Long userId;private Long examId;//编程语言类型(0 java 1 C++)private Integer programType;private Long questionId;//题目难度private Integer difficulty;//时间限制 msprivate Long timeLimit;//空间限制 kbprivate Long spaceLimit;//拼装完整的用户代码 用户提交的代码 + main函数private String userCode;//输入数据private List<String> inputList;//期望输出数据private List<String> outputList;
}
@Getter
@Setter
public class UserQuestionResultVO {//是够通过标识private Integer pass; // 0 未通过 1 通过private String exeMessage; //异常信息private List<UserExeResult> userExeResultList;@JsonIgnoreprivate Integer score;
}
@Getter
@Setter
public class UserExeResult {private String input;private String output; //期望输出private String exeOutput; //实际输出
}
然后在friend中调用
@Overridepublic R<UserQuestionResultVO> submitQuestion(SubmitQuestionDTO submitQuestionDTO) {Integer programType = submitQuestionDTO.getProgramType();if(ProgramType.JAVA.getValue().equals(programType)){JudgeSubmitDTO judgeSubmitDTO = makeJudgeSubmitDTO(submitQuestionDTO);return remoteJudgeService.doJudgeJavaCode(judgeSubmitDTO);}throw new ServiceException(ResultCode.PROGRAM_TYPE_ERR);}private JudgeSubmitDTO makeJudgeSubmitDTO(SubmitQuestionDTO submitQuestionDTO) {JudgeSubmitDTO judgeSubmitDTO =new JudgeSubmitDTO();Long questionId = submitQuestionDTO.getQuestionId();QuestionES questionES = questionRepository.findById(questionId).orElse(null);if(questionES!=null){BeanUtil.copyProperties(questionES,judgeSubmitDTO);}else{Question question = questionMapper.selectById(questionId);BeanUtil.copyProperties(question,judgeSubmitDTO);BeanUtil.copyProperties(question,questionES);questionRepository.save(questionES);}judgeSubmitDTO.setUserId(ThreadLocalUtil.get(Constants.USER_ID,Long.class));judgeSubmitDTO.setProgramType(submitQuestionDTO.getProgramType());judgeSubmitDTO.setUserCode(codeConnect(submitQuestionDTO.getUserCode(),questionES.getMainFuc()));List<QuestionCase> questionCaseList = JSONUtil.toList(questionES.getQuestionCase(), QuestionCase.class);List<String> inputList = questionCaseList.stream().map(QuestionCase::getInput).toList();List<String> outputList = questionCaseList.stream().map(QuestionCase::getOutput).toList();judgeSubmitDTO.setInputList(inputList);judgeSubmitDTO.setOutputList(outputList);return judgeSubmitDTO;}private String codeConnect(String userCode, String mainFunc) {String targetCharacter = "}";int targetLastIndex = userCode.lastIndexOf(targetCharacter);if (targetLastIndex != -1) {return userCode.substring(0, targetLastIndex) + "\n" + mainFunc + "\n" + userCode.substring(targetLastIndex);}throw new ServiceException(ResultCode.FAILED);}
}
@Getter
@Setter
public class QuestionCase {private String input;private String output;
}
记得还要给friend的启动类加上
@EnableFeignClients(basePackages = "com.ck.api")
不然找不到
1.2 初版代码-judge-无执行代码
我们用docker来执行java代码,就是代码沙箱–》隔离代码环境
<dependency><groupId>com.ck</groupId><artifactId>oj-api</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>com.github.docker-java</groupId><artifactId>docker-java</artifactId><version>3.3.4</version></dependency>
com.github.docker-java就是代码沙箱
@PostMapping("/judge/doJudgeJavaCode")R<UserQuestionResultVO> doJudgeJavaCode(@RequestBody JudgeSubmitDTO judgeSubmitDTO){return R.ok(judgeService.doJudgeJavaCode(judgeSubmitDTO));}
@Service
public class JudgeService implements IJudgeService {@Autowiredprivate ISandBoxService sandBoxService;@Overridepublic UserQuestionResultVO doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {SandBoxExecuteResult sandBoxExecuteResult = sandBoxService.exeJavaCode(judgeSubmitDTO.getUserCode(),judgeSubmitDTO.getInputList());return null;}
}
代码的执行我们用的是sandBoxService来执行,就是用代码沙箱
这个方法exeJavaCode的实现还不着急
@Data
public class SandBoxExecuteResult {private CodeRunStatus runStatus; //执行结果private String exeMessage; //异常信息private List<String> outputList; //执行结果private Long useMemory; //占用内存 kbprivate Long useTime; //消耗时间 mspublic static SandBoxExecuteResult fail(CodeRunStatus runStatus, String errorMsg) {SandBoxExecuteResult result = new SandBoxExecuteResult();result.setRunStatus(runStatus);result.setExeMessage(errorMsg);return result;}public static SandBoxExecuteResult fail(CodeRunStatus runStatus) {SandBoxExecuteResult result = new SandBoxExecuteResult();result.setRunStatus(runStatus);result.setExeMessage(runStatus.getMsg());return result;}public static SandBoxExecuteResult fail(CodeRunStatus runStatus, List<String> outputList,Long useMemory, Long useTime) {SandBoxExecuteResult result = new SandBoxExecuteResult();result.setRunStatus(runStatus);result.setExeMessage(runStatus.getMsg());result.setOutputList(outputList);result.setUseMemory(useMemory);result.setUseTime(useTime);return result;}public static SandBoxExecuteResult success(CodeRunStatus runStatus, List<String> outputList,Long useMemory, Long useTime) {SandBoxExecuteResult result = new SandBoxExecuteResult();result.setRunStatus(runStatus);result.setOutputList(outputList);result.setUseMemory(useMemory);result.setUseTime(useTime);return result;}
}
@Getter
public enum CodeRunStatus {RUNNING(1, "运行中"),SUCCEED(2, "运行成功"),FAILED(3, "运行失败"),NOT_ALL_PASSED(4, "未通过所有用例"),UNKNOWN_FAILED(5, "未知异常,请您稍后重试"),COMPILE_FAILED(6, "编译失败"),OUT_OF_MEMORY(7, "运行结果正确,但是超出空间限制"),OUT_OF_TIME(8, "运行结果正确,但是超出时间限制");private Integer value;private String msg;CodeRunStatus(Integer value, String msg) {this.value = value;this.msg = msg;}
}
然后就是继续完善doJudgeJavaCode方法了
public class JudgeConstants {public static final String ERROR_ANSWER = "未完全通过所有用例";public static final String OUT_OF_MEMORY = "超出内存限制,请优化代码";public static final String OUT_OF_TIME = "运行时间超出限制,请优化代码";public static final Integer ERROR_SCORE = 0;public static final Integer DEFAULT_SCORE = 100;public static final String EXAM_CODE_DIR = "user-code";public static final String CODE_DIR_POOL = "user-code-pool";public static final String DOCKER_USER_CODE_DIR = "/usr/share/java";public static final String USER_CODE_JAVA_CLASS_NAME = "Solution.java";public static final String USER_CODE_JAVA_FILE_NAME = "Solution";public static final String JAVA_ENV_IMAGE = "openjdk:8-jdk-alpine";public static final String JAVA_CONTAINER_PREFIX = "/";public static final String JAVA_CONTAINER_NAME = "oj-jdk";public static final String[] DOCKER_JAVAC_CMD = new String[] {"javac", "/usr/share/java/Solution.java"};public static final String[] DOCKER_JAVA_EXEC_CMD = new String[]{"java", "-cp", DOCKER_USER_CODE_DIR, USER_CODE_JAVA_FILE_NAME};// java -cp /usr/share/java Solution 1 2}
@Service
public class JudgeService implements IJudgeService {@Autowiredprivate ISandBoxService sandBoxService;@Autowiredprivate UserSubmitMapper userSubmitMapper;@Overridepublic UserQuestionResultVO doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {SandBoxExecuteResult sandBoxExecuteResult =sandBoxService.exeJavaCode(judgeSubmitDTO.getUserCode(),judgeSubmitDTO.getInputList());UserQuestionResultVO userQuestionResultVO = new UserQuestionResultVO();if(sandBoxExecuteResult!=null &&sandBoxExecuteResult.getRunStatus().getValue().equals(CodeRunStatus.SUCCEED.getValue())){List<String> exeOutputList = sandBoxExecuteResult.getOutputList();List<String> outputList = judgeSubmitDTO.getOutputList();if(exeOutputList.size()!=outputList.size()){whenOutPutListErr(userQuestionResultVO);}else {boolean flag = setUserExeResultList(judgeSubmitDTO, exeOutputList, outputList, userQuestionResultVO);if(!flag){whenOutPutListErr(userQuestionResultVO);}else{//每个数量都对得上if(sandBoxExecuteResult.getUseTime()>judgeSubmitDTO.getTimeLimit()){//时间超时whenRunTimeErr(userQuestionResultVO);}else if (sandBoxExecuteResult.getUseMemory()>judgeSubmitDTO.getSpaceLimit()){//空间跃出whenSpaceErr(userQuestionResultVO);}else {//代码正确了whenRunSucess(judgeSubmitDTO, userQuestionResultVO);}}}}else{whenRunErr(userQuestionResultVO, sandBoxExecuteResult);}insertUserSubmit(judgeSubmitDTO, userQuestionResultVO);return userQuestionResultVO;}private static void whenRunErr(UserQuestionResultVO userQuestionResultVO, SandBoxExecuteResult sandBoxExecuteResult) {userQuestionResultVO.setPass(Constants.FALSE);userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);if(sandBoxExecuteResult !=null){userQuestionResultVO.setExeMessage(sandBoxExecuteResult.getExeMessage());}else{userQuestionResultVO.setExeMessage(CodeRunStatus.UNKNOWN_FAILED.getMsg());}}private static void whenRunSucess(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVO userQuestionResultVO) {userQuestionResultVO.setPass(Constants.TRUE);userQuestionResultVO.setScore(JudgeConstants.DEFAULT_SCORE* judgeSubmitDTO.getDifficulty());}private static void whenSpaceErr(UserQuestionResultVO userQuestionResultVO) {userQuestionResultVO.setPass(Constants.FALSE);userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);userQuestionResultVO.setExeMessage(CodeRunStatus.OUT_OF_MEMORY.getMsg());}private static void whenRunTimeErr(UserQuestionResultVO userQuestionResultVO) {userQuestionResultVO.setPass(Constants.FALSE);userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);userQuestionResultVO.setExeMessage(CodeRunStatus.OUT_OF_TIME.getMsg());}private static boolean setUserExeResultList(JudgeSubmitDTO judgeSubmitDTO, List<String> exeOutputList, List<String> outputList, UserQuestionResultVO userQuestionResultVO) {boolean flag =true;List<UserExeResult> userExeResultList = new ArrayList<>();for (int index = 0; index < exeOutputList.size(); index++) {UserExeResult userExeResult = new UserExeResult();userExeResult.setExeOutput(exeOutputList.get(index));userExeResult.setOutput(outputList.get(index));userExeResult.setInput(judgeSubmitDTO.getInputList().get(index));userExeResultList.add(userExeResult);if(!exeOutputList.get(index).equals(outputList.get(index))){flag=false;}}userQuestionResultVO.setUserExeResultList(userExeResultList);return flag;}private static void whenOutPutListErr(UserQuestionResultVO userQuestionResultVO) {userQuestionResultVO.setPass(Constants.FALSE);userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);userQuestionResultVO.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());}private void insertUserSubmit(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVO userQuestionResultVO) {UserSubmit userSubmit = new UserSubmit();BeanUtil.copyProperties(userQuestionResultVO,userSubmit);BeanUtil.copyProperties(judgeSubmitDTO,userSubmit);//不同名的属性则会被保留userSubmitMapper.delete(new LambdaQueryWrapper<UserSubmit>().eq(UserSubmit::getUserId,userSubmit.getUserId()).eq(UserSubmit::getQuestionId,userSubmit.getQuestionId()).isNull(userSubmit.getExamId()==null,UserSubmit::getExamId).eq(userSubmit.getExamId()!=null,UserSubmit::getExamId,userSubmit.getExamId()));userSubmitMapper.insert(userSubmit);}}
这样就成功了
1.3 初版代码-judge-执行代码
<dependency><groupId>com.github.docker-java</groupId><artifactId>docker-java</artifactId><version>3.3.4</version></dependency>
docker来实现代码沙箱
要用到javac和java指令–.对文件操作–》userCode变为文件—》宿主机和容器挂载,同步文件
我们把文件存在user-code文件夹下
然后添加到gitignore
1.4 创建目录
private String userCodeDir;private String userCodeFileName;//创建并返回用户代码的文件private void createUserCodeFile(Long userId, String userCode) {String examCodeDir = System.getProperty("user.dir")+File.separator+ JudgeConstants.EXAM_CODE_DIR;if(!FileUtil.exist(examCodeDir)){FileUtil.mkdir(examCodeDir);}String time = LocalDateTimeUtil.format(LocalDateTime.now(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));userCodeDir = examCodeDir +File.separator +userId + Constants.UNDERLINE_SEPARATOR +time;userCodeFileName = userCodeDir+File.separator +JudgeConstants.USER_CODE_JAVA_CLASS_NAME;FileUtil.writeString(userCode,userCodeFileName,Constants.UTF8);}
这个代码也是会自动创建目录的
userCodeDir是一个二级目录,用来区分是哪个用户提交的,什么时间提交的
然后userCodeDir里面有一个文件就是Solution.java
public static final String UNDERLINE_SEPARATOR = "_";/*** UTF-8 字符集*/public static final String UTF8 = "UTF-8";
examCodeDir就是一级目录
反正这个方法就是来创建对应的文件的
然后就是操作docker了
1.5 拉取镜像-创建容器
private void initDockerSanBox() {DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost(dockerHost).build();dockerClient = DockerClientBuilder.getInstance(clientConfig).withDockerCmdExecFactory(new NettyDockerCmdExecFactory()).build();//拉取镜像pullJavaEnvImage();//创建容器 限制资源 控制权限HostConfig hostConfig = getHostConfig();CreateContainerCmd containerCmd = dockerClient.createContainerCmd(JudgeConstants.JAVA_ENV_IMAGE).withName(JudgeConstants.JAVA_CONTAINER_NAME);CreateContainerResponse createContainerResponse = containerCmd.withHostConfig(hostConfig).withAttachStderr(true).withAttachStdout(true).withTty(true).exec();//记录容器idcontainerId = createContainerResponse.getId();//启动容器dockerClient.startContainerCmd(containerId).exec();}
要操作docker,首先要和docker建立连接
dockerHost就是docker的地址
@Value("${sandbox.docker.host:tcp://localhost:2375}")private String dockerHost;
创建clientConfig建立连接
然后创建dockerClient去操作docker
private DockerClient dockerClient;
//拉取镜像pullJavaEnvImage();
的意思就是拉取java环境的镜像
//拉取java执行环境镜像 需要控制只拉取一次private void pullJavaEnvImage() {ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();List<Image> imageList = listImagesCmd.exec();for (Image image : imageList) {String[] repoTags = image.getRepoTags();if (repoTags != null && repoTags.length > 0 && JudgeConstants.JAVA_ENV_IMAGE.equals(repoTags[0])) {return;}}PullImageCmd pullImageCmd = dockerClient.pullImageCmd(JudgeConstants.JAVA_ENV_IMAGE);try {pullImageCmd.exec(new PullImageResultCallback()).awaitCompletion();} catch (InterruptedException e) {throw new RuntimeException(e);}}
比如要安装jdk的镜像
public static final String JAVA_ENV_IMAGE = "openjdk:8-jdk-alpine";
拉取这个就可以跑java代码了
ListImagesCmd listImagesCmd = dockerClient.listImagesCmd();
这个是获取docker里面所有的镜像
因为可能已经安装了jdk的镜像1,就不用再次安装了
for (Image image : imageList) {String[] repoTags = image.getRepoTags();if (repoTags != null && repoTags.length > 0 && JudgeConstants.JAVA_ENV_IMAGE.equals(repoTags[0])) {return;}}
这个意思就是有了java8的镜像就不用拉取镜像了
就相当于docker ps -a
PullImageCmd pullImageCmd = dockerClient.pullImageCmd(JudgeConstants.JAVA_ENV_IMAGE);
这个就是拉取镜像的意思,就是获取拉取镜像的指令
pullImageCmd.exec(new PullImageResultCallback()).awaitCompletion();
这个exec就是执行指令的意思
PullImageResultCallback参数的作用就是监听拉取镜像的过程
这个可以获取拉取镜像的进度状态等等
awaitCompletion方法的作用是,阻塞调用的意思,就是要在这里等待拉取镜像结束
然后继续回到方法initDockerSanBox
private String containerId;
//创建容器 限制资源 控制权限HostConfig hostConfig = getHostConfig();CreateContainerCmd containerCmd = dockerClient.createContainerCmd(JudgeConstants.JAVA_ENV_IMAGE).withName(JudgeConstants.JAVA_CONTAINER_NAME);CreateContainerResponse createContainerResponse = containerCmd.withHostConfig(hostConfig).withAttachStderr(true).withAttachStdout(true).withTty(true).exec();
这个就是创建容器的过程
这个containerCmd 是先创建指令,然后exec来执行
JAVA_ENV_IMAGE是镜像名字
public static final String JAVA_CONTAINER_NAME = “oj-jdk”;这个是容器的名字
在getHostConfig方法中
//限制资源 控制权限private HostConfig getHostConfig() {HostConfig hostConfig = new HostConfig();//设置挂载目录,指定用户代码路径hostConfig.setBinds(new Bind(userCodeDir, new Volume(JudgeConstants.DOCKER_USER_CODE_DIR)));//限制docker容器使用资源hostConfig.withMemory(memoryLimit);hostConfig.withMemorySwap(memorySwapLimit);hostConfig.withCpuCount(cpuLimit);hostConfig.withNetworkMode("none"); //禁用网络hostConfig.withReadonlyRootfs(true); //禁止在root目录写文件return hostConfig;}
setBinds是设置挂载目录
容器要操作外面文件的代码–》只能挂载了
后面的设置就是资源的设置了
memoryLimit这些都是nacos的配置
@Value("${sandbox.limit.memory:100000000}")private Long memoryLimit;@Value("${sandbox.limit.memory-swap:100000000}")private Long memorySwapLimit;@Value("${sandbox.limit.cpu:1}")private Long cpuLimit;
getHostConfig就是很重要的设置了
withAttachStderr和withAttachStdout就是标准错误输出和标准输出
withTty就是为容器配置一个伪终端
因为容器是要执行一些指令的,只有有这个伪终端,容器才会去执行相关指令
exec就是执行创建容器的指令
最后回到主方法exeJavaCode
我们就要开始编译代码了
1.6 编译代码
//编译//的使用docker编译private CompileResult compileCodeByDocker() {String cmdId = createExecCmd(JudgeConstants.DOCKER_JAVAC_CMD, null, containerId);DockerStartResultCallback resultCallback = new DockerStartResultCallback();CompileResult compileResult = new CompileResult();try {dockerClient.execStartCmd(cmdId).exec(resultCallback).awaitCompletion();if (CodeRunStatus.FAILED.equals(resultCallback.getCodeRunStatus())) {compileResult.setCompiled(false);compileResult.setExeMessage(resultCallback.getErrorMessage());} else {compileResult.setCompiled(true);}return compileResult;} catch (InterruptedException e) {//此处可以直接抛出 已做统一异常处理 也可再做定制化处理throw new RuntimeException(e);}}private String createExecCmd(String[] javaCmdArr, String inputArgs, String containerId) {if (!StrUtil.isEmpty(inputArgs)) {//当入参不为空时拼接入参String[] inputArray = inputArgs.split(" "); //入参javaCmdArr = ArrayUtil.append(JudgeConstants.DOCKER_JAVA_EXEC_CMD, inputArray);}ExecCreateCmdResponse cmdResponse = dockerClient.execCreateCmd(containerId).withCmd(javaCmdArr).withAttachStderr(true).withAttachStdin(true).withAttachStdout(true).exec();return cmdResponse.getId();}
@Getter
@Setter
@Slf4j
public class DockerStartResultCallback extends ExecStartResultCallback {private CodeRunStatus codeRunStatus; //记录执行成功还是失败private String errorMessage;private String message;@Overridepublic void onNext(Frame frame) {StreamType streamType = frame.getStreamType();if (StreamType.STDERR.equals(streamType)) {if (StrUtil.isEmpty(errorMessage)) {errorMessage = new String(frame.getPayload());} else {errorMessage = errorMessage + new String(frame.getPayload());}codeRunStatus = CodeRunStatus.FAILED;} else {String msgTmp = new String(frame.getPayload());if (StrUtil.isNotEmpty(msgTmp)) {message = new String(frame.getPayload());}codeRunStatus = CodeRunStatus.SUCCEED;}super.onNext(frame);}
}
@Data
public class CompileResult {private boolean compiled; //编译是否成功private String exeMessage; //编译输出信息 (错误信息)
}
createExecCmd就是创建编译指令
createExecCmd方法中
public static final String[] DOCKER_JAVAC_CMD = new String[] {"javac", "/usr/share/java/Solution.java"};
第一个参数是一个数组
javac是编译的指令
后面的就是java文件
因为挂载了
所以Solution.java就会在容器的/usr/share/java/目录下
第二个参数就是入参,编译的时候没有入参
第三个参数是容器的Id
继续在compileCodeByDocker
DockerStartResultCallback继承ExecStartResultCallback,这个是会监听命令执行过程–》因为编译也会出错的,所以得监听
DockerStartResultCallback里面的onNext方法的作用是docker引擎会把指令执行输出的结果转化为Frame,不管是正确还是失败
所以onNext就可以获取正确或者错误的输出了
因为错误的信息可能会很多
所以我们用 errorMessage = errorMessage + new String(frame.getPayload());
来累加错误
dockerClient.execStartCmd(cmdId).exec(resultCallback).awaitCompletion();
意思就是执行指令cmdId,resultCallback监听,awaitCompletion阻塞等待完成
这样编译就完成了
然后是对编译结果的判断
if(!compileResult.isCompiled()){//清空场地--.清空代码文件deleteContainer();deleteUserCodeFile();return SandBoxExecuteResult.fail(CodeRunStatus.COMPILE_FAILED,compileResult.getExeMessage());}
private void deleteUserCodeFile() {FileUtil.del(userCodeDir);}
private void deleteContainer() {//执行完成之后删除容器dockerClient.stopContainerCmd(containerId).exec();dockerClient.removeContainerCmd(containerId).exec();//断开和docker连接try {dockerClient.close();} catch (IOException e) {throw new RuntimeException(e);}}
分别就是容器和文件的清理,必须要清理,不然会一直在那里浪费空间