From bda3515f26e235ea1266c78a64f4e42f90c7490a Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 15 May 2026 10:07:02 +0800 Subject: [PATCH] feat(usr): SysUserMapper 动态查询 XML + JOIN 员工/部门 REQ-USR-004 --- backend/src/main/java/com/xly/erp/module/usr/mapper/SysUserMapper.java | 14 +++++++++++--- backend/src/main/java/com/xly/erp/module/usr/mapper/UserQueryParams.java | 18 ++++++++++++++++++ backend/src/main/resources/application.yml | 1 + backend/src/main/resources/mapper/usr/SysUserMapper.xml | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/test/java/com/xly/erp/module/usr/mapper/SysUserMapperQueryTest.java | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/xly/erp/module/usr/mapper/UserQueryParams.java create mode 100644 backend/src/main/resources/mapper/usr/SysUserMapper.xml create mode 100644 backend/src/test/java/com/xly/erp/module/usr/mapper/SysUserMapperQueryTest.java diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/SysUserMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/SysUserMapper.java index 98b9096..b99eca5 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/SysUserMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/SysUserMapper.java @@ -2,11 +2,14 @@ package com.xly.erp.module.usr.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xly.erp.module.usr.entity.SysUser; +import com.xly.erp.module.usr.vo.UserListItemVo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; +import java.util.List; + @Mapper public interface SysUserMapper extends BaseMapper { @@ -20,10 +23,7 @@ public interface SysUserMapper extends BaseMapper { /** * 原子累加失败登录次数;达到阈值 maxCount 时同步写 tLockUntil = NOW() + lockMinutes 分钟。 * 单 SQL,DB 层保证并发安全。返回受影响行数(应为 1)。 - */ - /* * MySQL 按 SET 子句从左到右求值,所以放在 +1 之后的引用看到的是新值。 - * 第二条 SET 用 `iFailedLoginCount >= maxCount` 即等价于"新计数 >= 阈值"判定。 */ @Update("UPDATE sys_user " + "SET iFailedLoginCount = iFailedLoginCount + 1, " + @@ -53,4 +53,12 @@ public interface SysUserMapper extends BaseMapper { "WHERE sUserCode = #{userCode} AND iIncrement <> #{excludedUserId})") boolean existsByUserCodeExcludingId(@Param("userCode") String userCode, @Param("excludedUserId") Integer excludedUserId); + + /** + * REQ-USR-004 动态查询。SQL 在 SysUserMapper.xml 定义。 + * QueryParams 必须已通过 service 层白名单校验。 + */ + List selectByQuery(@Param("p") UserQueryParams p); + + long countByQuery(@Param("p") UserQueryParams p); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserQueryParams.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserQueryParams.java new file mode 100644 index 0000000..1c20854 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserQueryParams.java @@ -0,0 +1,18 @@ +package com.xly.erp.module.usr.mapper; + +/** + * SysUserMapper.selectByQuery / countByQuery 入参(service 层规范化白名单后填入)。 + * sqlSortField / sqlSortOrder / sqlQueryColumn 必须已通过白名单校验; + * mapper XML 直接用 ${} 拼接到 SQL。 + */ +public class UserQueryParams { + public String sqlSortField; + public String sqlSortOrder; + public String sqlQueryColumn; // null 表示无 queryField 条件 + public String matchMode; // contains / notContains / equals + public String queryValue; // null/"" 表示跳过该条件 + public String userType; // null 表示不过滤 + public Integer isDeleted; // null 不过滤;0 / 1 过滤 + public Integer offset; + public Integer limit; +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 1660d18..52707cc 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -20,6 +20,7 @@ spring: validate-on-migrate: true mybatis-plus: + mapper-locations: classpath*:/mapper/**/*.xml configuration: map-underscore-to-camel-case: false log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl diff --git a/backend/src/main/resources/mapper/usr/SysUserMapper.xml b/backend/src/main/resources/mapper/usr/SysUserMapper.xml new file mode 100644 index 0000000..5bad4dc --- /dev/null +++ b/backend/src/main/resources/mapper/usr/SysUserMapper.xml @@ -0,0 +1,74 @@ + + + + + + FROM sys_user u + LEFT JOIN sys_employee e ON e.iIncrement = u.iEmployeeId + LEFT JOIN sys_department d ON d.iIncrement = e.iDepartmentId + + + + + + + + AND ${p.sqlQueryColumn} LIKE CONCAT('%', #{p.queryValue}, '%') + + + AND (${p.sqlQueryColumn} NOT LIKE CONCAT('%', #{p.queryValue}, '%') + OR ${p.sqlQueryColumn} IS NULL) + + + AND ${p.sqlQueryColumn} = #{p.queryValue} + + + + + AND u.sUserType = #{p.userType} + + + AND u.iIsDeleted = #{p.isDeleted} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/test/java/com/xly/erp/module/usr/mapper/SysUserMapperQueryTest.java b/backend/src/test/java/com/xly/erp/module/usr/mapper/SysUserMapperQueryTest.java new file mode 100644 index 0000000..86fbc01 --- /dev/null +++ b/backend/src/test/java/com/xly/erp/module/usr/mapper/SysUserMapperQueryTest.java @@ -0,0 +1,105 @@ +package com.xly.erp.module.usr.mapper; + +import com.xly.erp.module.usr.support.LoginTestSeeder; +import com.xly.erp.module.usr.vo.UserListItemVo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +class SysUserMapperQueryTest { + + @Autowired private SysUserMapper mapper; + @Autowired private LoginTestSeeder seeder; + + @BeforeEach + void setUp() { + seeder.reset(); + } + + private UserQueryParams baseParams() { + UserQueryParams p = new UserQueryParams(); + p.sqlSortField = "tCreateDate"; + p.sqlSortOrder = "desc"; + p.matchMode = "contains"; + p.offset = 0; + p.limit = 100; + return p; + } + + @Test + void count_noFilters_returnsAllRows() { + long total = mapper.countByQuery(baseParams()); + // seeder 插入 alice + admin + bob_deleted = 3 行 + assertEquals(3, total); + } + + @Test + void select_withSortByUsername_ascending() { + UserQueryParams p = baseParams(); + p.sqlSortField = "sUsername"; + p.sqlSortOrder = "asc"; + List rows = mapper.selectByQuery(p); + assertEquals(3, rows.size()); + // 期望升序:admin / alice / bob_deleted + assertEquals(LoginTestSeeder.USER_ADMIN, rows.get(0).getUsername()); + assertEquals(LoginTestSeeder.USER_OK, rows.get(1).getUsername()); + assertEquals(LoginTestSeeder.USER_DELETED, rows.get(2).getUsername()); + } + + @Test + void select_withQueryFieldUsername_contains() { + UserQueryParams p = baseParams(); + p.sqlQueryColumn = "u.sUsername"; + p.matchMode = "contains"; + p.queryValue = "ali"; + List rows = mapper.selectByQuery(p); + assertEquals(1, rows.size()); + assertEquals(LoginTestSeeder.USER_OK, rows.get(0).getUsername()); + } + + @Test + void select_joinsEmployeeAndDepartment_returnsBothNames() { + UserQueryParams p = baseParams(); + p.sqlQueryColumn = "u.sUsername"; + p.matchMode = "equals"; + p.queryValue = LoginTestSeeder.USER_OK; + List rows = mapper.selectByQuery(p); + assertEquals(1, rows.size()); + assertEquals("张三", rows.get(0).getEmployeeName()); + assertEquals("技术部", rows.get(0).getDepartmentName()); + } + + @Test + void select_withIsDeletedFilter_returnsOnlyMatching() { + UserQueryParams p = baseParams(); + p.isDeleted = 1; + List rows = mapper.selectByQuery(p); + assertEquals(1, rows.size()); + assertEquals(LoginTestSeeder.USER_DELETED, rows.get(0).getUsername()); + } + + @Test + void select_withUserTypeFilter_returnsOnlyAdmin() { + UserQueryParams p = baseParams(); + p.userType = "SUPER_ADMIN"; + List rows = mapper.selectByQuery(p); + assertEquals(1, rows.size()); + assertEquals(LoginTestSeeder.USER_ADMIN, rows.get(0).getUsername()); + } + + @Test + void select_pagination_limitsResults() { + UserQueryParams p = baseParams(); + p.limit = 2; + List rows = mapper.selectByQuery(p); + assertEquals(2, rows.size()); + } +} -- libgit2 0.22.2