-
Notifications
You must be signed in to change notification settings - Fork 4
fix(events): 响应 PR #9 Copilot CR — N+1 / seed 幂等 / admin seed / 测试 schema #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
src/main/java/com/involutionhell/backend/events/controller/AdminUserController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| package com.involutionhell.backend.events.controller; | ||
|
|
||
| import cn.dev33.satoken.annotation.SaCheckRole; | ||
| import cn.dev33.satoken.stp.StpUtil; | ||
| import com.involutionhell.backend.common.api.ApiResponse; | ||
| import com.involutionhell.backend.events.dto.AdminUserView; | ||
| import com.involutionhell.backend.events.dto.UpdateUserAdminRoleRequest; | ||
| import com.involutionhell.backend.usercenter.model.UserAccount; | ||
| import com.involutionhell.backend.usercenter.repository.UserAccountRepository; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PutMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import java.util.LinkedHashSet; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * 超管用户管理接口。@SaCheckRole("superadmin") 类级保护。 | ||
| * | ||
| * 路由: | ||
| * - GET /api/admin/users?q=xxx 列出全部用户(可按 username / display_name 模糊搜索) | ||
| * - PUT /api/admin/users/{id}/admin {admin: true|false} 授予 / 撤销 admin 角色 | ||
| * | ||
| * 为什么只有这两个接口: | ||
| * - 产品目前只需要"让超管给其他人挂 / 摘 admin",不需要编辑邮箱 / 头像等资料 | ||
| * - superadmin 角色不允许通过 API 授予;想加第二个 superadmin 只能走 DB,防止误操作 | ||
| * - user 角色由 AuthService OAuth 流程自动挂,也不用 API 管 | ||
| * | ||
| * 路径前缀 /api/admin/* 和 EventAdminController 保持同一家族,SaToken 白名单默认 | ||
| * 不放行,走登录 + 角色校验兜底。 | ||
| */ | ||
| @RestController | ||
| @RequestMapping("/api/admin/users") | ||
| @SaCheckRole("superadmin") | ||
| public class AdminUserController { | ||
|
|
||
| /** 允许由 API 授予的角色白名单。superadmin 不在此列——必须走 DB,防误操作。 */ | ||
| private static final String ROLE_ADMIN = "admin"; | ||
| private static final String ROLE_USER = "user"; | ||
| private static final String ROLE_SUPERADMIN = "superadmin"; | ||
|
|
||
| private final UserAccountRepository userAccountRepository; | ||
|
|
||
| public AdminUserController(UserAccountRepository userAccountRepository) { | ||
| this.userAccountRepository = userAccountRepository; | ||
| } | ||
|
|
||
| @GetMapping | ||
| public ApiResponse<List<AdminUserView>> list(@RequestParam(required = false) String q) { | ||
| List<UserAccount> all = userAccountRepository.findAll(); | ||
| String keyword = q == null ? null : q.trim().toLowerCase(Locale.ROOT); | ||
| List<AdminUserView> views = all.stream() | ||
| .filter(u -> matches(u, keyword)) | ||
| .map(AdminUserView::from) | ||
| .toList(); | ||
| return ApiResponse.ok(views); | ||
| } | ||
|
|
||
| private static boolean matches(UserAccount u, String kw) { | ||
| if (kw == null || kw.isEmpty()) return true; | ||
| if (u.username() != null | ||
| && u.username().toLowerCase(Locale.ROOT).contains(kw)) { | ||
| return true; | ||
| } | ||
| if (u.displayName() != null | ||
| && u.displayName().toLowerCase(Locale.ROOT).contains(kw)) { | ||
| return true; | ||
| } | ||
| if (u.email() != null | ||
| && u.email().toLowerCase(Locale.ROOT).contains(kw)) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * 切换某用户 admin 角色。 | ||
| * | ||
| * 规则: | ||
| * - 目标用户是 superadmin 时直接 403 —— 不允许给 superadmin 再 "去 admin 化" | ||
| * (superadmin 本来就包含 admin 语义,降级 superadmin 只能走 DB) | ||
| * - 自己不能给自己摘 admin(防止唯一 admin 把自己锁出来) | ||
| * - user 角色始终保留;superadmin 角色保留不动 | ||
| * - permissions 字段我们在这一阶段还不细管,直接保留原值;未来如果要按 role | ||
| * 派发权限,这里再扩 | ||
| */ | ||
| @PutMapping("/{userId}/admin") | ||
| public ApiResponse<AdminUserView> setAdminRole( | ||
| @PathVariable Long userId, | ||
| @RequestBody UpdateUserAdminRoleRequest req) { | ||
| if (req == null) return new ApiResponse<>(false, "请求体不能为空", null); | ||
|
|
||
| Optional<UserAccount> maybe = userAccountRepository.findById(userId); | ||
| if (maybe.isEmpty()) return new ApiResponse<>(false, "用户不存在", null); | ||
| UserAccount target = maybe.get(); | ||
|
|
||
| if (target.roles().contains(ROLE_SUPERADMIN)) { | ||
| return new ApiResponse<>(false, "superadmin 用户不允许通过 API 修改角色", null); | ||
| } | ||
|
|
||
| long self = StpUtil.getLoginIdAsLong(); | ||
| if (target.id().equals(self) && !req.admin()) { | ||
| return new ApiResponse<>(false, "不能给自己撤销 admin 角色", null); | ||
| } | ||
|
|
||
| // 维持原 roles 集合,移除 ROLE_ADMIN 后按请求再加回去;user 始终保留 | ||
| Set<String> next = new LinkedHashSet<>(target.roles()); | ||
| next.add(ROLE_USER); | ||
| if (req.admin()) { | ||
| next.add(ROLE_ADMIN); | ||
| } else { | ||
| next.remove(ROLE_ADMIN); | ||
| } | ||
|
|
||
| UserAccount updated = userAccountRepository.updateAuthorization( | ||
| target.id(), next, target.permissions()); | ||
| return ApiResponse.ok("角色已更新", AdminUserView.from(updated)); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
src/main/java/com/involutionhell/backend/events/dto/AdminUserView.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package com.involutionhell.backend.events.dto; | ||
|
|
||
| import com.involutionhell.backend.usercenter.model.UserAccount; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * 超管用户管理列表项。 | ||
| * | ||
| * 独立于现有的 UserView:这里加了 roles 完整快照(前端 checkbox 显隐需要), | ||
| * 不含 passwordHash 等敏感字段。 | ||
| * | ||
| * 放在 events 模块的 dto 包是因为"管理员界面"入口目前由 Events 模块承担; | ||
| * 之后如果拆出独立的 admin 模块,再连同 AdminUserController 一起搬过去。 | ||
| */ | ||
| public record AdminUserView( | ||
| Long id, | ||
| String username, | ||
| String displayName, | ||
| String email, | ||
| String avatarUrl, | ||
| Long githubId, | ||
| boolean enabled, | ||
| List<String> roles | ||
| ) { | ||
| public static AdminUserView from(UserAccount u) { | ||
| return new AdminUserView( | ||
| u.id(), | ||
| u.username(), | ||
| u.displayName(), | ||
| u.email(), | ||
| u.avatarUrl(), | ||
| u.githubId(), | ||
| u.enabled(), | ||
| List.copyOf(u.roles()) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/main/java/com/involutionhell/backend/events/dto/UpdateUserAdminRoleRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.involutionhell.backend.events.dto; | ||
|
|
||
| /** | ||
| * 超管界面的"切换用户 admin 角色"入参。 | ||
| * | ||
| * 刻意只暴露一个布尔字段 admin: | ||
| * true → 授予 admin 角色(普通用户变管理员) | ||
| * false → 撤销 admin 角色(降级回普通用户) | ||
| * | ||
| * 为什么不让前端直接传 roles 列表: | ||
| * - 如果暴露 roles 字段,前端可以伪造 "superadmin"、"owner" 等未定义角色, | ||
| * 后端就要做严格白名单,不如直接收一个布尔动作 | ||
| * - superadmin 角色永远不允许通过 API 授予 / 撤销(防止误操作把唯一站长 | ||
| * 降级锁死后台);想升级新的 superadmin 只能走数据库 | ||
| * - user 角色由 AuthService 在 OAuth 登录时自动挂上,前端不用管 | ||
| */ | ||
| public record UpdateUserAdminRoleRequest(boolean admin) {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
新增的批量统计方法缺少回归测试覆盖(例如:多 event_id 计数是否正确、未出现在结果集中的 id 是否按 0 处理、传入空集合是否短路不打 DB)。仓库里已有 JdbcTemplate 仓储的集成测试模式(H2 + test-schema.sql),建议为该方法补一组类似的 repository 集成测试,避免后续改 SQL/方言时回归。