2026-04-30-REQ-USR-003.md
6.31 KB
req_id: REQ-USR-003 date: 2026-04-30
spec_ref: docs/superpowers/specs/2026-04-30-REQ-USR-003.md
REQ-USR-003 用户查询 Implementation Plan
Execution: Parent skill
feature-tddexecutes this plan task-by-task.
Goal: 实现 GET /api/usr/users 单条件分页查询:tUser LEFT JOIN tStaff,按 field × match × value 动态过滤。
Architecture: 新增 UserListVO + UserListQuery 内部封装类(field/match/value/page)+ UserMapper#pageWithFilter 自定义动态 SQL + UserService#list + controller @GetMapping。
Tech Stack: 沿用;MyBatis 动态 SQL <script> + <foreach> / <if>。
Schema 改动
无。
文件变更清单
新增
backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java-
backend/src/main/java/com/xly/erp/module/usr/dto/UserListQuery.java(内部封装规范化后的查询参数)
修改
-
backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java— 追加pageWithFilter+countWithFilter -
backend/src/main/resources/mapper/usr/UserMapper.xml— 新建 XML(动态 SQL 复杂,注解 @Select 不易读) -
backend/src/main/java/com/xly/erp/module/usr/service/UserService.java— 追加list(...) -
backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java— 实现 list(字段映射 + 校验 + 调 mapper) -
backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java— 追加 GET 端点 - 测试:UserMapperIT(+1) / UserServiceImplTest(+10) / UserControllerIT(+8)
任务步骤
Task 1: UserListVO + UserMapper#pageWithFilter / countWithFilter + IT
Files:
- Create: vo/UserListVO.java
- Modify: mapper/UserMapper.java + resources/mapper/usr/UserMapper.xml
- Modify: test/.../mapper/UserMapperIT.java
API shape:
-
UserListVO11 字段(参 spec),@JsonProperty 锁定 JSON 名 -
UserMapper.pageWithFilter(XML)参数:@Param("field") String physicalCol, @Param("match") String matchOp, @Param("value") Object value, @Param("offset") int offset, @Param("size") int size;physicalCol由 service 映射成u.sUserName/s.sStaffName等 -
UserMapper.countWithFilter同参数(除 offset/size)
XML 关键结构:
<select id="pageWithFilter" resultType="com.xly.erp.module.usr.vo.UserListVO">
SELECT u.iIncrement, u.sUserName, s.sStaffName AS staffName, u.sUserNo,
s.sDepartment AS department, u.sUserType, u.sLanguage,
u.bDeleted, u.tLastLoginDate, u.sCreatedBy, u.tCreateDate
FROM tUser u
LEFT JOIN tStaff s ON s.iIncrement = u.iStaffId AND s.bDeleted = 0
<where>
<if test="value != null and value != ''">
<choose>
<when test="matchOp == 'contains'">${field} LIKE CONCAT('%', #{value}, '%')</when>
<when test="matchOp == 'notContains'">${field} NOT LIKE CONCAT('%', #{value}, '%')</when>
<when test="matchOp == 'equals'">
<choose>
<when test="field == 'DATE(u.tLastLoginDate)'">${field} = #{value}</when>
<otherwise>${field} = #{value}</otherwise>
</choose>
</when>
</choose>
</if>
</where>
ORDER BY u.iIncrement DESC
LIMIT #{offset}, #{size}
</select>
(XML 复杂度由 service 提供归一化的 field(物理列名 with prefix) / matchOp(英文常量) / value 后大幅简化)
- Step 1: 写失败 IT
pageWithFilter_filtersAndJoins - Step 2: 实现 entity/VO + mapper + XML
- Step 3: 子会话验证 PASS
- Step 4: Commit:
feat(usr): user list mapper + vo with join REQ-USR-003
Task 2: UserListQuery + UserService.list 主流程(合法 + 字段映射)
Files:
- Create: dto/UserListQuery.java(service 内部)
- Modify: service/UserService.java + impl/UserServiceImpl.java
- Modify: test/.../service/UserServiceImplTest.java
API shape:
-
UserService#list(String field, String match, String value, Integer pageNum, Integer pageSize) : Map<String, Object>(返回 records/total/pageNum/pageSize) - 内部:归一化(默认值/trim)→ field/match 校验 → value 解析(日期/布尔)→ 调 mapper.countWithFilter + pageWithFilter → 装结果 Map
字段映射表(service 静态常量):
"用户名" -> ("u.sUserName", STRING)
"员工名" -> ("s.sStaffName", STRING)
"用户号" -> ("u.sUserNo", STRING)
"部门" -> ("s.sDepartment", STRING)
"用户类型" -> ("u.sUserType", STRING)
"作废" -> ("u.bDeleted", BOOLEAN)
"登录日期" -> ("DATE(u.tLastLoginDate)", DATE)
"制单人" -> ("u.sCreatedBy", STRING)
match 映射:包含→contains,不包含→notContains,等于→equals
校验顺序:pageSize 上限 → field 枚举 → match 枚举 → field/match 兼容(布尔/日期仅 equals)→ value 解析(日期)
- Step 1: 写失败测试 4 条主流程用例
- listWithDefaults_invokesMapperWithUserNameContainsEmpty
- listWithEmptyValue_skipsFilterCondition
- listWithKeywordTrim
- listReturnsEmptyRecords_whenMapperReturnsEmptyPage
- Step 2: 实现 service
- Step 3: 子会话验证 PASS
- Step 4: Commit:
feat(usr): user list service + field/match mapping REQ-USR-003
Task 3: Service 异常分支(field/match/兼容/pageSize/日期格式/布尔)
- Step 1: 追加 6 用例
- listWithInvalidField_throws40001
- listWithInvalidMatch_throws40001
- listWithIncompatibleFieldMatch_throws40001
- listWithPageSizeExceeds100_throws40002
- listWithInvalidLoginDateFormat_throws40001
- listWithBooleanFieldEqualsTrue_passesIntegerOne
- Step 2: 实现校验分支
- Step 3: 子会话验证 PASS(期望 18 + 4 + 6 = 28 用例)
- Step 4: Commit:
feat(usr): user list error branches REQ-USR-003
Task 4: Controller GET + 8 IT + 全量回归
- Step 1: 追加 8 IT(参 spec 验收清单)
- Step 2: 实现 controller GET
- Step 3: 子会话跑全量回归(期望 ≥ 129 用例)
- Step 4: Commit:
test(usr): user list integration coverage REQ-USR-003
提交计划
| commit | 覆盖 |
|---|---|
feat(usr): user list mapper + vo with join REQ-USR-003 |
Task 1 |
feat(usr): user list service + field/match mapping REQ-USR-003 |
Task 2 |
feat(usr): user list error branches REQ-USR-003 |
Task 3 |
test(usr): user list integration coverage REQ-USR-003 |
Task 4 |