架构更新,上线部署

This commit is contained in:
Himit_ZH 2021-04-13 12:59:57 +08:00
parent 8c100cf4ba
commit ddfaa27d06
153 changed files with 2491 additions and 1769 deletions

View File

@ -2,15 +2,14 @@
基于前后端分离分布式架构的在线测评平台hoj
> 当前任务
在线Demo[http://oj.hcode.top](http://oj.hcode.top)
- [x] 测试HDU判题整套流程
- [x] 修改前端编辑器样式以及md格式转换
- [x] 修复代码编辑器bug
- [x] 测试比赛相关接口,验证权限及数据计算
- [x] 增加codeforce的vj判题
- [ ] 部署判题服务器到云服务器半正式上线HOJ
- [ ] 完善文档
> 上线日记
| 时间 | 内容 | 更新者 |
| ---------- | ------------ | -------- |
| 2020-10-26 | 正式开发 | Himit_ZH |
| 2021-04-10 | 首次上线测试 | Himit_ZH |
> 简略介绍
@ -53,37 +52,6 @@
> 开发记录
| 时间 | 更新内容 | 更新者 |
| ---------- | ------------------------------------------------------------ | --------------- |
| 2020-10-26 | 数据库设计,登录和注册接口,文档记录开始。 | Himit_ZH |
| 2020-10-28 | 用户模块接口,题目模块接口,比赛模块接口,排行模块接口 | Himit_ZH |
| 2020-10-30 | 评测模块接口判题服务系统初始化前端vue项目 | Himit_ZH |
| 2020-11-08 | 前端vue主页题目列表页登录注册重置密码弹窗逻辑 | Himit_ZH |
| 2020-11-16 | 前端提交列表页,提交详情页,题目详情页,排行(ACM,OI)页,比赛列表页,个人主页,个人设置页 | Himit_ZH |
| 2020-11-22 | 前端比赛首页,比赛题目列表,比赛排行榜,比赛公告,首页布局调整 | Himit_ZH |
| 2020-11-24 | 介绍页,导航栏移动端优化,首页优化,公告栏优化 | Himit_ZH |
| 2020-11-28 | 前端项目重构,加入管理端部分页面,增加case表 | Himit_ZH |
| 2020-12-01 | 前端管理端基本完成,准备开始前后端接口对接与测试 | Himit_ZH |
| 2020-12-21 | 管理端前后端接口对接基本完成,准备客户端接口对接 | Himit_ZH |
| 2021-01-04 | 客户端首页,题目,提交模块的接口对接完毕 | Himit_ZH |
| 2021-01-08 | 比赛列表页,排行榜,用户主页的接口对接完毕 | Himit_ZH |
| 2021-01-11 | 个人设置页,用户信息更新,头像上传,登录优化 | Himit_ZH、Howie |
| 2021-01-16 | 比赛首页比赛题目列表比赛题目详情等接口对接完毕定时爬取其它oj比赛及rating分完成 | Himit_ZH、Howie |
| 2021-01-19 | 比赛排行榜比赛题目对应提交重判比赛AC助手完成 | Himit_ZH |
| 2021-02-02 | 正式加入安全沙箱,判题机服务器 | Himit_ZH |
| 2021-02-04 | 完善判题机制加入pe可选择性判断加入测试点可选择性公开 | Himit_ZH |
| 2021-02-08 | 完善判题服务调度机制(负载均衡策略),完善题目评测数据上传与下载 | Himit_ZH |
| 2021-02-12 | 初始化构建HDU虚拟判题流程 | Howie、Himit_ZH |
| 2021-02-16 | 完善测试特殊判题题目的操作 | Himit_ZH |
| 2021-02-19 | 正式完善HDU虚拟判题以及题目添加 | Himit_ZH |
| 2021-02-20 | 测试HDU判题整套流程完成 | Himit_ZH |
| 2021-02-22 | 修改前端编辑器样式以及md格式转换 | Himit_ZH |
| 2021-02-24 | 完善测试比赛相关接口,验证权限及数据计算 | Himit_ZH |
| 2021-03-03 | 增加Codeforces的题目爬取 | Himit_ZH |
| 2021-03-26 | 增加Codeforces的虚拟判题修复题目爬取部分问题 | Himit_ZH |
# 二、系统架构
> 总概四大系统
@ -100,13 +68,16 @@
1. 前端用户提交数据。
2. 后端数据服务DataBackup获取到数据先将提交数据初始化同时将该提交的状态变成等待中写入数据库。
3. 通过Redis的发布订阅者,将该判题信息发送给对应的==等待判题频道订阅者==
4. 订阅者收到信息初始化传输数据使用springcloud alibaba通过nacos注册中心调用判题微服务。
5. 若是调用判题服务失败(没有空闲的判题服务器),则重新通过发布者将该提交信息发布到对应==等待判题频道==重回3。
6. 若是调用失败超过40次则将提交的状态修改为提交失败不再进行判题服务的调用。
3. 通过Redis,写入**等待判题队列**
4. 调用work处理者初始化传输数据使用springcloud alibaba通过nacos注册中心调用判题微服务。
5. 若是调用判题服务失败(没有空闲的判题服务器),则重新通过发布者将该提交信息发布到对应**等待判题队列**重回3。
6. 若是调用失败超过30次则将提交的状态修改为提交失败不再进行判题服务的调用。
7. 前端用户可看到提交变成提交失败可点击状态进行重新提交重回2重新提交不影响提交时间等数据
8. 判题微服务获取到提交数据:
- 若是远程调用则通过Redis的发布订者发布到对应的==远程判题等待提交频道==,对应==频道接收者==获取提交信息进行远程提交获取对应的提交ID再通过==远程判题等待结果频道===发布给对应频道接收者,该频道接收者接受到信息,进行结果获取,获取结果失败,则再次重复发布;获取成功,写回数据库。
- 若是远程调用进行远程提交获取对应的提交ID若是获取失败则判为提交失败若是获取成功就启用线程定时器每2秒根据提交ID获取判题结果。
定时尝试30次获取成功写回数据库获取失败就修改此次提交为提交失败。
- 若是自家题目提交则启用线程池多线程使用Http将对应测试点数据与代码提交给Go-Judge判题沙盒进行编译与评测最后获取各个评测点结果进行结果计算写回数据库。
> HOJ基本逻辑架构图

View File

@ -3,7 +3,6 @@ package top.hcode.hoj.common.exception;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.judge.remote.RemoteJudgeDispatcher;
@ -44,7 +43,7 @@ public class CloudHandler implements ToJudgeService {
// 调度判题服务器失败可能是判题服务器有故障或者全部达到判题最大数那么将该提交重新进入等待队列
@Override
public CommonResult submitProblemJudge(ToJudge toJudge) {
if (toJudge.getTryAgainNum() == 40) {
if (toJudge.getTryAgainNum() == 30) {
Judge judge = toJudge.getJudge();
judge.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus());
judge.setErrorMessage("Failed to connect the judgeServer. Please resubmit this submission again!");
@ -58,7 +57,7 @@ public class CloudHandler implements ToJudgeService {
}
Judge judge = toJudge.getJudge();
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), toJudge.getToken(),
judge.getCid() == 0, toJudge.getTryAgainNum() + 1);
judge.getCid() != 0, toJudge.getTryAgainNum() + 1);
}
return CommonResult.errorResponse("判题服务器繁忙或出错,提交进入重判队列,请等待管理员处理!", CommonResult.STATUS_ERROR);
}
@ -70,14 +69,13 @@ public class CloudHandler implements ToJudgeService {
@Override
public CommonResult remoteJudge(ToJudge toJudge) {
System.out.println(toJudge.getTryAgainNum());
// 将使用的账号放回对应列表
JSONObject account = new JSONObject();
account.set("username", toJudge.getUsername());
account.set("password", toJudge.getPassword());
redisUtils.llPush(Constants.Judge.getListNameByOJName(toJudge.getRemoteJudge().split("-")[0]), JSONUtil.toJsonStr(account));
if (toJudge.getTryAgainNum() == 40) {
if (toJudge.getTryAgainNum() == 30) {
Judge judge = toJudge.getJudge();
judge.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus());
judge.setErrorMessage("Failed to connect the judgeServer. Please resubmit this submission again!");
@ -93,7 +91,7 @@ public class CloudHandler implements ToJudgeService {
Judge judge = toJudge.getJudge();
remoteJudgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), toJudge.getToken(),
toJudge.getRemoteJudge(), judge.getCid() == 0, toJudge.getTryAgainNum() + 1);
toJudge.getRemoteJudge(), judge.getCid() != 0, toJudge.getTryAgainNum() + 1);
}
return CommonResult.errorResponse("判题服务器繁忙或出错,提交进入重判队列,请等待管理员处理!", CommonResult.STATUS_ERROR);

View File

@ -23,6 +23,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import top.hcode.hoj.common.result.CommonResult;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -55,13 +56,13 @@ public class GlobalExceptionHandler {
/**
* 401 -UnAuthorized UnauthenticatedException,token相关异常 即是认证出错 可能无法处理
* 没有登录没有token访问有@RequiresAuthentication的请求路径会报这个异常
* 没有登录没有token访问有@RequiresAuthentication的请求路径会报这个异常
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(value = UnauthenticatedException.class)
public CommonResult handleUnauthenticatedException(UnauthenticatedException e,
HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
httpResponse.setHeader("Url-Type", httpRequest.getHeader("Url-Type")); // 为了前端能区别请求来源
return CommonResult.errorResponse("请您先登录!", CommonResult.STATUS_ACCESS_DENIED);
}
@ -195,7 +196,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(value = MessagingException.class)
public CommonResult handler(MessagingException e) throws Exception {
log.error("邮箱系统异常-------------->{}", e.getMessage());
return CommonResult.errorResponse(e.getMessage(), CommonResult.STATUS_ERROR);
return CommonResult.errorResponse("服务器异常,请稍后尝试!", CommonResult.STATUS_ERROR);
}
/**
@ -204,7 +205,8 @@ public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(ServiceException.class)
public CommonResult handleServiceException(ServiceException e) {
return CommonResult.errorResponse("业务逻辑异常:" + e.getCause(), CommonResult.STATUS_ERROR);
log.error("业务逻辑异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("服务器异常,请稍后尝试!", CommonResult.STATUS_ERROR);
}
/**
@ -214,7 +216,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
public CommonResult handleDataIntegrityViolationException(DataIntegrityViolationException e) {
log.error("操作数据库出现异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("操作数据库出现异常:字段重复、有外键关联等", CommonResult.STATUS_ERROR);
return CommonResult.errorResponse("服务器异常,请稍后尝试!", CommonResult.STATUS_ERROR);
}
@ -225,7 +227,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(SQLException.class)
public CommonResult handleSQLException(SQLException e) {
log.error("操作数据库出现异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("操作失败!错误提示:"+e.getMessage(), CommonResult.STATUS_ERROR);
return CommonResult.errorResponse("操作失败!错误提示:" + e.getMessage(), CommonResult.STATUS_ERROR);
}
/**
@ -245,6 +247,6 @@ public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public CommonResult handleException(Exception e) {
log.error("系统通用异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("系统通用异常:" + e.getCause(), CommonResult.STATUS_ERROR);
return CommonResult.errorResponse("服务器异常,请稍后尝试!", CommonResult.STATUS_ERROR);
}
}

View File

@ -19,9 +19,9 @@ public class AsyncTaskConfig implements AsyncConfigurer {
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
// 线程池维护线程的最少数量
taskExecutor.setCorePoolSize(10);
taskExecutor.setCorePoolSize(50);
// 线程池维护线程的最大数量
taskExecutor.setMaxPoolSize(50);
taskExecutor.setMaxPoolSize(100);
// 缓存队列
taskExecutor.setQueueCapacity(200);
// 对拒绝task的处理策略

View File

@ -7,6 +7,8 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.hcode.hoj.utils.Constants;
import java.io.File;
/**
* 解决跨域问题
*/
@ -25,8 +27,8 @@ public class CorsConfig implements WebMvcConfigurer {
// 前端直接通过/public/img/图片名称即可拿到
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(Constants.File.IMG_API.getPath()+"**") // /public/img/**
.addResourceLocations("file:" + Constants.File.USER_AVATAR_FOLDER.getPath(),
"file:" + Constants.File.MARKDOWN_IMG_FOLDER.getPath());
registry.addResourceHandler(Constants.File.IMG_API.getPath() + "**") // /api/public/img/**
.addResourceLocations("file:" + Constants.File.USER_AVATAR_FOLDER.getPath() + File.separator,
"file:" + Constants.File.MARKDOWN_IMG_FOLDER.getPath() + File.separator);
}
}

View File

@ -18,11 +18,14 @@ import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPoolConfig;
import top.hcode.hoj.shiro.AccountRealm;
import top.hcode.hoj.shiro.JwtFilter;
@ -39,12 +42,17 @@ import java.util.Map;
public class ShiroConfig {
@Autowired
JwtFilter jwtFilter;
@Bean
public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO);
return sessionManager;
}
@Bean
public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,
SessionManager sessionManager,

View File

@ -30,7 +30,7 @@ import java.util.List;
* @Description:
*/
@RestController
@RequestMapping("/admin")
@RequestMapping("/api/admin")
public class AdminAccountController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
* @Description:
*/
@RestController
@RequestMapping("/admin/contest")
@RequestMapping("/api/admin/contest")
public class AdminContestController {
@Autowired

View File

@ -30,7 +30,7 @@ import java.util.List;
*/
@RestController
@RequestMapping("/admin/judge")
@RequestMapping("/api/admin/judge")
@RefreshScope
public class AdminJudgeController {
@ -102,9 +102,9 @@ public class AdminJudgeController {
Problem problem = problemService.getById(judge.getPid());
if (problem.getIsRemote()) { // 如果是远程oj判题
remoteJudgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, problem.getProblemId(),
judge.getCid() == 0, 1);
judge.getCid() != 0, 1);
} else {
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getCid() == 0, 1);
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getCid() != 0, 1);
}
return CommonResult.successResponse(judge, "重判成功!该提交已进入判题队列!");
} else {
@ -150,12 +150,12 @@ public class AdminJudgeController {
for (Judge judge : rejudgeList) {
// 进入重判队列等待调用判题服务
remoteJudgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, problem.getProblemId(),
judge.getCid() == 0, 1);
judge.getCid() != 0, 1);
}
} else {
for (Judge judge : rejudgeList) {
// 进入重判队列等待调用判题服务
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getCid() == 0, 1);
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getCid() != 0, 1);
}
}

View File

@ -33,7 +33,7 @@ import java.util.*;
*/
@RestController
@RefreshScope
@RequestMapping("/admin/problem")
@RequestMapping("/api/admin/problem")
public class AdminProblemController {
@Autowired
@ -66,7 +66,8 @@ public class AdminProblemController {
queryWrapper
.like("title", keyword).or()
.like("author", keyword).or()
.like("problem_id", keyword);
.like("problem_id", keyword)
.orderByAsc("gmt_create");
problemList = problemService.page(iPage, queryWrapper);
} else {
problemList = problemService.page(iPage, queryWrapper);

View File

@ -29,7 +29,7 @@ import java.util.*;
* @Description:
*/
@RestController
@RequestMapping("/admin/user")
@RequestMapping("/api/admin/user")
public class AdminUserController {
@Autowired

View File

@ -25,7 +25,7 @@ public class AnnouncementController {
@Autowired
private AnnouncementServiceImpl announcementDao;
@GetMapping("/admin/announcement")
@GetMapping("/api/admin/announcement")
@RequiresRoles("root") // 只有超级管理员能操作
public CommonResult getAnnouncementList(@RequestParam(value = "limit", required = false) Integer limit,
@RequestParam(value = "currentPage", required = false) Integer currentPage){
@ -39,7 +39,7 @@ public class AnnouncementController {
}
}
@DeleteMapping("/admin/announcement")
@DeleteMapping("/api/admin/announcement")
@RequiresRoles("root") // 只有超级管理员能操作
public CommonResult deleteAnnouncement(@Valid @RequestParam("aid")long aid){
boolean result = announcementDao.removeById(aid);
@ -50,7 +50,7 @@ public class AnnouncementController {
}
}
@PostMapping("/admin/announcement")
@PostMapping("/api/admin/announcement")
@RequiresRoles("root") // 只有超级管理员能操作
public CommonResult addAnnouncement(@RequestBody Announcement announcement){
boolean result = announcementDao.save(announcement);
@ -61,7 +61,7 @@ public class AnnouncementController {
}
}
@PutMapping("/admin/announcement")
@PutMapping("/api/admin/announcement")
@RequiresRoles("root") // 只有超级管理员能操作
public CommonResult updateAnnouncement(@RequestBody Announcement announcement){
boolean result = announcementDao.saveOrUpdate(announcement);

View File

@ -24,7 +24,7 @@ import java.util.List;
* @Description:
*/
@RestController
@RequestMapping("/admin/config")
@RequestMapping("/api/admin/config")
public class ConfigController {
@Autowired
@ -92,6 +92,7 @@ public class ConfigController {
MapUtil.builder().put("username", configVo.getEmailUsername())
.put("password", configVo.getEmailPassword())
.put("host", configVo.getEmailHost())
.put("port", configVo.getEmailPort())
.put("ssl", configVo.getEmailSsl()).map()
);
}
@ -133,6 +134,7 @@ public class ConfigController {
.put("dbPassword", configVo.getMysqlPassword())
.put("redisHost", configVo.getRedisHost())
.put("redisPort", configVo.getRedisPort())
.put("redisPassword", configVo.getRedisPassword())
.map()
);
}

View File

@ -28,7 +28,7 @@ import java.util.List;
* @Description:
*/
@RestController
@RequestMapping("/admin/dashboard")
@RequestMapping("/api/admin/dashboard")
public class DashboardController {
@Autowired

View File

@ -52,7 +52,7 @@ import java.util.stream.Collectors;
* @Description:
*/
@Controller
@RequestMapping("/file")
@RequestMapping("/api/file")
@Slf4j
public class FileController {
@ -120,16 +120,13 @@ public class FileController {
if (!"jpg,jpeg,gif,png,webp".toUpperCase().contains(suffix.toUpperCase())) {
return CommonResult.errorResponse("请选择jpg,jpeg,gif,png,webp格式的头像图片");
}
File savePathFile = new File(Constants.File.USER_AVATAR_FOLDER.getPath());
if (!savePathFile.exists()) {
//若不存在该目录则创建目录
savePathFile.mkdir();
}
//若不存在该目录则创建目录
FileUtil.mkdir(Constants.File.USER_AVATAR_FOLDER.getPath());
//通过UUID生成唯一文件名
String filename = IdUtil.simpleUUID() + "." + suffix;
try {
//将文件保存指定目录
image.transferTo(new File(Constants.File.USER_AVATAR_FOLDER.getPath() + filename));
image.transferTo(FileUtil.file(Constants.File.USER_AVATAR_FOLDER.getPath() + File.separator + filename));
} catch (Exception e) {
log.error("头像文件上传异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("服务器异常:头像上传失败!", CommonResult.STATUS_ERROR);
@ -152,7 +149,7 @@ public class FileController {
// 插入file表记录
top.hcode.hoj.pojo.entity.File imgFile = new top.hcode.hoj.pojo.entity.File();
imgFile.setName(filename).setFolderPath(Constants.File.USER_AVATAR_FOLDER.getPath())
.setFilePath(Constants.File.USER_AVATAR_FOLDER.getPath() + filename)
.setFilePath(Constants.File.USER_AVATAR_FOLDER.getPath() + File.separator + filename)
.setSuffix(suffix)
.setType("avatar")
.setUid(userRolesVo.getUid());
@ -437,7 +434,7 @@ public class FileController {
for (Judge judge : userSubmissionList) {
String filePath = userDir + File.separator + cpIdMap.getOrDefault(judge.getCpid(), "null")
+ "_(" + threadLocal.get().format(judge.getSubmitTime())+")";
+ "_(" + threadLocal.get().format(judge.getSubmitTime()) + ")";
// OI模式只取最后一次提交
if (!isACM) {
@ -453,7 +450,7 @@ public class FileController {
}
}
String zipFileName = "contest_"+contest.getId() + "_" + System.currentTimeMillis() + ".zip";
String zipFileName = "contest_" + contest.getId() + "_" + System.currentTimeMillis() + ".zip";
String zipPath = Constants.File.CONTEST_AC_SUBMISSION_TMP_FOLDER.getPath() + File.separator + zipFileName;
ZipUtil.zip(tmpFilesDir, zipPath);
// 将zip变成io流返回给前端
@ -569,16 +566,15 @@ public class FileController {
if (!"jpg,jpeg,gif,png,webp".toUpperCase().contains(suffix.toUpperCase())) {
return CommonResult.errorResponse("请选择jpg,jpeg,gif,png,webp格式的图片");
}
File savePathFile = new File(Constants.File.MARKDOWN_IMG_FOLDER.getPath());
if (!savePathFile.exists()) {
//若不存在该目录则创建目录
savePathFile.mkdir();
}
//若不存在该目录则创建目录
FileUtil.mkdir(Constants.File.MARKDOWN_IMG_FOLDER.getPath());
//通过UUID生成唯一文件名
String filename = IdUtil.simpleUUID() + "." + suffix;
try {
//将文件保存指定目录
image.transferTo(new File(Constants.File.MARKDOWN_IMG_FOLDER.getPath() + filename));
image.transferTo(FileUtil.file(Constants.File.MARKDOWN_IMG_FOLDER.getPath() + File.separator + filename));
} catch (Exception e) {
log.error("图片文件上传异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("服务器异常:图片文件上传失败!", CommonResult.STATUS_ERROR);
@ -586,7 +582,7 @@ public class FileController {
return CommonResult.successResponse(MapUtil.builder()
.put("link", Constants.File.USER_FILE_HOST.getPath() + Constants.File.IMG_API.getPath() + filename)
.put("filePath", Constants.File.MARKDOWN_IMG_FOLDER.getPath() + filename).map(),
.put("filePath", Constants.File.MARKDOWN_IMG_FOLDER.getPath() + File.separator + filename).map(),
"上传图片成功!");
}

View File

@ -25,10 +25,7 @@ import top.hcode.hoj.pojo.vo.ConfigVo;
import top.hcode.hoj.pojo.vo.UserHomeVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
import top.hcode.hoj.service.UserInfoService;
import top.hcode.hoj.service.impl.EmailServiceImpl;
import top.hcode.hoj.service.impl.UserAcproblemServiceImpl;
import top.hcode.hoj.service.impl.UserInfoServiceImpl;
import top.hcode.hoj.service.impl.UserRecordServiceImpl;
import top.hcode.hoj.service.impl.*;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.IpUtils;
import top.hcode.hoj.utils.JwtUtils;
@ -71,6 +68,9 @@ public class AccountController {
@Autowired
private UserAcproblemServiceImpl userAcproblemService;
@Autowired
private ProblemServiceImpl problemService;
@Autowired
private SessionMapper sessionDao;
@ -92,11 +92,11 @@ public class AccountController {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("email", email);
UserInfo userInfo = userInfoDao.getOne(queryWrapper);
if(userInfo!=null){
if (userInfo != null) {
return CommonResult.errorResponse("对不起!该邮箱已被注册,请更换新的邮箱!");
}
String numbers = RandomUtil.randomNumbers(6); // 随机生成6位数字的组合
redisUtils.set(Constants.Email.REGISTER_KEY_PREFIX.getValue()+email, numbers, 5 * 60);//默认验证码有效5分钟
redisUtils.set(Constants.Email.REGISTER_KEY_PREFIX.getValue() + email, numbers, 5 * 60);//默认验证码有效5分钟
emailService.sendCode(email, numbers);
return CommonResult.successResponse(MapUtil.builder()
.put("email", email)
@ -171,7 +171,7 @@ public class AccountController {
userInfoQueryWrapper.eq("email", email.trim());
UserInfo userInfo = userInfoDao.getOne(userInfoQueryWrapper);
String code = IdUtil.simpleUUID().substring(0, 21); // 随机生成20位数字与字母的组合
redisUtils.set(Constants.Email.RESET_PASSWORD_KEY_PREFIX.getValue()+userInfo.getUsername(), code, 10 * 60);//默认链接有效10分钟
redisUtils.set(Constants.Email.RESET_PASSWORD_KEY_PREFIX.getValue() + userInfo.getUsername(), code, 10 * 60);//默认链接有效10分钟
// 发送邮件
emailService.sendResetPassword(userInfo.getUsername(), code, email.trim());
return CommonResult.successResponse(null, "重置密码邮件已发送至指定邮箱,请稍稍等待一会。");
@ -194,7 +194,7 @@ public class AccountController {
if (StringUtils.isEmpty(password) || StringUtils.isEmpty(username) || StringUtils.isEmpty(code)) {
return CommonResult.errorResponse("用户名,新密码或验证码不能为空");
}
String codeKey = Constants.Email.RESET_PASSWORD_KEY_PREFIX.getValue()+username;
String codeKey = Constants.Email.RESET_PASSWORD_KEY_PREFIX.getValue() + username;
if (!redisUtils.hasKey(codeKey)) {
return CommonResult.errorResponse("重置密码链接不存在或已过期,请重新发送重置邮件");
@ -227,7 +227,7 @@ public class AccountController {
if (!configVo.getRegister()) { // 需要判断一下网站是否开启注册
return CommonResult.errorResponse("对不起!本站暂未开启注册功能!", CommonResult.STATUS_ACCESS_DENIED);
}
String codeKey = Constants.Email.REGISTER_KEY_PREFIX.getValue()+registerDto.getEmail();
String codeKey = Constants.Email.REGISTER_KEY_PREFIX.getValue() + registerDto.getEmail();
if (!redisUtils.hasKey(codeKey)) {
return CommonResult.errorResponse("验证码不存在或已过期");
}
@ -281,8 +281,10 @@ public class AccountController {
response.setHeader("Access-Control-Expose-Headers", "Authorization");
// 会话记录
sessionDao.insert(new Session().setUid(userRoles.getUid())
.setIp(IpUtils.getUserIpAddr(request)).setUserAgent(request.getHeader("User-Agent")));
sessionDao.insert(new Session()
.setUid(userRoles.getUid())
.setIp(IpUtils.getUserIpAddr(request))
.setUserAgent(request.getHeader("User-Agent")));
return CommonResult.successResponse(MapUtil.builder()
.put("uid", userRoles.getUid())
@ -327,7 +329,6 @@ public class AccountController {
* @Since 2021/01/07
*/
@GetMapping("/get-user-home-info")
@RequiresAuthentication
public CommonResult getUserHomeInfo(@RequestParam(value = "uid", required = false) String uid, HttpServletRequest request) {
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
@ -343,7 +344,19 @@ public class AccountController {
acProblemList.forEach(acProblem -> {
pidList.add(acProblem.getPid());
});
userHomeInfo.setSolvedList(pidList);
List<String> disPlayIdList = new LinkedList<>();
if (pidList.size()>0) {
QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();
problemQueryWrapper.in("id", pidList);
List<Problem> problems = problemService.list(problemQueryWrapper);
problems.forEach(problem -> {
disPlayIdList.add(problem.getProblemId());
});
}
userHomeInfo.setSolvedList(disPlayIdList);
return CommonResult.successResponse(userHomeInfo, "查询成功!");
}
@ -440,7 +453,7 @@ public class AccountController {
if (StringUtils.isEmpty(password) || StringUtils.isEmpty(newEmail) || StringUtils.isEmpty(oldEmail)) {
return CommonResult.errorResponse("请求参数不能为空!");
}
if (!Validator.isEmail(newEmail)){
if (!Validator.isEmail(newEmail)) {
return CommonResult.errorResponse("邮箱格式错误!");
}
// 获取当前登录的用户
@ -517,7 +530,7 @@ public class AccountController {
@PostMapping("/change-userInfo")
@RequiresAuthentication
public CommonResult changeEmail(@RequestBody HashMap<String,Object> params, HttpServletRequest request) {
public CommonResult changeEmail(@RequestBody HashMap<String, Object> params, HttpServletRequest request) {
// 获取当前登录的用户
HttpSession session = request.getSession();
@ -536,7 +549,7 @@ public class AccountController {
boolean result = userInfoDao.updateById(userInfo);
if (result){
if (result) {
return CommonResult.successResponse(MapUtil.builder()
.put("uid", userRolesVo.getUid())
.put("username", userRolesVo.getUsername())
@ -553,7 +566,7 @@ public class AccountController {
.put("cfUsername", userInfo.getCfUsername())
.put("roleList", userRolesVo.getRoles().stream().map(Role::getRole))
.map(), "更新个人信息成功!");
}else{
} else {
return CommonResult.errorResponse("更新个人信息失败!");
}

View File

@ -220,7 +220,8 @@ public class JudgeController {
// 将提交加入任务队列
if (judgeDto.getIsRemote()) { // 如果是远程oj判题
remoteJudgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getDisplayPid(),
judge.getCid() == 0, 1);
judge.getCid() != 0, 1);
} else {
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getCid() == 0, 1);
}
@ -280,10 +281,10 @@ public class JudgeController {
// 将提交加入任务队列
if (problem.getIsRemote()) { // 如果是远程oj判题
remoteJudgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, problem.getProblemId(),
judge.getCid() == 0, 1);
judge.getCid() != 0, 1);
} else {
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken,
judge.getCid() == 0, 1);
judge.getCid() != 0, 1);
}
return CommonResult.successResponse(judge, "重新提交成功!");
}
@ -340,6 +341,14 @@ public class JudgeController {
}
/**
* @MethodName updateSubmission
* @Params * @param null
* @Description 修改单个提交详情的分享权限
* @Return CommonResult
* @Since 2021/1/2
*/
@PutMapping("/submission")
@RequiresAuthentication
public CommonResult updateSubmission(@RequestBody Judge judge, HttpServletRequest request) {
@ -349,10 +358,12 @@ public class JudgeController {
if (!userRolesVo.getUid().equals(judge.getUid())) { // 判断该提交是否为当前用户的
return CommonResult.errorResponse("对不起,您不能修改他人的代码分享权限!");
}
if (judge.getCid() == 0) { // 如果是比赛提交不可分享
Judge judgeInfo = judgeService.getById(judge.getSubmitId());
if (judgeInfo.getCid() != 0) { // 如果是比赛提交不可分享
return CommonResult.errorResponse("对不起,您不能分享比赛题目的提交代码!");
}
boolean result = judgeService.updateById(judge);
judgeInfo.setShare(judge.getShare());
boolean result = judgeService.updateById(judgeInfo);
if (result) {
if (judge.getShare()) {
return CommonResult.successResponse(null, "设置代码公开成功!");

View File

@ -69,7 +69,8 @@ public class ProblemController {
@RequestParam(value = "currentPage", required = false) Integer currentPage,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "tagId", required = false) Long tagId,
@RequestParam(value = "difficulty", required = false) Integer difficulty) {
@RequestParam(value = "difficulty", required = false) Integer difficulty,
@RequestParam(value = "oj", required = false) String oj) {
// 页数每页题数若为空设置默认值
if (currentPage == null || currentPage < 1) currentPage = 1;
if (limit == null || limit < 1) limit = 10;
@ -83,7 +84,9 @@ public class ProblemController {
pid = Long.valueOf(keyword);
}
}
Page<ProblemVo> problemList = problemService.getProblemList(limit, currentPage, pid, keyword, difficulty, tagId);
Page<ProblemVo> problemList = problemService.getProblemList(limit, currentPage, pid, keyword,
difficulty, tagId, oj);
if (problemList.getTotal() == 0) { // 未查询到一条数据
return CommonResult.successResponse(problemList, "暂无数据");
} else {

View File

@ -23,5 +23,6 @@ import java.util.List;
@Repository
public interface ProblemMapper extends BaseMapper<Problem> {
List<ProblemVo> getProblemList(IPage page, @Param("pid") Long pid, @Param("keyword")String keyword,
@Param("difficulty")Integer difficulty, @Param("tid")Long tid);
@Param("difficulty")Integer difficulty, @Param("tid")Long tid,
@Param("oj")String oj);
}

View File

@ -44,8 +44,11 @@
<if test="tid != null">
and pt.tid = #{tid}
</if>
<if test="oj != null">
and p.problem_id like concat(#{oj},'%')
</if>
</where>
order by p.problem_id asc
order by length(p.problem_id) asc,p.problem_id asc
</select>
<!-- 子查询 :为了防止分页总数据数出错-->

View File

@ -1,70 +0,0 @@
package top.hcode.hoj.judge;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import top.hcode.hoj.judge.remote.RemoteJudgeReceiver;
import top.hcode.hoj.judge.self.JudgeReceiver;
import top.hcode.hoj.utils.Constants;
/**
* @Author: Himit_ZH
* @Date: 2021/2/5 16:50
* @Description: redis的发布订阅者模式的消息频道与订阅者的注册绑定
*/
@Configuration
@AutoConfigureAfter({JudgeReceiver.class, RemoteJudgeReceiver.class})
public class SubscriberConfig {
/**
* 消息监听适配器注入接受消息方法注入自己的判题服务的消息接收者
*
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter selfJudgeMessageListenerAdapter(JudgeReceiver receiver) {
return new MessageListenerAdapter(receiver); //当没有继承MessageListener时需要写方法名字
}
/**
* 消息监听适配器注入接受消息方法注入远程判题服务的消息接收者
*
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter remoteJudgeMessageListenerAdapter(RemoteJudgeReceiver receiver) {
return new MessageListenerAdapter(receiver); //当没有继承MessageListener时需要写方法名字
}
/**
* 创建消息监听容器
*
* @param redisConnectionFactory
* @param selfJudgeMessageListenerAdapter
* @param remoteJudgeMessageListenerAdapter
* @return
*/
@Bean
public RedisMessageListenerContainer getRedisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory,
MessageListenerAdapter selfJudgeMessageListenerAdapter,
MessageListenerAdapter remoteJudgeMessageListenerAdapter) {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
// 注册信息接收者绑定自己判题服务的频道
redisMessageListenerContainer.addMessageListener(selfJudgeMessageListenerAdapter, new PatternTopic(Constants.Judge.STATUS_JUDGE_WAITING.getName()));
// 注册信息接收者绑定远程虚拟判题的频道
redisMessageListenerContainer.addMessageListener(remoteJudgeMessageListenerAdapter, new PatternTopic(Constants.Judge.STATUS_REMOTE_JUDGE_WAITING_HANDLE.getName()));
return redisMessageListenerContainer;
}
}

View File

@ -6,6 +6,7 @@ import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
@ -21,6 +22,9 @@ public class RemoteJudgeDispatcher {
@Autowired
private JudgeServiceImpl judgeService;
@Autowired
private RemoteJudgeReceiver remoteJudgeReceiver;
public void sendTask(Long submitId, Long pid, String token, String remoteJudge, Boolean isContest, Integer tryAgainNum) {
JSONObject task = new JSONObject();
task.set("submitId", submitId);
@ -30,21 +34,19 @@ public class RemoteJudgeDispatcher {
task.set("isContest", isContest);
task.set("tryAgainNum", tryAgainNum);
try {
// 对应列表右边取出账号
String account = (String) redisUtils.lrPop(Constants.Judge.getListNameByOJName(remoteJudge.split("-")[0]));
if (account != null) {
JSONObject accountJson = JSONUtil.parseObj(account);
task.set("username", accountJson.getStr("username", null));
task.set("password", accountJson.getStr("password", null));
} else {
task.set("username", null);
task.set("password", null);
boolean isOk = redisUtils.llPush(Constants.Judge.STATUS_REMOTE_JUDGE_WAITING_HANDLE.getName(), JSONUtil.toJsonStr(task));
if (!isOk) {
judgeService.updateById(new Judge()
.setSubmitId(submitId)
.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus())
.setErrorMessage("Please try to submit again!")
);
}
redisUtils.sendMessage(Constants.Judge.STATUS_REMOTE_JUDGE_WAITING_HANDLE.getName(), JSONUtil.toJsonStr(task));
} catch (Exception e) {
log.error("调用redis消息发布异常,此次判题任务判为系统错误--------------->{}", e.getMessage());
log.error("调用redis将判题纳入判题等待队列异常,此次判题任务判为系统错误--------------->{}", e.getMessage());
judgeService.failToUseRedisPublishJudge(submitId, pid, isContest);
}finally {
remoteJudgeReceiver.processWaitingTask();
}
}
}

View File

@ -2,14 +2,10 @@ package top.hcode.hoj.judge.remote;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.pojo.entity.ToJudge;
import top.hcode.hoj.service.ToJudgeService;
@ -17,8 +13,11 @@ import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
import java.util.concurrent.TimeUnit;
@Component
public class RemoteJudgeReceiver implements MessageListener {
@Async
public class RemoteJudgeReceiver {
@Autowired
private ToJudgeService toJudgeService;
@ -32,41 +31,80 @@ public class RemoteJudgeReceiver implements MessageListener {
@Autowired
private RedisUtils redisUtils;
@Override
public void onMessage(Message message, byte[] bytes) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
String taskJson = (String) jackson2JsonRedisSerializer.deserialize(message.getBody());
JSONObject task = JSONUtil.parseObj(taskJson);
public void processWaitingTask() {
// 如果队列中还有任务则继续处理
if (redisUtils.lGetListSize(Constants.Judge.STATUS_REMOTE_JUDGE_WAITING_HANDLE.getName()) > 0) {
String taskJsonStr = (String) redisUtils.lrPop(Constants.Judge.STATUS_REMOTE_JUDGE_WAITING_HANDLE.getName());
// 再次检查
if (taskJsonStr != null) {
handleJudgeMsg(taskJsonStr);
}
}
}
private void handleJudgeMsg(String taskJsonStr) {
JSONObject task = JSONUtil.parseObj(taskJsonStr);
Long submitId = task.getLong("submitId");
String token = task.getStr("token");
Long pid = task.getLong("pid");
String remoteJudge = task.getStr("remoteJudge");
Boolean isContest = task.getBool("isContest");
String username = task.getStr("username");
String password = task.getStr("password");
Integer tryAgainNum = task.getInt("tryAgainNum");
System.out.println(username);
System.out.println(password);
// 如果对应远程判题oj的账号列表还有账号
String remoteJudgeAccountListName = Constants.Judge.getListNameByOJName(remoteJudge.split("-")[0]);
if (username == null || password == null) {
remoteJudgeDispatcher.sendTask(submitId, pid, token, remoteJudge, isContest, tryAgainNum);
return;
String account, username, password;
if (redisUtils.lGetListSize(remoteJudgeAccountListName) > 0) {
account = (String) redisUtils.lrPop(remoteJudgeAccountListName);
if (StringUtils.isEmpty(account)) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
remoteJudgeDispatcher.sendTask(submitId, pid, token, remoteJudge, isContest, tryAgainNum + 1);
} else {
JSONObject accountJson = JSONUtil.parseObj(account);
username = accountJson.getStr("username");
password = accountJson.getStr("password");
Judge judge = judgeService.getById(submitId);
// 调用判题服务
toJudgeService.remoteJudge(new ToJudge()
.setJudge(judge)
.setToken(token)
.setRemoteJudge(remoteJudge)
.setUsername(username)
.setPassword(password)
.setTryAgainNum(tryAgainNum));
// 如果队列中还有任务则继续处理
processWaitingTask();
}
} else {
if (tryAgainNum >= 40) {
// 获取调用多次失败可能为系统忙碌判为提交失败
Judge judge = new Judge();
judge.setSubmitId(submitId);
judge.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus());
judge.setErrorMessage("Failed to connect the judgeServer. Please resubmit this submission again!");
judgeService.updateById(judge);
} else {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
remoteJudgeDispatcher.sendTask(submitId, pid, token, remoteJudge, isContest, tryAgainNum + 1);
}
}
Judge judge = judgeService.getById(submitId);
// 调用判题服务
toJudgeService.remoteJudge(new ToJudge()
.setJudge(judge)
.setToken(token)
.setRemoteJudge(remoteJudge)
.setUsername(username)
.setPassword(password)
.setTryAgainNum(tryAgainNum));
}
}

View File

@ -6,6 +6,7 @@ import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
@ -25,7 +26,10 @@ public class JudgeDispatcher {
@Autowired
private JudgeServiceImpl judgeService;
public void sendTask(Long submitId, Long pid, String token, Boolean isContest,Integer tryAgainNum) {
@Autowired
private JudgeReceiver judgeReceiver;
public void sendTask(Long submitId, Long pid, String token, Boolean isContest, Integer tryAgainNum) {
JSONObject task = new JSONObject();
task.set("submitId", submitId);
task.set("pid", pid);
@ -34,10 +38,20 @@ public class JudgeDispatcher {
task.set("tryAgainNum", tryAgainNum);
try {
redisUtils.sendMessage(Constants.Judge.STATUS_JUDGE_WAITING.getName(), JSONUtil.toJsonStr(task));
boolean isOk = redisUtils.llPush(Constants.Judge.STATUS_JUDGE_WAITING.getName(), JSONUtil.toJsonStr(task));
if (!isOk) {
judgeService.updateById(new Judge()
.setSubmitId(submitId)
.setStatus(Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus())
.setErrorMessage("Please try to submit again!")
);
}
} catch (Exception e) {
log.error("调用redis消息发布异常,此次判题任务判为系统错误--------------->{}", e.getMessage());
log.error("调用redis将判题纳入判题等待队列异常,此次判题任务判为系统错误--------------->{}", e.getMessage());
judgeService.failToUseRedisPublishJudge(submitId, pid, isContest);
} finally {
// 调用判题任务处理
judgeReceiver.processWaitingTask();
}
}
}

View File

@ -2,18 +2,16 @@ package top.hcode.hoj.judge.self;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.pojo.entity.ToJudge;
import top.hcode.hoj.service.ToJudgeService;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
/**
* @Author: Himit_ZH
@ -23,26 +21,35 @@ import top.hcode.hoj.service.impl.JudgeServiceImpl;
* 3. 再次接受到信息再次查询是否有空闲判题服务器若有则进行判题否则回到2
*/
@Component
public class JudgeReceiver implements MessageListener {
@Async
public class JudgeReceiver {
@Autowired
private ToJudgeService toJudgeService;
@Autowired
private RedisUtils redisUtils;
@Autowired
private JudgeServiceImpl judgeService;
@Autowired
private JudgeDispatcher judgeDispatcher;
@Override
public void onMessage(Message message, byte[] bytes) {
public void processWaitingTask() {
// 如果队列中还有任务则继续处理
if (redisUtils.lGetListSize(Constants.Judge.STATUS_JUDGE_WAITING.getName()) > 0) {
String taskJsonStr = (String) redisUtils.lrPop(Constants.Judge.STATUS_JUDGE_WAITING.getName());
// 再次检查
if (taskJsonStr != null) {
handleJudgeMsg(taskJsonStr);
}
}
}
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
private void handleJudgeMsg(String taskJsonStr) {
String taskJson = (String) jackson2JsonRedisSerializer.deserialize(message.getBody());
JSONObject task = JSONUtil.parseObj(taskJson);
JSONObject task = JSONUtil.parseObj(taskJsonStr);
Long submitId = task.getLong("submitId");
String token = task.getStr("token");
Integer tryAgainNum = task.getInt("tryAgainNum");
@ -53,5 +60,10 @@ public class JudgeReceiver implements MessageListener {
.setToken(token)
.setRemoteJudge(null)
.setTryAgainNum(tryAgainNum));
// 接着处理任务
processWaitingTask();
}
}

View File

@ -25,6 +25,8 @@ public class ProblemDto {
private String uploadTestcaseDir;
private Boolean isSpj;
private List<Language> languages;
private List<Tag> tags;

View File

@ -44,6 +44,9 @@ public class ConfigVo {
@Value("${hoj.redis.port}")
private Integer redisPort;
@Value("${hoj.redis.password}")
private String redisPassword;
// jwt配置
@Value("${hoj.jwt.secret}")
private String tokenSecret;
@ -64,6 +67,9 @@ public class ConfigVo {
@Value("${hoj.mail.host}")
private String emailHost;
@Value("${hoj.mail.port}")
private Integer emailPort;
@Value("${hoj.mail.ssl}")
private Boolean emailSsl;

View File

@ -43,6 +43,6 @@ public class UserHomeVo {
private Integer score;
@ApiModelProperty(value = "已解决题目列表")
private List<Long> solvedList;
private List<String> solvedList;
}

View File

@ -20,7 +20,8 @@ import java.util.List;
*/
public interface ProblemService extends IService<Problem> {
Page<ProblemVo> getProblemList(int limit, int currentPage, Long pid, String title, Integer difficulty, Long tid);
Page<ProblemVo> getProblemList(int limit, int currentPage, Long pid, String title,
Integer difficulty, Long tid, String oj);
boolean adminUpdateProblem(ProblemDto problemDto);
@ -28,5 +29,5 @@ public interface ProblemService extends IService<Problem> {
ProblemStrategy.RemoteProblemInfo getOtherOJProblemInfo(String OJName, String problemId, String author) throws Exception;
boolean adminAddOtherOJProblem(ProblemStrategy.RemoteProblemInfo remoteProblemInfo,String OJName);
boolean adminAddOtherOJProblem(ProblemStrategy.RemoteProblemInfo remoteProblemInfo, String OJName);
}

View File

@ -115,9 +115,14 @@ public class ConfigServiceImpl implements ConfigService {
if (!StringUtils.isEmpty(params.get("emailPassword"))) {
configVo.setEmailPassword((String) params.get("emailPassword"));
}
if (!StringUtils.isEmpty(params.get("emailHost"))) {
configVo.setEmailHost((String) params.get("emailHost"));
if (!StringUtils.isEmpty(params.get("emailPort"))) {
configVo.setEmailPort((Integer) params.get("emailPort"));
}
if (!StringUtils.isEmpty(params.get("emailUsername"))) {
configVo.setEmailUsername((String) params.get("emailUsername"));
}
if (params.get("emailSsl") != null) {
configVo.setEmailSsl((Boolean) params.get("emailSsl"));
}
@ -176,6 +181,9 @@ public class ConfigServiceImpl implements ConfigService {
if (params.get("redisPort") != null) {
configVo.setRedisPort((Integer) params.get("redisPort"));
}
if (params.get("redisPassword") != null) {
configVo.setRedisPassword((String) params.get("redisPassword"));
}
return sendNewConfigToNacos();
}

View File

@ -65,12 +65,13 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
@Override
public Page<ProblemVo> getProblemList(int limit, int currentPage, Long pid, String title, Integer difficulty, Long tid) {
public Page<ProblemVo> getProblemList(int limit, int currentPage, Long pid, String title, Integer difficulty,
Long tid, String oj) {
//新建分页
Page<ProblemVo> page = new Page<>(currentPage, limit);
return page.setRecords(problemMapper.getProblemList(page, pid, title, difficulty, tid));
return page.setRecords(problemMapper.getProblemList(page, pid, title, difficulty, tid, oj));
}
@Override
@ -78,6 +79,10 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
public boolean adminUpdateProblem(ProblemDto problemDto) {
Problem problem = problemDto.getProblem();
if (!problemDto.getIsSpj()) {
problem.setSpjLanguage(null).setSpjCode(null);
}
problem.setProblemId(problem.getProblemId().toUpperCase());
// 后面许多表的更新或删除需要用到题目id
long pid = problemDto.getProblem().getId();
@ -300,6 +305,11 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
public boolean adminAddProblem(ProblemDto problemDto) {
Problem problem = problemDto.getProblem();
if (!problemDto.getIsSpj()) {
problem.setSpjLanguage(null).setSpjCode(null);
}
// 设置测试样例的版本号
problem.setCaseVersion(String.valueOf(System.currentTimeMillis()));
problem.setProblemId(problem.getProblemId().toUpperCase());
@ -431,9 +441,9 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
tagFlag.put(hasTag.getName(), hasTag);
}
for (Tag tmp : addTagList) {
if (tagFlag.get(tmp.getName()) == null){
if (tagFlag.get(tmp.getName()) == null) {
needAddTagList.add(tmp);
}else{
} else {
needAddTagList.add(tagFlag.get(tmp.getName()));
}
}

View File

@ -81,47 +81,19 @@ public class ScheduleServiceImpl implements ScheduleService {
if (files.isEmpty()) {
return;
}
StringBuilder stringBuilder = new StringBuilder();
List<Long> idLists = new LinkedList<>();
for (File file : files) {
stringBuilder.append(file.getFilePath()).append(" ");
idLists.add(file.getId());
}
//
// WINDOWS系统下删除 DEL + 指令符
// /P 删除每一个文件之前提示确认
// /F 强制删除只读文件
// /S 删除所有子目录中的指定的文件
// /Q 安静模式删除全局通配符时不要求确认
// /A 根据属性选择要删除的文件
Process process = null;
try {
String osName = System.getProperty("os.name");
String execution;
// 判断系统执行不同删除代码
if ("Windows 10".compareTo(osName) == 0) {
execution = "cmd.exe /c DEL /F /S /Q " + stringBuilder.toString();
} else {
execution = "rm -rf " + stringBuilder.toString();
}
process = Runtime.getRuntime().exec(execution);
process.waitFor();
int result = process.exitValue(); //为0表示执行成功非0表示shell执行出错
if (result != 0) {
log.error("头像删除异常----------------->Shell执行失败");
} else {
boolean isSuccess = fileService.removeByIds(idLists);
if (!isSuccess) {
log.error("数据库file表删除头像数据失败----------------->sql语句执行失败");
if(file.getDelete()) {
boolean delSuccess = FileUtil.del(file.getFilePath());
if (delSuccess) {
idLists.add(file.getId());
}
}
}
} catch (Exception e) {
log.error("头像删除异常--------------------->{}", e.getMessage());
} finally {
if (process != null) {
process.destroy();
}
boolean isSuccess = fileService.removeByIds(idLists);
if (!isSuccess) {
log.error("数据库file表删除头像数据失败----------------->sql语句执行失败");
}
}

View File

@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.hcode.hoj.pojo.vo.ConfigVo;
import java.util.List;
/**
* @Author: Himit_ZH
* @Date: 2020/12/2 23:17
@ -15,43 +17,66 @@ public class ConfigUtils {
private ConfigVo configVo;
public String getConfigContent() {
return "hoj:\n" +
" jwt:\n" +
" # 加密秘钥\n" +
" secret: "+configVo.getTokenSecret()+"\n" +
" secret: " + configVo.getTokenSecret() + "\n" +
" # token有效时长1小时单位秒\n" +
" expire: "+configVo.getTokenExpire()+"\n" +
" checkRefreshExpire: "+configVo.getCheckRefreshExpire()+"\n"+
" expire: " + configVo.getTokenExpire() + "\n" +
" checkRefreshExpire: " + configVo.getCheckRefreshExpire() + "\n" +
" header: token\n" +
" judge:\n" +
" # 调用判题服务器的token\n" +
" token: "+configVo.getJudgeToken()+"\n"+
" token: " + configVo.getJudgeToken() + "\n" +
" db:\n" +
" host: "+configVo.getMysqlHost()+"\n" +
" port: "+configVo.getMysqlPort()+"\n" +
" name: "+configVo.getMysqlDBName()+"\n" +
" username: "+configVo.getMysqlUsername()+"\n" +
" password: "+configVo.getMysqlPassword()+"\n" +
" host: " + configVo.getMysqlHost() + "\n" +
" port: " + configVo.getMysqlPort() + "\n" +
" name: " + configVo.getMysqlDBName() + "\n" +
" username: " + configVo.getMysqlUsername() + "\n" +
" password: " + configVo.getMysqlPassword() + "\n" +
" mail:\n" +
" ssl: "+configVo.getEmailSsl()+"\n" +
" username: "+configVo.getEmailUsername()+"\n" +
" password: "+configVo.getEmailPassword()+"\n" +
" host: "+configVo.getEmailHost()+"\n" +
" ssl: " + configVo.getEmailSsl() + "\n" +
" username: " + configVo.getEmailUsername() + "\n" +
" password: " + configVo.getEmailPassword() + "\n" +
" host: " + configVo.getEmailHost() + "\n" +
" port: " + configVo.getEmailPort() + "\n" +
" redis:\n" +
" host: "+configVo.getRedisHost()+"\n" +
" port: "+configVo.getRedisPort()+"\n" +
" host: " + configVo.getRedisHost() + "\n" +
" port: " + configVo.getRedisPort() + "\n" +
" password: " + configVo.getRedisPassword() + "\n" +
" web-config:\n" +
" base-url: "+configVo.getBaseUrl()+"\n" +
" name: "+configVo.getName()+"\n" +
" short-name: "+configVo.getShortName()+"\n" +
" register: "+configVo.getRegister()+"\n" +
" base-url: " + configVo.getBaseUrl() + "\n" +
" name: " + configVo.getName() + "\n" +
" short-name: " + configVo.getShortName() + "\n" +
" register: " + configVo.getRegister() + "\n" +
" footer:\n" +
" record:\n" +
" name: "+configVo.getRecordName()+"\n" +
" url: "+configVo.getRecordUrl()+"\n" +
" name: " + configVo.getRecordName() + "\n" +
" url: " + configVo.getRecordUrl() + "\n" +
" project:\n" +
" name: "+configVo.getProjectName()+"\n" +
" url: "+configVo.getProjectUrl();
" name: " + configVo.getProjectName() + "\n" +
" url: " + configVo.getProjectUrl() + "\n" +
" hdu:\n" +
" account:\n" +
" username: "+listToStr(configVo.getHduUsernameList())+ "\n" +
" password: "+listToStr(configVo.getHduPasswordList())+ "\n" +
" cf:\n" +
" account:\n" +
" username: "+listToStr(configVo.getCfUsernameList())+ "\n" +
" password: "+listToStr(configVo.getCfPasswordList());
}
private String listToStr(List<String> list) {
StringBuilder listStr = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i != list.size() - 1) {
listStr.append(list.get(i)).append(",");
} else {
listStr.append(list.get(i));
}
}
return listStr.toString();
}
}

View File

@ -1,11 +1,53 @@
package top.hcode.hoj.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author: Himit_ZH
* @Date: 2021/1/1 13:00
* @Description: 常量枚举类
*/
@Component
public class Constants {
public static String ojAddr;
public static String ojName;
public static String ojShortName;
public static String ojEmailBg;
public static String ojEmailFrom;
@Value("${hoj-backstage.addr}")
public void setOjAddr(String ojAddr) {
Constants.ojAddr = ojAddr;
}
@Value("${hoj-backstage.name}")
public void setOjName(String ojName) {
Constants.ojName = ojName;
}
@Value("${hoj-backstage.short-name}")
public void setOjShortName(String ojShortName) {
Constants.ojShortName = ojShortName;
}
@Value("${hoj-backstage.email-bg}")
public void setOjEmailBg(String ojEmailBg) {
Constants.ojEmailBg = ojEmailBg;
}
@Value("${hoj.mail.username}")
public void setOjEmailFrom(String ojEmailFrom) {
Constants.ojEmailFrom = ojEmailFrom;
}
/**
* @Description 提交评测结果的状态码
* @Since 2021/1/1
@ -143,19 +185,19 @@ public class Constants {
*/
public enum File {
USER_FILE_HOST("http://localhost:9010"),
USER_FILE_HOST(ojAddr),
USER_AVATAR_FOLDER("D:\\avatar\\"),
USER_AVATAR_FOLDER("/hoj/file/avatar"),
MARKDOWN_IMG_FOLDER("D:\\md\\"),
MARKDOWN_IMG_FOLDER("/hoj/md"),
IMG_API("/public/img/"),
IMG_API("/api/public/img/"),
TESTCASE_BASE_FOLDER("D:\\zip\\"),
TESTCASE_BASE_FOLDER("/hoj/file/zip"),
TESTCASE_DOWNLOAD_TMP_FOLDER("D:\\zip\\download"),
TESTCASE_DOWNLOAD_TMP_FOLDER("/hoj/file/zip/download"),
CONTEST_AC_SUBMISSION_TMP_FOLDER("D:\\zip\\contest_ac");
CONTEST_AC_SUBMISSION_TMP_FOLDER("/hoj/file/zip/contest_ac");
private final String path;
@ -175,14 +217,16 @@ public class Constants {
*/
public enum Email {
OJ_URL("http://localhost:8080"),
OJ_NAME("Hcode Online Judge"),
OJ_SHORT_NAME("HOJ"),
EMAIL_FROM("oj.hcode@qq.com"),
EMAIL_BACKGROUND_IMG("https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/HCODE.png"),
OJ_URL(ojAddr),
OJ_NAME(ojName),
OJ_SHORT_NAME(ojShortName),
EMAIL_FROM(ojEmailFrom),
EMAIL_BACKGROUND_IMG(ojEmailBg),
REGISTER_KEY_PREFIX("register-user:"),
RESET_PASSWORD_KEY_PREFIX("reset-password:");
private String value;
private final String value;
Email(String value) {
this.value = value;

View File

@ -51,13 +51,14 @@ public class IpUtils {
}
}
public static String getServiceIp(){
public static String getServiceIp() {
InetAddress address = null;
try {
address = InetAddress.getLocalHost();
return address.getHostAddress(); //返回IP地址
} catch (UnknownHostException e) {
log.error("本地ip获取异常---------->{}", e.getMessage());
}
return address.getHostAddress(); //返回IP地址
return null;
}
}

View File

@ -1,6 +1,6 @@
spring:
profiles:
active: dev
active: prod
---
# 消费者将要去访问的微服务名称注册成功进入nacos的微服务提供者
@ -9,7 +9,7 @@ service-url:
name: hoj-judge-server # 服务名
spring:
profiles: dev
profiles: prod
# 配置文件上传限制
servlet:
multipart:
@ -17,9 +17,12 @@ spring:
max-request-size: 50MB
mail:
properties.mail.smtp.ssl.enable: ${hoj.mail.ssl}
properties.mail.smtp.starttls.enable: true
properties.mail.smtp.starttls.required: true
username: ${hoj.mail.username}
password: ${hoj.mail.password}
host: ${hoj.mail.host}
port: ${hoj.mail.port}
redis:
host: ${hoj.redis.host}
port: ${hoj.redis.port}
@ -30,6 +33,7 @@ spring:
max-idle: 200 #连接池中的最大空闲连接
max-active: 500 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
password: ${hoj.redis.password}
datasource:
username: ${hoj.db.username}
password: ${hoj.db.password}
@ -69,7 +73,7 @@ shiro-redis:
enabled: true
redis-manager:
host: ${hoj.redis.host}:${hoj.redis.port}
password: ${hoj.redis.password}
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,俩端连接所用的时间 单位是秒
@ -84,6 +88,7 @@ logging:
nacos:
client:
naming: error
root: error
# 开启Hystrix断路器
feign:

View File

@ -1,23 +1,31 @@
hoj-backstage:
port: 6688
nacos-url: 172.18.0.2:8848
addr: http://oj.hcode.top
name: Hcode Online Judge
short-name: HOJ
email-bg: https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/HCODE.png # 邮箱系统发生邮件模板的背景图片地址
server:
port: 9010
port: ${hoj-backstage.port}
spring:
profiles:
active: dev
active: prod
application:
name: hoj-data-backup
cloud:
nacos:
discovery:
server-addr: 129.204.177.72:8848 # Nacos 作为服务注册中心
server-addr: ${hoj-backstage.nacos-url} # Nacos 作为服务注册中心 nacos的地址
config:
server-addr: 129.204.177.72:8848 #Nacos 作为配置中心地址
server-addr: ${hoj-backstage.nacos-url} #Nacos 作为配置中心地址 nacos的地址
file-extension: yml #指定yaml格式的配置
group: DEFAULT_GROUP # 指定分组
data-id: hoj-data-backup-dev.yml
data-id: hoj-data-backup-prod.yml
type: yaml
#namespace:命名空间ID 默认为public
url: http://129.204.177.72:8848
url: http://${hoj-backstage.nacos-url}
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.naces.config.file-extension}
# hoj-data-backup-dev.yml
# hoj-data-backup-prod.yml

View File

@ -1,6 +1,6 @@
spring:
profiles:
active: dev
active: prod
---
# 消费者将要去访问的微服务名称注册成功进入nacos的微服务提供者
@ -9,7 +9,7 @@ service-url:
name: hoj-judge-server # 服务名
spring:
profiles: dev
profiles: prod
# 配置文件上传限制
servlet:
multipart:
@ -17,9 +17,12 @@ spring:
max-request-size: 50MB
mail:
properties.mail.smtp.ssl.enable: ${hoj.mail.ssl}
properties.mail.smtp.starttls.enable: true
properties.mail.smtp.starttls.required: true
username: ${hoj.mail.username}
password: ${hoj.mail.password}
host: ${hoj.mail.host}
port: ${hoj.mail.port}
redis:
host: ${hoj.redis.host}
port: ${hoj.redis.port}
@ -30,6 +33,7 @@ spring:
max-idle: 200 #连接池中的最大空闲连接
max-active: 500 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
password: ${hoj.redis.password}
datasource:
username: ${hoj.db.username}
password: ${hoj.db.password}
@ -69,7 +73,7 @@ shiro-redis:
enabled: true
redis-manager:
host: ${hoj.redis.host}:${hoj.redis.port}
password: ${hoj.redis.password}
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,俩端连接所用的时间 单位是秒
@ -84,6 +88,7 @@ logging:
nacos:
client:
naming: error
root: error
# 开启Hystrix断路器
feign:

View File

@ -1,23 +1,31 @@
hoj-backstage:
port: 6688
nacos-url: 172.18.0.2:8848
addr: http://oj.hcode.top
name: Hcode Online Judge
short-name: HOJ
email-bg: https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/HCODE.png # 邮箱系统发生邮件模板的背景图片地址
server:
port: 9010
port: ${hoj-backstage.port}
spring:
profiles:
active: dev
active: prod
application:
name: hoj-data-backup
cloud:
nacos:
discovery:
server-addr: 129.204.177.72:8848 # Nacos 作为服务注册中心
server-addr: ${hoj-backstage.nacos-url} # Nacos 作为服务注册中心 nacos的地址
config:
server-addr: 129.204.177.72:8848 #Nacos 作为配置中心地址
server-addr: ${hoj-backstage.nacos-url} #Nacos 作为配置中心地址 nacos的地址
file-extension: yml #指定yaml格式的配置
group: DEFAULT_GROUP # 指定分组
data-id: hoj-data-backup-dev.yml
data-id: hoj-data-backup-prod.yml
type: yaml
#namespace:命名空间ID 默认为public
url: http://129.204.177.72:8848
url: http://${hoj-backstage.nacos-url}
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.naces.config.file-extension}
# hoj-data-backup-dev.yml
# hoj-data-backup-prod.yml

View File

@ -44,8 +44,11 @@
<if test="tid != null">
and pt.tid = #{tid}
</if>
<if test="oj != null">
and p.problem_id like concat(#{oj},'%')
</if>
</where>
order by p.problem_id asc
order by length(p.problem_id) asc,p.problem_id asc
</select>
<!-- 子查询 :为了防止分页总数据数出错-->

View File

@ -1,8 +1,7 @@
package top.hcode.hoj.controller;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -12,13 +11,11 @@ import top.hcode.hoj.common.CommonResult;
import top.hcode.hoj.common.exception.CompileError;
import top.hcode.hoj.common.exception.SystemError;
import top.hcode.hoj.pojo.entity.*;
import top.hcode.hoj.remoteJudge.submit.RemoteJudgeSubmitDispatcher;
import top.hcode.hoj.remoteJudge.RemoteJudgeToSubmit;
import top.hcode.hoj.service.impl.*;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.IpUtils;
import top.hcode.hoj.util.RedisUtils;
import java.util.HashMap;
import java.util.Date;
/**
@ -41,16 +38,31 @@ public class JudgeController {
private SystemConfigServiceImpl systemConfigService;
@Autowired
private RemoteJudgeSubmitDispatcher remoteJudgeSubmitDispatcher;
private RemoteJudgeToSubmit remoteJudgeToSubmit;
@Value("${hoj.judge.token}")
private String judgeToken;
@Value("${hoj-judger.max-task-num}")
private Integer maxTaskNum;
@Value("${hoj-judger.name}")
private String name;
@Autowired
private RedisUtils redisUtils;
@RequestMapping("/version")
public CommonResult getVersion() {
return CommonResult.successResponse(MapUtil.builder()
.put("version", "1.0.0")
.put("currentTime", new Date())
.put("judgeServerName", name)
.put("cpu", Runtime.getRuntime().availableProcessors())
.put("maxTaskNum", maxTaskNum)
.map(),
"运行正常"
);
}
@PostMapping(value = "/judge")
public CommonResult submitProblemJudge(@RequestBody ToJudge toJudge) {
@ -136,30 +148,8 @@ public class JudgeController {
String userCode = toJudge.getJudge().getCode();
String language = toJudge.getJudge().getLanguage();
// 发送消息
try {
remoteJudgeSubmitDispatcher.sendTask(username, password, remoteJudge, remotePid, submitId, uid, cid, pid, language, userCode);
return CommonResult.successResponse(null, "提交成功");
} catch (Exception e) {
// 将使用的账号放回对应列表
JSONObject account = new JSONObject();
account.set("username", username);
account.set("password", password);
redisUtils.llPush(Constants.RemoteJudge.getListNameByOJName(remoteJudge), JSONUtil.toJsonStr(account));
Judge judge = new Judge();
judge.setSubmitId(submitId)
.setStatus(Constants.Judge.STATUS_SYSTEM_ERROR.getStatus())
.setErrorMessage("Oops, something has gone wrong with the judgeServer. Please report this to administrator.");
judgeService.updateById(judge);
// 更新其它表
judgeService.updateOtherTable(submitId,
Constants.Judge.STATUS_SYSTEM_ERROR.getStatus(),
cid,
uid,
pid,
null);
log.error("调用redis消息发布异常,此次远程判题任务判为系统错误--------------->{}", e.getMessage());
return CommonResult.errorResponse(e.getMessage(), CommonResult.STATUS_ERROR);
}
// 调用远程判题
remoteJudgeToSubmit.sendTask(username, password, remoteJudge, remotePid, submitId, uid, cid, pid, language, userCode);
return CommonResult.successResponse(null, "提交成功");
}
}

View File

@ -660,6 +660,7 @@ public class JudgeStrategy {
String testCasesDir = Constants.JudgeDir.TEST_CASE_DIR.getContent() + "/problem_" + problemId;
// 无论有没有测试数据一旦执行该函数一律清空重新生成该题目对应的测试数据文件
FileUtil.del(testCasesDir);
for (int index = 0; index < testCases.size(); index++) {
JSONObject jsonObject = new JSONObject();

View File

@ -0,0 +1,95 @@
package top.hcode.hoj.remoteJudge;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.remoteJudge.task.RemoteJudgeFactory;
import top.hcode.hoj.remoteJudge.task.RemoteJudgeStrategy;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.util.Constants;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class RemoteJudgeGetResult {
@Autowired
private JudgeServiceImpl judgeService;
@Transactional
public void sendTask(String remoteJudge, String username, Long submitId, String uid,
Long cid, Long pid, Long resultSubmitId, String token,
HashMap<String, String> cookies) {
RemoteJudgeStrategy remoteJudgeStrategy = RemoteJudgeFactory.selectJudge(remoteJudge);
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
AtomicInteger count = new AtomicInteger(0);
Runnable getResultTask = new Runnable() {
@Override
@Transactional
public void run() {
count.getAndIncrement();
try {
Map<String, Object> result = remoteJudgeStrategy.result(resultSubmitId, username, token, cookies);
Integer status = (Integer) result.getOrDefault("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
if (status.intValue() != Constants.Judge.STATUS_PENDING.getStatus() &&
status.intValue() != Constants.Judge.STATUS_JUDGING.getStatus()) {
Judge judge = new Judge();
judge.setSubmitId(submitId);
Integer time = (Integer) result.getOrDefault("time", null);
Integer memory = (Integer) result.getOrDefault("memory", null);
String CEInfo = (String) result.getOrDefault("CEInfo", null);
judge.setStatus(status)
.setTime(time)
.setMemory(memory);
if (status.intValue() == Constants.Judge.STATUS_COMPILE_ERROR.getStatus()) {
judge.setErrorMessage(CEInfo);
} else if (status.intValue() == Constants.Judge.STATUS_SYSTEM_ERROR.getStatus()) {
judge.setErrorMessage("There is something wrong with the " + remoteJudge + ", please try again later");
}
// 写回数据库
judgeService.updateById(judge);
// 同步其它表
judgeService.updateOtherTable(submitId, status, cid, uid, pid, null);
scheduler.shutdown();
}
} catch (Exception ignored) {
} finally {
if (count.get() == 60) { // 60次失败则判为提交失败
// 更新此次提交状态为提交失败
UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();
judgeUpdateWrapper.set("status", Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus())
.eq("submit_id", submitId);
judgeService.update(judgeUpdateWrapper);
scheduler.shutdown();
}
}
}
};
final ScheduledFuture<?> beeperHandle = scheduler.scheduleAtFixedRate(
getResultTask, 2, 2, TimeUnit.SECONDS);
}
}

View File

@ -1,63 +0,0 @@
package top.hcode.hoj.remoteJudge;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import top.hcode.hoj.remoteJudge.result.RemoteJudgeResultReceiver;
import top.hcode.hoj.remoteJudge.submit.RemoteJudgeSubmitReceiver;
import top.hcode.hoj.util.Constants;
@Configuration
@AutoConfigureAfter({RemoteJudgeSubmitReceiver.class, RemoteJudgeResultReceiver.class})
public class RemoteJudgeSubscriberConfig {
/**
* 消息监听适配器注入接受消息方法处理提交代码到其它oj的消息
*
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter toSubmitMessageListenerAdapter(RemoteJudgeSubmitReceiver receiver) {
return new MessageListenerAdapter(receiver); //当没有继承MessageListener时需要写方法名字
}
/**
* 消息监听适配器注入接受消息方法处理获取提交结果的消息
*
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter getResultMessageListenerAdapter(RemoteJudgeResultReceiver receiver) {
return new MessageListenerAdapter(receiver); //当没有继承MessageListener时需要写方法名字
}
/**
* 创建消息监听容器
*
* @param redisConnectionFactory
* @param toSubmitMessageListenerAdapter
* @param getResultMessageListenerAdapter
* @return
*/
@Bean
public RedisMessageListenerContainer getRedisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory,
MessageListenerAdapter toSubmitMessageListenerAdapter,
MessageListenerAdapter getResultMessageListenerAdapter) {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
// 添加频道名字
redisMessageListenerContainer.addMessageListener(toSubmitMessageListenerAdapter, new PatternTopic(Constants.RemoteJudge.JUDGE_SUBMIT_HANDLER.getName()));
redisMessageListenerContainer.addMessageListener(getResultMessageListenerAdapter, new PatternTopic(Constants.RemoteJudge.JUDGE_RESULT_HANDLER.getName()));
return redisMessageListenerContainer;
}
}

View File

@ -1,16 +1,13 @@
package top.hcode.hoj.remoteJudge.submit;
package top.hcode.hoj.remoteJudge;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.remoteJudge.result.RemoteJudgeResultDispatcher;
import top.hcode.hoj.remoteJudge.task.RemoteJudgeFactory;
import top.hcode.hoj.remoteJudge.task.RemoteJudgeStrategy;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
@ -22,48 +19,43 @@ import java.util.Map;
@Component
@Slf4j
public class RemoteJudgeSubmitReceiver implements MessageListener {
public class RemoteJudgeToSubmit {
@Autowired
private RedisUtils redisUtils;
@Autowired
private RemoteJudgeResultDispatcher remoteJudgeResultDispatcher;
private RemoteJudgeGetResult remoteJudgeGetResult;
@Autowired
private JudgeServiceImpl judgeService;
@Override
public void onMessage(Message message, byte[] bytes) {
// log.debug("RemoteJudgeSubmitReceiver获取到消息{}", Arrays.toString(message.getBody()));
String source = (String) redisUtils.lrPop(Constants.RemoteJudge.JUDGE_WAITING_SUBMIT_QUEUE.getName());
// 如果竞争不到提交队列结束
if (source == null) {
return;
}
JSONObject task = JSONUtil.parseObj(source);
String remotePid = task.getStr("remotePid");
Long submitId = task.getLong("submitId");
String uid = task.getStr("uid");
Long cid = task.getLong("cid");
Long pid = task.getLong("pid");
String remoteJudge = task.getStr("remoteJudge");
String language = task.getStr("language");
String userCode = task.getStr("userCode");
String username = task.getStr("username");
String password = task.getStr("password");
@Value("${hoj-judger.name}")
private String name;
public void sendTask(String username, String password, String remoteJudge, String remotePid, Long submitId,
String uid, Long cid, Long pid, String language, String userCode) {
RemoteJudgeStrategy remoteJudgeStrategy = RemoteJudgeFactory.selectJudge(remoteJudge);
// 获取不到对应的题库或者题库写错了
if (remoteJudgeStrategy == null) {
log.error("暂不支持该{}题库---------------->请求失败", remoteJudge);
// 更新此次提交状态为系统失败
UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();
judgeUpdateWrapper.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus())
.set("error_message", "The judge server does not support this oj:" + remoteJudge)
.eq("submit_id", submitId);
judgeService.update(judgeUpdateWrapper);
return;
}
Map<String, Object> submitResult = null;
try {
submitResult = remoteJudgeStrategy.submit(username, password, remotePid, language, userCode);
} catch (Exception e) {
e.printStackTrace();
}finally {
log.error(remoteJudge + "的远程提交发生异常---------->{}", e.getMessage());
} finally {
// 将使用的账号放回对应列表
JSONObject account = new JSONObject();
account.set("username", username);
@ -78,19 +70,28 @@ public class RemoteJudgeSubmitReceiver implements MessageListener {
judgeUpdateWrapper.set("status", Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus())
.eq("submit_id", submitId);
judgeService.update(judgeUpdateWrapper);
// 更新其它表
judgeService.updateOtherTable(submitId,
Constants.Judge.STATUS_SYSTEM_ERROR.getStatus(),
cid,
uid,
pid,
null);
log.error("网络错误---------------->获取不到提交ID");
return;
}
// 提交成功顺便更新状态为-->STATUS_JUDGING 判题中...
judgeService.updateById(new Judge().setSubmitId(submitId).setStatus(Constants.Judge.STATUS_JUDGING.getStatus()));
try {
remoteJudgeResultDispatcher.sendTask(remoteJudge, username, submitId, uid, cid, pid,
(Long) submitResult.get("runId"), (String) submitResult.get("token"),
(HashMap<String, String>) submitResult.get("cookies"));
} catch (Exception e) {
log.error("调用redis消息发布异常,此次远程查询结果任务判为系统错误--------------->{}", e.getMessage());
}
judgeService.updateById(new Judge()
.setSubmitId(submitId)
.setStatus(Constants.Judge.STATUS_JUDGING.getStatus())
.setJudger(name)
);
// 调用获取远程判题结果
remoteJudgeGetResult.sendTask(remoteJudge, username, submitId, uid, cid, pid,
(Long) submitResult.get("runId"), (String) submitResult.get("token"),
(HashMap<String, String>) submitResult.get("cookies"));
}
}

View File

@ -1,51 +0,0 @@
package top.hcode.hoj.remoteJudge.result;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.RedisUtils;
import java.util.HashMap;
@Component
@Slf4j
public class RemoteJudgeResultDispatcher {
private final RedisUtils redisUtils;
public RemoteJudgeResultDispatcher(RedisUtils redisUtils) {
this.redisUtils = redisUtils;
}
/**
* @param remoteJudge 远程判题oj名字
* @param resultSubmitId 远程判题提交后返回的提交ID
* @param submitId 此次提交在HOJ的judge表中的主键主要用于更新提交结果
* @MethodName sendTask
* @Description TODO
* @Return
* @Since 2021/2/12
*/
public void sendTask(String remoteJudge,String username, Long submitId, String uid,
Long cid, Long pid, Long resultSubmitId, String token,
HashMap<String, String> cookies) throws Exception {
JSONObject task = new JSONObject();
task.set("submitId", submitId);
task.set("username", username);
task.set("uid", uid);
task.set("cid", cid);
task.set("pid", pid);
task.set("resultSubmitId", resultSubmitId);
task.set("remoteJudge", remoteJudge);
task.set("token", token);
task.set("cookies", JSONUtil.toJsonStr(cookies));
// 提醒判题机有待查询结果题目
redisUtils.sendMessage(Constants.RemoteJudge.JUDGE_RESULT_HANDLER.getName(), "New Result Query Added");
// 队列中插入待判ID和题库名称
redisUtils.lrPush(Constants.RemoteJudge.JUDGE_WAITING_RESULT_QUEUE.getName(), JSONUtil.toJsonStr(task));
}
}

View File

@ -1,125 +0,0 @@
package top.hcode.hoj.remoteJudge.result;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.remoteJudge.task.RemoteJudgeFactory;
import top.hcode.hoj.remoteJudge.task.RemoteJudgeStrategy;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.IpUtils;
import top.hcode.hoj.util.RedisUtils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RemoteJudgeResultReceiver implements MessageListener {
@Autowired
private RedisUtils redisUtils;
@Autowired
private RemoteJudgeResultDispatcher remoteJudgeResultDispatcher;
@Autowired
private JudgeServiceImpl judgeService;
@Override
@Transactional
public void onMessage(Message message, byte[] bytes) {
String source = (String) redisUtils.lrPop(Constants.RemoteJudge.JUDGE_WAITING_RESULT_QUEUE.getName());
// 如果竞争不到查询队列结束
if (source == null) {
return;
}
JSONObject task = JSONUtil.parseObj(source);
Long resultSubmitId = task.getLong("resultSubmitId");
String username = task.getStr("username"); // HDU不需要但是CF需要
Long submitId = task.getLong("submitId");
String uid = task.getStr("uid");
Long cid = task.getLong("cid");
Long pid = task.getLong("pid");
String remoteJudge = task.getStr("remoteJudge");
String token = task.getStr("token");
HashMap<String, String> cookies = JsonObjectToHashMap(JSONUtil.parseObj(task.getStr("cookies")));
RemoteJudgeStrategy remoteJudgeStrategy = RemoteJudgeFactory.selectJudge(remoteJudge);
// 获取不到对应的题库或者题库写错了
if (remoteJudgeStrategy == null) {
log.error("暂不支持该{}题库---------------->请求失败", remoteJudge);
return;
}
// TODO 获取对应的result修改到数据库
try {
Map<String, Object> result = remoteJudgeStrategy.result(resultSubmitId, username, token, cookies);
Judge judge = new Judge();
judge.setSubmitId(submitId);
Integer status = (Integer) result.getOrDefault("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
// TODO 如果结果没出来重新放入队列并更新状态为Waiting
if (status.intValue() == Constants.Judge.STATUS_PENDING.getStatus() ||
status.intValue() == Constants.Judge.STATUS_JUDGING.getStatus()) {
try {
TimeUnit.SECONDS.sleep(2);
remoteJudgeResultDispatcher.sendTask(remoteJudge, username, submitId, uid, cid, pid, resultSubmitId, token, cookies);
} catch (Exception e) {
log.error("重新查询结果任务出错------{}", e.getMessage());
}
return;
}
Integer time = (Integer) result.getOrDefault("time", null);
Integer memory = (Integer) result.getOrDefault("memory", null);
String CEInfo = (String) result.getOrDefault("CEInfo", null);
judge.setStatus(status)
.setTime(time)
.setMemory(memory);
// 写入当前远程判题OJ名字
judge.setJudger(remoteJudge);
if (status.intValue() == Constants.Judge.STATUS_COMPILE_ERROR.getStatus()) {
judge.setErrorMessage(CEInfo);
} else if (status.intValue() == Constants.Judge.STATUS_SYSTEM_ERROR.getStatus()) {
judge.setErrorMessage("There is something wrong with the " + remoteJudge + ", please try again later");
}
// 写回数据库
judgeService.updateById(judge);
/**
* @Description TODO 注意 如果是OI题目肯定score得分若不是则请传入null该方法是为了更新关联表保持数据一致
* @Since 2021/2/12
**/
judgeService.updateOtherTable(submitId, status, cid, uid, pid, null);
} catch (Exception e) {
log.error("获取结果出错------------>{}", e.getLocalizedMessage());
// 更新此次提交状态为提交失败
UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();
judgeUpdateWrapper.set("status", Constants.Judge.STATUS_SUBMITTED_FAILED.getStatus())
.eq("submit_id", submitId);
judgeService.update(judgeUpdateWrapper);
}
}
public static HashMap<String, String> JsonObjectToHashMap(JSONObject jsonObj) {
HashMap<String, String> data = new HashMap<String, String>();
for (String key : jsonObj.keySet()) {
data.put(key, jsonObj.getStr(key));
}
return data;
}
}

View File

@ -1,36 +0,0 @@
package top.hcode.hoj.remoteJudge.submit;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.hcode.hoj.util.Constants;
import top.hcode.hoj.util.RedisUtils;
@Component
@Slf4j
public class RemoteJudgeSubmitDispatcher {
@Autowired
private RedisUtils redisUtils;
public void sendTask(String username, String password, String remoteJudge, String remotePid, Long submitId,
String uid, Long cid, Long pid, String language, String userCode) throws Exception {
JSONObject task = new JSONObject();
task.set("submitId", submitId);
task.set("uid", uid);
task.set("cid", cid);
task.set("pid", pid);
task.set("remoteJudge", remoteJudge);
task.set("remotePid", remotePid);
task.set("userCode", userCode);
task.set("language", language);
task.set("username", username);
task.set("password", password);
redisUtils.lrPush(Constants.RemoteJudge.JUDGE_WAITING_SUBMIT_QUEUE.getName(), JSONUtil.toJsonStr(task));
redisUtils.sendMessage(Constants.RemoteJudge.JUDGE_SUBMIT_HANDLER.getName(), "New Problem Added");
}
}

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -62,25 +62,6 @@ public class Constants {
public enum RemoteJudge {
/**
* 提交问题队列
*/
JUDGE_WAITING_SUBMIT_QUEUE("Remote Waiting Submit Queue"),
/**
* 等待结果队列
*/
JUDGE_WAITING_RESULT_QUEUE("Remote Waiting Result Queue"),
/**
* 提交消息通知
*/
JUDGE_SUBMIT_HANDLER("Remote Submit Handler"),
/**
* 查询结果通知
*/
JUDGE_RESULT_HANDLER("Remote Result Handler"),
HDU_JUDGE("HDU"),
@ -121,20 +102,7 @@ public class Constants {
return name;
}
}
// public enum JudgeServer {
// HDU_JUDGE("HDU", "hdu.com");
// private String judgeName;
// private final String judgeHost;
//
// JudgeServer(String judgeName, String judgeHost) {
// this.judgeName = judgeName;
// this.judgeHost = judgeHost;
// }
//
// public final String getName() {
// return judgeName;
// }
// }
public enum JudgeDir {
@ -162,7 +130,7 @@ public class Constants {
public static List<String> defaultEnv = Arrays.asList(
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=en_US.UTF-8",
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8");
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8","HOME=/w");
public static List<String> python3Env = Arrays.asList("LANG=en_US.UTF-8",
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "PYTHONIOENCODING=utf-8");
@ -185,9 +153,9 @@ public class Constants {
PYTHON2("Python2", "main.py", "main.pyc", 3000L, 10000L, 128 * 1024 * 1024L, "/usr/bin/python -m py_compile ./{1}", defaultEnv),
PYTHON3("Python3", "main.py", "__pycache__/main.cpython-36.pyc", 3000L, 10000L, 128 * 1024 * 1024L, "/usr/bin/python3 -m py_compile ./{1}", defaultEnv),
PYTHON3("Python3", "main.py", "__pycache__/main.cpython-37.pyc", 3000L, 10000L, 128 * 1024 * 1024L, "/usr/bin/python3 -m py_compile ./{1}", defaultEnv),
GOLANG("Golang", "main.go", "main", 3000L, 5000L, 1024 * 1024 * 1024L, "/usr/bin/go build -o {1} {2}", defaultEnv),
GOLANG("Golang", "main.go", "main", 3000L, 5000L, 1024 * 1024 * 1024L, "/usr/bin/go build -o {2} {1}", defaultEnv),
SPJ_C("SPJ-C", "spj.c", "spj", 3000L, 5000L, 1024 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),

View File

@ -31,13 +31,14 @@ spring:
redis:
host: ${hoj.redis.host}
port: ${hoj.redis.port}
timeout: 100000
timeout: 60000
jedis:
pool:
min-idle: 20 #连接池中的最小空闲连接
min-idle: 40 #连接池中的最小空闲连接
max-idle: 100 #连接池中的最大空闲连接
max-active: 200 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
password: ${hoj.redis.password}
mybatis-plus:
@ -49,7 +50,8 @@ logging:
level:
com:
alibaba:
nacos: warn
nacos: error
root: error
# 暴露监控
management:
endpoints:

View File

@ -1,11 +1,10 @@
hoj-judger:
task-num: 0 #当前正在进行判题任务数
max-task-num: 4 # -1表示最大并行任务数为cpu核心数*2
ip: -1 # -1表示使用默认本地ipv4若是部署其它服务器务必使用公网ip
port: 8088
name: hoj-judger-1
ip: 127.0.0.1 # -1表示使用默认本地ipv4若是部署其它服务器务必使用公网ip
port: 8088 # 端口号
name: hoj-judger-1 # 判题机名字
nacos-url: 127.0.0.1:8848 # nacos地址
server:
port: ${hoj-judger.port}
spring:
@ -16,13 +15,13 @@ spring:
cloud:
nacos:
discovery:
server-addr: 129.204.177.72:8848 #配置Nacos地址
server-addr: ${hoj-judger.nacos-url} #配置Nacos地址
config:
server-addr: 129.204.177.72:8848 #Nacos 作为配置中心地址
server-addr: ${hoj-judger.nacos-url} #Nacos 作为配置中心地址
file-extension: yml #指定yaml格式的配置
group: DEFAULT_GROUP # 指定分组
#namespace:命名空间ID 默认为public
url: http://129.204.177.72:8848
url: http://${hoj-judger.nacos-url}
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.naces.config.file-extension}
# hoj-judge-server-prod.yml

View File

@ -31,13 +31,14 @@ spring:
redis:
host: ${hoj.redis.host}
port: ${hoj.redis.port}
timeout: 100000
timeout: 60000
jedis:
pool:
min-idle: 20 #连接池中的最小空闲连接
min-idle: 40 #连接池中的最小空闲连接
max-idle: 100 #连接池中的最大空闲连接
max-active: 200 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
password: ${hoj.redis.password}
mybatis-plus:
@ -49,7 +50,8 @@ logging:
level:
com:
alibaba:
nacos: warn
nacos: error
root: error
# 暴露监控
management:
endpoints:

View File

@ -1,11 +1,10 @@
hoj-judger:
task-num: 0 #当前正在进行判题任务数
max-task-num: 4 # -1表示最大并行任务数为cpu核心数*2
ip: -1 # -1表示使用默认本地ipv4若是部署其它服务器务必使用公网ip
port: 8088
name: hoj-judger-1
ip: 127.0.0.1 # -1表示使用默认本地ipv4若是部署其它服务器务必使用公网ip
port: 8088 # 端口号
name: hoj-judger-1 # 判题机名字
nacos-url: 127.0.0.1:8848 # nacos地址
server:
port: ${hoj-judger.port}
spring:
@ -16,13 +15,13 @@ spring:
cloud:
nacos:
discovery:
server-addr: 129.204.177.72:8848 #配置Nacos地址
server-addr: ${hoj-judger.nacos-url} #配置Nacos地址
config:
server-addr: 129.204.177.72:8848 #Nacos 作为配置中心地址
server-addr: ${hoj-judger.nacos-url} #Nacos 作为配置中心地址
file-extension: yml #指定yaml格式的配置
group: DEFAULT_GROUP # 指定分组
#namespace:命名空间ID 默认为public
url: http://129.204.177.72:8848
url: http://${hoj-judger.nacos-url}
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.naces.config.file-extension}
# hoj-judge-server-prod.yml

Some files were not shown because too many files have changed in this diff Show More