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-tdd executes 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:

  • UserListVO 11 字段(参 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 sizephysicalCol 由 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: Commitfeat(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: Commitfeat(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: Commitfeat(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: Committest(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