完善后台管理员权限控制,恢复CF的vjudge判题
This commit is contained in:
parent
64513d694c
commit
564492f5b2
|
@ -84,6 +84,7 @@ docker ps # 查看当前运行的容器状态
|
|||
| 2021-06-07 | 修正特殊判题,增加前台i18n | Himit_ZH |
|
||||
| 2021-06-08 | 添加后台i18n,路由懒加载 | Himit_ZH |
|
||||
| 2021-06-12 | 完善比赛赛制,具体请看在线文档 | Himit_ZH |
|
||||
| 2021-06-14 | 完善后台管理员权限控制,恢复CF的vjudge判题 | Himit_ZH |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -88,10 +88,10 @@ public class AdminAccountController {
|
|||
|
||||
@GetMapping("/logout")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root","admin"},logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
|
||||
public CommonResult logout() {
|
||||
SecurityUtils.getSubject().logout();
|
||||
return CommonResult.successResponse(null, "登出成功!");
|
||||
return CommonResult.successResponse(null, "success");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
|
@ -17,9 +18,12 @@ import top.hcode.hoj.pojo.dto.AnnouncementDto;
|
|||
import top.hcode.hoj.pojo.dto.ProblemDto;
|
||||
import top.hcode.hoj.pojo.entity.*;
|
||||
import top.hcode.hoj.pojo.vo.AnnouncementVo;
|
||||
import top.hcode.hoj.pojo.vo.UserRolesVo;
|
||||
import top.hcode.hoj.service.impl.*;
|
||||
import top.hcode.hoj.utils.Constants;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.validation.Valid;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
@ -53,7 +57,7 @@ public class AdminContestController {
|
|||
|
||||
@GetMapping("/get-contest-list")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getContestList(@RequestParam(value = "limit", required = false) Integer limit,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
|
@ -82,9 +86,22 @@ public class AdminContestController {
|
|||
|
||||
@GetMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
public CommonResult getContest(@Valid @RequestParam("cid") Long cid) {
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getContest(@Valid @RequestParam("cid") Long cid, HttpServletRequest request) {
|
||||
|
||||
// 获取本场比赛的状态
|
||||
Contest contest = contestService.getById(cid);
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
// 只有超级管理员和比赛拥有者才能操作
|
||||
if (!isRoot && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if (contest != null) { // 查询成功
|
||||
return CommonResult.successResponse(contest, "查询成功!");
|
||||
} else {
|
||||
|
@ -94,7 +111,7 @@ public class AdminContestController {
|
|||
|
||||
@DeleteMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = "root")
|
||||
@RequiresRoles(value = "root") // 只有超级管理员能删除比赛
|
||||
public CommonResult deleteContest(@Valid @RequestParam("cid") Long cid) {
|
||||
boolean result = contestService.removeById(cid);
|
||||
/*
|
||||
|
@ -109,7 +126,7 @@ public class AdminContestController {
|
|||
|
||||
@PostMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult addContest(@RequestBody Contest contest) {
|
||||
boolean result = contestService.save(contest);
|
||||
if (result) { // 添加成功
|
||||
|
@ -121,8 +138,19 @@ public class AdminContestController {
|
|||
|
||||
@PutMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
public CommonResult updateContest(@RequestBody Contest contest) {
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult updateContest(@RequestBody Contest contest, HttpServletRequest request) {
|
||||
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
// 只有超级管理员和比赛拥有者才能操作
|
||||
if (!isRoot && !userRolesVo.getUid().equals(contest.getUid())) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = contestService.saveOrUpdate(contest);
|
||||
if (result) { // 添加成功
|
||||
return CommonResult.successResponse(null, "修改成功!");
|
||||
|
@ -133,9 +161,21 @@ public class AdminContestController {
|
|||
|
||||
@PutMapping("/change-contest-visible")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult changeContestVisible(@RequestParam(value = "cid", required = true) Long cid,
|
||||
@RequestParam(value = "visible", required = true) Boolean visible) {
|
||||
@RequestParam(value = "uid", required = true) String uid,
|
||||
@RequestParam(value = "visible", required = true) Boolean visible,
|
||||
HttpServletRequest request) {
|
||||
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
// 只有超级管理员和比赛拥有者才能操作
|
||||
if (!isRoot && !userRolesVo.getUid().equals(uid)) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = contestService.saveOrUpdate(new Contest().setId(cid).setVisible(visible));
|
||||
if (result) { // 添加成功
|
||||
|
@ -151,7 +191,7 @@ public class AdminContestController {
|
|||
|
||||
@GetMapping("/get-problem-list")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
@Transactional
|
||||
public CommonResult getProblemList(@RequestParam(value = "limit", required = false) Integer limit,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
|
@ -210,7 +250,7 @@ public class AdminContestController {
|
|||
|
||||
@GetMapping("/problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getProblem(@Valid @RequestParam("pid") Long pid) {
|
||||
Problem problem = problemService.getById(pid);
|
||||
if (problem != null) { // 查询成功
|
||||
|
@ -222,12 +262,24 @@ public class AdminContestController {
|
|||
|
||||
@DeleteMapping("/problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
public CommonResult deleteProblem(@Valid @RequestParam("pid") Long pid) {
|
||||
boolean result = problemService.removeById(pid);
|
||||
/*
|
||||
problem的id为其他表的外键的表中的对应数据都会被一起删除!
|
||||
*/
|
||||
@RequiresRoles(value = {"root", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult deleteProblem(@RequestParam("pid") Long pid,
|
||||
@RequestParam(value = "cid", required = false) Long cid) {
|
||||
|
||||
boolean result = false;
|
||||
// 比赛id不为null,表示就是从比赛列表移除而已
|
||||
if (cid != null) {
|
||||
QueryWrapper<ContestProblem> contestProblemQueryWrapper = new QueryWrapper<>();
|
||||
contestProblemQueryWrapper.eq("cid", cid).eq("pid", pid);
|
||||
result = contestProblemService.remove(contestProblemQueryWrapper);
|
||||
} else {
|
||||
/*
|
||||
problem的id为其他表的外键的表中的对应数据都会被一起删除!
|
||||
*/
|
||||
result = problemService.removeById(pid);
|
||||
}
|
||||
|
||||
|
||||
if (result) { // 删除成功
|
||||
FileUtil.del(Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + pid);
|
||||
return CommonResult.successResponse(null, "删除成功!");
|
||||
|
@ -238,7 +290,7 @@ public class AdminContestController {
|
|||
|
||||
@PostMapping("/problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
@Transactional
|
||||
public CommonResult addProblem(@RequestBody ProblemDto problemDto) {
|
||||
|
||||
|
@ -248,7 +300,8 @@ public class AdminContestController {
|
|||
if (problem != null) {
|
||||
return CommonResult.errorResponse("该题目的Problem ID已存在,请更换!", CommonResult.STATUS_FAIL);
|
||||
}
|
||||
|
||||
// 设置为比赛题目
|
||||
problemDto.getProblem().setAuth(3);
|
||||
boolean result = problemService.adminAddProblem(problemDto);
|
||||
if (result) { // 添加成功
|
||||
// 顺便返回新的题目id,好下一步添加外键操作
|
||||
|
@ -261,9 +314,9 @@ public class AdminContestController {
|
|||
|
||||
@PutMapping("/problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
@Transactional
|
||||
public CommonResult updateProblem(@RequestBody ProblemDto problemDto) {
|
||||
public CommonResult updateProblem(@RequestBody ProblemDto problemDto, HttpServletRequest request) {
|
||||
|
||||
QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("problem_id", problemDto.getProblem().getProblemId().toUpperCase());
|
||||
|
@ -284,7 +337,7 @@ public class AdminContestController {
|
|||
|
||||
@GetMapping("/contest-problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getContestProblem(@RequestParam(value = "cid", required = true) Long cid,
|
||||
@RequestParam(value = "pid", required = true) Long pid) {
|
||||
QueryWrapper<ContestProblem> queryWrapper = new QueryWrapper<>();
|
||||
|
@ -299,7 +352,7 @@ public class AdminContestController {
|
|||
|
||||
@PutMapping("/contest-problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult setContestProblem(@RequestBody ContestProblem contestProblem) {
|
||||
boolean result = contestProblemService.saveOrUpdate(contestProblem);
|
||||
if (result) {
|
||||
|
@ -311,8 +364,18 @@ public class AdminContestController {
|
|||
|
||||
@PutMapping("/change-problem-auth")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "problem_admin", "admin"}, logical = Logical.OR)
|
||||
public CommonResult changeProblemAuth(@RequestBody Problem problem) {
|
||||
|
||||
// 普通管理员只能将题目变成隐藏题目和比赛题目
|
||||
boolean root = SecurityUtils.getSubject().hasRole("root");
|
||||
|
||||
boolean problemAdmin = SecurityUtils.getSubject().hasRole("problem_admin");
|
||||
|
||||
if (!problemAdmin && !root && problem.getAuth() == 1) {
|
||||
return CommonResult.errorResponse("修改失败!你无权限公开题目!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
boolean result = problemService.saveOrUpdate(problem);
|
||||
if (result) { // 更新成功
|
||||
return CommonResult.successResponse(null, "修改成功!");
|
||||
|
@ -323,7 +386,7 @@ public class AdminContestController {
|
|||
|
||||
@PostMapping("/add-problem-from-public")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult addProblemFromPublic(@RequestBody HashMap<String, String> params) {
|
||||
|
||||
String pidStr = params.get("pid");
|
||||
|
@ -344,11 +407,15 @@ public class AdminContestController {
|
|||
}
|
||||
|
||||
// 比赛中题目显示默认为原标题
|
||||
String displayName = problemService.getById(pid).getTitle();
|
||||
Problem problem = problemService.getById(pid);
|
||||
String displayName = problem.getTitle();
|
||||
|
||||
// 修改成比赛题目
|
||||
boolean updateProblem = problemService.saveOrUpdate(problem.setAuth(3));
|
||||
|
||||
boolean result = contestProblemService.saveOrUpdate(new ContestProblem()
|
||||
.setCid(cid).setPid(pid).setDisplayTitle(displayName).setDisplayId(displayId));
|
||||
if (result) { // 添加成功
|
||||
if (result && updateProblem) { // 添加成功
|
||||
return CommonResult.successResponse(null, "添加成功!");
|
||||
} else {
|
||||
return CommonResult.errorResponse("添加失败", CommonResult.STATUS_FAIL);
|
||||
|
@ -362,7 +429,7 @@ public class AdminContestController {
|
|||
|
||||
@GetMapping("/announcement")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getAnnouncementList(@RequestParam(value = "limit", required = false) Integer limit,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "cid", required = true) Long cid) {
|
||||
|
@ -379,7 +446,7 @@ public class AdminContestController {
|
|||
|
||||
@DeleteMapping("/announcement")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult deleteAnnouncement(@Valid @RequestParam("aid") Long aid) {
|
||||
boolean result = announcementService.removeById(aid);
|
||||
if (result) { // 删除成功
|
||||
|
@ -391,7 +458,7 @@ public class AdminContestController {
|
|||
|
||||
@PostMapping("/announcement")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
@Transactional
|
||||
public CommonResult addAnnouncement(@RequestBody AnnouncementDto announcementDto) {
|
||||
boolean result1 = announcementService.save(announcementDto.getAnnouncement());
|
||||
|
@ -406,7 +473,7 @@ public class AdminContestController {
|
|||
|
||||
@PutMapping("/announcement")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult updateAnnouncement(@RequestBody AnnouncementDto announcementDto) {
|
||||
boolean result = announcementService.saveOrUpdate(announcementDto.getAnnouncement());
|
||||
if (result) { // 更新成功
|
||||
|
|
|
@ -32,7 +32,7 @@ public class AdminDiscussionController {
|
|||
private DiscussionServiceImpl discussionService;
|
||||
|
||||
@PutMapping("/discussion")
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
@RequiresAuthentication
|
||||
public CommonResult updateDiscussion(@RequestBody Discussion discussion) {
|
||||
boolean isOk = discussionService.updateById(discussion);
|
||||
|
@ -45,7 +45,7 @@ public class AdminDiscussionController {
|
|||
|
||||
|
||||
@DeleteMapping("/discussion")
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
@RequiresAuthentication
|
||||
public CommonResult removeDiscussion(@RequestBody List<Integer> didList) {
|
||||
boolean isOk = discussionService.removeByIds(didList);
|
||||
|
@ -57,7 +57,7 @@ public class AdminDiscussionController {
|
|||
}
|
||||
|
||||
@GetMapping("/discussion-report")
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
@RequiresAuthentication
|
||||
public CommonResult getDiscussionReport(@RequestParam(value = "limit", defaultValue = "10") Integer limit,
|
||||
@RequestParam(value = "currentPage", defaultValue = "1") Integer currentPage) {
|
||||
|
@ -70,7 +70,7 @@ public class AdminDiscussionController {
|
|||
}
|
||||
|
||||
@PutMapping("/discussion-report")
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
@RequiresAuthentication
|
||||
public CommonResult updateDiscussionReport(@RequestBody DiscussionReport discussionReport) {
|
||||
boolean isOk = discussionReportService.updateById(discussionReport);
|
||||
|
|
|
@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
|
@ -54,7 +55,7 @@ public class AdminProblemController {
|
|||
|
||||
@GetMapping("/get-problem-list")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getProblemList(@RequestParam(value = "limit", required = false) Integer limit,
|
||||
@RequestParam(value = "currentPage", required = false) Integer currentPage,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
|
@ -90,10 +91,21 @@ public class AdminProblemController {
|
|||
|
||||
@GetMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
public CommonResult getProblem(@Valid @RequestParam("pid") Long pid) {
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getProblem(@Valid @RequestParam("pid") Long pid,HttpServletRequest request) {
|
||||
|
||||
Problem problem = problemService.getById(pid);
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 是否为超级管理员
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
boolean problem_admin = SecurityUtils.getSubject().hasRole("problem_admin");
|
||||
// 只有超级管理员、题目管理员和题目拥有者才能操作
|
||||
if (!isRoot && !problem_admin && !userRolesVo.getUsername().equals(problem.getAuthor())) {
|
||||
return CommonResult.errorResponse("对不起,你无权限操作!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
if (problem != null) { // 查询成功
|
||||
return CommonResult.successResponse(problem, "查询成功!");
|
||||
} else {
|
||||
|
@ -103,7 +115,7 @@ public class AdminProblemController {
|
|||
|
||||
@DeleteMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult deleteProblem(@Valid @RequestParam("pid") Long pid) {
|
||||
boolean result = problemService.removeById(pid);
|
||||
/*
|
||||
|
@ -119,7 +131,7 @@ public class AdminProblemController {
|
|||
|
||||
@PostMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult addProblem(@RequestBody ProblemDto problemDto) {
|
||||
|
||||
QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();
|
||||
|
@ -140,9 +152,9 @@ public class AdminProblemController {
|
|||
|
||||
@PutMapping("")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
@Transactional
|
||||
public CommonResult updateProblem(@RequestBody ProblemDto problemDto) {
|
||||
public CommonResult updateProblem(@RequestBody ProblemDto problemDto,HttpServletRequest request) {
|
||||
|
||||
QueryWrapper<Problem> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("problem_id", problemDto.getProblem().getProblemId().toUpperCase());
|
||||
|
@ -163,7 +175,7 @@ public class AdminProblemController {
|
|||
|
||||
@GetMapping("/get-problem-cases")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult getProblemCases(@Valid @RequestParam("pid") Long pid) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("pid", pid);
|
||||
|
@ -178,7 +190,7 @@ public class AdminProblemController {
|
|||
|
||||
@PostMapping("/compile-spj")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult compileSpj(@RequestBody CompileSpj compileSpj) {
|
||||
|
||||
if (StringUtils.isEmpty(compileSpj.getSpjSrc()) ||
|
||||
|
@ -192,7 +204,7 @@ public class AdminProblemController {
|
|||
|
||||
@GetMapping("/import-remote-oj-problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
@Transactional
|
||||
public CommonResult importRemoteOJProblem(@RequestParam("name") String name,
|
||||
@RequestParam("problemId") String problemId,
|
||||
|
|
|
@ -8,9 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.crazycake.shiro.RedisSessionDAO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.hcode.hoj.common.result.CommonResult;
|
||||
|
|
|
@ -46,14 +46,14 @@ public class ConfigController {
|
|||
* @Since 2020/12/3
|
||||
*/
|
||||
|
||||
@RequiresRoles(value = {"root","admin"},logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
|
||||
@RequestMapping("/get-service-info")
|
||||
public CommonResult getServiceInfo(){
|
||||
|
||||
return CommonResult.successResponse(configService.getServiceInfo());
|
||||
}
|
||||
|
||||
@RequiresRoles(value = {"root","admin"},logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
|
||||
@RequestMapping("/get-judge-service-info")
|
||||
public CommonResult getJudgeServiceInfo(){
|
||||
return CommonResult.successResponse(configService.getJudgeServiceInfo());
|
||||
|
|
|
@ -45,7 +45,7 @@ public class DashboardController {
|
|||
|
||||
@PostMapping("/get-sessions")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root","admin"},logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
|
||||
public CommonResult getSessions(HttpServletRequest request){
|
||||
|
||||
// 需要获取一下该token对应用户的数据
|
||||
|
@ -63,7 +63,7 @@ public class DashboardController {
|
|||
|
||||
@GetMapping("/get-dashboard-info")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root","admin"},logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
|
||||
public CommonResult getDashboardInfo(){
|
||||
int userNum = userInfoDao.count();
|
||||
int recentContestNum = contestDao.getWithinNext14DaysContests().size();
|
||||
|
|
|
@ -186,6 +186,7 @@ public class FileController {
|
|||
|
||||
@PostMapping("/upload-testcase-zip")
|
||||
@ResponseBody
|
||||
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult uploadTestcaseZip(@RequestParam("file") MultipartFile file) {
|
||||
//获取文件后缀
|
||||
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
|
||||
|
@ -256,7 +257,7 @@ public class FileController {
|
|||
|
||||
@GetMapping("/download-testcase")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "problem_admin"}, logical = Logical.OR)
|
||||
public void downloadTestcase(@RequestParam("pid") Long pid, HttpServletResponse response) {
|
||||
|
||||
String workDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + pid;
|
||||
|
@ -413,12 +414,35 @@ public class FileController {
|
|||
|
||||
@GetMapping("/download-contest-ac-submission")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public void downloadContestACSubmission(@RequestParam("cid") Long cid,
|
||||
@RequestParam(value = "excludeAdmin", defaultValue = "false") Boolean excludeAdmin,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
|
||||
Contest contest = contestService.getById(cid);
|
||||
|
||||
// 获取当前登录的用户
|
||||
HttpSession session = request.getSession();
|
||||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
|
||||
// 除非是root 其它管理员只能下载自己的比赛ac记录
|
||||
if (!userRolesVo.getUid().equals(contest.getUid())&&!isRoot){
|
||||
response.reset();
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("status", CommonResult.STATUS_FORBIDDEN);
|
||||
map.put("msg", "对不起,你无权限下载!");
|
||||
map.put("data", null);
|
||||
try {
|
||||
response.getWriter().println(JSONUtil.toJsonStr(map));
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isACM = contest.getType().intValue() == Constants.Contest.TYPE_ACM.getCode();
|
||||
|
||||
// cpid-->displayId
|
||||
|
@ -435,7 +459,8 @@ public class FileController {
|
|||
.eq(isACM, "status", Constants.Judge.STATUS_ACCEPTED.getStatus())
|
||||
.isNotNull(!isACM, "score") // OI模式取得分不为null的
|
||||
.between("submit_time", contest.getStartTime(), contest.getEndTime())
|
||||
.ne(excludeAdmin, "uid", contest.getUid())
|
||||
.ne(excludeAdmin, "uid", contest.getUid()) // 排除比赛创建者和root
|
||||
.ne(excludeAdmin, "username","root")
|
||||
.orderByDesc("submit_time");
|
||||
|
||||
List<Judge> judgeList = judgeService.list(judgeQueryWrapper);
|
||||
|
@ -620,7 +645,7 @@ public class FileController {
|
|||
@RequestMapping(value = "/upload-md-file", method = RequestMethod.POST)
|
||||
@RequiresAuthentication
|
||||
@ResponseBody
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {"root", "admin","problem_admin"}, logical = Logical.OR)
|
||||
public CommonResult uploadMd(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
|
||||
if (file == null) {
|
||||
return CommonResult.errorResponse("上传的文件不能为空!");
|
||||
|
@ -671,11 +696,11 @@ public class FileController {
|
|||
/**
|
||||
* @param file
|
||||
* @MethodName importProblem
|
||||
* @Description zip文件导入题目
|
||||
* @Description zip文件导入题目 仅超级管理员可操作
|
||||
* @Return
|
||||
* @Since 2021/5/27
|
||||
*/
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles("root")
|
||||
@RequiresAuthentication
|
||||
@ResponseBody
|
||||
@Transactional
|
||||
|
@ -827,11 +852,11 @@ public class FileController {
|
|||
/**
|
||||
* @param file
|
||||
* @MethodName importProblem
|
||||
* @Description zip文件导入题目
|
||||
* @Description zip文件导入题目 仅超级管理员可操作
|
||||
* @Return
|
||||
* @Since 2021/5/27
|
||||
*/
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles("root")
|
||||
@RequiresAuthentication
|
||||
@ResponseBody
|
||||
@Transactional
|
||||
|
@ -1026,13 +1051,13 @@ public class FileController {
|
|||
* @param pidList
|
||||
* @param response
|
||||
* @MethodName exportProblem
|
||||
* @Description 导出指定的题目包括测试数据生成zip
|
||||
* @Description 导出指定的题目包括测试数据生成zip 仅超级管理员可操作
|
||||
* @Return
|
||||
* @Since 2021/5/28
|
||||
*/
|
||||
@GetMapping("/export-problem")
|
||||
@RequiresAuthentication
|
||||
@RequiresRoles(value = {"root", "admin"}, logical = Logical.OR)
|
||||
@RequiresRoles("root")
|
||||
public void exportProblem(@RequestParam("pid") List<Long> pidList, HttpServletResponse response) {
|
||||
|
||||
QueryWrapper<Language> languageQueryWrapper = new QueryWrapper<>();
|
||||
|
|
|
@ -105,7 +105,8 @@ public class CommentController {
|
|||
|
||||
if (SecurityUtils.getSubject().hasRole("root")) {
|
||||
comment.setFromRole("root");
|
||||
} else if (SecurityUtils.getSubject().hasRole("admin")) {
|
||||
} else if (SecurityUtils.getSubject().hasRole("admin")
|
||||
|| SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
comment.setFromRole("admin");
|
||||
} else {
|
||||
comment.setFromRole("user");
|
||||
|
@ -142,7 +143,7 @@ public class CommentController {
|
|||
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
|
||||
// 如果不是评论本人 或者不是管理员 无权限删除该评论
|
||||
if (comment.getFromUid().equals(userRolesVo.getUid()) || SecurityUtils.getSubject().hasRole("root")
|
||||
|| SecurityUtils.getSubject().hasRole("admin")) {
|
||||
|| SecurityUtils.getSubject().hasRole("admin") || SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
|
||||
// 删除该数据
|
||||
boolean isDeleteComment = commentService.remove(new UpdateWrapper<Comment>().eq("id", comment.getId()));
|
||||
|
@ -233,7 +234,7 @@ public class CommentController {
|
|||
|
||||
if (SecurityUtils.getSubject().hasRole("root")) {
|
||||
reply.setFromRole("root");
|
||||
} else if (SecurityUtils.getSubject().hasRole("admin")) {
|
||||
} else if (SecurityUtils.getSubject().hasRole("admin") || SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
reply.setFromRole("admin");
|
||||
} else {
|
||||
reply.setFromRole("user");
|
||||
|
@ -259,7 +260,7 @@ public class CommentController {
|
|||
|
||||
// 如果不是评论本人 或者不是管理员 无权限删除该评论
|
||||
if (reply.getFromUid().equals(userRolesVo.getUid()) || SecurityUtils.getSubject().hasRole("root")
|
||||
|| SecurityUtils.getSubject().hasRole("admin")) {
|
||||
|| SecurityUtils.getSubject().hasRole("admin") || SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
|
||||
// 删除该数据
|
||||
boolean isOk = replyService.removeById(reply.getId());
|
||||
|
|
|
@ -149,7 +149,7 @@ public class DiscussionController {
|
|||
|
||||
if (SecurityUtils.getSubject().hasRole("root")) {
|
||||
discussion.setRole("root");
|
||||
} else if (SecurityUtils.getSubject().hasRole("admin")) {
|
||||
} else if (SecurityUtils.getSubject().hasRole("admin") || SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
discussion.setRole("admin");
|
||||
} else {
|
||||
// 如果不是管理员角色,一律重置为不置顶
|
||||
|
@ -187,7 +187,8 @@ public class DiscussionController {
|
|||
UpdateWrapper<Discussion> discussionUpdateWrapper = new UpdateWrapper<Discussion>().eq("id", did);
|
||||
// 如果不是是管理员,则需要附加当前用户的uid条件
|
||||
if (!SecurityUtils.getSubject().hasRole("root")
|
||||
&& !SecurityUtils.getSubject().hasRole("admin")) {
|
||||
&& !SecurityUtils.getSubject().hasRole("admin")
|
||||
&&!SecurityUtils.getSubject().hasRole("problem_admin")) {
|
||||
discussionUpdateWrapper.eq("uid", userRolesVo.getUid());
|
||||
}
|
||||
boolean isOk = discussionService.remove(discussionUpdateWrapper);
|
||||
|
|
|
@ -146,6 +146,9 @@ public class JudgeController {
|
|||
.setPid(contestProblem.getPid());
|
||||
|
||||
Problem problem = problemService.getById(contestProblem.getPid());
|
||||
if (problem.getAuth() == 2) {
|
||||
return CommonResult.errorResponse("错误!当前题目不可提交!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
judge.setDisplayPid(problem.getProblemId());
|
||||
|
||||
// 将新提交数据插入数据库
|
||||
|
@ -187,6 +190,11 @@ public class JudgeController {
|
|||
QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();
|
||||
problemQueryWrapper.eq("problem_id", judgeDto.getPid());
|
||||
Problem problem = problemService.getOne(problemQueryWrapper);
|
||||
|
||||
if (problem.getAuth() == 2) {
|
||||
return CommonResult.errorResponse("错误!当前题目不可提交!", CommonResult.STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
judge.setCpid(0L).setPid(problem.getId()).setDisplayPid(problem.getProblemId());
|
||||
|
||||
// 将新提交数据插入数据库
|
||||
|
@ -307,7 +315,8 @@ public class JudgeController {
|
|||
// 超级管理员与管理员有权限查看代码
|
||||
// 如果不是本人或者并未分享代码,则不可查看
|
||||
// 当此次提交代码不共享
|
||||
boolean admin = SecurityUtils.getSubject().hasRole("admin");// 是否为管理员
|
||||
boolean admin = SecurityUtils.getSubject().hasRole("admin")
|
||||
|| SecurityUtils.getSubject().hasRole("problem_admin");// 是否为管理员
|
||||
if (!judge.getShare() && !root && !admin) {
|
||||
if (userRolesVo != null) { // 当前是登陆状态
|
||||
// 需要判断是否为当前登陆用户自己的提交代码
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</choose>
|
||||
</where>
|
||||
) as ac
|
||||
from contest_problem cp
|
||||
where cp.cid = #{cid} order by cp.display_id asc
|
||||
from contest_problem cp,problem p
|
||||
where cp.cid = #{cid} and cp.pid=p.id and p.auth!=2 order by cp.display_id asc
|
||||
</select>
|
||||
</mapper>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package top.hcode.hoj.service;
|
||||
|
||||
import top.hcode.hoj.pojo.entity.ContestExplanation;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author Himit_ZH
|
||||
* @since 2020-10-23
|
||||
*/
|
||||
public interface ContestExplanationService extends IService<ContestExplanation> {
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package top.hcode.hoj.service;
|
||||
|
||||
import top.hcode.hoj.pojo.entity.ContestScore;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author Himit_ZH
|
||||
* @since 2020-10-23
|
||||
*/
|
||||
public interface ContestScoreService extends IService<ContestScore> {
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package top.hcode.hoj.service.impl;
|
||||
|
||||
import top.hcode.hoj.pojo.entity.ContestExplanation;
|
||||
import top.hcode.hoj.dao.ContestExplanationMapper;
|
||||
import top.hcode.hoj.service.ContestExplanationService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author Himit_ZH
|
||||
* @since 2020-10-23
|
||||
*/
|
||||
@Service
|
||||
public class ContestExplanationServiceImpl extends ServiceImpl<ContestExplanationMapper, ContestExplanation> implements ContestExplanationService {
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package top.hcode.hoj.service.impl;
|
||||
|
||||
import top.hcode.hoj.pojo.entity.ContestScore;
|
||||
import top.hcode.hoj.dao.ContestScoreMapper;
|
||||
import top.hcode.hoj.service.ContestScoreService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author Himit_ZH
|
||||
* @since 2020-10-23
|
||||
*/
|
||||
@Service
|
||||
public class ContestScoreServiceImpl extends ServiceImpl<ContestScoreMapper, ContestScore> implements ContestScoreService {
|
||||
|
||||
}
|
|
@ -31,7 +31,6 @@ import java.util.Properties;
|
|||
*/
|
||||
@Service
|
||||
@RefreshScope
|
||||
@Async
|
||||
@Slf4j(topic = "hoj")
|
||||
public class EmailServiceImpl implements EmailService {
|
||||
|
||||
|
@ -92,7 +91,7 @@ public class EmailServiceImpl implements EmailService {
|
|||
|
||||
/**
|
||||
* @MethodName isOk
|
||||
* @Params * @param null
|
||||
* @Params * @param null
|
||||
* @Description 验证当前邮箱系统是否已配置。
|
||||
* @Return
|
||||
* @Since 2021/6/12
|
||||
|
@ -111,6 +110,7 @@ public class EmailServiceImpl implements EmailService {
|
|||
* @Since 2021/1/14
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
public void sendCode(String email, String code) {
|
||||
DateTime expireTime = DateUtil.offsetMinute(new Date(), 10);
|
||||
JavaMailSenderImpl mailSender = getMailSender();
|
||||
|
@ -130,10 +130,8 @@ public class EmailServiceImpl implements EmailService {
|
|||
//利用模板引擎加载html文件进行渲染并生成对应的字符串
|
||||
String emailContent = templateEngine.process("emailTemplate_registerCode", context);
|
||||
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
|
||||
// 设置邮件标题
|
||||
mimeMessageHelper.setSubject("HOJ的注册邮件");
|
||||
mimeMessageHelper.setSubject(ojShortName.toUpperCase() + "的注册邮件");
|
||||
mimeMessageHelper.setText(emailContent, true);
|
||||
// 收件人
|
||||
mimeMessageHelper.setTo(email);
|
||||
|
@ -157,6 +155,7 @@ public class EmailServiceImpl implements EmailService {
|
|||
* @Since 2021/1/14
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
public void sendResetPassword(String username, String code, String email) {
|
||||
DateTime expireTime = DateUtil.offsetMinute(new Date(), 10);
|
||||
JavaMailSenderImpl mailSender = getMailSender();
|
||||
|
@ -177,7 +176,7 @@ public class EmailServiceImpl implements EmailService {
|
|||
//利用模板引擎加载html文件进行渲染并生成对应的字符串
|
||||
String emailContent = templateEngine.process("emailTemplate_resetPassword", context);
|
||||
|
||||
mimeMessageHelper.setSubject("HOJ的重置密码邮件");
|
||||
mimeMessageHelper.setSubject(ojShortName.toUpperCase() + "的重置密码邮件");
|
||||
|
||||
mimeMessageHelper.setText(emailContent, true);
|
||||
// 收件人
|
||||
|
@ -199,6 +198,7 @@ public class EmailServiceImpl implements EmailService {
|
|||
* @Since 2021/1/14
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
public void testEmail(String email) {
|
||||
JavaMailSenderImpl mailSender = getMailSender();
|
||||
MimeMessage mimeMessage = mailSender.createMimeMessage();
|
||||
|
@ -214,7 +214,7 @@ public class EmailServiceImpl implements EmailService {
|
|||
//利用模板引擎加载html文件进行渲染并生成对应的字符串
|
||||
String emailContent = templateEngine.process("emailTemplate_testEmail", context);
|
||||
|
||||
mimeMessageHelper.setSubject("HOJ的测试邮件");
|
||||
mimeMessageHelper.setSubject(ojShortName.toUpperCase() + "的测试邮件");
|
||||
|
||||
mimeMessageHelper.setText(emailContent, true);
|
||||
// 收件人
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="zh" xmlns:th=http://www.thymeleaf.org>
|
||||
<head>
|
||||
<meta charset=UTF-8>
|
||||
<title>用户注册验证码</title>
|
||||
<title>[[${OJ_SHORT_NAME}]]用户注册验证码</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background: white;
|
||||
|
@ -29,7 +29,7 @@
|
|||
padding: 5px 30px;
|
||||
margin: -25px auto 0 ;
|
||||
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.30) ">
|
||||
Dear New HOJer
|
||||
Dear New [[${OJ_SHORT_NAME}]]er
|
||||
</p>
|
||||
<br>
|
||||
<center>
|
||||
|
@ -38,7 +38,7 @@
|
|||
</h3>
|
||||
<p style="text-indent:2em;">
|
||||
您收到这封电子邮件是因为您 (也可能是某人冒充您的名义) 在<a style=" text-decoration: none;color: #1bc3fb "
|
||||
target="_blank" th:href="${OJ_URL}" rel="noopener"> HOJ </a>上进行注册。假如这不是您本人所申请,
|
||||
target="_blank" th:href="${OJ_URL}" rel="noopener"> [[${OJ_SHORT_NAME}]] </a>上进行注册。假如这不是您本人所申请,
|
||||
请不用理会这封电子邮件, 但是如果您持续收到这类的信件骚扰, 请您尽快联络管理员。
|
||||
</p>
|
||||
<div style="background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;text-align: center; ">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="zh" xmlns:th=http://www.thymeleaf.org>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>HOJ的重置密码邮件</title>
|
||||
<title>[[${OJ_SHORT_NAME}]]的重置密码邮件</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background: white;
|
||||
|
@ -37,7 +37,7 @@
|
|||
</h3>
|
||||
<p style="text-indent:2em;">
|
||||
您收到这封电子邮件是因为您 (也可能是某人冒充您的名义) 在<a style=" text-decoration: none;color: #1bc3fb "
|
||||
target="_blank" th:href="${OJ_URL}" rel="noopener"> HOJ </a>上进行密码重置操作。假如这不是您本人所申请, 请不用理会这封电子邮件, 但是如果您持续收到这类的信件骚扰, 请您尽快联络管理员。
|
||||
target="_blank" th:href="${OJ_URL}" rel="noopener"> [[${OJ_SHORT_NAME}]] </a>上进行密码重置操作。假如这不是您本人所申请, 请不用理会这封电子邮件, 但是如果您持续收到这类的信件骚扰, 请您尽快联络管理员。
|
||||
</p>
|
||||
<div style="background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;text-align: center; ">
|
||||
请点击下面的链接完成后续重置密码的流程:<br>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="zh" xmlns:th=http://www.thymeleaf.org>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>超级管理员测试邮箱可用性邮件</title>
|
||||
<title>[[${OJ_SHORT_NAME}]]超级管理员测试邮箱可用性邮件</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background: white;
|
||||
|
@ -38,7 +38,7 @@
|
|||
</h3>
|
||||
<p style="text-indent:2em;">
|
||||
您收到这封电子邮件是因为您在<a style=" text-decoration: none;color: #1bc3fb "
|
||||
target="_blank" th:href="${OJ_URL}" rel="noopener"> HOJ </a>上进行邮箱配置更新,然后进行邮箱可行性的测试。
|
||||
target="_blank" th:href="${OJ_URL}" rel="noopener"> [[${OJ_SHORT_NAME}]] </a>上进行邮箱配置更新,然后进行邮箱可行性的测试。
|
||||
</p>
|
||||
<div style="background: #fafafa repeating-linear-gradient(-45deg,#fff,#fff 1.125rem,transparent 1.125rem,transparent 2.25rem);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);margin:20px 0px;padding:15px;border-radius:5px;font-size:14px;color:#555555;text-align: center; ">
|
||||
经过本邮件的接收,可证实:<br>
|
||||
|
|
|
@ -105,6 +105,12 @@
|
|||
<artifactId>jsoup</artifactId>
|
||||
<version>1.13.1</version>
|
||||
</dependency>
|
||||
<!-- 爬虫模拟框架-->
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.htmlunit</groupId>
|
||||
<artifactId>htmlunit</artifactId>
|
||||
<version>2.50.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--单元测试-->
|
||||
<dependency>
|
||||
|
|
|
@ -1,40 +1,34 @@
|
|||
package top.hcode.hoj.remoteJudge.task.Impl;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.gargoylesoftware.htmlunit.BrowserVersion;
|
||||
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
|
||||
import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.html.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Connection;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import top.hcode.hoj.remoteJudge.task.RemoteJudgeStrategy;
|
||||
import top.hcode.hoj.util.Constants;
|
||||
import top.hcode.hoj.util.JsoupUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
@Slf4j(topic = "hoj")
|
||||
public class CodeForcesJudge implements RemoteJudgeStrategy {
|
||||
public static final String HOST = "https://codeforces.com";
|
||||
public static final String LOGIN_URL = "/enter";
|
||||
public static final String SUBMIT_URL = "/problemset/submit?csrf_token=%s";
|
||||
public static final String SUBMISSION_RESULT_URL = "/api/user.status?handle=%s&from=1&count=20";
|
||||
public static final String CE_INFO_URL = "/data/judgeProtocol";
|
||||
public static Map<String, String> headers = MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("Host", "codeforces.com")
|
||||
.put("origin","https://codeforces.com")
|
||||
.put("referer","https://codeforces.com")
|
||||
.map();
|
||||
public static final String HOST = "https://codeforces.com/";
|
||||
public static final String LOGIN_URL = "enter";
|
||||
public static final String SUBMIT_URL = "problemset/submit";
|
||||
public static final String SUBMISSION_RESULT_URL = "api/user.status?handle=%s&from=1&count=5";
|
||||
public static final String CE_INFO_URL = "data/submitSource";
|
||||
|
||||
private static final Map<String, Constants.Judge> statusMap = new HashMap<String, Constants.Judge>() {{
|
||||
put("FAILED", Constants.Judge.STATUS_SUBMITTED_FAILED);
|
||||
|
@ -62,47 +56,40 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
if (problemId == null || userCode == null) {
|
||||
return null;
|
||||
}
|
||||
String contestNum = problemId.replaceAll("\\D.*", "");
|
||||
String problemNum = problemId.replaceAll("^\\d*", "");
|
||||
|
||||
Map<String, Object> loginUtils = getLoginUtils(username, password);
|
||||
CodeForcesToken token = (CodeForcesToken) loginUtils.get("token");
|
||||
Connection connection = JsoupUtils.getConnectionFromUrl(HOST + String.format(SUBMIT_URL,token.csrf_token), headers, token.cookies);
|
||||
System.out.println(token.cookies);
|
||||
Connection.Response response = JsoupUtils.postResponse(connection, MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("csrf_token", token.csrf_token)
|
||||
.put("_tta", token._tta)
|
||||
.put("action", "submitSolutionFormSubmitted")
|
||||
.put("contestId", contestNum) // 比赛号
|
||||
.put("submittedProblemIndex", problemNum) // 题号:1A
|
||||
.put("programTypeId", getLanguage(language)) // 语言号
|
||||
.put("source", userCode + getRandomBlankString())
|
||||
.put("sourceFile", "")
|
||||
.map());
|
||||
if (response.statusCode() != 200) {
|
||||
|
||||
if (loginUtils == null) {
|
||||
log.error("进行题目提交时发生错误:登录失败,可能原因账号或密码错误,登录失败!" + CodeForcesJudge.class.getName() + ",题号:" + problemId);
|
||||
return null;
|
||||
}
|
||||
|
||||
WebClient webClient = (WebClient) loginUtils.getOrDefault("webClient", null);
|
||||
|
||||
boolean isSubmitted = submitCode(webClient, problemId, getLanguage(language), userCode);
|
||||
|
||||
if (!isSubmitted) {
|
||||
log.error("进行题目提交时发生错误:提交题目失败," + CodeForcesJudge.class.getName() + ",题号:" + problemId);
|
||||
return null;
|
||||
}
|
||||
// 获取提交的题目id
|
||||
Long maxRunId = getMaxRunId(connection, username, problemId);
|
||||
Long maxRunId = getMaxRunId(null, username, problemId);
|
||||
return MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("token", token.csrf_token)
|
||||
.put("cookies", token.cookies)
|
||||
.put("runId", maxRunId)
|
||||
.put("token", "")
|
||||
.put("cookies", new HashMap<String, String>())
|
||||
.map();
|
||||
}
|
||||
|
||||
private Long getMaxRunId(Connection connection, String username, String problemId) throws IOException {
|
||||
private Long getMaxRunId(Connection connection, String username, String problemId) {
|
||||
|
||||
String url = String.format(SUBMISSION_RESULT_URL, username);
|
||||
connection.url(HOST + url);
|
||||
connection.ignoreContentType(true);
|
||||
Connection.Response response = JsoupUtils.getResponse(connection, null);
|
||||
String resJson = HttpUtil.createGet(HOST + url).timeout(30000).execute().body();
|
||||
|
||||
String contestNum = problemId.replaceAll("\\D.*", "");
|
||||
String problemNum = problemId.replaceAll("^\\d*", "");
|
||||
try {
|
||||
Map<String, Object> json = JSONUtil.parseObj(response.body());
|
||||
Map<String, Object> json = JSONUtil.parseObj(resJson);
|
||||
List<Map<String, Object>> results = (List<Map<String, Object>>) json.get("result");
|
||||
for (Map<String, Object> result : results) {
|
||||
Long runId = Long.valueOf(result.get("id").toString());
|
||||
|
@ -113,7 +100,7 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("进行题目获取runID发生错误:提交题目失败," + CodeForcesJudge.class.getName()
|
||||
log.error("进行题目获取runID发生错误:获取提交ID失败," + CodeForcesJudge.class.getName()
|
||||
+ ",题号:" + problemId + ",异常描述:" + e.getMessage());
|
||||
return -1L;
|
||||
}
|
||||
|
@ -122,12 +109,15 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
|
||||
@Override
|
||||
public Map<String, Object> result(Long submitId, String username, String token, HashMap<String, String> cookies) throws Exception {
|
||||
String url = HOST + String.format(SUBMISSION_RESULT_URL, username);
|
||||
Connection connection = JsoupUtils.getConnectionFromUrl(url, headers, cookies);
|
||||
connection.ignoreContentType(true);
|
||||
Connection.Response response = JsoupUtils.getResponse(connection, null);
|
||||
|
||||
JSONObject jsonObject = JSONUtil.parseObj(response.body());
|
||||
String url = HOST + String.format(SUBMISSION_RESULT_URL, username);
|
||||
|
||||
String resJson = HttpUtil.createGet(url)
|
||||
.timeout(30000)
|
||||
.execute()
|
||||
.body();
|
||||
|
||||
JSONObject jsonObject = JSONUtil.parseObj(resJson);
|
||||
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("status", Constants.Judge.STATUS_JUDGING.getStatus());
|
||||
|
@ -149,17 +139,22 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
Constants.Judge resultStatus = statusMap.get(verdict);
|
||||
if (resultStatus == Constants.Judge.STATUS_COMPILE_ERROR) {
|
||||
|
||||
Connection CEInfoConnection = JsoupUtils.getConnectionFromUrl(HOST + CE_INFO_URL, headers, cookies);
|
||||
CEInfoConnection.ignoreContentType(true);
|
||||
String html = HttpUtil.createGet(HOST)
|
||||
.timeout(30000).execute().body();
|
||||
String csrfToken = ReUtil.get("data-csrf='(\\w+)'", html, 1);
|
||||
|
||||
Connection.Response CEInfoResponse = JsoupUtils.postResponse(CEInfoConnection, MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("csrf_token", token)
|
||||
.put("submissionId", submitId.toString()).map());
|
||||
String ceJson = HttpUtil.createPost(HOST + CE_INFO_URL)
|
||||
.form(MapUtil
|
||||
.builder(new HashMap<String, Object>())
|
||||
.put("csrf_token", csrfToken)
|
||||
.put("submissionId", "119327069").map())
|
||||
.timeout(30000)
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject CEInfoJson = JSONUtil.parseObj(ceJson);
|
||||
String CEInfo = CEInfoJson.getStr("checkerStdoutAndStderr#1");
|
||||
|
||||
|
||||
resultMap.put("CEInfo", UnicodeUtil.toString(CEInfoResponse.body()).replaceAll("(\\\\r)?\\\\n", "\n")
|
||||
.replaceAll("\\\\\\\\", "\\\\"));
|
||||
resultMap.put("CEInfo", CEInfo);
|
||||
}
|
||||
resultMap.put("status", resultStatus.getStatus());
|
||||
return resultMap;
|
||||
|
@ -170,21 +165,75 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
|
||||
@Override
|
||||
public Map<String, Object> getLoginUtils(String username, String password) throws Exception {
|
||||
// 获取token
|
||||
CodeForcesToken token = getTokens();
|
||||
Connection connection = JsoupUtils.getConnectionFromUrl(HOST + LOGIN_URL, headers, token.cookies);
|
||||
// 模拟一个浏览器
|
||||
WebClient webClient = new WebClient(BrowserVersion.CHROME);
|
||||
// 设置webClient的相关参数
|
||||
webClient.setCssErrorHandler(new SilentCssErrorHandler());
|
||||
//设置ajax
|
||||
webClient.setAjaxController(new NicelyResynchronizingAjaxController());
|
||||
//设置禁止js
|
||||
webClient.getOptions().setJavaScriptEnabled(false);
|
||||
//CSS渲染禁止
|
||||
webClient.getOptions().setCssEnabled(false);
|
||||
//超时时间
|
||||
webClient.getOptions().setTimeout(40000);
|
||||
|
||||
//设置js抛出异常:false
|
||||
webClient.getOptions().setThrowExceptionOnScriptError(false);
|
||||
//允许重定向
|
||||
webClient.getOptions().setRedirectEnabled(true);
|
||||
//允许cookie
|
||||
webClient.getCookieManager().setCookiesEnabled(true);
|
||||
|
||||
webClient.getOptions().setUseInsecureSSL(true);
|
||||
// 模拟浏览器打开一个目标网址
|
||||
HtmlPage page = webClient.getPage(HOST + LOGIN_URL);
|
||||
|
||||
HtmlForm form = (HtmlForm) page.getElementById("enterForm");
|
||||
HtmlTextInput usernameInput = form.getInputByName("handleOrEmail");
|
||||
HtmlPasswordInput passwordInput = form.getInputByName("password");
|
||||
usernameInput.setValueAttribute(username); //用户名
|
||||
passwordInput.setValueAttribute(password); //密码
|
||||
|
||||
HtmlSubmitInput button = (HtmlSubmitInput) page.getByXPath("//input[@class='submit']").get(0);
|
||||
|
||||
HtmlPage retPage = button.click();
|
||||
|
||||
if (retPage.getUrl().toString().equals(HOST)) {
|
||||
return MapUtil.builder(new HashMap<String, Object>()).put("webClient", webClient).map();
|
||||
} else {
|
||||
webClient.close();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean submitCode(WebClient webClient, String problemID, String languageID, String code) throws IOException {
|
||||
// 模拟浏览器打开一个目标网址
|
||||
HtmlPage page = webClient.getPage(HOST + SUBMIT_URL);
|
||||
|
||||
HtmlForm form = (HtmlForm) page.getByXPath("//form[@class='submit-form']").get(0);
|
||||
|
||||
// 题号
|
||||
HtmlTextInput problemCode = form.getInputByName("submittedProblemCode");
|
||||
problemCode.setValueAttribute(problemID);
|
||||
// 编程语言
|
||||
HtmlSelect programTypeId = form.getSelectByName("programTypeId");
|
||||
HtmlOption optionByValue = programTypeId.getOptionByValue(languageID);
|
||||
optionByValue.click();
|
||||
// 代码
|
||||
HtmlTextArea source = form.getTextAreaByName("source");
|
||||
source.setText(code + getRandomBlankString());
|
||||
|
||||
HtmlSubmitInput button = (HtmlSubmitInput) page.getByXPath("//input[@class='submit']").get(0);
|
||||
HtmlPage retPage = button.click();
|
||||
if (retPage.getUrl().toString().equals("https://codeforces.com/problemset/status?my=on")) {
|
||||
webClient.close();
|
||||
return true;
|
||||
} else {
|
||||
webClient.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
Connection.Response response = JsoupUtils.postResponse(connection, MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("csrf_token", token.csrf_token)
|
||||
.put("_tta", token._tta)
|
||||
.put("action", "enter")
|
||||
.put("remember", "on") // 是否记住登录
|
||||
.put("handleOrEmail", username)
|
||||
.put("password", password).map());
|
||||
// 混合cookie才能确认身份
|
||||
token.cookies.putAll(response.cookies());
|
||||
return MapUtil.builder(new HashMap<String, Object>()).put("token", token).map();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -254,48 +303,6 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static class CodeForcesToken {
|
||||
public String _tta;
|
||||
public String csrf_token;
|
||||
public Map<String, String> cookies;
|
||||
|
||||
public CodeForcesToken(String _tta, String csrf_token, Map<String, String> cookies) {
|
||||
this._tta = _tta;
|
||||
this.csrf_token = csrf_token;
|
||||
this.cookies = cookies;
|
||||
}
|
||||
}
|
||||
|
||||
public static CodeForcesToken getTokens() throws Exception {
|
||||
Connection connection = JsoupUtils.getConnectionFromUrl(HOST, headers, null);
|
||||
Connection.Response response = JsoupUtils.getResponse(connection, null);
|
||||
String html = response.body();
|
||||
String csrfToken = ReUtil.get("data-csrf='(\\w+)'", html, 1);
|
||||
String _39ce7 = null;
|
||||
Map<String, String> cookies = response.cookies();
|
||||
if (cookies.containsKey("39ce7")) {
|
||||
_39ce7 = cookies.get("39ce7");
|
||||
} else {
|
||||
throw new Exception("网络错误,无法找到cookies");
|
||||
}
|
||||
int _tta = 0;
|
||||
for (int c = 0; c < _39ce7.length(); c++) {
|
||||
_tta = (_tta + (c + 1) * (c + 2) * _39ce7.charAt(c)) % 1009;
|
||||
if (c % 3 == 0)
|
||||
_tta++;
|
||||
if (c % 2 == 0)
|
||||
_tta *= 2;
|
||||
if (c > 0)
|
||||
_tta -= (_39ce7.charAt(c / 2) / 2) * (_tta % 5);
|
||||
while (_tta < 0)
|
||||
_tta += 1009;
|
||||
while (_tta >= 1009)
|
||||
_tta -= 1009;
|
||||
}
|
||||
return new CodeForcesToken(Integer.toString(_tta), csrfToken, cookies);
|
||||
}
|
||||
|
||||
protected String getRandomBlankString() {
|
||||
StringBuilder string = new StringBuilder("\n");
|
||||
int random = new Random().nextInt(Integer.MAX_VALUE);
|
||||
|
@ -305,4 +312,5 @@ public class CodeForcesJudge implements RemoteJudgeStrategy {
|
|||
}
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package top.hcode.hoj.remoteJudge.task.Impl;
|
|||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Connection;
|
||||
import org.jsoup.helper.Validate;
|
||||
|
@ -95,7 +96,7 @@ public class HduJudge implements RemoteJudgeStrategy {
|
|||
connection.url(HOST + String.format(ERROR_URL, submitId));
|
||||
response = JsoupUtils.getResponse(connection, null);
|
||||
String compilationErrorInfo = ReUtil.get("<pre>([\\s\\S]*?)</pre>", response.body(), 1);
|
||||
result.put("CEInfo", compilationErrorInfo);
|
||||
result.put("CEInfo", HtmlUtil.unescape(compilationErrorInfo));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,58 +1,18 @@
|
|||
package top.hcode.hoj.remoteJudge.task.Impl;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import org.jsoup.Connection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import top.hcode.hoj.pojo.entity.Problem;
|
||||
import top.hcode.hoj.util.JsoupUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class HduJudgeTest {
|
||||
|
||||
public static Map<String, String> headers = MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("Host", "codeforces.com")
|
||||
.map();
|
||||
|
||||
|
||||
@Test
|
||||
void getLoginCookie() throws Exception {
|
||||
|
||||
Map<String, Object> loginUtils = getLoginUtils("username", "password");
|
||||
CodeForcesJudge.CodeForcesToken token = (CodeForcesJudge.CodeForcesToken) loginUtils.get("token");
|
||||
Connection CEInfoConnection = JsoupUtils.getConnectionFromUrl("https://codeforces.com/data/judgeProtocol", headers, token.cookies);
|
||||
CEInfoConnection.ignoreContentType(true);
|
||||
CEInfoConnection.header("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
|
||||
Connection.Response CEInfoResponse = JsoupUtils.postResponse(CEInfoConnection, MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("csrf_token", token.csrf_token)
|
||||
.put("submissionId", "111090897").map());
|
||||
System.out.println(CEInfoResponse.body());
|
||||
System.out.println(UnicodeUtil.toString(CEInfoResponse.body()));
|
||||
|
||||
}
|
||||
|
||||
public Map<String, Object> getLoginUtils(String username, String password) throws Exception {
|
||||
// 获取token
|
||||
CodeForcesJudge.CodeForcesToken token = CodeForcesJudge.getTokens();
|
||||
Connection connection = JsoupUtils.getConnectionFromUrl("https://codeforces.com/enter", headers, token.cookies);
|
||||
|
||||
Connection.Response response = JsoupUtils.postResponse(connection, MapUtil
|
||||
.builder(new HashMap<String, String>())
|
||||
.put("csrf_token", token.csrf_token)
|
||||
.put("_tta", token._tta)
|
||||
.put("action", "enter")
|
||||
// .put("remember", "on") // 是否记住登录
|
||||
.put("handleOrEmail", username)
|
||||
.put("password", password).map());
|
||||
// 混合cookie才能确认身份
|
||||
token.cookies.putAll(response.cookies());
|
||||
return MapUtil.builder(new HashMap<String, Object>()).put("token", token).map();
|
||||
}
|
||||
}
|
|
@ -794,10 +794,11 @@ const adminApi = {
|
|||
data
|
||||
})
|
||||
},
|
||||
admin_deleteContestProblem (pid) {
|
||||
admin_deleteContestProblem (pid,cid) {
|
||||
return ajax('/api/admin/contest/problem', 'delete', {
|
||||
params: {
|
||||
id
|
||||
pid,
|
||||
cid
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -842,11 +843,12 @@ const adminApi = {
|
|||
}
|
||||
})
|
||||
},
|
||||
admin_changeContestVisible(cid,visible){
|
||||
admin_changeContestVisible(cid,visible,uid){
|
||||
return ajax('/api/admin/contest/change-contest-visible', 'put', {
|
||||
params: {
|
||||
cid,
|
||||
visible
|
||||
visible,
|
||||
uid
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
|
@ -213,6 +213,7 @@ export const CONTEST_TYPE = {
|
|||
export const USER_TYPE = {
|
||||
REGULAR_USER: 'user',
|
||||
ADMIN: 'admin',
|
||||
PROBLEM_ADMIN:'problem_admin',
|
||||
SUPER_ADMIN: 'root'
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import moment from 'moment'
|
||||
import utils from './utils'
|
||||
import time from './time'
|
||||
import {PROBLEM_LEVEL} from './constants'
|
||||
|
||||
// 友好显示时间
|
||||
function fromNow (time) {
|
||||
|
@ -11,7 +12,7 @@ function parseRole(num){
|
|||
if(num==1000){
|
||||
return '超级管理员'
|
||||
}else if(num==1001){
|
||||
return '管理员'
|
||||
return '普通管理员'
|
||||
}else if(num==1002){
|
||||
return '用户(默认)'
|
||||
}else if(num==1003){
|
||||
|
@ -24,6 +25,8 @@ function parseRole(num){
|
|||
return '用户(禁止提交&禁止发讨论)'
|
||||
}else if(num==1007){
|
||||
return '用户(禁止提交&禁言)'
|
||||
}else if(num==1008){
|
||||
return '题目管理员'
|
||||
}
|
||||
}
|
||||
function parseContestType(num){
|
||||
|
@ -35,13 +38,7 @@ function parseContestType(num){
|
|||
}
|
||||
|
||||
function parseProblemLevel(num){
|
||||
if(num==0){
|
||||
return 'Easy'
|
||||
}else if(num==1){
|
||||
return 'Mid'
|
||||
}else if(num==2){
|
||||
return 'Hard'
|
||||
}
|
||||
return PROBLEM_LEVEL[num].name;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -29,6 +29,7 @@ export const m = {
|
|||
Last_Login: 'Last Login',
|
||||
Super_Admin:'Super Admin',
|
||||
Admin:'Admin',
|
||||
All_Problem_Admin:'Problem Admin',
|
||||
Total_Users:'Total Users',
|
||||
Today_Submissions:'Today Submissions',
|
||||
Recent_14_Days_Contests:'Recent 14 Days Contests',
|
||||
|
@ -132,7 +133,9 @@ export const m = {
|
|||
Add_Contest_Problem:'Add Contest Problem',
|
||||
Remote_OJ:'Remote OJ',
|
||||
Add:'Add',
|
||||
Remove:'Remove',
|
||||
Delete_Problem_Tips:'Are you sure you want to delete this problem? Note: the relevant submission data for this issue will also be deleted.',
|
||||
Remove_Problem_Tips:'Are you sure you want to remove the problem from the competition?',
|
||||
Add_Successfully:'Add Successfully',
|
||||
Download_Testcase_Success:'The testcase of this problem has been downloaded successfully!',
|
||||
Enter_The_Problem_Display_ID_in_the_Contest:'Enter The Problem Display ID in the Contest',
|
||||
|
|
|
@ -28,7 +28,8 @@ export const m = {
|
|||
// /views/admin/Dashboard.vue
|
||||
Last_Login: '最近登录',
|
||||
Super_Admin:'超级管理员',
|
||||
Admin:'管理员',
|
||||
Admin:'普通管理员',
|
||||
All_Problem_Admin:'题目管理员',
|
||||
Total_Users:'总用户数',
|
||||
Today_Submissions:'今日总交题数',
|
||||
Recent_14_Days_Contests:'最近两周比赛',
|
||||
|
@ -126,13 +127,15 @@ export const m = {
|
|||
Add_From_Public_Problem:'从公共题库添加题目',
|
||||
Auth:'权限',
|
||||
Public_Problem:'公开题目',
|
||||
Private_Problem:'私有题目',
|
||||
Private_Problem:'隐藏题目',
|
||||
Contest_Problem:'比赛题目',
|
||||
Download_Testcase:'下载评测数据',
|
||||
Add_Contest_Problem:'添加比赛题目',
|
||||
Remote_OJ:'远程OJ',
|
||||
Add:'添加',
|
||||
Remove:'移除',
|
||||
Delete_Problem_Tips:'确定要删除此问题吗?注意:该问题的相关提交数据也将被删除。',
|
||||
Remove_Problem_Tips:'你是否确定要将该题目移除比赛?',
|
||||
Add_Successfully:'添加成功',
|
||||
Download_Testcase_Success:'该题目的评测数据已经被成功下载!',
|
||||
Enter_The_Problem_Display_ID_in_the_Contest:'请输入该题目在比赛中展示ID',
|
||||
|
|
|
@ -26,7 +26,7 @@ const getters = {
|
|||
},
|
||||
isContestAdmin: (state, getters, _, rootGetters) => {
|
||||
return rootGetters.isAuthenticated &&
|
||||
(state.contest.author === rootGetters.userInfo.author || rootGetters.isSuperAdmin)
|
||||
(state.contest.author === rootGetters.userInfo.username || rootGetters.isSuperAdmin)
|
||||
},
|
||||
canSubmit:(state, getters)=>{
|
||||
return state.intoAccess||state.submitAccess || state.contest.auth === CONTEST_TYPE.PUBLIC ||getters.isContestAdmin
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { USER_TYPE, PROBLEM_PERMISSION } from '@/common/constants'
|
||||
import { USER_TYPE } from '@/common/constants'
|
||||
import storage from '@/common/storage'
|
||||
const state = {
|
||||
userInfo: storage.get('userInfo'),
|
||||
|
@ -16,6 +16,7 @@ const getters = {
|
|||
isAdminRole: (state, getters) => {
|
||||
if(getters.userInfo.roleList){
|
||||
return getters.userInfo.roleList.indexOf(USER_TYPE.ADMIN)!=-1 ||
|
||||
getters.userInfo.roleList.indexOf(USER_TYPE.PROBLEM_ADMIN)!=-1 ||
|
||||
getters.userInfo.roleList.indexOf(USER_TYPE.SUPER_ADMIN)!=-1
|
||||
}else{
|
||||
return false;
|
||||
|
@ -28,6 +29,13 @@ const getters = {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
isProblemAdmin:(state, getters) => {
|
||||
if(getters.userInfo.roleList){
|
||||
return getters.userInfo.roleList.indexOf(USER_TYPE.PROBLEM_ADMIN) !=-1
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
}}</span>
|
||||
<p>
|
||||
{{
|
||||
isSuperAdmin == true ? $t('m.Super_Admin') : $t('m.Admin')
|
||||
isSuperAdmin == true
|
||||
? $t('m.Super_Admin')
|
||||
: isProblemAdmin == true
|
||||
? $t('m.All_Problem_Admin')
|
||||
: $t('m.Admin')
|
||||
}}
|
||||
</p>
|
||||
</el-col>
|
||||
|
@ -338,7 +342,7 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'isSuperAdmin']),
|
||||
...mapGetters(['userInfo', 'isSuperAdmin', 'isProblemAdmin']),
|
||||
https() {
|
||||
return document.URL.slice(0, 5) === 'https';
|
||||
},
|
||||
|
|
|
@ -40,9 +40,11 @@
|
|||
<el-menu-item index="/admin/problem/create">{{
|
||||
$t('m.Create_Problem')
|
||||
}}</el-menu-item>
|
||||
<el-menu-item index="/admin/problem/batch-operation">{{
|
||||
$t('m.Export_Import_Problem')
|
||||
}}</el-menu-item>
|
||||
<el-menu-item
|
||||
index="/admin/problem/batch-operation"
|
||||
v-if="isSuperAdmin"
|
||||
>{{ $t('m.Export_Import_Problem') }}</el-menu-item
|
||||
>
|
||||
</el-submenu>
|
||||
<el-submenu index="contest">
|
||||
<template slot="title"
|
||||
|
@ -260,6 +262,7 @@
|
|||
}}</mu-list-item-title>
|
||||
</mu-list-item>
|
||||
<mu-list-item
|
||||
v-if="isSuperAdmin"
|
||||
button
|
||||
:ripple="false"
|
||||
slot="nested"
|
||||
|
|
|
@ -63,7 +63,8 @@
|
|||
<template v-slot="{ row }">
|
||||
<el-switch
|
||||
v-model="row.visible"
|
||||
@change="changeContestVisible(row.id, row.visible)"
|
||||
:disabled="!isSuperAdmin && userInfo.uid != row.uid"
|
||||
@change="changeContestVisible(row.id, row.visible, row.uid)"
|
||||
>
|
||||
</el-switch>
|
||||
</template>
|
||||
|
@ -78,59 +79,65 @@
|
|||
</vxe-table-column>
|
||||
<vxe-table-column min-width="150" :title="$t('m.Option')">
|
||||
<template v-slot="{ row }">
|
||||
<div style="margin-bottom:10px">
|
||||
<el-tooltip effect="dark" :content="$t('m.Edit')" placement="top">
|
||||
<el-button
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
@click.native="goEdit(row.id)"
|
||||
type="primary"
|
||||
<template v-if="isSuperAdmin || userInfo.uid == row.uid">
|
||||
<div style="margin-bottom:10px">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Edit')"
|
||||
placement="top"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.View_Contest_Problem_List')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-tickets"
|
||||
size="mini"
|
||||
@click.native="goContestProblemList(row.id)"
|
||||
type="success"
|
||||
<el-button
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
@click.native="goEdit(row.id)"
|
||||
type="primary"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.View_Contest_Problem_List')"
|
||||
placement="top"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div style="margin-bottom:10px">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.View_Contest_Announcement_List')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-info"
|
||||
size="mini"
|
||||
@click.native="goContestAnnouncement(row.id)"
|
||||
type="info"
|
||||
<el-button
|
||||
icon="el-icon-tickets"
|
||||
size="mini"
|
||||
@click.native="goContestProblemList(row.id)"
|
||||
type="success"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div style="margin-bottom:10px">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.View_Contest_Announcement_List')"
|
||||
placement="top"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
icon="el-icon-info"
|
||||
size="mini"
|
||||
@click.native="goContestAnnouncement(row.id)"
|
||||
type="info"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Download_Contest_AC_Submission')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click.native="openDownloadOptions(row.id)"
|
||||
type="warning"
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Download_Contest_AC_Submission')"
|
||||
placement="top"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-button
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click.native="openDownloadOptions(row.id)"
|
||||
type="warning"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Delete')"
|
||||
|
@ -216,7 +223,7 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isSuperAdmin']),
|
||||
...mapGetters(['isSuperAdmin', 'userInfo']),
|
||||
},
|
||||
methods: {
|
||||
// 切换页码回调
|
||||
|
@ -273,8 +280,8 @@ export default {
|
|||
});
|
||||
});
|
||||
},
|
||||
changeContestVisible(contestId, visible) {
|
||||
api.admin_changeContestVisible(contestId, visible).then((res) => {
|
||||
changeContestVisible(contestId, visible, uid) {
|
||||
api.admin_changeContestVisible(contestId, visible, uid).then((res) => {
|
||||
myMessage.success(this.$i18n.t('m.Update_Successfully'));
|
||||
});
|
||||
},
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
<el-form-item :label="$t('m.Port')" required label-width="80px">
|
||||
<el-input
|
||||
v-model="smtp.emailPort"
|
||||
type="number"
|
||||
:placeholder="$t('m.Port')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
@ -233,7 +234,6 @@
|
|||
>
|
||||
<el-input
|
||||
v-model="databaseConfig.redisHost"
|
||||
type="password"
|
||||
:placeholder="'Redis ' + $t('m.Host')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
@ -246,7 +246,7 @@
|
|||
>
|
||||
<el-input
|
||||
v-model="databaseConfig.redisPort"
|
||||
type="password"
|
||||
type="number"
|
||||
:placeholder="'Redis ' + $t('m.Port')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
@ -327,7 +327,9 @@ export default {
|
|||
myMessage.success(this.$i18n.t('m.Update_Successfully'));
|
||||
this.saved = true;
|
||||
},
|
||||
() => {}
|
||||
() => {
|
||||
this.saved = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
testSMTPConfig() {
|
||||
|
@ -339,7 +341,7 @@ export default {
|
|||
this.loadingBtnTest = true;
|
||||
api.admin_testSMTPConfig(value).then(
|
||||
(res) => {
|
||||
myMessage.success(this.$i18n.t('m.Post_Successfully'));
|
||||
myMessage.success(this.$i18n.t('m.Post_successfully'));
|
||||
this.loadingBtnTest = false;
|
||||
},
|
||||
() => {
|
||||
|
|
|
@ -384,7 +384,16 @@
|
|||
:value="1000"
|
||||
:key="1000"
|
||||
></el-option>
|
||||
<el-option label="管理员" :value="1001" :key="1001"></el-option>
|
||||
<el-option
|
||||
label="普通管理员"
|
||||
:value="1001"
|
||||
:key="1001"
|
||||
></el-option>
|
||||
<el-option
|
||||
label="题目管理员"
|
||||
:value="1008"
|
||||
:key="1008"
|
||||
></el-option>
|
||||
<el-option
|
||||
label="用户(默认)"
|
||||
:value="1002"
|
||||
|
@ -423,6 +432,8 @@
|
|||
<el-switch
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
:active-text="$t('m.Normal')"
|
||||
:inactive-text="$t('m.Disable')"
|
||||
v-model="selectUser.status"
|
||||
>
|
||||
</el-switch>
|
||||
|
@ -688,7 +699,7 @@ export default {
|
|||
api
|
||||
.admin_deleteUsers(ids)
|
||||
.then((res) => {
|
||||
myMessage.success(res.data.msg);
|
||||
myMessage.success(this.$i18n.$t('m.Delete_successfully'));
|
||||
this.selectedUsers = [];
|
||||
this.getUserList(this.currentPage);
|
||||
})
|
||||
|
|
|
@ -77,9 +77,13 @@
|
|||
v-model="row.auth"
|
||||
@change="changeProblemAuth(row)"
|
||||
size="small"
|
||||
:disabled="row.auth == 3 && !contestId"
|
||||
:disabled="!isSuperAdmin && !isProblemAdmin && !contestId"
|
||||
>
|
||||
<el-option :label="$t('m.Public_Problem')" :value="1"></el-option>
|
||||
<el-option
|
||||
:label="$t('m.Public_Problem')"
|
||||
:value="1"
|
||||
:disabled="!isSuperAdmin && !isProblemAdmin"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('m.Private_Problem')"
|
||||
:value="2"
|
||||
|
@ -108,6 +112,7 @@
|
|||
effect="dark"
|
||||
:content="$t('m.Download_Testcase')"
|
||||
placement="top"
|
||||
v-if="isSuperAdmin || isProblemAdmin"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-download"
|
||||
|
@ -118,7 +123,27 @@
|
|||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" :content="$t('m.Delete')" placement="top">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Remove')"
|
||||
placement="top"
|
||||
v-if="contestId"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
@click.native="removeProblem(row.id)"
|
||||
type="warning"
|
||||
>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('m.Delete')"
|
||||
placement="top"
|
||||
v-if="isSuperAdmin || isProblemAdmin"
|
||||
>
|
||||
<el-button
|
||||
icon="el-icon-delete-solid"
|
||||
size="mini"
|
||||
|
@ -198,6 +223,7 @@ import utils from '@/common/utils';
|
|||
import ContestAddProblem from '@/components/admin/ContestAddProblem.vue';
|
||||
import myMessage from '@/common/message';
|
||||
import { REMOTE_OJ } from '@/common/constants';
|
||||
import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
name: 'ProblemList',
|
||||
components: {
|
||||
|
@ -230,6 +256,9 @@ export default {
|
|||
this.getProblemList(this.currentPage);
|
||||
this.REMOTE_OJ = Object.assign({}, REMOTE_OJ);
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo', 'isSuperAdmin', 'isProblemAdmin']),
|
||||
},
|
||||
methods: {
|
||||
handleDblclick(row) {
|
||||
row.isEditing = true;
|
||||
|
@ -308,7 +337,7 @@ export default {
|
|||
this.routeName === 'admin-problem-list'
|
||||
? 'admin_deleteProblem'
|
||||
: 'admin_deleteContestProblem';
|
||||
api[funcName](id)
|
||||
api[funcName](id, null)
|
||||
.then((res) => {
|
||||
myMessage.success(this.$i18n.t('m.Delete_successfully'));
|
||||
this.getProblemList(this.currentPage);
|
||||
|
@ -318,6 +347,22 @@ export default {
|
|||
() => {}
|
||||
);
|
||||
},
|
||||
removeProblem(pid) {
|
||||
this.$confirm(this.$i18n.t('m.Remove_Problem_Tips'), 'Tips', {
|
||||
type: 'warning',
|
||||
}).then(
|
||||
() => {
|
||||
api
|
||||
.admin_deleteContestProblem(pid, this.contestId)
|
||||
.then((res) => {
|
||||
myMessage.success('success');
|
||||
this.getProblemList(this.currentPage);
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
},
|
||||
updateProblem(row) {
|
||||
let data = Object.assign({}, row);
|
||||
let funcName = '';
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
:color="CONTEST_STATUS_REVERSE[contests[index].status]['color']"
|
||||
>
|
||||
<i class="fa fa-circle" aria-hidden="true"></i>
|
||||
{{ CONTEST_STATUS_REVERSE[contests[index].status]['name'] }}
|
||||
{{
|
||||
$t(
|
||||
'm.' +
|
||||
CONTEST_STATUS_REVERSE[contests[index].status]['name']
|
||||
)
|
||||
}}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue