完善后台管理员权限控制,恢复CF的vjudge判题

This commit is contained in:
Himit_ZH 2021-06-14 00:05:55 +08:00
parent 64513d694c
commit 564492f5b2
39 changed files with 506 additions and 398 deletions

View File

@ -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 |

View File

@ -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");
}

View File

@ -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) { // 更新成功

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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());

View File

@ -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();

View File

@ -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<>();

View File

@ -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());

View File

@ -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);

View File

@ -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) { // 当前是登陆状态
// 需要判断是否为当前登陆用户自己的提交代码

View File

@ -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>

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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);
// 收件人

View File

@ -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">&nbsp;HOJ&nbsp;</a>上进行注册。假如这不是您本人所申请,
target="_blank" th:href="${OJ_URL}" rel="noopener">&nbsp;[[${OJ_SHORT_NAME}]]&nbsp;</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; ">

View File

@ -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">&nbsp;HOJ&nbsp;</a>上进行密码重置操作。假如这不是您本人所申请, 请不用理会这封电子邮件, 但是如果您持续收到这类的信件骚扰, 请您尽快联络管理员。
target="_blank" th:href="${OJ_URL}" rel="noopener">&nbsp;[[${OJ_SHORT_NAME}]]&nbsp;</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>

View File

@ -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">&nbsp;HOJ&nbsp;</a>上进行邮箱配置更新,然后进行邮箱可行性的测试。
target="_blank" th:href="${OJ_URL}" rel="noopener">&nbsp;[[${OJ_SHORT_NAME}]]&nbsp;</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>

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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
}
})
},

View File

@ -213,6 +213,7 @@ export const CONTEST_TYPE = {
export const USER_TYPE = {
REGULAR_USER: 'user',
ADMIN: 'admin',
PROBLEM_ADMIN:'problem_admin',
SUPER_ADMIN: 'root'
}

View File

@ -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 {

View File

@ -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',

View File

@ -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',

View File

@ -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

View File

@ -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 = {

View File

@ -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';
},

View File

@ -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"

View File

@ -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'));
});
},

View File

@ -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;
},
() => {

View File

@ -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);
})

View File

@ -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 = '';

View File

@ -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>