Commit fe2e63069a6353b94bf39b50dfb7311f16f78391
1 parent
c6c415dc
feat(usr): UserMapper.xml searchUsers REQ-USR-003
Showing
3 changed files
with
128 additions
and
0 deletions
backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java
| 1 | 1 | package com.xly.erp.module.usr.mapper; |
| 2 | 2 | |
| 3 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| 4 | +import com.baomidou.mybatisplus.core.metadata.IPage; | |
| 5 | +import com.xly.erp.module.usr.dto.UserQueryDTO; | |
| 4 | 6 | import com.xly.erp.module.usr.entity.UserEntity; |
| 7 | +import com.xly.erp.module.usr.vo.UserListItemVO; | |
| 8 | +import org.apache.ibatis.annotations.Param; | |
| 5 | 9 | |
| 6 | 10 | public interface UserMapper extends BaseMapper<UserEntity> { |
| 11 | + | |
| 12 | + /** REQ-USR-003 用户列表查询:跨表 JOIN tStaff,按 query 过滤 + 分页。XML 实现。 */ | |
| 13 | + IPage<UserListItemVO> searchUsers(IPage<UserListItemVO> page, @Param("query") UserQueryDTO query); | |
| 7 | 14 | } | ... | ... |
backend/src/main/resources/mapper/usr/UserMapper.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
| 3 | +<mapper namespace="com.xly.erp.module.usr.mapper.UserMapper"> | |
| 4 | + | |
| 5 | + <!-- REQ-USR-003 用户列表查询:LEFT JOIN tStaff + 动态 WHERE。 | |
| 6 | + query.column 由 service 层白名单映射,绝不接受用户原始输入。 --> | |
| 7 | + <select id="searchUsers" resultType="com.xly.erp.module.usr.vo.UserListItemVO"> | |
| 8 | + SELECT | |
| 9 | + u.iIncrement, u.sUserName, s.sStaffName, u.sUserNo, | |
| 10 | + s.sDepartment, u.sUserType, u.sLanguage, u.bDeleted, | |
| 11 | + u.tLastLoginDate, u.sCreatedBy, u.tCreateDate | |
| 12 | + FROM tUser u | |
| 13 | + LEFT JOIN tStaff s ON u.iStaffId = s.iIncrement AND s.bDeleted = 0 | |
| 14 | + <where> | |
| 15 | + <if test="query.queryField != 'deleted'"> | |
| 16 | + u.bDeleted = 0 | |
| 17 | + </if> | |
| 18 | + <if test="query.column != null and query.column != '' and query.queryValue != null and query.queryValue != ''"> | |
| 19 | + AND | |
| 20 | + <choose> | |
| 21 | + <when test="query.matchType == 'equals'"> | |
| 22 | + ${query.column} = #{query.queryValue} | |
| 23 | + </when> | |
| 24 | + <when test="query.matchType == 'notContains'"> | |
| 25 | + ${query.column} NOT LIKE CONCAT('%', #{query.queryValue}, '%') | |
| 26 | + </when> | |
| 27 | + <otherwise> | |
| 28 | + ${query.column} LIKE CONCAT('%', #{query.queryValue}, '%') | |
| 29 | + </otherwise> | |
| 30 | + </choose> | |
| 31 | + </if> | |
| 32 | + </where> | |
| 33 | + ORDER BY u.tCreateDate DESC, u.iIncrement DESC | |
| 34 | + </select> | |
| 35 | +</mapper> | ... | ... |
backend/src/test/java/com/xly/erp/module/usr/mapper/UserMapperSearchIT.java
0 → 100644
| 1 | +package com.xly.erp.module.usr.mapper; | |
| 2 | + | |
| 3 | +import com.baomidou.mybatisplus.core.metadata.IPage; | |
| 4 | +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | |
| 5 | +import com.xly.erp.module.usr.dto.UserQueryDTO; | |
| 6 | +import com.xly.erp.module.usr.entity.StaffEntity; | |
| 7 | +import com.xly.erp.module.usr.entity.UserEntity; | |
| 8 | +import com.xly.erp.module.usr.vo.UserListItemVO; | |
| 9 | +import org.junit.jupiter.api.Test; | |
| 10 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 11 | +import org.springframework.boot.test.context.SpringBootTest; | |
| 12 | +import org.springframework.test.annotation.Rollback; | |
| 13 | +import org.springframework.test.context.ActiveProfiles; | |
| 14 | +import org.springframework.transaction.annotation.Transactional; | |
| 15 | + | |
| 16 | +import java.time.LocalDateTime; | |
| 17 | + | |
| 18 | +import static org.assertj.core.api.Assertions.assertThat; | |
| 19 | + | |
| 20 | +@SpringBootTest | |
| 21 | +@ActiveProfiles("test") | |
| 22 | +@Transactional | |
| 23 | +@Rollback | |
| 24 | +class UserMapperSearchIT { | |
| 25 | + | |
| 26 | + @Autowired UserMapper userMapper; | |
| 27 | + @Autowired StaffMapper staffMapper; | |
| 28 | + | |
| 29 | + private Integer insertStaff(String name) { | |
| 30 | + StaffEntity s = new StaffEntity(); | |
| 31 | + s.setSStaffNo("st_" + System.nanoTime()); | |
| 32 | + s.setSStaffName(name); | |
| 33 | + s.setSDepartment("研发部"); | |
| 34 | + s.setBDeleted(false); | |
| 35 | + s.setTCreateDate(LocalDateTime.now()); | |
| 36 | + staffMapper.insert(s); | |
| 37 | + return s.getIIncrement(); | |
| 38 | + } | |
| 39 | + | |
| 40 | + private Integer insertUser(String userName, Integer staffId) { | |
| 41 | + UserEntity u = new UserEntity(); | |
| 42 | + u.setSUserNo("uno_" + System.nanoTime()); | |
| 43 | + u.setSUserName(userName); | |
| 44 | + u.setIStaffId(staffId); | |
| 45 | + u.setSUserType("普通用户"); | |
| 46 | + u.setSLanguage("zh"); | |
| 47 | + u.setBCanModifyDocs(false); | |
| 48 | + u.setSPasswordHash("$2a$10$x"); | |
| 49 | + u.setBDeleted(false); | |
| 50 | + u.setTCreateDate(LocalDateTime.now()); | |
| 51 | + userMapper.insert(u); | |
| 52 | + return u.getIIncrement(); | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Test | |
| 56 | + void searchUsers_emptyFilter_returnsAllUndeletedAsPage() { | |
| 57 | + Integer staffId = insertStaff("张三"); | |
| 58 | + insertUser("alice_" + System.nanoTime(), staffId); | |
| 59 | + insertUser("bob_" + System.nanoTime(), null); | |
| 60 | + | |
| 61 | + UserQueryDTO query = new UserQueryDTO(); | |
| 62 | + IPage<UserListItemVO> result = userMapper.searchUsers(new Page<>(1, 50), query); | |
| 63 | + | |
| 64 | + assertThat(result.getTotal()).isGreaterThanOrEqualTo(2L); | |
| 65 | + assertThat(result.getRecords()).extracting(UserListItemVO::getSUserName) | |
| 66 | + .anyMatch(n -> n.startsWith("alice_") || n.startsWith("bob_")); | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Test | |
| 70 | + void searchUsers_filterByUserName_filtersCorrectly() { | |
| 71 | + String alicePrefix = "ali_" + System.nanoTime(); | |
| 72 | + insertUser(alicePrefix + "_alice", null); | |
| 73 | + insertUser("bob_unmatch_" + System.nanoTime(), null); | |
| 74 | + | |
| 75 | + UserQueryDTO query = new UserQueryDTO(); | |
| 76 | + query.setQueryField("username"); | |
| 77 | + query.setColumn("u.sUserName"); | |
| 78 | + query.setMatchType("contains"); | |
| 79 | + query.setQueryValue(alicePrefix); | |
| 80 | + | |
| 81 | + IPage<UserListItemVO> result = userMapper.searchUsers(new Page<>(1, 50), query); | |
| 82 | + | |
| 83 | + assertThat(result.getRecords()).hasSize(1); | |
| 84 | + assertThat(result.getRecords().get(0).getSUserName()).startsWith(alicePrefix); | |
| 85 | + } | |
| 86 | +} | ... | ... |