Files
xh-medical-sign-web/clinical-web/docs/users-permissions-api.md
T

337 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用户与权限页面接口核验与后端缺口
> 核验基准:MEDISIGN 患者电子签 API 文档([https://ipad.shenynet.com/doc.html#/home](https://ipad.shenynet.com/doc.html#/home))。
## 1. 核验结论
当前页面已经可以使用后端接口完成用户列表和用户生命周期的主要操作:查询、分页、关键字搜索、院区筛选、查看详情、新增、编辑和停用。角色列表也已经接入,用于展示角色卡片。
最新接口文档没有提供用户与角色的关联数据,因此用户表中的角色只能显示“未返回角色”;角色接口也没有返回用户人数,角色卡片不会再根据当前用户列表臆造人数。
页面上暂时保留但无法真正执行的功能是:
- 批量导入用户;
- 重置密码;
- 为用户分配或移除角色;
- 角色人数统计。
另外,`/api/v1/permissions` 管理的是 API 权限定义(资源、路径和动作),不是用户或角色的权限分配关系。模板文档权限使用 `/api/v1/templates/{templateId}/permissions`,两者也不能混用。
## 2. 当前已接入接口
| 页面功能 | 接口 | 状态 | 说明 |
| -------- | --------------------------- | ------ | -------------------------------------------------------- |
| 用户列表 | `GET /api/v1/users` | 已接入 | 支持 `page`、`size`、`keyword`、`campusId`;前端已接分页 |
| 用户详情 | `GET /api/v1/users/{id}` | 已接入 | 用于打开编辑弹窗 |
| 新增用户 | `POST /api/v1/users` | 已接入 | 创建时提交账号、密码、基础资料、院区、科室和数据范围 |
| 编辑用户 | `PUT /api/v1/users/{id}` | 已接入 | 不修改用户名和密码 |
| 停用用户 | `DELETE /api/v1/users/{id}` | 已接入 | 后端是软停用,前端有二次确认 |
| 角色列表 | `GET /api/v1/roles` | 已接入 | 支持 `page`、`size`、`keyword`;最新响应没有 `userCount` |
| 院区字典 | `GET /api/v1/campuses` | 已接入 | 用于院区显示和用户表单 |
| 科室字典 | `GET /api/v1/departments` | 已接入 | 用于科室显示和用户表单,可按院区筛选 |
### 2.1 已遵守的接口约束
- 所有请求使用统一响应包装,并通过 `X-Token` 携带登录令牌;
- 用户列表的 `page` 从 1 开始,`size` 范围为 1–200;
- 创建用户时密码只出现在请求中,不能出现在任何响应或日志中;
- 编辑接口不接收用户名和密码;
- 停用接口不执行物理删除,只将状态改为 `DISABLED`;
- 用户接口返回的手机号和邮箱可能是脱敏值。前端编辑时,如果用户没有重新填写,不会把脱敏值回传覆盖原联系方式;
- 用户角色、角色人数和最终权限判断必须由后端提供和校验,前端不自行推导。
## 3. 建议补充的后端接口
### 3.1 用户角色关联(最高优先级)
用户列表和详情最好直接在 `UserResponse` 中增加角色摘要,避免列表为每个用户额外请求一次接口:
```json
{
"roles": [
{
"id": "角色 UUID",
"code": "DOCTOR",
"name": "医生"
}
]
}
```
同时提供角色关系的读取和覆盖更新接口,供用户编辑弹窗使用:
```http
GET /api/v1/users/{userId}/roles
PUT /api/v1/users/{userId}/roles
Content-Type: application/json
```
请求体建议为:
```json
{
"roleIds": ["角色 UUID 1", "角色 UUID 2"]
}
```
`PUT` 使用“完整替换”语义,便于前端重复提交保持幂等。响应建议返回用户和最新角色摘要:
```json
{
"code": 0,
"message": "OK",
"data": {
"userId": "用户 UUID",
"roles": [
{
"id": "角色 UUID",
"code": "DOCTOR",
"name": "医生"
}
],
"updatedAt": "2026-09-01T08:00:00Z"
}
}
```
后端应校验角色是否存在、是否停用、当前操作人是否有分配权限,并记录角色变更审计日志。建议错误码包括 `400`、`401`、`403`、`404` 和 `409`。
### 3.2 重置密码
建议提供:
```http
POST /api/v1/users/{userId}/password/reset
Content-Type: application/json
```
请求体:
```json
{
"newPassword": "管理员输入的新密码",
"forceChangeOnNextLogin": true
}
```
响应使用 `ApiResponseVoid`,不返回密码:
```json
{
"code": 0,
"message": "密码已重置",
"data": {}
}
```
密码必须在服务端进行强度校验和哈希存储;密码不能写入日志、审计详情或响应。若产品要求服务端生成临时密码,应通过受控的一次性展示或院内安全渠道交付,不建议作为普通接口字段长期返回。建议错误码包括 `400`、`401`、`403`、`404` 和 `429`。
### 3.3 批量导入用户
批量导入不建议直接把文件上传后立即写库,建议拆成“预览校验 + 确认导入”:
```http
POST /api/v1/users/import/preview
Content-Type: multipart/form-data
```
表单字段:
| 字段 | 类型 | 说明 |
| ---------- | ---- | ---------------------------------------- |
| `file` | file | CSV 或 XLSX 文件,建议限制文件大小和行数 |
| `campusId` | UUID | 没有院区列时使用的默认院区,可选 |
预览响应至少应包含:
```json
{
"code": 0,
"data": {
"importId": "导入批次 UUID",
"fileName": "users.xlsx",
"totalRows": 100,
"validRows": 96,
"invalidRows": 4,
"rows": [
{
"rowNo": 12,
"status": "INVALID",
"errors": ["用户名已存在"],
"username": "zhangsan",
"displayName": "张三"
}
]
}
}
```
确认导入:
```http
POST /api/v1/users/import/commit
Content-Type: application/json
```
```json
{
"importId": "导入批次 UUID"
}
```
如果导入量较大,确认接口返回任务号并异步执行:
```http
GET /api/v1/users/import/jobs/{jobId}
```
导入文件建议支持 `username`、`displayName`、`employeeNo`、`phone`、`email`、`campusId`、`departmentId`、`roleCodes` 和 `dataScope` 列。密码应遵循统一的临时密码或首次登录设置策略,不建议在普通导入文件中保存明文密码。导入必须逐行返回校验错误,不能因为一行错误而静默跳过整批数据。
### 3.4 角色人数统计
当前 `GET /api/v1/roles` 的响应没有 `userCount`。如果原型需要保留角色卡片人数,建议新增专用汇总接口:
```http
GET /api/v1/roles/summary?campusId={campusId}
```
响应建议为:
```json
{
"code": 0,
"data": [
{
"roleId": "角色 UUID",
"code": "DOCTOR",
"name": "医生",
"description": "可查看并发起签署任务",
"status": "ENABLED",
"userCount": 18
}
]
}
```
需要和后端确认人数口径:建议只统计当前操作人数据权限范围内、状态为 `ENABLED` 的用户,并按当前页面选择的院区过滤;如果产品要统计全院,应明确忽略页面院区筛选。统计接口也必须执行当前用户的权限范围校验。
### 3.5 API 权限分配(仅在产品需要时补充)
现有 `/api/v1/permissions` 只是 API 权限目录的增删改查。若需要让管理员把 API 权限分配给角色,还需要关系接口:
```http
GET /api/v1/roles/{roleId}/permissions
PUT /api/v1/roles/{roleId}/permissions
```
请求体可以使用完整替换语义:
```json
{
"permissionIds": ["API 权限 UUID 1", "API 权限 UUID 2"]
}
```
这组接口与模板文档权限接口不同。模板的角色、科室和用户授权继续使用:
```http
GET /api/v1/templates/{templateId}/permissions
POST /api/v1/templates/{templateId}/permissions
DELETE /api/v1/templates/{templateId}/permissions/{permissionId}
```
## 4. 接口错误与审计要求
所有新增接口建议继续使用统一响应格式,并至少覆盖:
| HTTP 状态 | 场景 |
| --------- | ---------------------------------- |
| `400` | 参数、格式、文件内容或业务校验失败 |
| `401` | 未登录或令牌失效 |
| `403` | 当前用户没有对应管理权限 |
| `404` | 用户、角色、导入批次或权限不存在 |
| `409` | 用户名重复、角色关系并发冲突等 |
| `429` | 密码重置或批量任务触发频率限制 |
| `500` | 服务端异常 |
用户创建、编辑、停用、密码重置、角色变更和批量导入都应产生审计记录,至少包含操作人、目标用户、操作类型、结果、时间和链路追踪 ID;不得记录密码、完整手机号、身份证号或完整病历信息。
## 5. 推荐实施顺序
1. 先补用户角色摘要和角色绑定接口,解除用户表“未返回角色”的限制;
2. 补密码重置接口,前端再把现有提示改为重置密码弹窗;
3. 补批量导入预览、确认和任务查询;
4. 根据产品是否保留角色卡片人数,补角色统计接口;
5. 如果要管理 API 权限,再补角色与 API 权限的关联接口,不能复用 `/api/v1/permissions` CRUD 代替。
## 6. 以最新 OpenAPI 核验授权链路(2026-09-02)
### 6.1 已有能力与当前状态
| 核验项 | 最新接口/实现 | 状态 |
| ------------------ | ----------------------------------------------------------------------- | ------------------------------------ |
| 登录认证 | `POST /api/v1/auth/login` 返回 `X-Token`、`dataScope` 和 `permissions` | 已具备 |
| 当前用户刷新 | `GET /api/v1/auth/me` 返回 `UserResponse`,该 schema 没有 `permissions` | 字段不足 |
| 用户页面登录保护 | 路由只判断是否存在 Token | 已具备认证,缺少权限门控 |
| 用户/角色/权限 API | `/users`、`/roles`、`/permissions` 的 CRUD 接口均在 OpenAPI 中 | API 封装已接入;权限目录暂无对应页面 |
| 用户角色关联 | 最新 OpenAPI 没有用户-角色关系接口 | 后端缺口 |
| 角色 API 权限关联 | 最新 OpenAPI 没有角色-API 权限关系接口 | 后端缺口 |
| 模板文档权限 | `/api/v1/templates/{templateId}/permissions` | 独立能力,不能替代 API 权限 |
当前侧边栏会向所有已登录用户展示“用户与权限”和“文档权限”,直接输入管理端 URL 也可以进入;按钮是否真正可执行目前依赖后端返回 `403`。这不能替代前端菜单/路由门控,但安全边界必须仍由后端执行。
### 6.2 后端需要确认或补充的内容
1. **补齐当前用户权限刷新**
建议修改 `GET /api/v1/auth/me` 的成功响应,使其至少包含登录摘要中的 `dataScope` 和 `permissions: string[]`。权限集合只返回当前用户实际拥有的启用权限,不返回密码或未脱敏敏感信息。若不改现有接口,需新增等价的 `GET /api/v1/auth/me/permissions`,但应只保留一个权威来源。
2. **定义并初始化权限编码**
OpenAPI 只建议使用 `system:<资源>:<动作>` 格式,没有给出本页面实际权限编码。建议后端确认并初始化:
- `system:user:query`、`system:user:create`、`system:user:update`、`system:user:disable`;
- `system:role:query`、`system:role:create`、`system:role:update`、`system:role:disable`;
- `system:permission:query`、`system:permission:create`、`system:permission:update`、`system:permission:disable`。
上述编码是建议值,最终以后端权限中心的命名为准;报表权限还应单独定义 query/export 权限,不能用页面名称或前端隐藏按钮代替。
3. **提供 API 权限关系接口**
`/api/v1/permissions` 只负责权限目录增删改查。若管理端需要真正分配 API 权限,需提供:
```http
GET /api/v1/roles/{roleId}/permissions
PUT /api/v1/roles/{roleId}/permissions
Content-Type: application/json
```
```json
{ "permissionIds": ["API 权限 UUID 1", "API 权限 UUID 2"] }
```
用户角色关系仍需提供:
```http
GET /api/v1/users/{userId}/roles
PUT /api/v1/users/{userId}/roles
```
关系更新建议使用完整替换、幂等语义,校验角色/权限状态和当前操作人权限,并记录审计日志。
4. **所有管理接口必须服务端授权**
用户、角色、API 权限及模板权限接口都应按操作区分 query/create/update/disable 权限;用户 `dataScope` 只能限制数据范围,不能替代操作权限。客户端传入的 `campusId`、`departmentId` 不得扩大操作人的数据范围,越权统一返回 `403`。
5. **补齐错误和审计契约**
所有授权关系和管理写接口建议明确 `400/401/403/404/409/500`;权限变更、用户停用、角色绑定和 API 权限绑定应记录操作人、目标、动作、结果、时间和 traceId,不记录密码、完整手机号、身份证号或病历内容。
### 6.3 前端联调验收
- 登录后调用 `/auth/me` 能拿到最新权限,权限撤销后旧会话访问受保护接口立即得到 `403`;
- 无用户查询权限的账号不能看到用户数据,直接访问 URL 也不能绕过后端;
- 只有对应 create/update/disable 权限的按钮才可用,前端隐藏只是体验优化,不能作为安全措施;
- 用户角色和角色 API 权限变更后,重新登录或刷新当前用户即可看到最新权限;
- Mock 模式仅用于页面演示,真实权限验证必须设置 `VITE_USE_MOCK=false`,使用不同权限账号检查 Network 请求和 401/403 响应。