长沙手机网站首页设计公司,莱芜都市论坛,wordpress无法搜索插件,c2c网站代表有哪些文章目录 在线OJ项目核心思路1. 项目介绍2.预备知识理解多进程编程为啥采用多进程而不使用多线程?标准输入标准输出标准错误 3.项目实现题目API实现相关实体类定义新增/修改题目获取题目列表 编译运行编译运行流程 4.统一功能处理 在线OJ项目核心思路 1. 项目介绍
… 文章目录 在线OJ项目核心思路1. 项目介绍2.预备知识理解多进程编程为啥采用多进程而不使用多线程?标准输入标准输出标准错误 3.项目实现题目API实现相关实体类定义新增/修改题目获取题目列表 编译运行编译运行流程 4.统一功能处理 在线OJ项目核心思路 1. 项目介绍
该项目是一个类似于力扣的在线OJ平台可以进行题目的编写和提交编译运行以及结果展示使用的技术栈有Java、MySQL、SpringBoot、MyBatis、Redis、Nginx、Docker
主要功能如下:
登录和注册(Session持久化密码加盐)图形验证码验证登录题目管理(题目的添加和修改)题目提交(编译运行)题目编译/运行结果展示NginxDocker实现负载均衡 2.预备知识
理解多进程编程
什么是进程?
进程可以看做操作系统中一个正在运行的程序的一个抽象也可以把进程看做是程序的一次运行过程。在操作系统内部进程是操作系统进行资源分配的基本单位 使用 PCB(进程控制块) 描述进程 组织使用一定的数据结构来组织常见做法就是使用双向链表 进程之间是相互独立的
什么是多进程?
一个CPU运行多个进程
由于CPU的运行速度极快虽然CPU在一直进行切换但是咱们坐在电脑前的用户是感知不到这个切换过程的
进程和线程的关系
进程是包含线程的一个进程里可以有一个线程也可以有多个线程每个进程都有独立的内存空间(虚拟地址空间)同一个进程的多个线程之间共用这个虚拟地址空间进程是操作系统分配资源的基本单位线程是操作系统调度执行的基本单位如果一个进程挂了, 不会影响到其他进程. 如果一个线程挂了, 则整个进程都要异常终止.进程更重量, 线程更轻量. 创建/销毁/调度线程比进程更高效.
Java中的多进程编程
Java中中对系统提供的进程创建、进程终止、进程程序替换、进程间通信进程了限制最终只给用户提供了两个操作
进程的创建
创建出一个新的进程让这个新的进程来执行一系列任务被创建出来的进程称为子进程创建子进程的进程称为父进程,服务器的进程就相当于一个父进程
根据收到的用户发送过来的代码再 创建出一个子进程一个父进程可以有多个子进程但是一个子进程只能有一个父进程
为啥采用多进程而不使用多线程?
一个操作系统上是运行了很多进程的因为进程之间是相互隔离的一个进程挂了是不会影响到其它进程的。如果使用多线程我们并不知道用户提交的会提交什么样的代码很可能提交一些恶意代码导致线程崩溃而线程挂了很有可能就影响到了我们的整个服务进程。所以一定要采用多进程而不是多线程。
标准输入标准输出标准错误
java和javac是一个控制台程序它的输出是输出到“标准输出”和标准错误这两个特殊的文件当中的一个进程启动的时候就会自动打开三个文件
标准输入对应到键盘标准输出对应到显示器标椎错误对应到显示器
Runtime是Java中内置的一个单例类
通过runtime.exec方法参数是一个字符串表示一个可执行程序的路径执行这个方法就会把指定路径的可执行程序创建出一个子进程并执行。runtime.exec()方法返回的是一个Process类表示的就是一个子进程后续通过这个子进程来进行操作 获取标准输入process.getInputStream()该方法能把process这个子进程的标准输出给读取出来获取标准错误process.getErrorStream()该方法能把process这个子进程的标准错误给读取出来进程等待 process.waitFor()该方法能能让主进程进行阻塞等待等待子进程process执行完毕。
3.项目实现
题目API实现
相关实体类定义
题目实体类
public class Problem {private Integer id;private String title;private String levels;private String description;private String templateCode;private String testCode;private Date createTime;private Date updateTime;
}新增/修改题目
新增修改题目通过判断url中的querystr里是否存在题目Id,来判断是修改题目还是新增题目
约定请求
post
{id : ,title : 题目标题,levels : 题目难度,description : 题干,templateCode : 题目代码模板,testCode : 题目测试用例
}响应
{code : 200,message : data:
}PostMapping(/add)
public Response add(RequestBody Problem problem) {if (problem null || problem.getTitle() null || .equals(problem.getTitle().trim()) || problem.getLevels() null ||.equals(problem.getLevels().trim()) || problem.getTestCode() null || .equals(problem.getTestCode().trim()) ||problem.getTemplateCode() null || .equals(problem.getTemplateCode().trim())) {return Response.fail(题目参数不完整);}int ret problemService.add(problem);if (ret 1) {return Response.success(200,添加成功);}return Response.fail(添加失败);
}获取题目列表
请求
post
{/problem/all
}响应
{code : 200,message:,data:[{id : 1,title: 两数之和,levels: 简单,description: 题干,template: 题目模板}]
}编译运行
通过Answer表示编译运行结果约定
错误码为0表示运行成功错误码为1表示编译错误错误码为2表示运行错误错误码为1表示提交了违规代码
public class Answer {// 错误码 0表示运行成功,1表示编译错误,2表示运行错误,-1表示违规代码private Integer errorCode;// 标准输出private String stdout;// 错误信息private String errorInfo;
}Task类描述的是每一次代码的提交
通过UUID生成唯一的目录保证每个用户提交的代码相互隔离
public class Task {// 存放临时文件目录private String workDir;// 运行文件路径private String className;// 编译文件路径private String classFile;// 存放编译错误信息文件private String compileErrorFile;// 标准输出文件private String stdoutFile;// 标准错误文件private String stderrFile;public Task() {this.workDir ./tmp/UUID.randomUUID().toString()/;this.className Solution;this.classFile workDir Solution.java;this.compileErrorFile workDircompileErrInfo.txt;this.stdoutFile workDirstdout.txt;this.stderrFile workDirstderr.txt;}
}编译运行流程
请求
{problemId : 题目id,code : 提交的代码
}响应
{code : 200,message : 信息,data:{errorCode : 错误码,stdout: 标准输出,derrorInfo, 出错信息}
}编译运行流程
对用户提交代码进行判空从数据库中查询出测试用例进和提交代码进行拼接形成完整代码。对用户提交代码进行安全校验判断其是否提交操作系统命令、文件网络等危险操作代码把拼接好的代码写入到对应文件进行编译和运行。
如下方法表示一次编译或者运行
通过判断stdoutFile是否为空来判断是编译还是运行从子进程process中的标准错误流中读取数据写入到task类的唯一的编译错误信息文件中再判断文件内容是否为空如果编译错误信息文件不为空说明编译出错直接返回如果编译错误信息文件为空说明编译正确再对编译后的字节码进行运行运行后再进行判断标准错误信息文件是否为空如果为空说明运行正常读取到标准输入文件里的信息返回给用户
/*** 编译运行* param cmd 执行的命令* param stdoutFile* param stderrFile* return*/
public static int run(String cmd,String stdoutFile,String stderrFile) {Runtime runtime Runtime.getRuntime();int exitCode -1;try {// 执行命令获得子进程Process process runtime.exec(cmd);// 编译if (stdoutFile null) {try (InputStream stderrInoutStream process.getErrorStream();OutputStream stderrOutputSteam new FileOutputStream(stderrFile);){int ch;// 将错误信息读入到错误日志文件while ((ch stderrInoutStream.read()) ! -1) {stderrOutputSteam.write(ch);}}}// 说明是运行if (stdoutFile ! null) {try (InputStream stderrInoutStream process.getErrorStream();OutputStream stderrOutputSteam new FileOutputStream(stderrFile);InputStream stdoutInputStream process.getInputStream();OutputStream stdOutputStream new FileOutputStream(stdoutFile)){// 获取标准错误输入流int ch;// 将错误信息读入到错误日志文件while ((ch stderrInoutStream.read()) ! -1) {stderrOutputSteam.write(ch);}// 将子进程标准输出写入到指定文件while ((ch stdoutInputStream.read()) ! -1) {stdOutputStream.write(ch);}}}// 进程等待exitCode process.waitFor();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}return exitCode;
}拼接编译命令时通过 -d指定编译后的文件存放到指定位置不然找不到字节码文件位置。
// 2.拼接编译命令
String compileCmd String.format(javac -encoding utf8 %s -d %s,classFile,workDir);
//4.运行代码
String runCmd String.format(java -classpath %s %s,workDir,className);4.统一功能处理
统一登录拦截
定义拦截器
创建自定义拦截器实现Handlerlnterceptor接口的preHandle(执行具体方法之前的预处理)方法将自定义拦截器加入WebMvcConfigurer的addInterceptors
提供一个管理员页面来对题目进行添加和修改。管理员页面使用拦截器对普通用户进行拦截.
Configuration
public class AppConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns(/**).excludePathPatterns(/user/login).excludePathPatterns(/user/reg).excludePathPatterns(/user/verificationCode).excludePathPatterns(/login.html).excludePathPatterns(/reg.html).excludePathPatterns(/css/**).excludePathPatterns(/js/**).excludePathPatterns(/img/**);registry.addInterceptor(new AdminInterceptor()).addPathPatterns(/admin.html).addPathPatterns(/addProblem.html).addPathPatterns(/problem/update).addPathPatterns(/problem/add);}
}统一格式返回
统一的数据返回格式使用ControllerAdviceResponseBodyAdvice实现
ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {Resourceprivate ObjectMapper objectMapper;Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof Response) {return body;}if (body instanceof String) {try {return objectMapper.writeValueAsString(body);} catch (JsonProcessingException e) {e.printStackTrace();}}return Response.success(body);}
}统一异常处理
ControllerAdvice
public class ExceptionAdvice {ExceptionHandler(Exception.class)ResponseBodypublic Response exceptionAdvice(Exception e) {return Response.fail(服务器异常);}
}