Commit 3b22285edc75c3b61bb19a8b27949fbe94d356c3
1 parent
a89d9a1d
docs(plan:REQ-USR-001): 任务级 TDD 计划
Showing
1 changed file
with
197 additions
and
0 deletions
docs/superpowers/plans/2026-06-01-REQ-USR-001.md
0 → 100644
| 1 | +# REQ-USR-001 增加用户 — 任务级 TDD 计划(后端) | |
| 2 | + | |
| 3 | +> 阶段:后端(backend)。作用域:`backend/**`(controller / service / repository(mapper) / DTO / VO / entity / 校验 / 配置 / REST 契约实现)。**禁止**写 `frontend/**`。 | |
| 4 | +> 上游 SSoT:spec `docs/superpowers/specs/2026-06-01-REQ-USR-001.md`;需求卡片 `docs/01-需求清单/USR-用户管理/REQ-USR-001.md`;DB 设计 `docs/03-数据库设计文档.md`;API 契约 `docs/05-API接口契约.md`;技术规范 `docs/04-技术规范.md`;配置 `config-vars.yaml`。 | |
| 5 | +> 本计划告诉 TDD 执行者**做什么 / 文件边界 / 测试意图 / API 形状 / 完成判据**;具体代码由红-绿-提交循环产出,不在此 dump 整文件。 | |
| 6 | + | |
| 7 | +--- | |
| 8 | + | |
| 9 | +## Goal(目标) | |
| 10 | + | |
| 11 | +实现后台管理员新建用户账号的唯一端点 `POST /api/usr/users`:接收 `CreateUserDTO`,校验参数与权限,对用户名查重,BCrypt 哈希初始密码,写入 `usr_user` 并按需为新用户批量授权 `usr_user_permission`,返回 `Result<{ id }>`。同时因这是项目首个后端 REQ,需先一次性搭好 `backend/` Maven 骨架与 `common/**` 公共基础设施(统一响应 / 异常 / 安全 / BaseEntity / 配置),供本 REQ 及后续 REQ 复用。 | |
| 12 | + | |
| 13 | +## Architecture(架构 / 分层) | |
| 14 | + | |
| 15 | +遵循 `docs/04 § 1.2`,根包 `com.xly.erp`(来自 `config-vars.yaml backend.base_package`): | |
| 16 | + | |
| 17 | +``` | |
| 18 | +backend/ | |
| 19 | +├── pom.xml # spring-boot 3 / mybatis-plus / mysql / flyway-core+flyway-mysql / spring-security / jjwt / mapstruct / hutool / bcrypt(随 spring-security) / spring-boot-starter-test | |
| 20 | +├── src/main/java/com/xly/erp/ | |
| 21 | +│ ├── ErpApplication.java # 启动类 @SpringBootApplication + @MapperScan("com.xly.erp.**.mapper") | |
| 22 | +│ ├── common/ | |
| 23 | +│ │ ├── response/Result.java # 统一响应体 Result<T> | |
| 24 | +│ │ ├── response/ResultCode.java # 错误码枚举(0/40001/40301/40901/40101/40302/40401/42201) | |
| 25 | +│ │ ├── exception/BusinessException.java # 业务异常 | |
| 26 | +│ │ ├── exception/GlobalExceptionHandler.java # @RestControllerAdvice:BusinessException / MethodArgumentNotValidException / DuplicateKeyException / 兜底 | |
| 27 | +│ │ ├── base/BaseEntity.java # 标准列公共字段(iIncrement/sId/sBrandsId/sSubsidiaryId/tCreateDate)+ MP 自动填充元信息 | |
| 28 | +│ │ ├── config/MybatisPlusConfig.java # MP 配置(自动填充 MetaObjectHandler、可选分页插件) | |
| 29 | +│ │ ├── config/SecurityConfig.java # SecurityFilterChain:放行 /api/usr/login + swagger,其余需认证;注册 BCryptPasswordEncoder Bean;挂 JwtAuthenticationFilter | |
| 30 | +│ │ └── security/JwtAuthenticationFilter.java + JwtUtil.java + SecurityUtil.java # JWT 解析过滤器、JWT 工具、取当前登录用户名工具 | |
| 31 | +│ └── modules/usr/ | |
| 32 | +│ ├── controller/UsrUserController.java # 仅 @Valid + 委派 | |
| 33 | +│ ├── service/UsrUserService.java # 接口 | |
| 34 | +│ ├── service/impl/UsrUserServiceImpl.java # 业务实现 @Transactional | |
| 35 | +│ ├── mapper/UsrUserMapper.java # BaseMapper<UsrUser> | |
| 36 | +│ ├── mapper/UsrUserPermissionMapper.java # BaseMapper<UsrUserPermission> | |
| 37 | +│ ├── mapper/UsrEmployeeMapper.java # BaseMapper<UsrEmployee>(读校验) | |
| 38 | +│ ├── mapper/UsrPermissionMapper.java # BaseMapper<UsrPermission>(读校验) | |
| 39 | +│ ├── entity/UsrUser.java / UsrUserPermission.java / UsrEmployee.java / UsrPermission.java | |
| 40 | +│ └── dto/CreateUserDTO.java | |
| 41 | +├── src/main/resources/ | |
| 42 | +│ ├── application.yml # 端口/数据源/MP/Flyway locations/JWT(值引用 config-vars,不硬编码密钥) | |
| 43 | +│ └── application-test.yml # 测试 profile(连同一测试库;Flyway 自动 apply V1) | |
| 44 | +└── src/test/java/com/xly/erp/ # 测试,包结构镜像主代码 | |
| 45 | +``` | |
| 46 | + | |
| 47 | +- **跨模块**:本 REQ 仅触及 `modules/usr/**` 与 `common/**`。`common/**` 为首次创建的公共基础设施(非业务模块改动),属项目骨架初始化。 | |
| 48 | +- **数据访问**:只走 Mapper(MyBatis-Plus),查重 / 存在性校验用 `LambdaQueryWrapper` 或 MP 内置;Controller 禁止直接调 Mapper。 | |
| 49 | + | |
| 50 | +## Tech Stack(技术栈,源自 docs/04 § 零 + config-vars) | |
| 51 | + | |
| 52 | +- Spring Boot 3.x / Java 17 / Maven 3.9.x;MyBatis-Plus;MySQL 8.x;Flyway 10.x(`flyway-core` + `flyway-mysql`,启动时自动 apply `sql/migrations/`)。 | |
| 53 | +- Spring Security + JWT(jjwt);`BCryptPasswordEncoder` 哈希密码;MapStruct(可选,本 REQ 转换简单可手写);Hutool。 | |
| 54 | +- 根包 `com.xly.erp`;端口 `5172`(`config-vars.yaml backend.http_port`);DB schema `xlyweberp_vibe_erp_test`;JWT 密钥取 `config-vars.yaml secrets.jwt_secret`。 | |
| 55 | +- 命令(docs/04 § 零):build `mvn -q -B -DskipTests package`;lint `mvn -q -B checkstyle:check`;unit `mvn -q -B test`;e2e 无。 | |
| 56 | +- migration:**不新增**,复用 `V1__initial_schema.sql`(spec § 4 / D4)。 | |
| 57 | + | |
| 58 | +## 合同级常量(跨 task 必须一致) | |
| 59 | + | |
| 60 | +- REST:`POST /api/usr/users`。 | |
| 61 | +- 错误码(`ResultCode` 枚举,spec § 6 / docs/05):`SUCCESS=0`、`PARAM_INVALID=40001`、`FORBIDDEN=40301`、`USERNAME_EXISTS=40901`。(本枚举同时预留后续 REQ 用:`UNAUTHORIZED=40101`、`ACCOUNT_DISABLED=40302`、`NOT_FOUND=40401`、`PAGE_PARAM_INVALID=42201`,由本 REQ 一次性建好枚举,避免后续重复改公共文件。) | |
| 62 | +- 默认值:`initialPassword` 缺省 `666666`;`sUserType` 缺省 `普通用户`;`iCanModifyBill` 缺省 `0`;`sBrandsId`/`sSubsidiaryId` 由 DB 列默认 `1111111111` 兜底(实体不强制赋值)。 | |
| 63 | +- 枚举取值:`sUserType ∈ {普通用户, 超级管理员}`;`sLanguage ∈ {中文, 英文, 繁体}`(必填,spec D1:后端不强制业务默认)。 | |
| 64 | +- 系统字段:`tCreateDate`=当前时间;`sCreator`=当前登录用户名(SecurityContext 取 `sUserName`);`sPassword`=BCrypt(initialPassword);`iIsVoid=0`;`tLastLoginDate=null`。 | |
| 65 | + | |
| 66 | +## 关键签名(首次出现处给出,跨 task 保持一致) | |
| 67 | + | |
| 68 | +- `Result<T>`:静态工厂 `Result.success(T data)`、`Result.success()`、`Result.fail(ResultCode code, String message)`;字段 `int code` / `String message` / `T data`。 | |
| 69 | +- `ResultCode`:枚举常量 `(int code, String message)`,含 `getCode()` / `getMessage()`。 | |
| 70 | +- `BusinessException extends RuntimeException`:构造 `BusinessException(ResultCode code)` 与 `BusinessException(ResultCode code, String message)`;暴露 `getResultCode()`。 | |
| 71 | +- `BaseEntity`:受保护公共字段 `Integer iIncrement`(`@TableId(type = IdType.AUTO)`)/ `String sId` / `String sBrandsId` / `String sSubsidiaryId` / `LocalDateTime tCreateDate`(`@TableField(fill = INSERT)`)。 | |
| 72 | +- `CreateUserDTO`:字段与校验注解见下「DTO 形状」。 | |
| 73 | +- `UsrUserService#createUser(CreateUserDTO dto)` 返回 `Integer`(新建用户主键 `iIncrement`)。 | |
| 74 | +- `UsrUserController#createUser(@Valid @RequestBody CreateUserDTO dto)` 返回 `Result<Map<String,Object>>`(`data.id` = 新主键,键名 `id`)。 | |
| 75 | +- `SecurityUtil.currentUserName()` 返回 `String`(当前 JWT 主体的 `sUserName`)。 | |
| 76 | +- `SecurityUtil.currentUserType()` 返回 `String`(当前主体 `sUserType`,用于管理员判定)。 | |
| 77 | + | |
| 78 | +### DTO 形状(`CreateUserDTO`) | |
| 79 | + | |
| 80 | +| 字段 | 类型 | 校验注解 | | |
| 81 | +|---|---|---| | |
| 82 | +| `sUserName` | String | `@NotBlank` + `@Pattern(regexp="^[A-Za-z0-9_]{3,20}$")` | | |
| 83 | +| `sUserNo` | String | `@Size(max=50)` | | |
| 84 | +| `iEmployeeId` | Integer | —(存在性在 Service 校验) | | |
| 85 | +| `sUserType` | String | `@Pattern(regexp="^(普通用户|超级管理员)$")`(为空时 Service 兜底 `普通用户`,故不加 `@NotBlank`) | | |
| 86 | +| `sLanguage` | String | `@NotBlank` + `@Pattern(regexp="^(中文|英文|繁体)$")` | | |
| 87 | +| `iCanModifyBill` | Integer | `@Min(0)` + `@Max(1)`(为空时 Service 兜底 0) | | |
| 88 | +| `permissionIds` | `List<Integer>` | —(元素存在性在 Service 校验) | | |
| 89 | +| `initialPassword` | String | `@Size(max=100)`(为空时 Service 兜底 `666666`) | | |
| 90 | + | |
| 91 | +> 注:`@Valid` 失败由 `GlobalExceptionHandler` 统一转 `40001`。枚举/格式越界既可由注解触发(如已传非法值),也由 Service 兜底前的显式校验保证;Service 对「关联 id 不存在」「枚举越界(兜底后再判)」抛 `BusinessException(PARAM_INVALID)`。 | |
| 92 | + | |
| 93 | +--- | |
| 94 | + | |
| 95 | +## 任务清单(每个 task = red → green → 子会话验证 PASS → commit;粒度 2-5 分钟) | |
| 96 | + | |
| 97 | +> 说明:T1-T3 为项目骨架初始化(首个 REQ 必需,否则无可编译/可测的工程)。骨架就绪后 T4 起进入本 REQ 业务红绿循环。每个 task 完成后单独 commit(业务类 commit subject 带 `REQ-USR-001`)。 | |
| 98 | + | |
| 99 | +### T1 — Maven 骨架 + 启动 + 健康自检(绿) | |
| 100 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/ErpApplicationTests.java::contextLoads` —— Spring 上下文能加载(`@SpringBootTest`,激活 `test` profile)。意图:证明 pom 依赖 / 启动类 / `application.yml` 自洽、Flyway 能对测试库 apply V1、MyBatis-Plus 装配成功。 | |
| 101 | +- [ ] **实现**:`backend/pom.xml`(按 Tech Stack 声明依赖与构建命令对应插件,含 `checkstyle` 插件以支撑 lint 命令)、`backend/src/main/java/com/xly/erp/ErpApplication.java`、`backend/src/main/resources/application.yml`(端口 5172、数据源指向 `config-vars` 的 DB、`spring.flyway.locations=filesystem:../sql/migrations`、JWT 配置占位读 env/配置)、`application-test.yml`(test profile 连同一库)。 | |
| 102 | +- [ ] **验证**:子会话跑 `mvn -q -B test -Dtest=ErpApplicationTests`(或全量 `mvn -q -B test`)PASS。 | |
| 103 | +- [ ] **commit**:`chore(usr): 初始化 backend Maven 骨架与启动类` | |
| 104 | + | |
| 105 | +### T2 — 公共响应与异常基础设施 | |
| 106 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/common/response/ResultTest.java::successCarriesCodeZeroAndData` + `failCarriesBusinessCodeAndMessage`;`backend/src/test/java/com/xly/erp/common/exception/GlobalExceptionHandlerTest.java::businessExceptionMapsToResult` —— 校验 `Result.success(data).code==0`,`Result.fail(USERNAME_EXISTS,msg)` 带 `40901` 与 message;`GlobalExceptionHandler` 把 `BusinessException` 转为对应 `Result`(可用单元方式直接调 handler 方法断言返回 `Result`)。 | |
| 107 | +- [ ] **实现**:`common/response/Result.java`、`common/response/ResultCode.java`(含「合同级常量」全部错误码)、`common/exception/BusinessException.java`、`common/exception/GlobalExceptionHandler.java`(处理 `BusinessException`→对应码、`MethodArgumentNotValidException`→`40001`、`DuplicateKeyException`→`40901`、其余→通用系统错误且记 ERROR 日志不泄栈)。 | |
| 108 | +- [ ] **验证**:子会话跑相关测试类 PASS。 | |
| 109 | +- [ ] **commit**:`feat(usr): 统一响应体/错误码枚举/全局异常处理 REQ-USR-001` | |
| 110 | + | |
| 111 | +### T3 — BaseEntity + MP 自动填充 + Security/JWT 基础设施 | |
| 112 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/common/security/JwtUtilTest.java::generateThenParseRoundTrip` —— 用 `config-vars` 的 `jwt_secret` 签发含 `sUserName` + `sUserType` claim 的 token,再解析能取回相同值且校验通过;`SecurityConfigTest`(轻量:断言 `BCryptPasswordEncoder` Bean 存在且 `encode/matches("666666")` 成立,可作 `@SpringBootTest` 注入或单元 new)。 | |
| 113 | +- [ ] **实现**:`common/base/BaseEntity.java`(标准列字段 + MP 注解)、`common/config/MybatisPlusConfig.java`(`MetaObjectHandler` 填充 `tCreateDate`;可注册分页插件供后续 REQ)、`common/config/SecurityConfig.java`(`SecurityFilterChain`:放行 `/api/usr/login`、swagger、actuator/health;其余 `authenticated()`;注册 `BCryptPasswordEncoder` Bean;将 `JwtAuthenticationFilter` 加在 `UsernamePasswordAuthenticationFilter` 之前;无状态 session)、`common/security/JwtUtil.java`、`common/security/JwtAuthenticationFilter.java`(解析 `Authorization: Bearer`,把 `sUserName`/`sUserType` 放入 `Authentication` 的 principal/authorities)、`common/security/SecurityUtil.java`(`currentUserName()` / `currentUserType()` 从 `SecurityContextHolder` 取)。 | |
| 114 | +- [ ] **验证**:子会话跑 `JwtUtilTest` + Security 相关测试 PASS。 | |
| 115 | +- [ ] **commit**:`feat(usr): BaseEntity 与 MP 自动填充、Security/JWT 基础设施 REQ-USR-001` | |
| 116 | + | |
| 117 | +### T4 — USR 实体 + Mapper + CreateUserDTO(含 Bean Validation) | |
| 118 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/modules/usr/dto/CreateUserDTOValidationTest.java`: | |
| 119 | + - `::rejectsBadUserName` —— `sUserName="ab"`(<3 位)或含非法字符触发约束违反。 | |
| 120 | + - `::rejectsIllegalLanguage` —— `sLanguage="日文"` 违反 `@Pattern`。 | |
| 121 | + - `::acceptsMinimalValidBody` —— `{sUserName:"good_user", sLanguage:"中文"}` 无违反(`sUserType`/`iCanModifyBill`/`initialPassword` 允许空,Service 兜底)。 | |
| 122 | + (用 `jakarta.validation.Validator` 直接 validate DTO 断言 violations 数量。) | |
| 123 | +- [ ] **实现**:`modules/usr/entity/{UsrUser,UsrUserPermission,UsrEmployee,UsrPermission}.java`(`@TableName` 映射对应表,字段名与匈牙利前缀列名一致;`UsrUser`/关联表可继承 `BaseEntity` 复用标准列,业务列按 docs/03 列出,`sPassword` 标 `@TableField` 不参与序列化输出可用 VO 层控制——本 REQ 不返回实体)、`modules/usr/mapper/{UsrUserMapper,UsrUserPermissionMapper,UsrEmployeeMapper,UsrPermissionMapper}.java`(继承 `BaseMapper<T>`)、`modules/usr/dto/CreateUserDTO.java`(按「DTO 形状」加校验注解)。 | |
| 124 | +- [ ] **验证**:子会话跑 `CreateUserDTOValidationTest` PASS。 | |
| 125 | +- [ ] **commit**:`feat(usr): 用户相关实体/Mapper/CreateUserDTO 校验 REQ-USR-001` | |
| 126 | + | |
| 127 | +### T5 — Service:用户名查重 + 密码哈希 + 落库(核心写) | |
| 128 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/modules/usr/service/UsrUserServiceImplTest.java`(Mockito 单元,mock 4 个 Mapper + `BCryptPasswordEncoder` + `SecurityUtil`): | |
| 129 | + - `::createUserHashesPasswordAndSetsAuditFields` —— 给最小合法 DTO(不传 `initialPassword`),断言传给 `UsrUserMapper.insert` 的 `UsrUser`:`sPassword` 非明文 `666666` 且 `encoder.encode` 被以 `666666` 调用、`iIsVoid==0`、`sUserType=="普通用户"`、`sCreator==当前用户名`、`tLastLoginDate==null`,返回新主键。 | |
| 130 | + - `::duplicateUserNameThrows40901` —— 查重命中(mock 查到已存在)→ 抛 `BusinessException(USERNAME_EXISTS)`。 | |
| 131 | + - `::duplicateKeyExceptionTranslatesTo40901` —— `insert` 抛 `DuplicateKeyException` → Service 转 `BusinessException(USERNAME_EXISTS)`(并发兜底)。 | |
| 132 | +- [ ] **实现**:`modules/usr/service/UsrUserService.java`(接口 `Integer createUser(CreateUserDTO)`)+ `modules/usr/service/impl/UsrUserServiceImpl.java`:先按 `sUserName` 查重命中抛 `40901`;兜底默认值(`sUserType`/`iCanModifyBill`/`initialPassword`/`sLanguage` 非法再校验抛 `40001`);`BCryptPasswordEncoder.encode` 哈希;填审计字段;`insert` 捕获 `DuplicateKeyException` 转 `40901`;方法标 `@Transactional(rollbackFor = Exception.class)`。 | |
| 133 | +- [ ] **验证**:子会话跑 `UsrUserServiceImplTest`(上述三个用例)PASS。 | |
| 134 | +- [ ] **commit**:`feat(usr): 新增用户 Service 查重/哈希/落库 REQ-USR-001` | |
| 135 | + | |
| 136 | +### T6 — Service:关联职员/权限存在性校验 + 权限批量授权 | |
| 137 | +- [ ] **测试**:续 `UsrUserServiceImplTest`: | |
| 138 | + - `::nonExistentEmployeeThrows40001` —— 传 `iEmployeeId` 但 `UsrEmployeeMapper` 查不到 → `BusinessException(PARAM_INVALID)`,不 insert。 | |
| 139 | + - `::nonExistentPermissionThrows40001` —— `permissionIds` 含库中不存在 id → `40001`,回滚(不写 user_permission)。 | |
| 140 | + - `::grantsDedupedPermissions` —— `permissionIds=[a,a,b]`(均存在)→ 去重后对 `UsrUserPermissionMapper` 批量插入 2 行 `(newUserId,a)`/`(newUserId,b)`(断言插入次数/内容)。 | |
| 141 | +- [ ] **实现**:在 `UsrUserServiceImpl.createUser` 内:`iEmployeeId` 非空时校验 `usr_employee` 存在(不存在抛 `40001`);`permissionIds` 非空时去重并逐个/批量校验 `usr_permission` 存在(缺失抛 `40001`),用户落库后用 `iIncrement` 批量写 `usr_user_permission`;全程同一 `@Transactional`。 | |
| 142 | +- [ ] **验证**:子会话跑上述三用例 PASS。 | |
| 143 | +- [ ] **commit**:`feat(usr): 新增用户关联职员/权限校验与授权写入 REQ-USR-001` | |
| 144 | + | |
| 145 | +### T7 — Controller + 权限前置(管理员判定) | |
| 146 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/modules/usr/controller/UsrUserControllerTest.java`(`@WebMvcTest(UsrUserController.class)` + MockMvc,mock `UsrUserService` 与安全上下文): | |
| 147 | + - `::adminCreateReturnsCodeZeroWithId` —— 模拟管理员(`sUserType=超级管理员`)POST 合法 body → HTTP 200,响应 `code==0`,`data.id` 为返回主键;响应体不含 `sPassword`/明文密码。 | |
| 148 | + - `::nonAdminReturns40301` —— 模拟普通用户(`sUserType=普通用户`)→ `code==40301`,Service.createUser 不被调用。 | |
| 149 | + - `::invalidBodyReturns40001` —— `sUserName` 非法 → `code==40001`(经 `@Valid` + `GlobalExceptionHandler`)。 | |
| 150 | +- [ ] **实现**:`modules/usr/controller/UsrUserController.java`:`@PostMapping("/api/usr/users")`,`@Valid @RequestBody CreateUserDTO`;入口判定 `SecurityUtil.currentUserType()` 非 `超级管理员` 抛 `BusinessException(FORBIDDEN)`(先于业务校验,spec § 3.9);委派 `service.createUser`;返回 `Result.success(Map.of("id", newId))`。Controller 不直接调 Mapper、不写业务逻辑。 | |
| 151 | +- [ ] **验证**:子会话跑 `UsrUserControllerTest` PASS。 | |
| 152 | +- [ ] **commit**:`feat(usr): 新增用户 Controller 与管理员权限前置 REQ-USR-001` | |
| 153 | + | |
| 154 | +### T8 — 端到端验收回归(按 spec § 7 验收标准收口) | |
| 155 | +- [ ] **测试**:`backend/src/test/java/com/xly/erp/modules/usr/UsrUserCreateIT.java`(`@SpringBootTest` + MockMvc,test profile 连测试库,Flyway 已 apply V1;测试内预置/清理 fixture 数据)覆盖 spec § 7: | |
| 156 | + - `::ac1_normalCreatePersistsHashedPassword` —— 管理员合法 body → `code=0`,库中新增一行,`sPassword` 可被 `encoder.matches("666666", hash)` 通过,`iIsVoid=0`、`sCreator`=调用者、`tCreateDate` 已填。 | |
| 157 | + - `::ac2_duplicateUserNameRollsBack` —— 重复 `sUserName` → `40901`,无新增行。 | |
| 158 | + - `::ac4_permissionGrantWritesRows` —— `permissionIds=[a,b]`(先插 fixture 权限)→ `usr_user_permission` 新增 2 行。 | |
| 159 | + - `::ac5_nonAdminForbidden` —— 普通用户/无 token → `40301` / 401,不创建记录。 | |
| 160 | + - `::ac7_responseHasNoPassword` —— 成功响应体仅含 `data.id`,无密码字段。 | |
| 161 | + (IT 通过真实 BCrypt + 真实库写入做端到端确认;如环境无法连库,子会话需明确报告而非静默跳过。) | |
| 162 | +- [ ] **实现**:仅在前序 task 暴露缺口时做最小修补(如审计字段填充、权限去重边界),不引入新公共契约。 | |
| 163 | +- [ ] **验证**:子会话跑 `UsrUserCreateIT` PASS(连库);随后跑全量 `mvn -q -B test` 全绿,`mvn -q -B checkstyle:check` 通过。 | |
| 164 | +- [ ] **commit**:`test(usr): 新增用户端到端验收回归 REQ-USR-001` | |
| 165 | + | |
| 166 | +--- | |
| 167 | + | |
| 168 | +## 自审 | |
| 169 | + | |
| 170 | +### 占位符扫描 | |
| 171 | +- 全文无 `【人工填写】` / `TBD` / `TODO` / 待定占位(DB 文档 D1/D5 的「需用户审阅」遗留标记不在本后端 REQ 作用域,按 spec D1 取「`sLanguage` 必填、不强制业务默认」继续)。 | |
| 172 | + | |
| 173 | +### Spec coverage(spec 每节 → task 映射) | |
| 174 | +- § 1 Goal → T7(端点)+ 全部 task。 | |
| 175 | +- § 2.1 输入 / DTO 字段与校验 → T4(DTO 校验)+ T5/T6(Service 兜底与存在性)。 | |
| 176 | +- § 2.2 输出 `Result<{id}>` → T2(Result)+ T7(Controller 组装)。 | |
| 177 | +- § 3.1 用户名唯一(含并发 DuplicateKey 转 40901)→ T5。 | |
| 178 | +- § 3.2 密码默认 + BCrypt + 不落明文/不进响应 → T3(encoder)+ T5(哈希)+ T7/T8(响应不含密码)。 | |
| 179 | +- § 3.3 用户类型默认与约束 → T4(注解)+ T5(兜底/越界 40001)。 | |
| 180 | +- § 3.4 语言取值约束 → T4(`@Pattern`)。 | |
| 181 | +- § 3.5 关联职员存在性 → T6。 | |
| 182 | +- § 3.6 权限组多对多授权 + 去重 + 唯一索引 → T6。 | |
| 183 | +- § 3.7 新建即生效 `iIsVoid=0` → T5(审计/状态字段)+ T8(AC1)。 | |
| 184 | +- § 3.8 制单人/创建时间审计 → T3(MetaObjectHandler)+ T5(显式赋值 sCreator)。 | |
| 185 | +- § 3.9 权限前置(非管理员 40301,先于业务)→ T7。 | |
| 186 | +- § 4 约束(分层/包路径/命名/统一响应/异常/事务/认证/数据访问/配置/schema)→ T1-T7 分层落位,schema 复用 V1(无新 migration)。 | |
| 187 | +- § 5 Schema 引用 → T4(实体/Mapper 映射 4 表)。 | |
| 188 | +- § 6 错误码 → T2(ResultCode 枚举)。 | |
| 189 | +- § 7 验收标准 1-7 → T8(IT 覆盖 AC1/2/4/5/7;AC3 参数非法在 T4+T7,AC6 默认密码在 T5+T8 AC1)。 | |
| 190 | +- § 8 decisions(D1-D5)→ 已体现于「合同级常量」「DTO 形状」与不新增 migration(D4)。 | |
| 191 | + | |
| 192 | +### 类型一致性 | |
| 193 | +- `Result` / `ResultCode` / `BusinessException` 签名在 T2 定义,T5/T6/T7 一致引用。 | |
| 194 | +- `CreateUserDTO` 字段与校验在 T4 锁定,T5/T6/T7/T8 一致使用。 | |
| 195 | +- `UsrUserService#createUser(CreateUserDTO):Integer` 与 `UsrUserController#createUser` 返回 `Result<Map>`(`data.id`)跨 T5/T7 一致。 | |
| 196 | +- 错误码字面量 `0/40001/40301/40901` 与 docs/05、spec § 6 一致。 | |
| 197 | +- REST 路径 `/api/usr/users`、放行路径 `/api/usr/login` 与 docs/05 一致。 | ... | ... |