--- req_id: REQ-USR-003 date: 2026-05-08 module: usr --- # Spec: REQ-USR-003 — 查询用户 ## 目标 超级管理员可按 8 种查询字段 + 3 种匹配方式组合筛选,分页浏览本品牌下的用户列表;列表同时展示关联职员的员工名与部门。 ## 输入 / 触发 HTTP 请求:`GET /api/usr/users`(需 Bearer Token 鉴权) Query 参数: | 参数 | 类型 | 必填 | 默认值 | 说明 | |---|---|---|---|---| | queryField | String | 否 | `username` | 查询字段枚举,见下表 | | matchType | String | 否 | `contains` | 匹配方式枚举:`contains` / `notContains` / `equals` | | queryValue | String | 否 | — | 查询值;空字符串或不传 = 全部 | | page | int | 否 | 1 | 页码,从 1 开始 | | pageSize | int | 否 | 20 | 每页条数,最大 100 | queryField 枚举(前端显示名 → 参数值 → 后端列): | 前端显示 | 参数值 | 后端列 | |---|---|---| | 用户名 | `username` | `u.sUsername` | | 员工名 | `staffName` | `s.sStaffName` | | 用户号 | `userCode` | `u.sUserCode` | | 部门 | `department` | `s.sDepartment` | | 用户类型 | `userType` | `u.sUserType` | | 作废 | `disabled` | `u.bIsDisabled` | | 登录日期 | `lastLoginDate` | `u.tLastLoginDate` | | 制单人 | `creator` | `u.sCreatorUsername` | ## 输出 / 结果 HTTP 200,`Result>` `PageVO` 结构(通用分页包装): ```json { "total": 25, "page": 1, "pageSize": 20, "list": [ ...UserListItemVO... ] } ``` `UserListItemVO` 字段(禁止返回 sPasswordHash / iLoginFailCount / tLockUntil): | JSON 字段 | 来源列 | 可 null | |---|---|---| | sId | usr_user.sId | 否 | | sUsername | usr_user.sUsername | 否 | | sUserCode | usr_user.sUserCode | 否 | | sUserType | usr_user.sUserType | 否 | | sLanguage | usr_user.sLanguage | 否 | | bIsDisabled | usr_user.bIsDisabled | 否 | | tLastLoginDate | usr_user.tLastLoginDate | 是 | | sCreatorUsername | usr_user.sCreatorUsername | 是 | | tCreateDate | usr_user.tCreateDate | 否 | | sStaffName | tStaff.sStaffName | 是(LEFT JOIN) | | sDepartment | tStaff.sDepartment | 是(LEFT JOIN) | ## 业务规则 1. **多租户隔离**:所有查询必须附加 `u.sBrandsId = principal.brandId()` 2. **queryValue 为空**:忽略查询条件,返回该品牌全部用户 3. **匹配方式映射**(文本字段:username / staffName / userCode / department / userType / creator): - `contains` → `LIKE '%value%'` - `notContains` → `NOT LIKE '%value%'` - `equals` → `= 'value'` 4. **布尔字段(disabled)**:仅有意义的匹配是 equals;queryValue="是" → `bIsDisabled = 1`;"否" → `bIsDisabled = 0`;其他值(包括 contains/notContains 的非 "是"/"否")忽略条件 5. **日期字段(lastLoginDate)**:queryValue 格式 `YYYY-MM-DD`;无论 matchType 为何,均使用 `DATE(tLastLoginDate) = 'YYYY-MM-DD'` 6. **pageSize 截断**:pageSize > 100 时强制截断为 100 7. **分页越界**:page 超出总页数时返回最后一页(MyBatis-Plus `Page` 默认行为) 8. **职员 JOIN**:`LEFT JOIN tStaff s ON u.sEmployeeId = s.sId AND s.sBrandsId = u.sBrandsId AND s.bDeleted = 0`;未关联职员时 sStaffName / sDepartment 为 null ## 边界与约束 - 接口为只读,无写副作用 - 鉴权失败返回 401(SecurityConfig.authenticationEntryPoint 已配置) - 单次最多返回 100 条 ## 依赖的 schema 表 / 字段 - `usr_user (别名 u)`:sId, sUsername, sUserCode, sUserType, sLanguage, bIsDisabled, sEmployeeId, sCreatorUsername, tLastLoginDate, tCreateDate, sBrandsId - `tStaff (别名 s)`:sId, sStaffName, sDepartment, sBrandsId, bDeleted ## 依赖的接口 - `POST /api/auth/login`(REQ-USR-004,获取 Bearer Token 供本接口鉴权使用) ## 验收标准 1. `GET /api/usr/users`(无参)HTTP 200,返回本品牌所有用户,不含密码字段 2. `queryField=username&matchType=contains&queryValue=admin` 仅返回 sUsername 含 "admin" 的用户 3. `queryField=disabled&matchType=equals&queryValue=是` 仅返回 bIsDisabled=1 的记录 4. 无匹配条件时返回 `{ total: 0, list: [] }` 而非 4xx/5xx 5. page=999(超出总页)返回最后一页而不报错 6. Token 品牌 A 无法查到品牌 B 的用户(多租户隔离) 7. 响应 JSON 中不含 sPasswordHash / iLoginFailCount / tLockUntil 8. 无 Token 请求返回 401