package space.anyi.serve.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import space.anyi.codesandbox.CodeContext; import space.anyi.codesandbox.CodeResponse; import space.anyi.codesandbox.CodeSandBox; import space.anyi.codesandbox.TestCase; import space.anyi.serve.entity.contest.Contest; import space.anyi.serve.entity.contest.ContestProblem; import space.anyi.serve.entity.contest.Submission; import space.anyi.serve.entity.contest.SubmissionVo; import space.anyi.serve.entity.contest.SubmitDto; import space.anyi.serve.entity.problem.Problem; import space.anyi.serve.entity.problem.ProblemTest; import space.anyi.serve.entity.user.User; import space.anyi.serve.mapper.ContestProblemMapper; import space.anyi.serve.mapper.ProblemTestMapper; import space.anyi.serve.mapper.SubmissionMapper; import space.anyi.serve.service.ContestService; import space.anyi.serve.service.ProblemService; import space.anyi.serve.service.SubmissionService; import space.anyi.serve.util.SecurityUtils; import java.util.ArrayList; import java.util.List; import java.util.Objects; @Service public class SubmissionServiceImpl extends ServiceImpl implements SubmissionService { private final SubmissionMapper submissionMapper; private final ProblemTestMapper problemTestMapper; private final ContestProblemMapper contestProblemMapper; private final ProblemService problemService; private final ContestService contestService; private final CodeSandBox codeSandBox; public SubmissionServiceImpl(SubmissionMapper submissionMapper, ProblemTestMapper problemTestMapper, ContestProblemMapper contestProblemMapper, ProblemService problemService, ContestService contestService, CodeSandBox codeSandBox) { this.submissionMapper = submissionMapper; this.problemTestMapper = problemTestMapper; this.contestProblemMapper = contestProblemMapper; this.problemService = problemService; this.contestService = contestService; this.codeSandBox = codeSandBox; } @Override @Transactional public SubmissionVo submit(SubmitDto dto, Long userId) { Problem problem = problemService.getById(dto.getProblemId()); if (problem == null) { throw new IllegalArgumentException("题目不存在"); } if (dto.getContestId() != null) { validateContestSubmission(dto.getContestId(), userId); } List problemTests = problemTestMapper.selectList( new LambdaQueryWrapper() .eq(ProblemTest::getProblemId, dto.getProblemId()) .orderByAsc(ProblemTest::getSortOrder)); if (problemTests.isEmpty()) { throw new IllegalArgumentException("该题目还没有评测用例,无法提交"); } Submission submission = new Submission(); submission.setProblemId(dto.getProblemId()); submission.setUserId(userId); submission.setContestId(dto.getContestId()); submission.setCode(dto.getCode()); submission.setLanguage(dto.getLanguage()); submission.setStatus(Submission.STATUS_JUDGING); submission.setTotalTestCases(problemTests.size()); submission.setPassedTestCases(0); submissionMapper.insert(submission); List testCases = new ArrayList<>(); for (ProblemTest problemTest : problemTests) { testCases.add(new TestCase(problemTest.getId(), problemTest.getInput(), problemTest.getOutput())); } CodeContext context = new CodeContext(submission.getId(), dto.getCode(), dto.getLanguage(), problem.getTimeLimitMs().longValue(), problem.getMemoryLimitMb() * 1024L, testCases); CodeResponse response = codeSandBox.process(context); submission.setStatus(response.status()); submission.setTimeUsedMs(response.usedTime() == null ? 0 : response.usedTime().intValue()); submission.setMemoryUsedKb(response.usedMemory() == null ? 0 : response.usedMemory().intValue()); submission.setJudgeDetail(response.message()); if (response.testCaseResponses() != null) { long passed = response.testCaseResponses().stream() .filter(c -> Submission.STATUS_AC.equals(c.status())) .count(); submission.setPassedTestCases((int) passed); } submission.setScore(resolveScore(dto.getContestId(), submission, problem)); submissionMapper.updateById(submission); problem.setTryCount(problem.getTryCount() == null ? 1 : problem.getTryCount() + 1); if (Submission.STATUS_AC.equals(submission.getStatus())) { problem.setAcceptedCount(problem.getAcceptedCount() == null ? 1 : problem.getAcceptedCount() + 1); } problemService.updateById(problem); return SubmissionVo.from(submission); } @Override public SubmissionVo getDetail(Long submissionId, Long userId) { Submission submission = submissionMapper.selectById(submissionId); if (submission == null) { throw new IllegalArgumentException("提交记录不存在"); } String role = SecurityUtils.getCurrentRole(); if (!User.ROLE_ADMIN.equals(role) && !User.ROLE_COACH.equals(role) && !Objects.equals(submission.getUserId(), userId)) { throw new IllegalArgumentException("无权限查看该提交记录"); } return SubmissionVo.from(submission); } @Override public Page queryMySubmissions(Long userId, Integer pageNum, Integer pageSize) { Page page = page(new Page<>(pageNum, pageSize), new LambdaQueryWrapper() .eq(Submission::getUserId, userId) .orderByDesc(Submission::getCreatedAt)); return convertToVoPage(page); } @Override public Page queryByProblemId(Long problemId, Integer pageNum, Integer pageSize) { Page page = page(new Page<>(pageNum, pageSize), new LambdaQueryWrapper() .eq(Submission::getProblemId, problemId) .orderByDesc(Submission::getCreatedAt)); return convertToVoPage(page); } private void validateContestSubmission(Long contestId, Long userId) { Contest contest = contestService.getById(contestId); if (contest == null) { throw new IllegalArgumentException("比赛不存在"); } if (!Contest.STATUS_RUNNING.equals(contest.getStatus())) { throw new IllegalArgumentException("比赛未在进行中"); } if (!contestService.isRegistered(contestId, userId)) { throw new IllegalArgumentException("未报名该比赛"); } } private Integer resolveScore(Long contestId, Submission submission, Problem problem) { if (!Submission.STATUS_AC.equals(submission.getStatus())) { return 0; } if (contestId == null) { return 0; } ContestProblem contestProblem = contestProblemMapper.selectOne(new LambdaQueryWrapper() .eq(ContestProblem::getContestId, contestId) .eq(ContestProblem::getProblemId, submission.getProblemId())); return contestProblem == null || contestProblem.getScore() == null ? 0 : contestProblem.getScore(); } private Page convertToVoPage(Page page) { Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); result.setRecords(SubmissionVo.from(page.getRecords())); return result; } }