完善判题服务调度机制(负载均衡策略),完善题目评测数据上传与下载,添加数据库脚本与网站配置文件

This commit is contained in:
Himit_ZH 2021-02-08 01:10:24 +08:00
parent 791942ca03
commit 4f01cf61c0
319 changed files with 3217 additions and 886 deletions

View File

@ -25,6 +25,7 @@
| 2021-01-19 | 比赛排行榜比赛题目对应提交重判比赛AC助手完成 | Himit_ZH |
| 2021-02-02 | 正式加入安全沙箱,判题机服务器 | Himit_ZH |
| 2021-02-04 | 完善判题机制加入pe可选择性判断加入测试点可选择性公开 | Himit_ZH |
| 2021-02-08 | 完善判题服务调度机制(负载均衡策略),完善题目评测数据上传与下载 | Himit_ZH |
# 二、系统架构
@ -213,6 +214,7 @@ problem表
| spj_language | String | | 特判程序的语言 |
| isRemoveEndBlank | boolean | | 是否默认去除用户代码的文末空格 |
| openCaseResult | boolean | | 是否默认开启该题目的测试样例结果查看 |
| caseVersion | String | | 题目测试数据的版本号 |
| gmt_create | datetime | | 创建时间 |
| gmt_modified | datetime | | 修改时间 |

View File

@ -173,6 +173,7 @@
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
</dependencies>
</project>

View File

@ -1,26 +1,53 @@
package top.hcode.hoj.common.exception;
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.stereotype.Component;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.judge.JudgeDispatcher;
import top.hcode.hoj.pojo.entity.CompileSpj;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.pojo.entity.ToJudge;
import top.hcode.hoj.service.ToJudgeService;
import java.util.concurrent.TimeUnit;
/**
* @Author: Himit_ZH
* @Date: 2020/10/30 10:21
* @Description:
*/
@Component
@RefreshScope
public class CloudHandler implements ToJudgeService {
@Autowired
private JudgeDispatcher judgeDispatcher;
@Value("${hoj.judge.token}")
private String judgeToken;
// 调度判题服务器失败可能是判题服务器有故障或者全部达到判题最大数那么将该提交重新进入等待队列
@Override
public CommonResult submitProblemJudge(Judge judge) {
public CommonResult submitProblemJudge(ToJudge toJudge) {
// 线程沉睡两秒再将任务重新发布避免过快问题同时判题服务过多导致的失败
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Judge judge = toJudge.getJudge();
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getPid() == 0);
return CommonResult.errorResponse("判题机系统出错,提交进入重判队列,请等待管理员处理!", CommonResult.STATUS_ERROR);
}
@Override
public CommonResult initTestCase(Long pid, Boolean isSpj) {
return CommonResult.errorResponse("判题机评测数据初始化失败!", CommonResult.STATUS_ERROR);
public CommonResult compileSpj(CompileSpj compileSpj) {
return CommonResult.errorResponse("没有可用的判题服务,请重新尝试!");
}
}

View File

@ -1,11 +1,13 @@
package top.hcode.hoj.common.result;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class CommonResult implements Serializable {
public final static Integer STATUS_SUCCESS = 200;
public final static Integer STATUS_FAIL = 400;

View File

@ -1,20 +0,0 @@
package top.hcode.hoj.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @Author: Himit_ZH
* @Date: 2020/10/29 22:49
* @Description:
*/
@Configuration
public class CloudConfig {
@Bean
@LoadBalanced //设置实现负载均衡Ribbon
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

View File

@ -18,7 +18,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
// 自己定义了一个 RedisTemplate
@Bean
@Bean(name = "redisTemplate")
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 我们为了自己开发方便一般直接使用 <String, Object>

View File

@ -1,5 +1,6 @@
package top.hcode.hoj.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
@ -14,7 +15,8 @@ import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
public RestTemplate restTemplate(ClientHttpRequestFactory factory)
{
return new RestTemplate(factory);
}

View File

@ -0,0 +1,22 @@
package top.hcode.hoj.config;
import com.netflix.loadbalancer.IRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import top.hcode.hoj.judge.JudgeChooseRule;
/**
* @Author: Himit_ZH
* @Date: 2021/2/4 23:10
* @Description:
*/
@Slf4j
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 随机的负载均衡策略对象
return new JudgeChooseRule();
}
}

View File

@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresRoles;
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.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
@ -12,8 +14,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.judge.JudgeDispatcher;
import top.hcode.hoj.pojo.entity.*;
import top.hcode.hoj.service.ToJudgeService;
import top.hcode.hoj.service.impl.*;
import top.hcode.hoj.utils.Constants;
@ -30,11 +32,9 @@ import java.util.List;
@RequestMapping("/admin/judge")
@RequiresAuthentication
@RequiresRoles("root") // 只有超级管理员能操作
@RefreshScope
public class AdminJudgeController {
@Autowired
private ToJudgeService toJudgeService;
@Autowired
private JudgeServiceImpl judgeService;
@ -50,6 +50,12 @@ public class AdminJudgeController {
@Autowired
private JudgeCaseServiceImpl judgeCaseService;
@Value("${hoj.judge.token}")
private String judgeToken;
@Autowired
private JudgeDispatcher judgeDispatcher;
@GetMapping("/rejudge")
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public CommonResult rejudge(@RequestParam("submitId") Long submitId) {
@ -85,8 +91,8 @@ public class AdminJudgeController {
judge.setJudger(null).setTime(null).setMemory(null).setErrorMessage(null);
boolean result = judgeService.updateById(judge);
if (result) {
// 异步调用判题机
toJudgeService.submitProblemJudge(judge);
// 调用判题服务
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getPid() == 0);
return CommonResult.successResponse(judge, "重判成功!该提交已进入判题队列!");
} else {
return CommonResult.successResponse(judge, "重判失败!请重新尝试!");
@ -102,7 +108,7 @@ public class AdminJudgeController {
queryWrapper.eq("cid", cid).eq("pid", pid);
List<Judge> rejudgeList = judgeService.list(queryWrapper);
if (rejudgeList.size()==0) {
if (rejudgeList.size() == 0) {
return CommonResult.errorResponse("当前该题目无提交,不可重判!");
}
List<Long> submitIdList = new LinkedList<>();
@ -124,9 +130,9 @@ public class AdminJudgeController {
if (resetContestRecordResult && resetJudgeResult) {
// 异步调用重判服务
judgeService.rejudgeContestProblem(rejudgeList);
judgeService.rejudgeContestProblem(rejudgeList, judgeToken);
return CommonResult.successResponse(null, "重判成功!该题目对应的全部提交已进入判题队列!");
}else{
} else {
return CommonResult.errorResponse("重判失败!请重新尝试!");
}

View File

@ -7,6 +7,8 @@ import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresRoles;
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.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@ -26,104 +28,119 @@ import java.util.*;
* @Description:
*/
@RestController
@RefreshScope
@RequestMapping("/admin/problem")
@RequiresAuthentication
@RequiresRoles(value = {"root","admin"},logical = Logical.OR)
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
public class AdminProblemController {
@Autowired
private ProblemServiceImpl problemService;
@Autowired
private ProblemCaseServiceImpl problemCaseService;
@Autowired
private ToJudgeService toJudgeService;
@Value("${hoj.judge.token}")
private String judgeToken;
@GetMapping("/get-problem-list")
public CommonResult getProblemList(@RequestParam(value = "limit", required = false) Integer limit,
@RequestParam(value = "currentPage", required = false) Integer currentPage,
@RequestParam(value = "keyword", required = false) String keyword){
@RequestParam(value = "keyword", required = false) String keyword) {
if (currentPage == null || currentPage < 1) currentPage = 1;
if (limit == null || limit < 1) limit = 10;
IPage<Problem> iPage = new Page<>(currentPage,limit);
IPage<Problem> iPage = new Page<>(currentPage, limit);
IPage<Problem> problemList = null;
if (!StringUtils.isEmpty(keyword)){
if (!StringUtils.isEmpty(keyword)) {
QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();
queryWrapper
.like("title", keyword).or()
.like("author", keyword);
problemList = problemService.page(iPage, queryWrapper);
}else{
} else {
problemList = problemService.page(iPage);
}
if (problemList.getTotal() == 0) { // 未查询到一条数据
return CommonResult.successResponse(problemList,"暂无数据");
return CommonResult.successResponse(problemList, "暂无数据");
} else {
return CommonResult.successResponse(problemList, "获取成功");
}
}
@GetMapping("")
public CommonResult getProblem(@Valid @RequestParam("id")Long id){
public CommonResult getProblem(@Valid @RequestParam("id") Long id) {
Problem problem = problemService.getById(id);
if (problem !=null) { // 查询成功
return CommonResult.successResponse(problem,"查询成功!");
if (problem != null) { // 查询成功
return CommonResult.successResponse(problem, "查询成功!");
} else {
return CommonResult.errorResponse("查询失败!",CommonResult.STATUS_FAIL);
return CommonResult.errorResponse("查询失败!", CommonResult.STATUS_FAIL);
}
}
@DeleteMapping("")
public CommonResult deleteProblem(@Valid @RequestParam("id")Long id){
public CommonResult deleteProblem(@Valid @RequestParam("id") Long id) {
boolean result = problemService.removeById(id);
/*
problem的id为其他表的外键的表中的对应数据都会被一起删除
*/
if (result) { // 删除成功
return CommonResult.successResponse(null,"删除成功!");
return CommonResult.successResponse(null, "删除成功!");
} else {
return CommonResult.errorResponse("删除失败!",CommonResult.STATUS_FAIL);
return CommonResult.errorResponse("删除失败!", CommonResult.STATUS_FAIL);
}
}
@PostMapping("")
public CommonResult addProblem(@RequestBody ProblemDto problemDto){
public CommonResult addProblem(@RequestBody ProblemDto problemDto) {
boolean result = problemService.adminAddProblem(problemDto);
toJudgeService.initTestCase(problemDto.getProblem().getId(),
!StringUtils.isEmpty(problemDto.getProblem().getSpjCode()));
if (result) { // 添加成功
return CommonResult.successResponse(null,"添加成功!");
return CommonResult.successResponse(null, "添加成功!");
} else {
return CommonResult.errorResponse("添加失败",CommonResult.STATUS_FAIL);
return CommonResult.errorResponse("添加失败", CommonResult.STATUS_FAIL);
}
}
@PutMapping("")
@Transactional
public CommonResult updateProblem(@RequestBody ProblemDto problemDto){
public CommonResult updateProblem(@RequestBody ProblemDto problemDto) {
boolean result = problemService.adminUpdateProblem(problemDto);
if (result) { // 更新成功
return CommonResult.successResponse(null,"修改成功!");
return CommonResult.successResponse(null, "修改成功!");
} else {
return CommonResult.errorResponse("修改失败",CommonResult.STATUS_FAIL);
return CommonResult.errorResponse("修改失败", CommonResult.STATUS_FAIL);
}
}
@GetMapping("/get-problem-cases")
public CommonResult getProblemCases(@Valid @RequestParam("pid") Long pid){
public CommonResult getProblemCases(@Valid @RequestParam("pid") Long pid) {
Map<String, Object> map = new HashMap<>();
map.put("pid", pid);
map.put("status", 0);
List<ProblemCase> problemCases = (List<ProblemCase>)problemCaseService.listByMap(map);
if (problemCases !=null){
return CommonResult.successResponse(problemCases,"获取该题目的评测样例列表成功!");
}else{
List<ProblemCase> problemCases = (List<ProblemCase>) problemCaseService.listByMap(map);
if (problemCases != null) {
return CommonResult.successResponse(problemCases, "获取该题目的评测样例列表成功!");
} else {
return CommonResult.errorResponse("获取该题目的评测样例列表失败!");
}
}
@PostMapping("/compile-spj")
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj) {
if (StringUtils.isEmpty(compileSpj.getSpjSrc()) ||
compileSpj.getPid() == null ||
StringUtils.isEmpty(compileSpj.getSpjLanguage())) {
return CommonResult.errorResponse("参数不能为空!");
}
compileSpj.setToken(judgeToken);
return toJudgeService.compileSpj(compileSpj);
}
}

View File

@ -1,26 +1,32 @@
package top.hcode.hoj.controller.admin;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.core.io.file.FileWriter;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.entity.ProblemCase;
import top.hcode.hoj.pojo.entity.Role;
import top.hcode.hoj.pojo.entity.UserInfo;
import top.hcode.hoj.pojo.vo.ExcelUserVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
import top.hcode.hoj.service.impl.FileServiceImpl;
import top.hcode.hoj.service.impl.ProblemCaseServiceImpl;
import top.hcode.hoj.service.impl.UserInfoServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
@ -28,12 +34,9 @@ import top.hcode.hoj.utils.RedisUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.net.URLEncoder;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @Author: Himit_ZH
@ -54,6 +57,9 @@ public class FileController {
@Autowired
private UserInfoServiceImpl userInfoService;
@Autowired
private ProblemCaseServiceImpl problemCaseService;
@RequestMapping("/generate-user-excel")
@RequiresAuthentication
@RequiresRoles("root")
@ -67,10 +73,10 @@ public class FileController {
EasyExcel.write(response.getOutputStream(), ExcelUserVo.class).sheet("用户数据").doWrite(getGenerateUsers(key));
}
public List<ExcelUserVo> getGenerateUsers(String key){
public List<ExcelUserVo> getGenerateUsers(String key) {
List<ExcelUserVo> result = new LinkedList<>();
Map<Object, Object> userInfo = redisUtils.hmget(key);
for (Object hashKey:userInfo.keySet()){
for (Object hashKey : userInfo.keySet()) {
String username = (String) hashKey;
String password = (String) userInfo.get(hashKey);
result.add(new ExcelUserVo().setUsername(username).setPassword(password));
@ -78,11 +84,11 @@ public class FileController {
return result;
}
@RequestMapping(value = "/upload-avatar",method = RequestMethod.POST)
@RequestMapping(value = "/upload-avatar", method = RequestMethod.POST)
@RequiresAuthentication
@ResponseBody
@Transactional
public CommonResult uploadAvatar(@RequestParam("image") MultipartFile image, HttpServletRequest request){
public CommonResult uploadAvatar(@RequestParam("image") MultipartFile image, HttpServletRequest request) {
if (image == null) {
return CommonResult.errorResponse("上传的头像图片文件不能为空!");
}
@ -100,13 +106,13 @@ public class FileController {
savePathFile.mkdir();
}
//通过UUID生成唯一文件名
String filename = IdUtil.simpleUUID()+"."+suffix;
String filename = IdUtil.simpleUUID() + "." + suffix;
try {
//将文件保存指定目录
image.transferTo(new File(Constants.File.USER_AVATAR_FOLDER.getPath()+ filename));
image.transferTo(new File(Constants.File.USER_AVATAR_FOLDER.getPath() + filename));
} catch (Exception e) {
log.error("头像文件上传异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("服务器异常:头像上传失败!",CommonResult.STATUS_ERROR);
return CommonResult.errorResponse("服务器异常:头像上传失败!", CommonResult.STATUS_ERROR);
}
// 获取当前登录用户
@ -115,18 +121,18 @@ public class FileController {
// 将当前用户所属的file表中avatar类型的实体的delete设置为1
fileService.updateFileToDeleteByUidAndType(userRolesVo.getUid(),"avatar");
fileService.updateFileToDeleteByUidAndType(userRolesVo.getUid(), "avatar");
//更新user_info里面的avatar
UpdateWrapper<UserInfo> userInfoUpdateWrapper = new UpdateWrapper<>();
userInfoUpdateWrapper.set("avatar", Constants.File.USER_FILE_HOST.getPath()+Constants.File.USER_AVATAR_API.getPath()+filename)
userInfoUpdateWrapper.set("avatar", Constants.File.USER_FILE_HOST.getPath() + Constants.File.USER_AVATAR_API.getPath() + filename)
.eq("uuid", userRolesVo.getUid());
userInfoService.update(userInfoUpdateWrapper);
// 插入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() + filename)
.setSuffix(suffix)
.setType("avatar")
.setUid(userRolesVo.getUid());
@ -135,7 +141,7 @@ public class FileController {
.put("uid", userRolesVo.getUid())
.put("username", userRolesVo.getUsername())
.put("nickname", userRolesVo.getNickname())
.put("avatar", Constants.File.USER_FILE_HOST.getPath()+Constants.File.USER_AVATAR_API.getPath()+filename)
.put("avatar", Constants.File.USER_FILE_HOST.getPath() + Constants.File.USER_AVATAR_API.getPath() + filename)
.put("email", userRolesVo.getEmail())
.put("number", userRolesVo.getNumber())
.put("school", userRolesVo.getSchool())
@ -149,4 +155,116 @@ public class FileController {
.map(), "设置新头像成功!");
}
@PostMapping("/upload-testcase-zip")
@ResponseBody
public CommonResult uploadTestcaseZip(@RequestParam("file") MultipartFile file) {
//获取文件后缀
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
if (!"zip".toUpperCase().contains(suffix.toUpperCase())) {
return CommonResult.errorResponse("请上传zip格式的测试数据压缩包");
}
String fileDirId = IdUtil.simpleUUID();
String fileDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + fileDirId;
String filePath = fileDir + File.separator + file.getOriginalFilename();
// 文件夹不存在就新建
FileUtil.mkdir(fileDir);
try {
file.transferTo(new File(filePath));
} catch (IOException e) {
log.error("评测数据文件上传异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("服务器异常:评测数据上传失败!", CommonResult.STATUS_ERROR);
}
// 将压缩包压缩到指定文件夹
ZipUtil.unzip(filePath, fileDir);
// 删除zip文件
FileUtil.del(filePath);
// 检查文件是否存在
File testCaseFileList = new File(fileDir);
File[] files = testCaseFileList.listFiles();
if (files == null || files.length == 0) {
FileUtil.del(fileDir);
return CommonResult.errorResponse("评测数据压缩包里文件不能为空!");
}
HashMap<String, String> inputData = new HashMap<>();
HashMap<String, String> outputData = new HashMap<>();
// 遍历读取与检查是否in和out文件一一对应否则报错
for (File tmp : files) {
String tmpPreName = tmp.getName().substring(0, tmp.getName().lastIndexOf("."));
if (tmp.getName().endsWith("in")) {
inputData.put(tmpPreName, tmp.getName());
} else if (tmp.getName().endsWith("out")) {
outputData.put(tmpPreName, tmp.getName());
}
}
// 进行数据对应检查,同时生成返回数据
List<HashMap<String, String>> problemCaseList = new LinkedList<>();
for (String key : inputData.keySet()) {
// 若有名字不对应直接返回失败
if (outputData.getOrDefault(key, null) == null) {
FileUtil.del(fileDir);
return CommonResult.errorResponse("请检查数据压缩包里面的in和out文件是否一一对应");
}
HashMap<String, String> testcaseMap = new HashMap<>();
testcaseMap.put("input", inputData.get(key));
testcaseMap.put("output", outputData.get(key));
problemCaseList.add(testcaseMap);
}
return CommonResult.successResponse(MapUtil.builder()
.put("fileList", problemCaseList)
.put("fileListDir", fileDir)
.map()
, "上传测试数据成功!");
}
@GetMapping("/download-testcase")
@RequiresAuthentication
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
public void downloadTestcase(@RequestParam("pid") Long pid, HttpServletResponse response) throws IOException {
QueryWrapper<ProblemCase> problemCaseQueryWrapper = new QueryWrapper<>();
problemCaseQueryWrapper.eq("pid", pid);
List<ProblemCase> problemCaseList = problemCaseService.list(problemCaseQueryWrapper);
Assert.notEmpty(problemCaseList, "对不起,该题目的评测数据为空!");
String workDir = Constants.File.TESTCASE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + IdUtil.simpleUUID();
FileUtil.mkdir(workDir);
// 写入本地
for (int i = 0; i < problemCaseList.size(); i++) {
String filePreName = workDir + File.separator + (i + 1);
String inputName = filePreName + ".in";
String outputName = filePreName + ".out";
FileWriter infileWriter = new FileWriter(inputName);
infileWriter.write(problemCaseList.get(i).getInput());
FileWriter outfileWriter = new FileWriter(outputName);
outfileWriter.write(problemCaseList.get(i).getOutput());
}
String fileName = "problem_" + pid + "_testcase_"+System.currentTimeMillis()+".zip";
// 将对应文件夹的文件压缩成zip
ZipUtil.zip(workDir, Constants.File.TESTCASE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
// 将zip变成io流返回给前端
FileReader fileReader = new FileReader(Constants.File.TESTCASE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
BufferedInputStream bins = new BufferedInputStream(fileReader.getInputStream());//放到缓冲流里面
OutputStream outs = response.getOutputStream();//获取文件输出IO流
BufferedOutputStream bouts = new BufferedOutputStream(outs);
response.setContentType("application/x-download");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
int bytesRead = 0;
byte[] buffer = new byte[8192];
//开始向网络传输文件流
while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
bouts.write(buffer, 0, bytesRead);
}
bouts.flush();
bins.close();
outs.close();
bouts.close();
// 清空临时文件
FileUtil.del(workDir);
FileUtil.del(Constants.File.TESTCASE_DOWNLOAD_TMP_FOLDER.getPath() + File.separator + fileName);
}
}

View File

@ -6,25 +6,24 @@ import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
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.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.dao.JudgeCaseMapper;
import top.hcode.hoj.dao.JudgeMapper;
import top.hcode.hoj.judge.JudgeDispatcher;
import top.hcode.hoj.pojo.dto.SubmitIdListDto;
import top.hcode.hoj.pojo.dto.ToJudgeDto;
import top.hcode.hoj.pojo.entity.*;
import top.hcode.hoj.pojo.vo.JudgeVo;
import top.hcode.hoj.pojo.vo.UserRolesVo;
import top.hcode.hoj.service.JudgeService;
import top.hcode.hoj.service.ProblemService;
import top.hcode.hoj.service.ToJudgeService;
import top.hcode.hoj.service.impl.*;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.IpUtils;
@ -42,6 +41,7 @@ import java.util.List;
*/
@RestController
@RequestMapping("/api")
@RefreshScope
public class JudgeController {
@ -57,9 +57,6 @@ public class JudgeController {
@Autowired
private ProblemService problemService;
@Autowired
private ToJudgeService toJudgeService;
@Autowired
private UserRecordServiceImpl userRecordService;
@ -72,6 +69,12 @@ public class JudgeController {
@Autowired
private ContestRecordServiceImpl contestRecordService;
@Value("${hoj.judge.token}")
private String judgeToken;
@Autowired
private JudgeDispatcher judgeDispatcher;
// @Autowired
// private RestTemplate restTemplate;
@ -131,7 +134,14 @@ public class JudgeController {
// 是否为超级管理员或者该比赛的创建者则为比赛管理者
boolean root = SecurityUtils.getSubject().hasRole("root");
if (!root && !contest.getUid().equals(userRolesVo.getUid())) {
return CommonResult.errorResponse("比赛未开始,不可提交!");
if (contest.getStatus().intValue() == Constants.Contest.STATUS_SCHEDULED.getCode()) {
return CommonResult.errorResponse("比赛未开始,不可提交!");
}
// 需要检查是否有权限在当前比赛进行提交
CommonResult checkResult = contestService.checkJudgeAuth(judgeDto.getProtectContestPwd(), contest, userRolesVo.getUid());
if (checkResult != null) {
return checkResult;
}
}
// 查询获取对应的pid和cpid
@ -194,8 +204,8 @@ public class JudgeController {
if (result != 1 || !updateContestRecord || !updateUserRecord) {
return CommonResult.errorResponse("数据提交失败", CommonResult.STATUS_ERROR);
}
// 异步调用判题机
toJudgeService.submitProblemJudge(judge);
// 将提交加入任务队列
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getPid() == 0);
return CommonResult.successResponse(judge, "数据提交成功!");
}
@ -355,12 +365,12 @@ public class JudgeController {
// 如果该题不支持开放测试点结果查看
if (!problem.getOpenCaseResult()) {
return CommonResult.successResponse(null,"对不起,该题测试样例详情不支持开放!");
return CommonResult.successResponse(null, "对不起,该题测试样例详情不支持开放!");
}
// 若是比赛题目只支持IO查看测试点情况ACM强制禁止查看
if (problem.getAuth() == 3 && problem.getType().intValue() == Constants.Contest.TYPE_ACM.getCode()) {
return CommonResult.successResponse(null,"对不起,该题测试样例详情不能查看!");
return CommonResult.successResponse(null, "对不起,该题测试样例详情不能查看!");
}
@ -370,7 +380,7 @@ public class JudgeController {
List<JudgeCase> judgeCaseList = judgeCaseService.list(wrapper);
if (judgeCaseList.size() == 0) { // 未查询到一条数据
return CommonResult.successResponse(null,"暂无数据");
return CommonResult.successResponse(null, "暂无数据");
} else {
return CommonResult.successResponse(judgeCaseList, "获取成功");
}

View File

@ -3,7 +3,7 @@
<mapper namespace="top.hcode.hoj.dao.JudgeMapper">
<select id="getCommonJudgeList" resultType="top.hcode.hoj.pojo.vo.JudgeVo" useCache="false">
select j.uid,j.submit_id,j.submit_time,j.uid,j.username,j.uid,j.pid,j.status,j.share,
j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.ip,j.judger
j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.judger
from judge j
<where>
j.cid = 0 AND j.cpid = 0
@ -30,7 +30,7 @@
<select id="getContestJudgeList" resultType="top.hcode.hoj.pojo.vo.JudgeVo" useCache="false">
select j.uid,j.submit_id,j.submit_time,j.uid,j.username,j.uid,cp.display_id,
j.status,j.share,j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.ip,j.judger
j.status,j.share,j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.judger
from judge j,contest_problem cp
<where>
j.pid=cp.pid

View File

@ -0,0 +1,82 @@
package top.hcode.hoj.judge;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: Himit_ZH
* @Date: 2021/2/4 16:44
* @Description: 任务调度的自定义负载均衡策略
*/
@Slf4j
public class JudgeChooseRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties discoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
// 获取配置文件中所配置的集群名称
String clusterName = discoveryProperties.getClusterName();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
// 需要请求的微服务名称
String serviceId = loadBalancer.getName();
// 获取该微服务的所有健康实例
List<Instance> instances = getInstances(serviceId);
System.out.println(instances);
// 进行匹配筛选的实例列表
List<Instance> metadataMatchInstances;
// 过滤出小于或等于规定最大并发判题任务数的服务实例
metadataMatchInstances = instances.stream()
.filter(instance ->
Integer.parseInt(instance.getMetadata().getOrDefault("currentTaskNum", "0"))<=
Integer.parseInt(instance.getMetadata().getOrDefault("maxTaskNum","4"))
).collect(Collectors.toList());
// 如果为空闲判题服务器的数量为空则该判题请求重新进入等待队列
if (CollectionUtils.isEmpty(metadataMatchInstances)) {
return null;
}
// 基于随机权重的负载均衡算法选取其中一个实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(metadataMatchInstances);
return new NacosServer(instance);
}
private List<Instance> getInstances(String serviceId) {
// 获取服务发现的相关API
NamingService namingService = discoveryProperties.namingServiceInstance();
try {
// 获取该微服务的所有健康实例
return namingService.selectInstances(serviceId, true);
} catch (NacosException e) {
log.error("获取微服务健康实例发生异常--------->{}", e);
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,77 @@
package top.hcode.hoj.judge;
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.stereotype.Component;
import top.hcode.hoj.dao.JudgeMapper;
import top.hcode.hoj.pojo.entity.ContestRecord;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.pojo.entity.ProblemCount;
import top.hcode.hoj.pojo.entity.ToJudge;
import top.hcode.hoj.service.impl.ContestRecordServiceImpl;
import top.hcode.hoj.service.impl.JudgeServiceImpl;
import top.hcode.hoj.service.impl.ProblemCountServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
/**
* @Author: Himit_ZH
* @Date: 2021/2/5 16:44
* @Description:
*/
@Component
@Slf4j
public class JudgeDispatcher {
@Autowired
private RedisUtils redisUtils;
@Autowired
private JudgeMapper judgeMapper;
@Autowired
private ProblemCountServiceImpl problemCountService;
@Autowired
private ContestRecordServiceImpl contestRecordService;
public void sendTask(Long submitId, Long pid, String token, Boolean isContest) {
JSONObject task = new JSONObject();
task.set("submitId", submitId);
task.set("pid", pid);
task.set("token", token);
task.set("isContest", isContest);
try {
redisUtils.sendMessage(Constants.Judge.STATUS_JUDGE_WAITING.getName(), JSONUtil.toJsonStr(task));
} catch (Exception e) {
log.error("调用redis消息发布异常,此次判题任务判为系统错误--------------->{}", e.getMessage());
UpdateWrapper<Judge> judgeUpdateWrapper = new UpdateWrapper<>();
judgeUpdateWrapper.eq("submit_id", submitId)
.set("error_message", "The something has gone wrong with the data Backup server. Please report this to administrator.")
.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
judgeMapper.update(null, judgeUpdateWrapper);
// 更新problem_count
if (!isContest) {
QueryWrapper<ProblemCount> problemCountQueryWrapper = new QueryWrapper<ProblemCount>();
problemCountQueryWrapper.eq("pid", pid);
ProblemCount problemCount = problemCountService.getOne(problemCountQueryWrapper);
problemCount.setSe(problemCount.getSe() + 1);
problemCountService.saveOrUpdate(problemCount);
} else {
// 更新contest_record表
UpdateWrapper<ContestRecord> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("submit_id", submitId) // submit_id一定只有一个
.set("first_blood", false)
.set("status", Constants.Contest.RECORD_NOT_AC_NOT_PENALTY.getCode());
contestRecordService.update(updateWrapper);
}
}
}
}

View File

@ -0,0 +1,53 @@
package top.hcode.hoj.judge;
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.stereotype.Component;
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;
/**
* @Author: Himit_ZH
* @Date: 2021/2/5 16:43
* @Description:
*/
@Component
public class JudgeReceiver implements MessageListener {
@Autowired
private ToJudgeService toJudgeService;
@Autowired
private JudgeServiceImpl judgeService;
@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);
// StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// //接收的topic
// String channel = stringRedisSerializer.deserialize(message.getChannel());
// 进行反序列化获取信息
String taskJson = (String)jackson2JsonRedisSerializer.deserialize(message.getBody());
JSONObject task = JSONUtil.parseObj(taskJson);
Long submitId = task.getLong("submitId");
String token = task.getStr("token");
Judge judge = judgeService.getById(submitId);
// 调用判题服务
toJudgeService.submitProblemJudge(new ToJudge().setJudge(judge).setToken(token));
}
}

View File

@ -0,0 +1,50 @@
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.utils.Constants;
/**
* @Author: Himit_ZH
* @Date: 2021/2/5 16:50
* @Description:
*/
@Configuration
@AutoConfigureAfter({JudgeReceiver.class})
public class SubscriberConfig {
/**
* 消息监听适配器注入接受消息方法输入方法名字 反射方法
*
* @param receiver
* @return
*/
@Bean
public MessageListenerAdapter getMessageListenerAdapter(JudgeReceiver receiver) {
return new MessageListenerAdapter(receiver); //当没有继承MessageListener时需要写方法名字
}
/**
* 创建消息监听容器
*
* @param redisConnectionFactory
* @param messageListenerAdapter
* @return
*/
@Bean
public RedisMessageListenerContainer getRedisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory, MessageListenerAdapter messageListenerAdapter) {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
// 添加频道名字
redisMessageListenerContainer.addMessageListener(messageListenerAdapter, new PatternTopic(Constants.Judge.STATUS_JUDGE_WAITING.getName()));
return redisMessageListenerContainer;
}
}

View File

@ -21,6 +21,10 @@ public class ProblemDto {
private List<ProblemCase> samples;
private Boolean isUploadTestCase;
private String uploadTestcaseDir;
private List<Language> languages;
private List<Tag> tags;

View File

@ -27,4 +27,6 @@ public class ToJudgeDto {
@NotBlank(message = "提交的比赛id所属不能为空若并非比赛提交请设置为0")
private Long cid;
private String protectContestPwd;
}

View File

@ -48,6 +48,9 @@ public class ConfigVo {
@Value("${hoj.jwt.checkRefreshExpire}")
private String checkRefreshExpire;
@Value("${hoj.judge.token}")
private String judgeToken;
// 邮箱配置
@Value("${hoj.mail.username}")
private String emailUsername;

View File

@ -2,6 +2,7 @@ package top.hcode.hoj.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.dto.ToJudgeDto;
import top.hcode.hoj.pojo.vo.ContestVo;
import top.hcode.hoj.pojo.entity.Contest;
import com.baomidou.mybatisplus.extension.service.IService;
@ -9,14 +10,18 @@ import top.hcode.hoj.pojo.vo.UserRolesVo;
/**
* <p>
* 服务类
* 服务类
* </p>
*
* @author Himit_ZH
* @since 2020-10-23
*/
public interface ContestService extends IService<Contest> {
Page<ContestVo> getContestList(Integer limit, Integer currentPage,Integer type,Integer status,String keyword);
Page<ContestVo> getContestList(Integer limit, Integer currentPage, Integer type, Integer status, String keyword);
ContestVo getContestInfoById(long cid);
CommonResult checkContestAuth(Contest contest, UserRolesVo userRolesVo,Boolean isRoot);
CommonResult checkContestAuth(Contest contest, UserRolesVo userRolesVo, Boolean isRoot);
CommonResult checkJudgeAuth(String protectContestPwd, Contest contest, String uid);
}

View File

@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import top.hcode.hoj.pojo.entity.Judge;
import com.baomidou.mybatisplus.extension.service.IService;
import top.hcode.hoj.pojo.entity.ToJudge;
import top.hcode.hoj.pojo.vo.JudgeVo;
import java.util.List;
@ -30,5 +31,6 @@ public interface JudgeService extends IService<Judge> {
String uid, Boolean beforeContestSubmit);
void rejudgeContestProblem(List<Judge> judgeList);
void rejudgeContestProblem(List<Judge> judgeList, String judgeToken);
}

View File

@ -3,6 +3,8 @@ package top.hcode.hoj.service;
public interface ScheduleService {
void deleteAvatar();
void deleteTestCase();
void getOjContestsList();
void getCodeforcesRating();

View File

@ -4,25 +4,23 @@ package top.hcode.hoj.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import top.hcode.hoj.common.exception.CloudHandler;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.config.RibbonConfig;
import top.hcode.hoj.pojo.entity.CompileSpj;
import top.hcode.hoj.pojo.entity.ToJudge;
@FeignClient(value = "hoj-judge-server",fallback = CloudHandler.class) //需要的判题微服务名
//需要的判题微服务名
@FeignClient(value = "hoj-judge-server", fallback = CloudHandler.class, configuration = RibbonConfig.class)
@Component
@Async
public interface ToJudgeService {
@PostMapping(value = "/judge")
public CommonResult submitProblemJudge(@RequestBody Judge judge);
@GetMapping("/init-test-case")
public CommonResult initTestCase(@RequestParam("pid")Long pid, @RequestParam("isSpj")Boolean isSpj);
public CommonResult submitProblemJudge(@RequestBody ToJudge toJudge);
@PostMapping(value = "/compile-spj")
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj);
}

View File

@ -1,8 +1,11 @@
package top.hcode.hoj.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.client.utils.JSONUtils;
import com.sun.management.OperatingSystemMXBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
@ -25,7 +28,7 @@ import java.util.List;
/**
* @Author: Himit_ZH
* @Date: 2020/12/3 11:03
* @Description:
* @Description: 动态修改网站配置获取后台服务状态及判题服务器的状态
*/
@Service
public class ConfigServiceImpl implements ConfigService {
@ -42,32 +45,46 @@ public class ConfigServiceImpl implements ConfigService {
@Autowired
private DiscoveryClient discoveryClient;
private static final String NACOS_URL = "http://129.204.177.72:8848";
private static final String DATA_ID = "hoj-data-backup-dev.yml";
private static final String GROUP = "DEFAULT_GROUP";
private static final String TYPE = "yaml";
private static OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
@Value("${service-url.name}")
private String judgeServiceName;
@Value("${spring.application.name}")
private String currentServiceName;
@Value("${spring.cloud.nacos.url}")
private String NACOS_URL;
@Value("${spring.cloud.nacos.config.data-id}")
private String DATA_ID;
@Value("${spring.cloud.nacos.config.group}")
private String GROUP;
@Value("${spring.cloud.nacos.config.type}")
private String TYPE;
private OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
@Override
public JSONObject getServiceInfo() {
JSONObject result = new JSONObject();
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("hoj-data-backup");
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(currentServiceName);
// 获取nacos中心配置所在的机器环境
String response = restTemplate.getForObject(NACOS_URL+"/nacos/v1/ns/operator/metrics", String.class);
String response = restTemplate.getForObject(NACOS_URL + "/nacos/v1/ns/operator/metrics", String.class);
JSONObject jsonObject = JSONObject.parseObject(response);
// 获取当前数据后台所在机器环境
int cores = Runtime.getRuntime().availableProcessors(); // 当前机器的cpu核数
double cpuLoad = osmxb.getSystemCpuLoad();
String percentCpuLoad = String.format("%.2f", cpuLoad * 100)+"%"; // 当前服务所在机器cpu使用率
String percentCpuLoad = String.format("%.2f", cpuLoad * 100) + "%"; // 当前服务所在机器cpu使用率
double totalVirtualMemory = osmxb.getTotalPhysicalMemorySize(); // 当前服务所在机器总内存
double freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize(); // 当前服务所在机器空闲内存
double value = freePhysicalMemorySize/totalVirtualMemory;
String percentMemoryLoad = String.format("%.2f", (1-value)*100)+"%"; // 当前服务所在机器内存使用率
double value = freePhysicalMemorySize / totalVirtualMemory;
String percentMemoryLoad = String.format("%.2f", (1 - value) * 100) + "%"; // 当前服务所在机器内存使用率
result.put("nacos", jsonObject);
result.put("backupCores", cores);
@ -80,17 +97,16 @@ public class ConfigServiceImpl implements ConfigService {
@Override
public List<JSONObject> getJudgeServiceInfo() {
List<JSONObject> serviceInfoList = new LinkedList<>();
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("hoj-judge-server");
for (ServiceInstance serviceInstance:serviceInstances){
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(judgeServiceName);
for (ServiceInstance serviceInstance : serviceInstances) {
String result = restTemplate.getForObject(serviceInstance.getUri() + "/get-sys-config", String.class);
JSONObject jsonObject = JSONObject.parseObject(result);
jsonObject.put("service",serviceInstance);
jsonObject.put("service", serviceInstance);
serviceInfoList.add(jsonObject);
}
return serviceInfoList;
}
@Override
public boolean setEmailConfig(HashMap<String, Object> params) {
if (!StringUtils.isEmpty(params.get("emailHost"))) {
@ -175,7 +191,7 @@ public class ConfigServiceImpl implements ConfigService {
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
String result = restTemplate.postForObject(NACOS_URL + "/nacos/v1/cs/configs", request, String.class);
if (result.equals("true")) {
if (result != null && result.equals("true")) {
return true;
} else {
return false;

View File

@ -3,7 +3,9 @@ package top.hcode.hoj.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.dto.ToJudgeDto;
import top.hcode.hoj.pojo.entity.ContestRegister;
import top.hcode.hoj.pojo.vo.ContestVo;
import top.hcode.hoj.pojo.entity.Contest;
@ -80,4 +82,26 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
}
return null;
}
@Override
public CommonResult checkJudgeAuth(String protectContestPwd, Contest contest, String uid) {
if (contest.getAuth().intValue() == Constants.Contest.AUTH_PRIVATE.getCode() ||
contest.getAuth().intValue() == Constants.Contest.AUTH_PROTECT.getCode()) {
QueryWrapper<ContestRegister> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("cid", contest.getId()).eq("uid", uid);
ContestRegister register = contestRegisterService.getOne(queryWrapper, false);
// 如果还没注册
if (register == null) {
// 如果提交附带密码不为空且跟当前比赛的密码相等则进行注册并且此次提交可以提交,同时注册到数据库
if (!StringUtils.isEmpty(protectContestPwd) && contest.getPwd().equals(protectContestPwd)) {
contestRegisterService.saveOrUpdate(new ContestRegister().setUid(uid).setCid(contest.getId()));
return null;
} else {
return CommonResult.errorResponse("对不起,提交失败!请您先成功注册该比赛!", CommonResult.STATUS_ACCESS_DENIED);
}
}
}
return null;
}
}

View File

@ -4,13 +4,13 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import top.hcode.hoj.judge.JudgeDispatcher;
import top.hcode.hoj.pojo.entity.Judge;
import top.hcode.hoj.dao.JudgeMapper;
import top.hcode.hoj.pojo.vo.JudgeVo;
import top.hcode.hoj.service.JudgeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.hcode.hoj.service.ToJudgeService;
import java.util.List;
@ -29,7 +29,7 @@ public class JudgeServiceImpl extends ServiceImpl<JudgeMapper, Judge> implements
private JudgeMapper judgeMapper;
@Autowired
private ToJudgeService toJudgeService;
private JudgeDispatcher judgeDispatcher;
@Override
public IPage<JudgeVo> getCommonJudgeList(Integer limit, Integer currentPage, Long pid, Integer status, String username,
@ -48,11 +48,14 @@ public class JudgeServiceImpl extends ServiceImpl<JudgeMapper, Judge> implements
return judgeMapper.getContestJudgeList(page, displayId, cid, status, username, uid, beforeContestSubmit);
}
@Override
@Async
public void rejudgeContestProblem(List<Judge> judgeList) {
public void rejudgeContestProblem(List<Judge> judgeList, String judgeToken) {
for (Judge judge : judgeList) {
toJudgeService.submitProblemJudge(judge);
// 进入重判队列等待调用判题服务
judgeDispatcher.sendTask(judge.getSubmitId(), judge.getPid(), judgeToken, judge.getPid() == 0);
}
}
}

View File

@ -1,10 +1,14 @@
package top.hcode.hoj.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import top.hcode.hoj.dao.ProblemCaseMapper;
import top.hcode.hoj.pojo.dto.ProblemDto;
import top.hcode.hoj.pojo.entity.*;
import top.hcode.hoj.pojo.vo.ProblemVo;
@ -13,7 +17,9 @@ import top.hcode.hoj.service.ProblemService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import top.hcode.hoj.service.ToJudgeService;
import top.hcode.hoj.utils.Constants;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -36,6 +42,9 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
@Autowired
private ProblemCaseServiceImpl problemCaseService;
@Autowired
private ProblemCaseMapper problemCaseMapper;
@Autowired
private ProblemLanguageServiceImpl problemLanguageService;
@ -48,8 +57,6 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
@Autowired
private ProblemCountServiceImpl problemCountService;
@Autowired
private ToJudgeService toJudgeService;
@Override
public Page<ProblemVo> getProblemList(int limit, int currentPage, Long pid, String title, Integer difficulty, Long tid) {
@ -63,12 +70,8 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
@Override
@Transactional
public boolean adminUpdateProblem(ProblemDto problemDto) {
// 更新problem表
Problem problem = problemDto.getProblem();
if (!StringUtils.isEmpty(problem.getSpjLanguage())) { // 如果是特判题目规格化特判语言 SPJ-C SPJ-C++
problem.setSpjLanguage("SPJ-" + problem.getSpjLanguage());
}
boolean problemUpdateResult = problemMapper.updateById(problem) == 1;
// 后面许多表的更新或删除需要用到题目id
long pid = problemDto.getProblem().getId();
@ -82,6 +85,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
Map<Long, Integer> mapOldPT = new HashMap<>();
Map<Long, Integer> mapOldPL = new HashMap<>();
List<Long> needDeleteProblemCases = new LinkedList<>();
HashMap<Long, ProblemCase> oldProblemMap = new HashMap<>();
// 登记一下原有的tag的id
oldProblemTags.stream().forEach(problemTag -> {
@ -94,6 +98,7 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
// 登记一下原有的case的id
oldProblemCases.stream().forEach(problemCase -> {
needDeleteProblemCases.add(problemCase.getId());
oldProblemMap.put(problemCase.getId(), problemCase);
});
@ -167,39 +172,114 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
addLanguagesToProblemResult = problemLanguageService.saveOrUpdateBatch(problemLanguageList);
}
/**
* 处理problem_case表的增加与删除
*/
// 可能需要更新或新增加的case列表
List<ProblemCase> problemCaseList = new LinkedList<>();
// 遍历上传的case列表如果
for (ProblemCase problemCase : problemDto.getSamples()) {
if (problemCase.getId() != null) { // 已存在的case
needDeleteProblemCases.remove(problemCase.getId());
problemCaseList.add(problemCase);
} else {
problemCaseList.add(problemCase);
boolean checkProblemCase = true;
// 如果是选择上传测试文件的则需要遍历对应文件夹读取数据写入数据库,先前的题目数据一并清空
if (problemDto.getIsUploadTestCase()) {
// 如果是选择了上传测试文件的模式但是数据为空那就是修改题目但是没修改测试数据需要判断
if (problemDto.getSamples().size() > 0) {
int sumScore = 0;
String testcaseDir = problemDto.getUploadTestcaseDir();
// 此时ProblemCase里面的input和output存的是文件名字拼接上文件路径即可访问
for (ProblemCase problemCase : problemDto.getSamples()) {
FileReader infileReader = new FileReader(testcaseDir + File.separator + problemCase.getInput());
String input = infileReader.readString();
FileReader outfileReader = new FileReader(testcaseDir + File.separator + problemCase.getOutput());
String output = outfileReader.readString();
Assert.notBlank(input, problemCase.getInput() + "的评测数据为空!修改题目失败!");
Assert.notBlank(output, problemCase.getInput() + "的评测数据为空!修改题目失败!");
// 如果不为空则正常将数据写入
problemCase.setPid(pid);
problemCase.setInput(input);
problemCase.setOutput(output);
if (problemCase.getScore() != null) {
sumScore += problemCase.getScore();
}
}
// 设置oi总分数根据每个测试点的加和
if (problem.getType().intValue() == Constants.Contest.TYPE_OI.getCode()) {
problem.setIoScore(sumScore);
}
// 将原先题目的评测数据清空重新插入新的
QueryWrapper<ProblemCase> problemCaseQueryWrapper = new QueryWrapper<>();
problemCaseQueryWrapper.eq("pid", pid);
boolean remove = problemCaseService.remove(problemCaseQueryWrapper);
boolean add = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
// 设置新的测试样例版本号
problem.setCaseVersion(String.valueOf(System.currentTimeMillis()));
checkProblemCase = remove && add;
}
} else {
int sumScore = 0;
// 新增加的case列表
List<ProblemCase> newProblemCaseList = new LinkedList<>();
// 需要修改的case列表
List<ProblemCase> needUpdateProblemCaseList = new LinkedList<>();
// 遍历上传的case列表如果还存在则从需要删除的测试样例列表移除该id
for (ProblemCase problemCase : problemDto.getSamples()) {
if (problemCase.getId() != null) { // 已存在的case
needDeleteProblemCases.remove(problemCase.getId());
// 跟原先的数据做对比如果变动 则加入需要修改的case列表
ProblemCase oldProblemCase = oldProblemMap.get(problemCase.getId());
if (!oldProblemCase.getInput().equals(problemCase.getInput()) ||
!oldProblemCase.getOutput().equals(problemCase.getOutput())) {
needUpdateProblemCaseList.add(problemCase);
} else if (problem.getType().intValue() == Constants.Contest.TYPE_OI.getCode()) {
if (oldProblemCase.getScore().intValue() != problemCase.getScore()) {
needUpdateProblemCaseList.add(problemCase);
}
}
} else {
newProblemCaseList.add(problemCase);
}
if (problemCase.getScore() != null) {
sumScore += problemCase.getScore();
}
}
// 设置oi总分数根据每个测试点的加和
if (problem.getType().intValue() == Constants.Contest.TYPE_OI.getCode()) {
problem.setIoScore(sumScore);
}
// 执行批量删除操作
boolean deleteCasesFromProblemResult = true;
if (needDeleteProblemCases.size() > 0) {
deleteCasesFromProblemResult = problemCaseService.removeByIds(needDeleteProblemCases);
}
// 执行批量添加操作
boolean addCasesToProblemResult = true;
if (newProblemCaseList.size() > 0) {
addCasesToProblemResult = problemCaseService.saveBatch(newProblemCaseList);
}
// 执行批量修改操作
boolean updateCasesToProblemResult = true;
if (needUpdateProblemCaseList.size() > 0) {
updateCasesToProblemResult = problemCaseService.saveOrUpdateBatch(needUpdateProblemCaseList);
}
checkProblemCase = addCasesToProblemResult && deleteCasesFromProblemResult && updateCasesToProblemResult;
// 只要有新添加修改删除都需要更新版本号
if (needDeleteProblemCases.size() > 0 || newProblemCaseList.size() > 0 || needUpdateProblemCaseList.size() > 0) {
problem.setCaseVersion(String.valueOf(System.currentTimeMillis()));
}
}
// 执行批量删除操作
boolean deleteCasesFromProblemResult = true;
if (needDeleteProblemCases.size() > 0) {
deleteCasesFromProblemResult = problemCaseService.removeByIds(needDeleteProblemCases);
}
// 执行批量添加或更新操作
boolean addCasesToProblemResult = true;
if (problemCaseList.size() > 0) {
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemCaseList);
}
// 更新problem表
boolean problemUpdateResult = problemMapper.updateById(problem) == 1;
// 评测数据有被修改则需要对判题机本地的数据进行重新初始化
toJudgeService.initTestCase(problemDto.getProblem().getId(),
!StringUtils.isEmpty(problemDto.getProblem().getSpjCode()));
if (problemUpdateResult && deleteCasesFromProblemResult && deleteLanguagesFromProblemResult && deleteTagsFromProblemResult
&& addCasesToProblemResult && addLanguagesToProblemResult && addTagsToProblemResult) {
if (problemUpdateResult && checkProblemCase && deleteLanguagesFromProblemResult && deleteTagsFromProblemResult
&& addLanguagesToProblemResult && addTagsToProblemResult) {
// 修改数据库成功后如果有进行文件上传操作则进行删除
if (problemDto.getIsUploadTestCase() && problemDto.getSamples().size() > 0) {
FileUtil.del(problemDto.getUploadTestcaseDir());
}
return true;
} else {
return false;
@ -211,11 +291,10 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
public boolean adminAddProblem(ProblemDto problemDto) {
Problem problem = problemDto.getProblem();
if (!StringUtils.isEmpty(problem.getSpjLanguage())) { // 如果是特判题目规格化特判语言 SPJ-C SPJ-C++
problem.setSpjLanguage("SPJ-" + problem.getSpjLanguage());
}
// 设置测试样例的版本号
problem.setCaseVersion(String.valueOf(System.currentTimeMillis()));
boolean addProblemResult = problemMapper.insert(problem) == 1;
long pid = problemDto.getProblem().getId();
long pid = problem.getId();
// 为新的题目添加对应的language
List<ProblemLanguage> problemLanguageList = new LinkedList<>();
for (Language language : problemDto.getLanguages()) {
@ -223,10 +302,44 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
}
boolean addLangToProblemResult = problemLanguageService.saveOrUpdateBatch(problemLanguageList);
boolean addCasesToProblemResult = false;
// 为新的题目添加对应的case
problemDto.getSamples().forEach(problemCase -> problemCase.setPid(pid)); // 设置好新题目的pid
boolean addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
if (problemDto.getIsUploadTestCase()) { // 如果是选择上传测试文件的则需要遍历对应文件夹读取数据写入数据库
int sumScore = 0;
String testcaseDir = problemDto.getUploadTestcaseDir();
// 此时ProblemCase里面的input和output存的是文件名字拼接上文件路径即可访问
for (ProblemCase problemCase : problemDto.getSamples()) {
FileReader infileReader = new FileReader(testcaseDir + File.separator + problemCase.getInput());
String input = infileReader.readString();
FileReader outfileReader = new FileReader(testcaseDir + File.separator + problemCase.getOutput());
String output = outfileReader.readString();
Assert.notBlank(input, problemCase.getInput() + "的评测数据为空!新建题目失败!");
Assert.notBlank(output, problemCase.getInput() + "的评测数据为空!新建题目失败!");
// 如果不为空则正常将数据写入
problemCase.setPid(pid);
problemCase.setInput(input);
problemCase.setOutput(output);
if (problemCase.getScore() != null) {
sumScore += problemCase.getScore();
}
}
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
// 设置oi总分数根据每个测试点的加和
if (problem.getType().intValue() == Constants.Contest.TYPE_OI.getCode()) {
problem.setIoScore(sumScore);
}
} else {
// oi题目需要求取平均值给每个测试点初始oi的score值默认总分100分
if (problem.getType().intValue() == Constants.Contest.TYPE_OI.getCode()) {
problem.setIoScore(100);
final int averScore = 100 / problemDto.getSamples().size();
problemDto.getSamples().forEach(problemCase -> problemCase.setPid(pid).setScore(averScore)); // 设置好新题目的pid及分数
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
} else {
problemDto.getSamples().forEach(problemCase -> problemCase.setPid(pid)); // 设置好新题目的pid
addCasesToProblemResult = problemCaseService.saveOrUpdateBatch(problemDto.getSamples());
}
}
// 为新的题目添加对应的tag可能tag是原表已有也可能是新的所以需要判断
List<ProblemTag> problemTagList = new LinkedList<>();
@ -241,8 +354,13 @@ public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem> impl
// 为新的题目初始化problem_count表
boolean initProblemCountResult = problemCountService.save(new ProblemCount().setPid(pid));
if (addProblemResult && addCasesToProblemResult && addLangToProblemResult
&& addTagsToProblemResult && initProblemCountResult) {
// 修改数据库成功后如果有进行文件上传操作则进行删除
if (problemDto.getIsUploadTestCase()) {
FileUtil.del(problemDto.getUploadTestcaseDir());
}
return true;
} else {
return false;

View File

@ -2,6 +2,7 @@ package top.hcode.hoj.service.impl;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
@ -125,6 +126,24 @@ public class ScheduleServiceImpl implements ScheduleService {
}
/**
* @MethodName deleteTestCase
* @Params * @param null
* @Description 每天3点定时删除指定文件夹的上传测试数据
* @Return
* @Since 2021/2/7
*/
@Scheduled(cron = "0 0 3 * * *")
// @Scheduled(cron = "0/5 * * * * *")
@Override
public void deleteTestCase() {
boolean result = FileUtil.del(Constants.File.TESTCASE_BASE_FOLDER.getPath());
if (!result){
log.error("每日定时任务异常------------------------>{}","清除本地的题目测试数据失败!");
}
}
/**
* 每两小时获取其他OJ的比赛列表并保存在redis里
* 保存格式

View File

@ -53,4 +53,6 @@ public class UserRecordServiceImpl extends ServiceImpl<UserRecordMapper, UserRec
public UserHomeVo getUserHomeInfo(String uid) {
return userRecordMapper.getUserHomeInfo(uid);
}
}

View File

@ -93,7 +93,7 @@ public class JwtFilter extends AuthenticatingFilter {
* @return
*/
private void refreshToken(HttpServletRequest request,HttpServletResponse response,String userId) throws IOException {
boolean lock = redisUtils.getLock(TOKEN_LOCK + userId, 60);// 获取锁60s
boolean lock = redisUtils.getLock(TOKEN_LOCK + userId, 20);// 获取锁20s
if (lock) {
String newToken = jwtUtils.generateToken(userId);
response.setHeader("Access-Control-Allow-Credentials", "true");

View File

@ -1,6 +1,7 @@
package top.hcode.hoj.utils;
import com.sun.org.apache.xpath.internal.objects.XNull;
import org.apache.poi.ss.formula.functions.T;
/**
* @Author: Himit_ZH
@ -18,16 +19,17 @@ public class Constants {
STATUS_COMPILE_ERROR(-2, "Compile Error", "ce"),
STATUS_WRONG_ANSWER(-1, "Wrong Answer", "wa"),
STATUS_ACCEPTED(0, "Accepted", "ac"),
STATUS_LIMIT_EXCEEDED(1, "Time Limit Exceeded", "tle"),
STATUS_TIME_LIMIT_EXCEEDED(1, "Time Limit Exceeded", "tle"),
STATUS_MEMORY_LIMIT_EXCEEDED(2, "Memory Limit Exceeded", "mle"),
STATUS_RUNTIME_ERROR(3, "Runtime Error", "re"),
STATUS_SYSTEM_ERROR(4, "System Error", "se"),
STATUS_PENDING(5, "Pending", null),
STATUS_COMPILING(6, "Compiling",null),
STATUS_COMPILING(6, "Compiling", null),
STATUS_JUDGING(7, "Judging", null),
STATUS_PARTIAL_ACCEPTED(8, "Partial Accepted", "pa"),
STATUS_SUBMITTING(9, "Submitting", null),
STATUS_NULL(10, "No Status",null);
STATUS_NULL(10, "No Status", null),
STATUS_JUDGE_WAITING(-100, "Waiting Queue", null);
private Judge(Integer status, String name, String columnName) {
this.status = status;
@ -69,17 +71,17 @@ public class Constants {
TYPE_ACM(0, "ACM"),
TYPE_OI(1, "OI"),
STATUS_SCHEDULED(-1,"Scheduled"),
STATUS_RUNNING(0,"Running"),
STATUS_ENDED(1,"Ended"),
STATUS_SCHEDULED(-1, "Scheduled"),
STATUS_RUNNING(0, "Running"),
STATUS_ENDED(1, "Ended"),
AUTH_PUBLIC(0,"Public"),
AUTH_PRIVATE(1,"Private"),
AUTH_PROTECT(2,"Protect"),
AUTH_PUBLIC(0, "Public"),
AUTH_PRIVATE(1, "Private"),
AUTH_PROTECT(2, "Protect"),
RECORD_NOT_AC_PENALTY(-1,"未AC通过算罚时"),
RECORD_NOT_AC_NOT_PENALTY(0,"未AC通过不罚时"),
RECORD_AC(1,"AC通过");
RECORD_NOT_AC_PENALTY(-1, "未AC通过算罚时"),
RECORD_NOT_AC_NOT_PENALTY(0, "未AC通过不罚时"),
RECORD_AC(1, "AC通过");
private final Integer code;
private final String name;
@ -130,7 +132,11 @@ public class Constants {
USER_FILE_HOST("http://localhost:9010"),
USER_AVATAR_FOLDER("D:\\avatar\\"),
USER_AVATAR_API("/public/img/");
USER_AVATAR_API("/public/img/"),
TESTCASE_BASE_FOLDER("D:\\zip\\"),
TESTCASE_DOWNLOAD_TMP_FOLDER("D:\\zip\\download");
private final String path;

View File

@ -30,12 +30,12 @@ public final class RedisUtils {
boolean result = false;
try {
boolean isExist = hasKey(lockName);
if(!isExist){
set(lockName,0);
expire(lockName,expireTime<=0? 3600:expireTime);
if (!isExist) {
set(lockName, 0);
expire(lockName, expireTime <= 0 ? 3600 : expireTime);
}
long reVal = incr(lockName,1);
if(1==reVal){
long reVal = incr(lockName, 1);
if (1 == reVal) {
//获取到锁
result = true;
}
@ -44,13 +44,15 @@ public final class RedisUtils {
}
return result;
}
public boolean releaseLock(String lockName) {
return expire(lockName, 30);
return expire(lockName, 10);
}
/**
* 指定缓存失效时间
*
* @param key
* @param time 时间()
*/
@ -68,6 +70,7 @@ public final class RedisUtils {
/**
* 根据key 获取过期时间
*
* @param key 不能为null
* @return 时间() 返回0代表为永久有效
*/
@ -77,6 +80,7 @@ public final class RedisUtils {
/**
* 判断key是否存在
*
* @param key
* @return true 存在 false不存在
*/
@ -92,6 +96,7 @@ public final class RedisUtils {
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
@ -110,6 +115,7 @@ public final class RedisUtils {
/**
* 普通缓存获取
*
* @param key
* @return
*/
@ -119,6 +125,7 @@ public final class RedisUtils {
/**
* 普通缓存放入
*
* @param key
* @param value
* @return true成功 false失败
@ -137,6 +144,7 @@ public final class RedisUtils {
/**
* 普通缓存放入并设置时间
*
* @param key
* @param value
* @param time 时间() time要大于0 如果time小于等于0 将设置无限期
@ -160,6 +168,7 @@ public final class RedisUtils {
/**
* 递增
*
* @param key
* @param delta 要增加几(大于0)
*/
@ -173,6 +182,7 @@ public final class RedisUtils {
/**
* 递减
*
* @param key
* @param delta 要减少几(小于0)
*/
@ -188,6 +198,7 @@ public final class RedisUtils {
/**
* HashGet
*
* @param key 不能为null
* @param item 不能为null
*/
@ -197,6 +208,7 @@ public final class RedisUtils {
/**
* 获取hashKey对应的所有键值
*
* @param key
* @return 对应的多个键值
*/
@ -206,6 +218,7 @@ public final class RedisUtils {
/**
* HashSet
*
* @param key
* @param map 对应多个键值
*/
@ -222,6 +235,7 @@ public final class RedisUtils {
/**
* HashSet 并设置时间
*
* @param key
* @param map 对应多个键值
* @param time 时间()
@ -333,6 +347,7 @@ public final class RedisUtils {
/**
* 根据key获取Set中的所有值
*
* @param key
*/
public Set<Object> sGet(String key) {
@ -484,12 +499,12 @@ public final class RedisUtils {
/**
* 将list放入缓存
* 将list右边放入缓存
*
* @param key
* @param value
*/
public boolean lSet(String key, Object value) {
public boolean lrPush(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
@ -499,14 +514,30 @@ public final class RedisUtils {
}
}
/**
* 将list左边放入缓存
*
* @param key
* @param value
*/
public boolean llPush(String key, Object value) {
try {
redisTemplate.opsForList().leftPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* 将list右边放入缓存
*
* @param key
* @param value
* @param time 时间()
*/
public boolean lSet(String key, Object value, long time) {
public boolean lrPush(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
@ -527,7 +558,7 @@ public final class RedisUtils {
* @param value
* @return
*/
public boolean lSet(String key, List<Object> value) {
public boolean lrPush(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
@ -539,6 +570,27 @@ public final class RedisUtils {
}
public boolean llPush(String key, List<Object> value) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public Object lrPop(String key) {
try {
return redisTemplate.opsForList().rightPop(key, 5, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
@ -547,7 +599,7 @@ public final class RedisUtils {
* @param time 时间()
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
public boolean llPush(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
@ -598,4 +650,17 @@ public final class RedisUtils {
}
/**
* 给特定频道发布消息
*
* @param channel 管道主题
* @param message 消息
* @return
*/
public void sendMessage(String channel, Object message) throws Exception {
redisTemplate.convertAndSend(channel, message);
}
}

View File

@ -5,7 +5,8 @@ spring:
---
# 消费者将要去访问的微服务名称注册成功进入nacos的微服务提供者
service-url:
hoj-judge-server: http://hoj-judge-server
hoj-judge-server: http://hoj-judge-server # 服务访问base_url
name: hoj-judge-server # 服务名
#激活Sentinel对Feign的支持
@ -28,12 +29,13 @@ spring:
redis:
host: ${hoj.redis.host}
port: ${hoj.redis.port}
timeout: 6000
timeout: 60000
jedis:
pool:
min-idle: 10
max-idle: 20
max-active: 50
min-idle: 10 #连接池中的最小空闲连接
max-idle: 50 #连接池中的最大空闲连接
max-active: 100 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
datasource:
username: ${hoj.db.username}
password: ${hoj.db.password}
@ -72,4 +74,19 @@ mybatis-plus:
shiro-redis:
enabled: true
redis-manager:
host: ${hoj.redis.host}:${hoj.redis.port}
host: ${hoj.redis.host}:${hoj.redis.port}
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,俩端连接所用的时间 单位是秒
ReadTimeout: 6000
# 指的是建立连接后从服务器读取到可用资源的时间
ConnectTimeout: 5000
logging:
level:
com:
alibaba:
nacos:
client:
naming: error

View File

@ -13,7 +13,11 @@ spring:
server-addr: 129.204.177.72:8848 #Nacos 作为配置中心地址
file-extension: yml #指定yaml格式的配置
group: DEFAULT_GROUP # 指定分组
data-id: hoj-data-backup-dev.yml
type: yaml
#namespace:命名空间ID 默认为public
url: http://129.204.177.72:8848
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.naces.config.file-extension}
# hoj-data-backup-dev.yml

View File

@ -5,13 +5,20 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mysql.cj.protocol.PacketReceivedTimeHolder;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.dao.*;
import top.hcode.hoj.pojo.entity.Contest;
@ -33,6 +40,7 @@ import top.hcode.hoj.utils.JsoupUtils;
import top.hcode.hoj.utils.RedisUtils;
import java.io.IOException;
import java.net.*;
import java.text.MessageFormat;
import java.util.*;
@ -99,10 +107,23 @@ public class DataBackupApplicationTests {
System.out.println(commandList);
}
@Autowired
private NacosDiscoveryProperties discoveryProperties;
@Test
public void Test2() {
RoleAuthsVo roleAuths = roleAuthMapper.getRoleAuths(1000L);
System.out.println(roleAuths);
String clusterName = discoveryProperties.getClusterName();
System.out.println(clusterName);
// 获取该微服务的所有健康实例
// 获取服务发现的相关API
NamingService namingService = discoveryProperties.namingServiceInstance();
try {
// 获取该微服务的所有健康实例
List<Instance> instances = namingService.selectInstances("hoj-judge-server", true);
System.out.println(instances);
} catch (NacosException e) {
e.printStackTrace();
}
}
@Test
@ -111,17 +132,43 @@ public class DataBackupApplicationTests {
System.out.println(serviceIp);
}
@Autowired
private RestTemplate restTemplate;
@Test
public void Test4() {
//// int todayJudgeNum = judgeMapper.getTodayJudgeNum();
// List<ContestVo> withinNext14DaysContests = contestMapper.getWithinNext14DaysContests();
// System.out.println(withinNext14DaysContests);
String result = restTemplate.getForObject("http://129.204.177.72:8848/nacos/v1/ns/instance?ip=192.168.226.1&port=8010&serviceName=hoj-judge-server&metadata=%7B%22maxTaskNum%22%3A8%2C%22currentTaskNum%22%3A1%7D", String.class);
System.out.println(result);
}
@Test
public void Test5() throws IOException {
Enumeration<NetworkInterface> ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
}
String siteLocalAddress = null;
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
String hostAddress = addr.getHostAddress();
if (addr instanceof Inet4Address) {
if (addr.isSiteLocalAddress()) {
siteLocalAddress = hostAddress;
} else {
break;
}
}
}
}
System.out.println(siteLocalAddress == null ? "" : siteLocalAddress);
}
@Autowired

View File

@ -5,7 +5,8 @@ spring:
---
# 消费者将要去访问的微服务名称注册成功进入nacos的微服务提供者
service-url:
hoj-judge-server: http://hoj-judge-server
hoj-judge-server: http://hoj-judge-server # 服务访问base_url
name: hoj-judge-server # 服务名
#激活Sentinel对Feign的支持
@ -28,12 +29,13 @@ spring:
redis:
host: ${hoj.redis.host}
port: ${hoj.redis.port}
timeout: 6000
timeout: 60000
jedis:
pool:
min-idle: 10
max-idle: 20
max-active: 50
min-idle: 10 #连接池中的最小空闲连接
max-idle: 50 #连接池中的最大空闲连接
max-active: 100 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
datasource:
username: ${hoj.db.username}
password: ${hoj.db.password}
@ -72,4 +74,19 @@ mybatis-plus:
shiro-redis:
enabled: true
redis-manager:
host: ${hoj.redis.host}:${hoj.redis.port}
host: ${hoj.redis.host}:${hoj.redis.port}
ribbon:
# 指的是建立连接所用的时间,适用于网络状况正常的情况下,俩端连接所用的时间 单位是秒
ReadTimeout: 6000
# 指的是建立连接后从服务器读取到可用资源的时间
ConnectTimeout: 5000
logging:
level:
com:
alibaba:
nacos:
client:
naming: error

View File

@ -13,7 +13,11 @@ spring:
server-addr: 129.204.177.72:8848 #Nacos 作为配置中心地址
file-extension: yml #指定yaml格式的配置
group: DEFAULT_GROUP # 指定分组
data-id: hoj-data-backup-dev.yml
type: yaml
#namespace:命名空间ID 默认为public
url: http://129.204.177.72:8848
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.naces.config.file-extension}
# hoj-data-backup-dev.yml

View File

@ -3,7 +3,7 @@
<mapper namespace="top.hcode.hoj.dao.JudgeMapper">
<select id="getCommonJudgeList" resultType="top.hcode.hoj.pojo.vo.JudgeVo" useCache="false">
select j.uid,j.submit_id,j.submit_time,j.uid,j.username,j.uid,j.pid,j.status,j.share,
j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.ip,j.judger
j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.judger
from judge j
<where>
j.cid = 0 AND j.cpid = 0
@ -30,7 +30,7 @@
<select id="getContestJudgeList" resultType="top.hcode.hoj.pojo.vo.JudgeVo" useCache="false">
select j.uid,j.submit_id,j.submit_time,j.uid,j.username,j.uid,cp.display_id,
j.status,j.share,j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.ip,j.judger
j.status,j.share,j.time,j.memory,j.length,j.language,j.cid,j.cpid,j.judger
from judge j,contest_problem cp
<where>
j.pid=cp.pid

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