新增下载人查询接口,并把管理员判定收敛到服务端

- Gallery 增加 downloaderName,仅在管理员查询任务列表时填充;普通用户
  既不获取也不下发他人身份
- 新增 GET /GalleryManage/downloader?gid=&AuthCode=,按 gid 返回实际下载人
  昵称,非管理员一律拒绝
- /validate 下发 isAdmin,前端不再各自硬编码 userId === 3
- 管理员判定统一走 UserService.ADMIN_USER_ID
- 补充列表填充、按 gid 查询的权限与缺任务分支测试
This commit is contained in:
root
2026-09-20 22:54:23 +08:00
parent d949415e4d
commit 305036ddfe
9 changed files with 202 additions and 2 deletions
@@ -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){
@@ -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();
}
@@ -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();
@@ -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; //收藏人
@@ -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<Integer, String> 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();
}
/**
* 通过图片名查询图片
*
@@ -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;
}
}
@@ -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"))
@@ -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,前端据此提示。 */
@@ -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 ----------
/** 库里已有该任务时直接返回,不再去外部站点解析。 */