2026-06-01-REQ-USR-001.md
10.9 KB
REQ-USR-001 增加用户 — 实现规格(后端)
阶段:后端(backend)。作用域限定 controller / service / repository / DTO / 校验 / SQL migration / REST 契约。 SSoT 引用:需求卡片
docs/01-需求清单/USR-用户管理/REQ-USR-001.md;DB 设计docs/03-数据库设计文档.md;API 契约docs/05-API接口契约.md;技术规范docs/04-技术规范.md。 本规格只消费已锁定事实,忽略 UI 描述(控件类型/按钮位置/布局),但校验规则与业务规则全部下沉到后端 DTO + Service。
1. Goal(目标)
后台管理员新建用户账号:指定用户名、密码(默认初始化)、用户类型、语言、可选关联职员、可选权限组授权,保存后账号立即生效可用。对外仅提供一个端点:POST /api/usr/users。
2. 输入 / 输出
2.1 输入(请求)
-
Method / Path:
POST /api/usr/users -
Auth:需要 Bearer JWT,且调用方必须为管理员 / 超级管理员(
sUserType = 超级管理员)。普通用户调用返回40301。 -
请求体(JSON)→
CreateUserDTO:
| DTO 字段 | 类型 | 必填 | 校验 | 落库列(usr_user) |
说明 |
|---|---|---|---|---|---|
sUserName |
String | 是 |
@NotBlank + @Pattern(regexp="^[A-Za-z0-9_]{3,20}$")
|
sUserName |
登录账号,3-20 位字母/数字/下划线,全局唯一 |
sUserNo |
String | 否 | @Size(max=50) |
sUserNo |
用户号,可由前端在选择职员后带出(后端按传入值落库;为空则存 null) |
iEmployeeId |
Integer | 否 | — | iEmployeeId |
关联职员 ID;传入时必须为 usr_employee 中存在的记录,否则 40001
|
sUserType |
String | 是 |
@NotBlank + 取值 ∈ {普通用户,超级管理员},默认 普通用户
|
sUserType |
用户类型;前端未传时由 Service 兜底为 普通用户
|
sLanguage |
String | 是 |
@NotBlank + 取值 ∈ {中文,英文,繁体} |
sLanguage |
界面语言 |
iCanModifyBill |
Integer | 否 | 取值 ∈ {0,1},默认 0 | iCanModifyBill |
单据修改权限 |
permissionIds |
List<Integer> | 否 | 元素须为 usr_permission 中存在的 id(去重) |
写 usr_user_permission
|
权限组勾选;为空 / null 表示不授权 |
initialPassword |
String | 否 |
@Size(max=100),默认 666666
|
sPassword(BCrypt 哈希) |
初始密码;未传时取默认 666666,BCrypt 哈希后入库 |
系统生成字段(不接受前端传入,由后端填充):
tCreateDate=当前时间;sCreator=当前登录用户(从 JWT/SecurityContext 取sUserName);sPassword=BCrypt(initialPassword);iIsVoid=0(新建即生效);sBrandsId/sSubsidiaryId=表默认值1111111111(标准列默认,多租户隔离)。tLastLoginDate保持 null。
2.2 输出(响应)
- 成功:
Result<{ id: number }>,code=0,data.id= 新建用户主键usr_user.iIncrement。 - 失败:统一
Result(见 § 6 错误码),message给可读中文提示,不抛栈。
3. 业务规则
-
用户名全局唯一:插入前按
sUserName查重(命中唯一索引uk_usr_user_username),存在则抛BusinessException(40901)。即便并发命中唯一约束(DuplicateKeyException),也统一转换为40901。 -
密码初始化 + 哈希存储:
initialPassword缺省为666666;一律经BCryptPasswordEncoder哈希后写入sPassword,禁止明文落库 / 进日志 / 进响应。响应与任何查询均不返回密码。 -
用户类型默认与约束:
sUserType缺省普通用户;取值仅限 {普通用户,超级管理员},越界40001。 -
语言取值约束:
sLanguage仅限 {中文,英文,繁体}(依据 docs/03 业务含义与 REQ 卡片下拉来源),越界40001。默认值见 § 8 决策记录。 -
关联职员可选且需存在:传
iEmployeeId时校验usr_employee存在;不存在40001。职员删除时外键ON DELETE SET NULL,与本 REQ 写入无冲突。 -
权限组授权(多对多):
permissionIds非空时,校验每个 id 在usr_permission存在(不存在40001),去重后为新用户在usr_user_permission批量插入(iUserId, iPermissionId)行;唯一索引uk_usr_user_permission防重复授权。 -
新建即生效:
iIsVoid=0,无需额外激活流程,保存后即可登录(登录由 REQ-USR-004 负责)。 -
制单人 / 创建时间审计:
sCreator取当前登录用户名,tCreateDate取当前时间;按 docs/04 § 3.4 可由BaseEntity+ MP 自动填充,或在 Service 显式赋值(实现二选一,结果一致)。 -
权限校验前置:仅
超级管理员/ 管理员可调用;非管理员40301(在 Spring Security 或 Service 入口判定,先于业务校验)。
4. 约束(技术 / 安全)
-
分层(docs/04 § 1.2):
UsrUserController(仅@Valid校验 + 委派)→UsrUserService/UsrUserServiceImpl(业务)→UsrUserMapper/UsrUserPermissionMapper(MyBatis-Plus)。Controller 禁止直接操作 Mapper。 -
包路径:
com.xly.erp.modules.usr.{controller,service,service.impl,mapper,entity,dto,vo}。本 REQ 仅触及modules/usr/**,不跨模块。 -
命名(docs/04 § 1.3):类
UsrUserController/UsrUserServiceImpl/UsrUserMapper;方法createUser;REST 路径/api/usr/users。 -
统一响应(docs/04 § 1.4):返回
Result<T>,code=0成功;错误码集中在ResultCode枚举。 -
异常处理(docs/04 § 1.5):业务错误抛
BusinessException(ResultCode,msg),由GlobalExceptionHandler转Result;@Valid校验失败由全局处理器转40001。 -
事务(docs/04 § 1.6):
createUser涉及usr_user+usr_user_permission多表写,方法上加@Transactional(rollbackFor = Exception.class),任一步失败整体回滚。 -
认证 / 密码(docs/04 § 1.7):受保护接口经
JwtAuthenticationFilter;密码用BCryptPasswordEncoder,禁明文。 -
数据访问(docs/04 § 3.4):只走 Mapper;查重 / 校验用
LambdaQueryWrapper或 MP 内置。 -
配置:JWT 密钥、DB 凭据只从
config-vars.yaml/application.yml读取,不硬编码。admin_init(admin/666666)为系统初始管理员,与本 REQ 默认密码666666一致。 -
schema:本 REQ 所需表(
usr_user/usr_employee/usr_permission/usr_user_permission)已由sql/migrations/V1__initial_schema.sql建好,无需新增 migration。
5. Schema 引用(docs/03 SSoT)
-
写:
usr_user(主键iIncrement;唯一索引uk_usr_user_usernameonsUserName;列见 docs/03 § usr_user)。 -
写:
usr_user_permission(关联表;唯一索引uk_usr_user_permissionon(iUserId,iPermissionId);外键fk_usr_up_user/fk_usr_up_permission均 CASCADE)。 -
读:
usr_employee(校验iEmployeeId存在);usr_permission(校验permissionIds存在)。 - 实体:
UsrUser↔usr_user,UsrUserPermission↔usr_user_permission,UsrEmployee↔usr_employee,UsrPermission↔usr_permission(匈牙利前缀列名,实体字段与列名映射保持一致)。
6. API 引用 / 错误码(docs/05 SSoT)
- 端点契约见
docs/05-API接口契约.md§ REQ-USR-001。 - 错误码:
-
40001— 参数校验失败(字段格式 / 必填 / 枚举越界 / 关联 id 不存在)。 -
40901— 用户名已存在(sUserName唯一冲突)。 -
40301— 无权限(非管理员调用)。 -
0— 成功,返回data.id。
-
7. 验收标准(Acceptance Criteria)
-
正常新增:管理员携带合法 body(
sUserName合规、sUserType/sLanguage合法)调用POST /api/usr/users→code=0,data.id为正整数;usr_user新增一行,sPassword为 BCrypt 哈希(非明文,可被BCryptPasswordEncoder.matches("666666", hash)校验通过),iIsVoid=0,sCreator=调用者用户名,tCreateDate已填。 -
重复用户名:以已存在的
sUserName调用 →code=40901,无新增行(事务回滚)。 -
参数非法:
sUserName不满足 3-20 位字母数字下划线 /sUserType或sLanguage越界 /iEmployeeId或permissionIds引用不存在的 id →code=40001,无副作用。 -
权限组授权:传
permissionIds=[a,b](均存在)→usr_user_permission新增 2 行(newUserId, a/b);重复 id 去重后仅写一次。 -
越权访问:普通用户(
sUserType=普通用户)或无 / 失效 token 调用 →code=40301(无权限)/ 401(未认证),不创建记录。 -
默认密码:不传
initialPassword时,按666666哈希入库;可用666666经后续登录流程通过。 -
响应不含密码:成功响应体仅含
data.id,绝不含sPassword或明文密码。
8. 自主决策记录(decisions)
| # | 问题 | 选择 | 依据 | 置信度 |
|---|---|---|---|---|
| D1 |
sLanguage 默认值(REQ 卡片标"无默认",docs/03 标默认 中文 且带"需用户审阅"占位) |
后端不强制默认,sLanguage 为必填、必须由请求显式传入合法值;若 DB 层因 NOT NULL DEFAULT 中文 而存在兜底,仅作为防御性约束,不作为业务默认 |
REQ 卡片「语言 必填=是、默认值=—」优先表达业务意图;docs/03 的 中文 默认带审阅占位,不作为锁定事实。取必填可避免歧义 |
medium |
| D2 | 管理员判定口径 | 以 sUserType=超级管理员 视为有调用权限;普通用户禁止。具体角色映射若后续有 Spring Security 角色体系再细化 |
docs/05 标注"仅管理员/超级管理员可调用",本库用户类型枚举仅 {普通用户,超级管理员},无独立管理员角色表 | medium |
| D3 |
sUserNo 来源 |
后端按请求传入值落库(前端在选择职员后带出员工编号/姓名填入),后端不主动从职员表反查覆盖 | REQ 卡片业务规则"关联职员选择后自动输入"属前端交互;契约 sUserNo(可选) 直接接收,后端阶段不重复实现前端联动 |
high |
| D4 | 不新增 migration | 复用 V1__initial_schema.sql 已建的 4 张表 |
4 张依赖表(usr_user 写、usr_employee/usr_permission 读、usr_user_permission 写)均已在 V1 建好,结构与 docs/03 一致,无 schema 变更 | high |
| D5 | 密码字段命名 | 请求体用 initialPassword,落库列 sPassword(BCrypt) |
与 docs/05 契约一致(initialPassword(可选,默认 666666));docs/04 § 1.7 要求 BCrypt |
high |
注:docs/03 中
sLanguage/usr_company.sVersion/usr_permission粒度的「需用户审阅」占位为 DB 文档遗留标记,不在本后端 REQ 作用域内消解;本规格按上表 D1 取最有依据解读继续,不阻塞。