From 305036ddfe627a09ce161c947b56a5f50b4f5552 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Sep 2026 22:54:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=8B=E8=BD=BD=E4=BA=BA?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=B9=B6=E6=8A=8A?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E5=88=A4=E5=AE=9A=E6=94=B6=E6=95=9B?= =?UTF-8?q?=E5=88=B0=E6=9C=8D=E5=8A=A1=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gallery 增加 downloaderName,仅在管理员查询任务列表时填充;普通用户 既不获取也不下发他人身份 - 新增 GET /GalleryManage/downloader?gid=&AuthCode=,按 gid 返回实际下载人 昵称,非管理员一律拒绝 - /validate 下发 isAdmin,前端不再各自硬编码 userId === 3 - 管理员判定统一走 UserService.ADMIN_USER_ID - 补充列表填充、按 gid 查询的权限与缺任务分支测试 --- .../Controller/GalleryManageController.java | 8 ++ .../Controller/PublicController.java | 6 +- .../lionwebsite/Dao/normal/UserMapper.java | 3 + .../com/lion/lionwebsite/Domain/Gallery.java | 5 ++ .../Service/GalleryManageService.java | 47 +++++++++++ .../lion/lionwebsite/Service/UserService.java | 14 ++++ .../GalleryManageControllerTest.java | 23 +++++ .../Controller/PublicControllerTest.java | 15 +++- .../lionwebsite/Service/GalleryQueryTest.java | 83 +++++++++++++++++++ 9 files changed, 202 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/lion/lionwebsite/Controller/GalleryManageController.java b/src/main/java/com/lion/lionwebsite/Controller/GalleryManageController.java index a2fb2b0..63624fd 100644 --- a/src/main/java/com/lion/lionwebsite/Controller/GalleryManageController.java +++ b/src/main/java/com/lion/lionwebsite/Controller/GalleryManageController.java @@ -67,6 +67,14 @@ public class GalleryManageController { return galleryManageService.deleteGalleryByGid(gid, AuthCode); } + /** 按 gid 查实际下载人昵称,仅管理员可用。 */ + @GetMapping("/downloader") + public String selectDownloader(Integer gid, String AuthCode){ + if(gid == null) + return Response._failure("参数不全"); + return galleryManageService.selectDownloaderByGid(gid, AuthCode); + } + @PostMapping("/collect") public String collectGallery(Integer gid, String AuthCode){ diff --git a/src/main/java/com/lion/lionwebsite/Controller/PublicController.java b/src/main/java/com/lion/lionwebsite/Controller/PublicController.java index 64a0f25..0996edb 100644 --- a/src/main/java/com/lion/lionwebsite/Controller/PublicController.java +++ b/src/main/java/com/lion/lionwebsite/Controller/PublicController.java @@ -6,6 +6,7 @@ import com.lion.lionwebsite.Service.PublicService; import com.lion.lionwebsite.Service.QueryService; import com.lion.lionwebsite.Service.RemoteService; import com.lion.lionwebsite.Service.SubService; +import com.lion.lionwebsite.Service.UserService; import com.lion.lionwebsite.Util.Response; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -82,9 +83,12 @@ public class PublicController { Response response = Response.generateResponse(); User user = publicService.getUserId(AuthCode); String isAvailable = remoteService.isDead() ? "false": "true"; + // 管理员标记随登录一起下发,前端据此决定是否显示下载人信息与筛选。 + String isAdmin = user.getId() == UserService.ADMIN_USER_ID ? "true" : "false"; response.success(String.format("{\"userId\": %d, " + "\"username\": \"%s\", " + - "\"isAvailable\": %s}", user.getId(), user.getUsername(), isAvailable)); + "\"isAvailable\": %s, " + + "\"isAdmin\": %s}", user.getId(), user.getUsername(), isAvailable, isAdmin)); return response.toJSONString(); } diff --git a/src/main/java/com/lion/lionwebsite/Dao/normal/UserMapper.java b/src/main/java/com/lion/lionwebsite/Dao/normal/UserMapper.java index 7fea6ff..16dae8f 100644 --- a/src/main/java/com/lion/lionwebsite/Dao/normal/UserMapper.java +++ b/src/main/java/com/lion/lionwebsite/Dao/normal/UserMapper.java @@ -14,6 +14,9 @@ public interface UserMapper { @Select("select * from User where username=#{username}") User selectUserByUsername(String username); + @Select("select * from User where id=#{id}") + User selectUserById(int id); + @Select("select AuthCode from User") String[] selectAllAuthCode(); diff --git a/src/main/java/com/lion/lionwebsite/Domain/Gallery.java b/src/main/java/com/lion/lionwebsite/Domain/Gallery.java index 53c87a9..4c59ea2 100644 --- a/src/main/java/com/lion/lionwebsite/Domain/Gallery.java +++ b/src/main/java/com/lion/lionwebsite/Domain/Gallery.java @@ -48,6 +48,11 @@ public class Gallery { @JsonProperty("downloader") private int downloader; //下载人 + /** 下载人昵称。仅管理员查询时填充,普通用户拿不到,避免暴露他人信息。 */ + @JsonProperty("downloaderName") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String downloaderName; + @JsonProperty("collector") @JsonInclude(JsonInclude.Include.NON_EMPTY) private String collector; //收藏人 diff --git a/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java b/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java index b39d94d..314d017 100644 --- a/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java +++ b/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java @@ -13,6 +13,7 @@ import com.lion.lionwebsite.Util.GalleryUtil; import com.lion.lionwebsite.Util.Response; import tools.jackson.core.JacksonException; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.Data; @@ -254,10 +255,56 @@ public class GalleryManageService { } } + // 下载人昵称只对管理员下发;一次建表避免逐条查询。 + if (userId == UserService.ADMIN_USER_ID) + fillDownloaderNames(galleries); + response.success(new ObjectMapper().valueToTree(galleries).toString()); return response.toJSONString(); } + /** 给任务列表补上下载人昵称,找不到用户时保留空值而不是报错。 */ + private void fillDownloaderNames(Gallery[] galleries) { + User[] users = userMapper.selectAllUser(); + Map names = new HashMap<>(); + if (users != null) + for (User user : users) + names.put(user.getId(), user.getUsername()); + + for (Gallery gallery : galleries) + gallery.setDownloaderName(names.get(gallery.getDownloader())); + } + + /** + * 查询某个任务的实际下载人昵称。 + * 只有管理员可调用,普通用户一律拒绝,避免暴露他人身份。 + * + * @param gid 任务 gid + * @param AuthCode 调用方授权码 + */ + public String selectDownloaderByGid(int gid, String AuthCode) { + Response response = Response.generateResponse(); + User requester = userMapper.selectUserByAuthCode(AuthCode); + if (requester == null || requester.getId() != UserService.ADMIN_USER_ID) { + response.failure("无权查看下载人"); + return response.toJSONString(); + } + + Gallery gallery = galleryMapper.selectGalleryByGid(gid); + if (gallery == null) { + response.failure("任务不存在"); + return response.toJSONString(); + } + + User downloader = userMapper.selectUserById(gallery.getDownloader()); + ObjectNode node = objectMapper.createObjectNode(); + node.put("gid", gid); + node.put("downloader", gallery.getDownloader()); + node.put("downloaderName", downloader == null ? "" : downloader.getUsername()); + response.success(node); + return response.toJSONString(); + } + /** * 通过图片名查询图片 * diff --git a/src/main/java/com/lion/lionwebsite/Service/UserService.java b/src/main/java/com/lion/lionwebsite/Service/UserService.java index a3d43d8..bed1cde 100644 --- a/src/main/java/com/lion/lionwebsite/Service/UserService.java +++ b/src/main/java/com/lion/lionwebsite/Service/UserService.java @@ -135,4 +135,18 @@ public class UserService{ public int getUserId(String AuthCode){ return userMapper.selectUserByAuthCode(AuthCode).getId(); } + + /** + * 管理员判定。项目没有角色表,历史上以 id=3(狮子)作为管理员, + * 前端也是用 userId === 3 判断;这里把这个约定收敛到一处, + * 权限相关的服务端校验统一走它。 + */ + public static final int ADMIN_USER_ID = 3; + + public boolean isAdmin(String AuthCode){ + if(AuthCode == null) + return false; + User user = userMapper.selectUserByAuthCode(AuthCode); + return user != null && user.getId() == ADMIN_USER_ID; + } } diff --git a/src/test/java/com/lion/lionwebsite/Controller/GalleryManageControllerTest.java b/src/test/java/com/lion/lionwebsite/Controller/GalleryManageControllerTest.java index 5490d5a..a0dbdc9 100644 --- a/src/test/java/com/lion/lionwebsite/Controller/GalleryManageControllerTest.java +++ b/src/test/java/com/lion/lionwebsite/Controller/GalleryManageControllerTest.java @@ -157,6 +157,29 @@ class GalleryManageControllerTest { // ---------- deleteTask ---------- + // ---------- 下载人查询 ---------- + + /** 缺 gid 时在控制器层拦住,不进服务层。 */ + @Test + void selectDownloaderRejectsMissingGid() throws Exception { + mockMvc.perform(get("/GalleryManage/downloader").param("AuthCode", "code")) + .andExpect(status().isOk()) + .andExpect(content().string(containsFailure())); + + verifyNoInteractions(galleryManageService); + } + + /** 正常请求必须把 gid 与授权码原样传给服务层。 */ + @Test + void selectDownloaderDelegatesWithGidAndAuthCode() throws Exception { + when(galleryManageService.selectDownloaderByGid(500, "code")).thenReturn("{}"); + + mockMvc.perform(get("/GalleryManage/downloader").param("gid", "500").param("AuthCode", "code")) + .andExpect(status().isOk()); + + verify(galleryManageService).selectDownloaderByGid(500, "code"); + } + @Test void deleteTaskRejectsMissingGid() throws Exception { mockMvc.perform(delete("/GalleryManage").param("AuthCode", "code")) diff --git a/src/test/java/com/lion/lionwebsite/Controller/PublicControllerTest.java b/src/test/java/com/lion/lionwebsite/Controller/PublicControllerTest.java index d631478..077ec2f 100644 --- a/src/test/java/com/lion/lionwebsite/Controller/PublicControllerTest.java +++ b/src/test/java/com/lion/lionwebsite/Controller/PublicControllerTest.java @@ -94,7 +94,20 @@ class PublicControllerTest { .andExpect(status().isOk()) .andExpect(content().string(org.hamcrest.Matchers.containsString("\\\"userId\\\": 7"))) .andExpect(content().string(org.hamcrest.Matchers.containsString("\\\"username\\\": \\\"alice\\\""))) - .andExpect(content().string(org.hamcrest.Matchers.containsString("\\\"isAvailable\\\": true"))); + .andExpect(content().string(org.hamcrest.Matchers.containsString("\\\"isAvailable\\\": true"))) + // 普通用户 isAdmin=false,前端据此隐藏下载人信息。 + .andExpect(content().string(org.hamcrest.Matchers.containsString("\\\"isAdmin\\\": false"))); + } + + /** 管理员(id=3)必须带上 isAdmin=true,前端据此显示下载人信息与筛选。 */ + @Test + void validateMarksAdminAccount() throws Exception { + when(publicService.getUserId("admin")).thenReturn(new User(3, "admin", "狮子", null, true)); + when(remoteService.isDead()).thenReturn(false); + + mockMvc.perform(post("/validate").param("AuthCode", "admin")) + .andExpect(status().isOk()) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\\\"isAdmin\\\": true"))); } /** 存储节点掉线时 isAvailable 必须为 false,前端据此提示。 */ diff --git a/src/test/java/com/lion/lionwebsite/Service/GalleryQueryTest.java b/src/test/java/com/lion/lionwebsite/Service/GalleryQueryTest.java index d46c420..b014e3a 100644 --- a/src/test/java/com/lion/lionwebsite/Service/GalleryQueryTest.java +++ b/src/test/java/com/lion/lionwebsite/Service/GalleryQueryTest.java @@ -116,6 +116,89 @@ class GalleryQueryTest { assertTrue(ok(service.selectAllGallery(7))); } + // ---------- 下载人昵称与筛选(仅管理员) ---------- + + /** 管理员查询时每条任务都要带上下载人昵称,前端详情直接显示。 */ + @Test + void selectAllGalleryFillsDownloaderNameForAdmin() { + Gallery[] all = {gallery(1, "A", "下载完成"), gallery(2, "B", "下载完成")}; + all[0].setDownloader(24); + all[1].setDownloader(26); + when(galleries.selectAllGallery()).thenReturn(all); + when(collectMapper.selectGidByCollector(3)).thenReturn(new ArrayList<>()); + when(users.selectAllUser()).thenReturn(new User[]{ + user(3, "狮子"), user(24, "bot叔叔"), user(26, "贱笑")}); + + String json = service.selectAllGallery(3); + + assertTrue(ok(json), "实际输出: " + json); + assertEquals("bot叔叔", all[0].getDownloaderName()); + assertEquals("贱笑", all[1].getDownloaderName()); + } + + /** 普通用户不得拿到任何下载人昵称,避免泄露他人身份。 */ + @Test + void selectAllGalleryOmitsDownloaderNameForRegularUser() { + Gallery[] all = {gallery(1, "A", "下载完成")}; + all[0].setDownloader(24); + when(galleries.selectAllGallery()).thenReturn(all); + when(collectMapper.selectGidByCollector(7)).thenReturn(new ArrayList<>()); + + String json = service.selectAllGallery(7); + + assertTrue(ok(json)); + assertNull(all[0].getDownloaderName(), "普通用户不应填充下载人昵称"); + assertFalse(json.contains("downloaderName"), "响应体不应出现 downloaderName: " + json); + } + + private static User user(int id, String username) { + User u = new User(); + u.setId(id); + u.setUsername(username); + return u; + } + + // ---------- selectDownloaderByGid(管理员专用) ---------- + + /** 管理员按 gid 查到实际下载人昵称。 */ + @Test + void selectDownloaderByGidResolvesNameForAdmin() { + Gallery g = gallery(500, "A", "下载完成"); + g.setDownloader(24); + when(users.selectUserByAuthCode("admin")).thenReturn(user(3, "狮子")); + when(galleries.selectGalleryByGid(500)).thenReturn(g); + when(users.selectUserById(24)).thenReturn(user(24, "bot叔叔")); + + String json = service.selectDownloaderByGid(500, "admin"); + + assertTrue(ok(json), "实际输出: " + json); + assertTrue(json.contains("bot叔叔"), "实际输出: " + json); + } + + /** 普通用户查下载人必须被拒,不得泄露他人身份。 */ + @Test + void selectDownloaderByGidRejectsRegularUser() { + when(users.selectUserByAuthCode("code")).thenReturn(user(7, "tester")); + + String json = service.selectDownloaderByGid(500, "code"); + + assertFalse(ok(json)); + assertTrue(json.contains("无权"), "实际输出: " + json); + verify(galleries, never()).selectGalleryByGid(anyInt()); + } + + /** 任务不存在时回业务失败而不是 NPE。 */ + @Test + void selectDownloaderByGidReportsMissingTask() { + when(users.selectUserByAuthCode("admin")).thenReturn(user(3, "狮子")); + when(galleries.selectGalleryByGid(404)).thenReturn(null); + + String json = service.selectDownloaderByGid(404, "admin"); + + assertFalse(ok(json)); + assertTrue(json.contains("任务不存在"), "实际输出: " + json); + } + // ---------- selectTaskByLink / ByGid ---------- /** 库里已有该任务时直接返回,不再去外部站点解析。 */