From b608dd84b4c5c3a1ffed4488e7168a22cb6542f2 Mon Sep 17 00:00:00 2001 From: zichun Date: Wed, 6 May 2026 21:56:33 +0800 Subject: [PATCH] feat(usr): user query service REQ-USR-003 --- backend/src/main/java/com/xly/erp/module/usr/service/UserService.java | 6 ++++++ backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 0 deletions(-) diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java index de9c23e..dc010c8 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java @@ -1,7 +1,10 @@ package com.xly.erp.module.usr.service; +import com.xly.erp.common.response.PageResult; import com.xly.erp.module.usr.dto.UserCreateDTO; +import com.xly.erp.module.usr.dto.UserQueryDTO; import com.xly.erp.module.usr.dto.UserUpdateDTO; +import com.xly.erp.module.usr.vo.UserListItemVO; import com.xly.erp.module.usr.vo.UserVO; public interface UserService { @@ -10,4 +13,7 @@ public interface UserService { /** REQ-USR-002 用户修改 */ UserVO update(Integer id, UserUpdateDTO dto); + + /** REQ-USR-003 用户列表查询 */ + PageResult search(UserQueryDTO query); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java index ace1fbf..d1cd0f5 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java @@ -1,9 +1,13 @@ package com.xly.erp.module.usr.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.xly.erp.common.exception.BizException; import com.xly.erp.common.response.ErrorCode; +import com.xly.erp.common.response.PageResult; import com.xly.erp.module.usr.dto.UserCreateDTO; +import com.xly.erp.module.usr.dto.UserQueryDTO; import com.xly.erp.module.usr.dto.UserUpdateDTO; import com.xly.erp.module.usr.entity.PermissionCategoryEntity; import com.xly.erp.module.usr.entity.StaffEntity; @@ -14,6 +18,7 @@ import com.xly.erp.module.usr.mapper.StaffMapper; import com.xly.erp.module.usr.mapper.UserMapper; import com.xly.erp.module.usr.mapper.UserPermissionMapper; import com.xly.erp.module.usr.service.UserService; +import com.xly.erp.module.usr.vo.UserListItemVO; import com.xly.erp.module.usr.vo.UserVO; import lombok.RequiredArgsConstructor; import org.springframework.dao.DuplicateKeyException; @@ -24,6 +29,8 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; /** REQ-USR-001 用户新增 */ @Service @@ -162,4 +169,47 @@ public class UserServiceImpl implements UserService { return UserVO.from(target, categoryIds); } + + /** REQ-USR-003 用户列表查询 — queryField 白名单映射 + LEFT JOIN tStaff 分页 */ + private static final Map QUERY_COLUMN_MAP = Map.ofEntries( + Map.entry("username", "u.sUserName"), + Map.entry("staffname", "s.sStaffName"), + Map.entry("userno", "u.sUserNo"), + Map.entry("department", "s.sDepartment"), + Map.entry("usertype", "u.sUserType"), + Map.entry("language", "u.sLanguage"), + Map.entry("deleted", "u.bDeleted"), + Map.entry("lastLoginDate", "u.tLastLoginDate"), + Map.entry("createdBy", "u.sCreatedBy")); + + private static final Set MATCH_TYPES = Set.of("contains", "notContains", "equals"); + + @Override + @Transactional(readOnly = true) + public PageResult search(UserQueryDTO query) { + // 1. queryField 白名单 + 列映射(防 SQL 注入) + if (query.getQueryField() != null && !query.getQueryField().isEmpty()) { + String mapped = QUERY_COLUMN_MAP.get(query.getQueryField()); + if (mapped == null) { + throw new BizException(ErrorCode.PARAM_INVALID, "queryField 非法: " + query.getQueryField()); + } + query.setColumn(mapped); + } + + // 2. matchType 白名单 + if (query.getMatchType() != null && !query.getMatchType().isEmpty() + && !MATCH_TYPES.contains(query.getMatchType())) { + throw new BizException(ErrorCode.PARAM_INVALID, "matchType 非法: " + query.getMatchType()); + } + + // 3. 默认值兜底 + int pageNum = query.getPageNum() == null ? 1 : query.getPageNum(); + int pageSize = query.getPageSize() == null ? 20 : query.getPageSize(); + + // 4. MP 分页查询 + IPage page = new Page<>(pageNum, pageSize); + IPage result = userMapper.searchUsers(page, query); + + return PageResult.of(result); + } } diff --git a/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java b/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java index 5bc9a73..8c966ec 100644 --- a/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java +++ b/backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java @@ -1,9 +1,13 @@ package com.xly.erp.module.usr.service; import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.xly.erp.common.exception.BizException; import com.xly.erp.common.response.ErrorCode; +import com.xly.erp.common.response.PageResult; import com.xly.erp.module.usr.dto.UserCreateDTO; +import com.xly.erp.module.usr.dto.UserQueryDTO; import com.xly.erp.module.usr.dto.UserUpdateDTO; import com.xly.erp.module.usr.entity.PermissionCategoryEntity; import com.xly.erp.module.usr.entity.StaffEntity; @@ -14,6 +18,7 @@ import com.xly.erp.module.usr.mapper.StaffMapper; import com.xly.erp.module.usr.mapper.UserMapper; import com.xly.erp.module.usr.mapper.UserPermissionMapper; import com.xly.erp.module.usr.service.impl.UserServiceImpl; +import com.xly.erp.module.usr.vo.UserListItemVO; import com.xly.erp.module.usr.vo.UserVO; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -410,4 +415,74 @@ class UserServiceImplTest { verify(userPermissionMapper).delete(any(Wrapper.class)); verify(userPermissionMapper, never()).insert((UserPermissionEntity) any()); } + + // ============================================================ + // REQ-USR-003 search 系列 + // ============================================================ + + @Test + void search_emptyDb_returnsEmptyPage() { + UserQueryDTO query = new UserQueryDTO(); + IPage emptyPage = new Page<>(1, 20); + emptyPage.setTotal(0L); + when(userMapper.searchUsers(any(IPage.class), any(UserQueryDTO.class))).thenReturn(emptyPage); + + PageResult result = service.search(query); + assertThat(result.getTotal()).isZero(); + assertThat(result.getList()).isEmpty(); + } + + @Test + void search_invalidQueryField_throws40010() { + UserQueryDTO query = new UserQueryDTO(); + query.setQueryField("invalid_field"); + + assertThatThrownBy(() -> service.search(query)) + .isInstanceOf(BizException.class) + .extracting(e -> ((BizException) e).getCode()) + .isEqualTo(ErrorCode.PARAM_INVALID.getCode()); + } + + @Test + void search_invalidMatchType_throws40010() { + UserQueryDTO query = new UserQueryDTO(); + query.setMatchType("like"); // 非白名单 + + assertThatThrownBy(() -> service.search(query)) + .isInstanceOf(BizException.class) + .extracting(e -> ((BizException) e).getCode()) + .isEqualTo(ErrorCode.PARAM_INVALID.getCode()); + } + + @Test + void search_passesMappedColumnToMapper() { + UserQueryDTO query = new UserQueryDTO(); + query.setQueryField("username"); + query.setQueryValue("alice"); + IPage emptyPage = new Page<>(1, 20); + when(userMapper.searchUsers(any(IPage.class), any(UserQueryDTO.class))).thenReturn(emptyPage); + + service.search(query); + + ArgumentCaptor cap = ArgumentCaptor.forClass(UserQueryDTO.class); + verify(userMapper).searchUsers(any(IPage.class), cap.capture()); + assertThat(cap.getValue().getColumn()).isEqualTo("u.sUserName"); + } + + @Test + void search_appliesDefaultPagination_whenNullPageNumOrSize() { + UserQueryDTO query = new UserQueryDTO(); + query.setPageNum(null); + query.setPageSize(null); + IPage emptyPage = new Page<>(1, 20); + when(userMapper.searchUsers(any(IPage.class), any(UserQueryDTO.class))).thenReturn(emptyPage); + + service.search(query); + + ArgumentCaptor pageCap = ArgumentCaptor.forClass(IPage.class); + verify(userMapper).searchUsers(pageCap.capture(), any(UserQueryDTO.class)); + IPage page = pageCap.getValue(); + assertThat(page.getCurrent()).isEqualTo(1L); + assertThat(page.getSize()).isEqualTo(20L); + } } -- libgit2 0.22.2