diff --git a/src/main/java/cn/kastner/oj/controller/ContestRestController.java b/src/main/java/cn/kastner/oj/controller/ContestRestController.java index fe6417c..c0061eb 100644 --- a/src/main/java/cn/kastner/oj/controller/ContestRestController.java +++ b/src/main/java/cn/kastner/oj/controller/ContestRestController.java @@ -8,6 +8,7 @@ import cn.kastner.oj.query.ContestQuery; import cn.kastner.oj.query.RankingQuery; import cn.kastner.oj.service.ContestService; +import org.apache.poi.ss.usermodel.Workbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -15,6 +16,9 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; import java.util.List; @RestController @@ -167,6 +171,23 @@ public RankingDTO getRanking(@PathVariable String id, RankingQuery query) throws return contestService.getRanking(id, query); } + @GetMapping("/{id}/ranking/export") + public void exportRanking( + @PathVariable String id, RankingQuery query, HttpServletResponse response) + throws ContestException { + response.setHeader("content-type", "application/octet-stream"); + response.setContentType("application/octet-stream"); + String fileName = contestService.findById(id).getName() + "排名"; + response.setHeader("Content-Disposition", "attachment;filename=" + fileName); + + Workbook workbook = contestService.exportRanking(id, query); + try (OutputStream os = response.getOutputStream()) { + workbook.write(os); + } catch (IOException e) { + throw new ContestException(ContestException.EXPORT_ERROR); + } + } + @PatchMapping("/{id}/status") @PreAuthorize("hasAnyRole('ADMIN', 'STUFF')") public ContestDTO setContestStatus(@PathVariable String id, @RequestParam ContestOption option) diff --git a/src/main/java/cn/kastner/oj/domain/ContestProblem.java b/src/main/java/cn/kastner/oj/domain/ContestProblem.java index 50ed43b..aa314a8 100644 --- a/src/main/java/cn/kastner/oj/domain/ContestProblem.java +++ b/src/main/java/cn/kastner/oj/domain/ContestProblem.java @@ -44,7 +44,7 @@ public class ContestProblem { @Transient private List timeList = new ArrayList<>(); - @OneToOne + @OneToOne(fetch = FetchType.LAZY) @NotFound(action = NotFoundAction.IGNORE) private Submission firstSubmission; diff --git a/src/main/java/cn/kastner/oj/domain/RankingUser.java b/src/main/java/cn/kastner/oj/domain/RankingUser.java index fce38ea..ed2fd51 100644 --- a/src/main/java/cn/kastner/oj/domain/RankingUser.java +++ b/src/main/java/cn/kastner/oj/domain/RankingUser.java @@ -42,7 +42,7 @@ public class RankingUser { private Long time = 0L; - @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "rankingUser") + @OneToMany(fetch = FetchType.EAGER, mappedBy = "rankingUser") @Fetch(FetchMode.SUBSELECT) @OrderBy("id DESC ") @JsonIgnore diff --git a/src/main/java/cn/kastner/oj/domain/TimeCost.java b/src/main/java/cn/kastner/oj/domain/TimeCost.java index 21c5eb5..192d49c 100644 --- a/src/main/java/cn/kastner/oj/domain/TimeCost.java +++ b/src/main/java/cn/kastner/oj/domain/TimeCost.java @@ -16,7 +16,7 @@ public class TimeCost { @Column(length = 40) private String id = UUID.randomUUID().toString(); - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NotFound(action = NotFoundAction.IGNORE) private ContestProblem contestProblem; @@ -30,11 +30,9 @@ public class TimeCost { private Boolean firstPassed = false; - private Boolean frozen = false; - private Double score = 0.0; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NotFound(action = NotFoundAction.IGNORE) private RankingUser rankingUser; diff --git a/src/main/java/cn/kastner/oj/exception/ContestException.java b/src/main/java/cn/kastner/oj/exception/ContestException.java index 312cd30..1853c26 100644 --- a/src/main/java/cn/kastner/oj/exception/ContestException.java +++ b/src/main/java/cn/kastner/oj/exception/ContestException.java @@ -34,6 +34,8 @@ public class ContestException extends AppException { public static final String BAD_CONTEST_STATUS = "不合法的比赛状态"; + public static final String EXPORT_ERROR = "导出错误"; + public ContestException(String message) { super(message); switch (message) { @@ -97,6 +99,10 @@ public ContestException(String message) { this.code = -16; this.status = HttpStatus.FORBIDDEN; break; + case EXPORT_ERROR: + this.code = -17; + this.status = HttpStatus.INTERNAL_SERVER_ERROR; + break; default: this.code = -1; this.status = HttpStatus.INTERNAL_SERVER_ERROR; diff --git a/src/main/java/cn/kastner/oj/factory/RankingUserFactory.java b/src/main/java/cn/kastner/oj/factory/RankingUserFactory.java index 0f8f0b7..0157183 100644 --- a/src/main/java/cn/kastner/oj/factory/RankingUserFactory.java +++ b/src/main/java/cn/kastner/oj/factory/RankingUserFactory.java @@ -29,7 +29,6 @@ public static RankingUser create(User user, Contest contest, Group group) { TimeCost timeCost = new TimeCost(); timeCost.setContestProblem(contestProblem); timeCost.setRankingUser(rankingUser); - timeCost.setFrozen(false); timeCostList.add(timeCost); } rankingUser.setTimeList(timeCostList); diff --git a/src/main/java/cn/kastner/oj/repository/ContestProblemRepository.java b/src/main/java/cn/kastner/oj/repository/ContestProblemRepository.java index 4d308e5..ebfeae9 100644 --- a/src/main/java/cn/kastner/oj/repository/ContestProblemRepository.java +++ b/src/main/java/cn/kastner/oj/repository/ContestProblemRepository.java @@ -21,4 +21,6 @@ public interface ContestProblemRepository void deleteAllByProblemAndContest(Iterable problems, Contest contest); void deleteAllByContest(Contest contest); + + Integer countByContest(Contest contest); } diff --git a/src/main/java/cn/kastner/oj/service/ContestService.java b/src/main/java/cn/kastner/oj/service/ContestService.java index 2c3a2d5..b9fc508 100644 --- a/src/main/java/cn/kastner/oj/service/ContestService.java +++ b/src/main/java/cn/kastner/oj/service/ContestService.java @@ -6,6 +6,7 @@ import cn.kastner.oj.exception.ProblemException; import cn.kastner.oj.query.ContestQuery; import cn.kastner.oj.query.RankingQuery; +import org.apache.poi.ss.usermodel.Workbook; import java.util.List; @@ -49,4 +50,6 @@ void deleteProblems(List problemIdList, String contestId) void joinContest(String id, String password) throws ContestException; RankingDTO getRanking(String id, RankingQuery query) throws ContestException; + + Workbook exportRanking(String id, RankingQuery query) throws ContestException; } diff --git a/src/main/java/cn/kastner/oj/service/impl/ContestServiceImpl.java b/src/main/java/cn/kastner/oj/service/impl/ContestServiceImpl.java index 5a01552..df9fe4e 100644 --- a/src/main/java/cn/kastner/oj/service/impl/ContestServiceImpl.java +++ b/src/main/java/cn/kastner/oj/service/impl/ContestServiceImpl.java @@ -17,6 +17,10 @@ import cn.kastner.oj.service.ContestService; import cn.kastner.oj.util.CommonUtil; import cn.kastner.oj.util.DTOMapper; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -243,7 +247,9 @@ public List addUsersByGroups(List groupIdList, String co for (Group group : groupList) { for (User user : group.getUserSet()) { if (!userSet.contains(user)) { - addRankingUserList.add(RankingUserFactory.create(user, contest, group)); + RankingUser rankingUser = RankingUserFactory.create(user, contest, group); + timeCostRepository.saveAll(rankingUser.getTimeList()); + addRankingUserList.add(rankingUser); } } } @@ -387,7 +393,6 @@ public List addProblems(List problemIdList, String contestId TimeCost timeCost = new TimeCost(); timeCost.setContestProblem(contestProblem); timeCost.setRankingUser(rankingUser); - timeCost.setFrozen(true); addTimeCostList.add(timeCost); } @@ -436,7 +441,6 @@ public void addProblem(String problemId, String contestId, Integer score) TimeCost timeCost = new TimeCost(); timeCost.setContestProblem(contestProblem); timeCost.setRankingUser(rankingUser); - timeCost.setFrozen(true); addTimeCostList.add(timeCost); @@ -606,20 +610,13 @@ public RankingDTO getRanking(String id, RankingQuery query) throws ContestExcept return rankingDTO; } else if (contest.getStatus() == ContestStatus.ENDED) { Set rankingUserList = rankingUserRepository.findByContest(contest); + + if (!rankingUserList.isEmpty()) { + rankingUserList = filterWithQuery(rankingUserList, query); + } for (RankingUser ru : rankingUserList) { ru.setTimeList(timeCostRepository.findByRankingUser(ru)); } - if (!rankingUserList.isEmpty()) { - if (null != query.getGroupId()) { - rankingUserList = rankingUserList.stream().filter( - rankingUserDTO -> query.getGroupId().equals(rankingUserDTO.getGroupId()) - ).collect(Collectors.toSet()); - } else if (null != query.getTeacherId()) { - rankingUserList = rankingUserList.stream().filter( - rankingUserDTO -> query.getTeacherId().equals(rankingUserDTO.getTeacherId()) - ).collect(Collectors.toSet()); - } - } contest.setRankingUserList(rankingUserList); return mapper.contestToRankingDTO(contest); } else { @@ -628,6 +625,85 @@ public RankingDTO getRanking(String id, RankingQuery query) throws ContestExcept } + private Set filterWithQuery( + Set rankingUserList, RankingQuery query) { + if (null != query.getGroupId()) { + return rankingUserList.stream().filter( + rankingUserDTO -> query.getGroupId().equals(rankingUserDTO.getGroupId()) + ).collect(Collectors.toSet()); + } else if (null != query.getTeacherId()) { + return rankingUserList.stream().filter( + rankingUserDTO -> query.getTeacherId().equals(rankingUserDTO.getTeacherId()) + ).collect(Collectors.toSet()); + } + return rankingUserList; + } + + @Override + public Workbook exportRanking(String id, RankingQuery query) throws ContestException { + Contest contest = + contestRepository + .findById(id) + .orElseThrow(() -> new ContestException(ContestException.NO_SUCH_CONTEST)); + Set rankingUserSet = rankingUserRepository.findByContest(contest); + rankingUserSet = filterWithQuery(rankingUserSet, query); + XSSFWorkbook workbook = new XSSFWorkbook(); + XSSFSheet sheet = workbook.createSheet("排名信息"); + + int rowNum = 0; + Row header = sheet.createRow(rowNum++); + int headerColumnNum = 0; + header.createCell(headerColumnNum++).setCellValue("排名"); + header.createCell(headerColumnNum++).setCellValue("学号"); + header.createCell(headerColumnNum++).setCellValue("姓名"); + if (contest.getJudgeType() == JudgeType.IMMEDIATELY) { + header.createCell(headerColumnNum++).setCellValue("提交数"); + header.createCell(headerColumnNum++).setCellValue("通过数"); + } else { + header.createCell(headerColumnNum++).setCellValue("总分"); + } + header.createCell(headerColumnNum++).setCellValue("指导教师"); + header.createCell(headerColumnNum++).setCellValue("小组/班级"); + for (int i = 'A', j = 0; j < contestProblemRepository.countByContest(contest); i++, j++) { + header.createCell(headerColumnNum++).setCellValue(String.format("%c", i)); + } + + for (RankingUser rankingUser : rankingUserSet) { + Row row = sheet.createRow(rowNum++); + int columnNum = 0; + row.createCell(columnNum++).setCellValue(rankingUser.getRankingNumber()); + row.createCell(columnNum++).setCellValue(rankingUser.getUser().getStudentNumber()); + row.createCell(columnNum++).setCellValue(rankingUser.getUser().getName()); + if (contest.getJudgeType() == JudgeType.IMMEDIATELY) { + row.createCell(columnNum++).setCellValue(rankingUser.getAcceptCount()); + row.createCell(columnNum++).setCellValue(rankingUser.getSubmitCount()); + } else { + row.createCell(columnNum++).setCellValue(rankingUser.getScore()); + } + Optional teacherOptional = userRepository.findById(rankingUser.getTeacherId()); + if (teacherOptional.isPresent()) { + row.createCell(columnNum++).setCellValue(teacherOptional.get().getName()); + } else { + columnNum++; + } + Optional groupOptional = groupRepository.findById(rankingUser.getGroupId()); + if (groupOptional.isPresent()) { + row.createCell(columnNum++).setCellValue(groupOptional.get().getName()); + } else { + columnNum++; + } + List timeCostList = timeCostRepository.findByRankingUser(rankingUser); + for (TimeCost timeCost : timeCostList) { + if (contest.getJudgeType() == JudgeType.IMMEDIATELY) { + row.createCell(columnNum++).setCellValue(timeCost.getPassed() ? 1 : 0); + } else { + row.createCell(columnNum++).setCellValue(timeCost.getScore()); + } + } + } + return workbook; + } + private void requirePassword(Contest contest) throws ContestException { if (ContestType.SECRET_WITH_PASSWORD.equals(contest.getContestType()) && contest.getPassword() == null) { diff --git a/src/main/java/cn/kastner/oj/task/RankingComputingTask.java b/src/main/java/cn/kastner/oj/task/RankingComputingTask.java index c9169c2..585e030 100644 --- a/src/main/java/cn/kastner/oj/task/RankingComputingTask.java +++ b/src/main/java/cn/kastner/oj/task/RankingComputingTask.java @@ -60,7 +60,7 @@ public void computeRank() { i--; } } - redisTemplate.opsForValue().set("rankingUserList:" + contest.getId(), mapper.toRankingUserDTOs(rankingUserList)); + redisTemplate.opsForValue().set("rankingUserList:" + contest.getId(), mapper.toRankingUserDTOs(rankingUserRepository.saveAll(rankingUserList))); } } }