Jelajahi Sumber

# feat: 比赛详情/排行榜按类型鉴权,私有比赛不可自助报名,教练仅可管理自己创建的比赛

yangyi 2 bulan lalu
induk
melakukan
b5c1a86283

+ 2 - 1
src/main/java/space/anyi/serve/config/SecurityConfig.java

@@ -53,7 +53,8 @@ public class SecurityConfig {
                 .authorizeHttpRequests(authorize -> authorize
                         .requestMatchers("/auth/login", "/auth/register").permitAll()
                         .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
-                        .requestMatchers(HttpMethod.GET, "/problem", "/problem/{id}", "/category", "/contest").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/problem", "/problem/{id}", "/category",
+                                "/contest", "/contest/{id}", "/contest/{id}/ranking").permitAll()
                         .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                         .anyRequest().authenticated()
                 )

+ 24 - 9
src/main/java/space/anyi/serve/controller/ContestController.java

@@ -52,36 +52,51 @@ public class ContestController {
             @NotNull(message = "页码不能为null") @RequestParam(defaultValue = "1") Integer pageNum,
             @Parameter(description = "分页大小")
             @NotNull(message = "分页大小不能为null") @RequestParam(defaultValue = "10") Integer pageSize) {
-        boolean onlyPublic = SecurityUtils.isAnonymous();
+        boolean onlyPublic = !SecurityUtils.isStaff();
         Page<Contest> page = Page.of(pageNum, pageSize);
         Page<ContestVo> result = contestService.queryByPage(title, status, onlyPublic, page);
         return Response.ok(new PageVo<>(result.getTotal(), result.getRecords()));
     }
 
-    @Operation(summary = "查询比赛详情", security = @SecurityRequirement(name = "openApiSecurityScheme"))
+    @Operation(summary = "查询比赛详情")
     @GetMapping("{id}")
     public Response<ContestDetailVo> getDetail(@Parameter(description = "比赛ID") @PathVariable Long id,
                                                @Parameter(description = "访问密码(密码型比赛需传入)") @RequestParam(required = false) String password) {
         Long userId = SecurityUtils.getCurrentUserId();
         ContestVo base = contestService.getBaseInfo(id);
+        if (User.ROLE_ADMIN.equals(SecurityUtils.getCurrentRole())) {
+            return Response.ok(contestService.getDetail(id, userId));
+        }
         if (Contest.TYPE_PASSWORD.equals(base.getType())) {
-            if (!contestService.checkPassword(id, password)) {
+            if (userId == null || !contestService.checkPassword(id, password)) {
                 return Response.ok(ContestDetailVo.from(base));
             }
         } else if (Contest.TYPE_PRIVATE.equals(base.getType())) {
-            String role = SecurityUtils.getCurrentRole();
-            boolean staff = User.ROLE_COACH.equals(role) || User.ROLE_ADMIN.equals(role);
-            if (!contestService.isRegistered(id, userId)
-                    && !contestService.isCreator(id, userId) && !staff) {
+            if (!contestService.isCreator(id, userId)) {
                 return Response.error("无权限查看该比赛");
             }
         }
         return Response.ok(contestService.getDetail(id, userId));
     }
 
-    @Operation(summary = "查询比赛排行榜", security = @SecurityRequirement(name = "openApiSecurityScheme"))
+    @Operation(summary = "查询比赛排行榜")
     @GetMapping("{id}/ranking")
-    public Response<List<RankVo>> getRanking(@Parameter(description = "比赛ID") @PathVariable Long id) {
+    public Response<List<RankVo>> getRanking(@Parameter(description = "比赛ID") @PathVariable Long id,
+                                             @Parameter(description = "访问密码(密码型比赛需传入)") @RequestParam(required = false) String password) {
+        Long userId = SecurityUtils.getCurrentUserId();
+        ContestVo base = contestService.getBaseInfo(id);
+        if (User.ROLE_ADMIN.equals(SecurityUtils.getCurrentRole())) {
+            return Response.ok(contestService.getRanking(id));
+        }
+        if (Contest.TYPE_PASSWORD.equals(base.getType())) {
+            if (userId == null || !contestService.checkPassword(id, password)) {
+                return Response.error("无权限查看该比赛排行榜");
+            }
+        } else if (Contest.TYPE_PRIVATE.equals(base.getType())) {
+            if (!contestService.isCreator(id, userId)) {
+                return Response.error("无权限查看该比赛排行榜");
+            }
+        }
         return Response.ok(contestService.getRanking(id));
     }
 

+ 20 - 0
src/main/java/space/anyi/serve/service/impl/ContestServiceImpl.java

@@ -140,6 +140,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
         if (contest == null) {
             throw new IllegalArgumentException("比赛不存在");
         }
+        assertCoachOwner(contest);
         assertNotStarted(contest);
         contest.setTitle(dto.getTitle());
         contest.setDescription(dto.getDescription());
@@ -182,6 +183,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
         if (contest == null) {
             throw new IllegalArgumentException("比赛不存在");
         }
+        assertCoachOwner(contest);
         assertNotStarted(contest);
         Problem problem = problemService.getById(dto.getProblemId());
         if (problem == null) {
@@ -201,6 +203,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
         if (contest == null) {
             throw new IllegalArgumentException("比赛不存在");
         }
+        assertCoachOwner(contest);
         assertNotStarted(contest);
         ContestProblem contestProblem = new ContestProblem();
         contestProblem.setContestId(contestId);
@@ -218,6 +221,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
         if (contest == null) {
             throw new IllegalArgumentException("比赛不存在");
         }
+        assertCoachOwner(contest);
         assertNotStarted(contest);
         return contestProblemMapper.delete(new LambdaQueryWrapper<ContestProblem>()
                 .eq(ContestProblem::getContestId, contestId)
@@ -231,6 +235,9 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
         if (contest == null) {
             throw new IllegalArgumentException("比赛不存在");
         }
+        if (Contest.TYPE_PRIVATE.equals(contest.getType())) {
+            throw new IllegalArgumentException("私有比赛不可自助报名");
+        }
         if (!Contest.STATUS_PENDING.equals(contest.getStatus()) && !Contest.STATUS_RUNNING.equals(contest.getStatus())) {
             throw new IllegalArgumentException("比赛未开始或已结束,无法报名");
         }
@@ -250,6 +257,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
 
     @Override
     public List<ContestRegistrationVo> queryRegistrations(Long contestId) {
+        assertCoachOwner(getById(contestId));
         return contestRegistrationMapper.selectByContestId(contestId);
     }
 
@@ -278,4 +286,16 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> impl
             throw new IllegalArgumentException("比赛已开始,无法修改");
         }
     }
+
+    private void assertCoachOwner(Contest contest) {
+        if (contest == null) {
+            throw new IllegalArgumentException("比赛不存在");
+        }
+        if (User.ROLE_COACH.equals(SecurityUtils.getCurrentRole())) {
+            Long userId = SecurityUtils.getCurrentUserId();
+            if (!Objects.equals(contest.getCreatedBy(), userId)) {
+                throw new IllegalArgumentException("教练只能管理自己创建的比赛");
+            }
+        }
+    }
 }