# 角色管理 — 前端对接文档 > 遵循《业务中台产品接口说明书》全局契约 > 接口基础路径:`/api/roles` > 权限控制:纯 `hasAuthority`,权限码由 `DataInitializer` 种子化到权限资源树 --- ## 1. 接口总览 | 方法 | 路径 | 用途 | 权限码 | |------|------|------|--------| | `POST` | `/api/roles/page` | 分页查询角色列表 | `crm:role:list` | | `POST` | `/api/roles/saveOrUpdate` | 新增或编辑角色 | `crm:role:save` | | `GET` | `/api/roles/detail` | 角色详情(含已授权资源集合) | `crm:role:detail` | | `POST` | `/api/roles/assign-resources` | 分配角色权限资源(祖先补全 + 全量替换) | `crm:role:assign` | | `POST` | `/api/roles/delete` | 删除角色(级联清理关联表) | `crm:role:delete` | **认证方式**:所有接口需在请求头携带 `Authorization: Bearer {token}`,且 token 对应用户必须拥有对应权限码。 **请求格式**: - `GET` 请求:参数放 URL query - `POST` 请求:`Content-Type: application/x-www-form-urlencoded`,参数为表单字段 > ⚠️ 注意:POST 请求不使用 JSON body,复杂字段(对象/数组)各自 `JSON.stringify` 后作为普通表单字段传递。 --- ## 2. 数据模型 ### 2.1 SysRole(列表/编辑) ```typescript interface SysRole { id: string; // 角色 ID(字符串,雪花 ID) roleName: string; // 角色名称 roleCode: string; // 角色编码(如 ROLE_SALES) dataScope: number; // 数据范围:1=本人 2=本部门 3=本部门及子部门 4=全部 sort: number; // 排序号,值越小越靠前 remark: string | null; // 备注 builtin: boolean; // 是否内置角色(true=系统内置,不可删除、不可改编码) } ``` ### 2.2 RoleDetailVO(详情接口返回) ```typescript interface RoleDetailVO { id: string; // 角色 ID roleName: string; // 角色名称 roleCode: string; // 角色编码 dataScope: number; // 数据范围:1=本人 2=本部门 3=本部门及子部门 4=全部 sort: number; // 排序 remark: string | null; // 备注 builtin: boolean; // 是否内置角色 resourceIds: string[]; // 已授权的资源 ID 集合(存储态,已含祖先补全) } ``` > ⚠️ **ID 一律按字符串收发**。后端返回的 id 是字符串(雪花 ID 超出 JS Number 安全范围),前端回传原样传字符串即可,不要转 Number。 ### 2.3 dataScope 枚举 | 值 | 含义 | 说明 | |----|------|------| | 1 | 仅本人 | 只能看到自己创建的数据 | | 2 | 本部门 | 能看到本部门所有成员的数据 | | 3 | 本部门及子部门 | 能看到本部门及其下属部门的数据 | | 4 | 全部数据 | 能看到所有数据 | > 注:值 5(自定义)存在于枚举中,但角色接口不允许设置,仅 1-4 合法。 --- ## 3. 统一响应结构 所有接口返回统一结构: ```typescript interface Result { code: number; // 0=成功,非 0=失败 success: boolean; // 是否成功 message: string; // 错误信息(成功时为 null) data: T; // 业务数据(无数据时为 null) } ``` 分页接口的 `data` 为 `PageResult`: ```typescript interface PageResult { content: T[]; // 数据列表 total: number; // 总条数 size: number; // 每页条数 current: number; // 当前页码(从 1 开始) pages: number; // 总页数 empty: boolean; // 是否为空结果 } ``` --- ## 4. 接口详情 ### 4.1 分页查询角色列表 ``` POST /api/roles/page ``` **权限码**:`crm:role:list` **请求参数**(表单字段): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `current` | number | 否 | 1 | 当前页码 | | `size` | number | 否 | 10 | 每页条数 | | `keyword` | string | 否 | - | 按角色名称模糊搜索 | **请求示例**: ``` POST /api/roles/page Content-Type: application/x-www-form-urlencoded Authorization: Bearer {token} current=1&size=10&keyword=销售 ``` **响应示例**: ```json { "code": 0, "success": true, "data": { "content": [ { "id": "1", "roleName": "管理员", "roleCode": "ROLE_ADMIN", "dataScope": 4, "sort": 0, "remark": null, "builtin": true }, { "id": "2", "roleName": "销售", "roleCode": "ROLE_SALES", "dataScope": 2, "sort": 1, "remark": "销售团队角色", "builtin": false } ], "total": 2, "size": 10, "current": 1, "pages": 1, "empty": false } } ``` --- ### 4.2 新增或编辑角色 ``` POST /api/roles/saveOrUpdate ``` **权限码**:`crm:role:save` **请求参数**(表单字段): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `id` | string | 否 | - | 不传=新增,传=编辑 | | `roleName` | string | 是 | - | 角色名称 | | `roleCode` | string | 是 | - | 角色编码(编辑内置角色时不可修改) | | `dataScope` | number | 否 | 1 | 数据范围 1-4 | | `sort` | number | 否 | 0 | 排序号 | | `remark` | string | 否 | - | 备注 | > ⚠️ `builtin` 字段不接受前端传入。新建角色固定 `builtin=false`,内置角色由系统初始化创建。 **校验规则**: | 规则 | 触发条件 | 错误码 | 错误信息 | |------|----------|--------|----------| | 角色名称为空 | `roleName` 为空 | 61010 | 角色名称不能为空 | | 角色编码为空 | `roleCode` 为空 | 61010 | 角色编码不能为空 | | 数据范围不合法 | `dataScope` 不在 1-4 | 61010 | 数据范围不合法 | | 编码已存在 | `roleCode` 与其他角色重复 | 61011 | 角色编码已存在:{roleCode} | | 内置角色改编码 | 编辑内置角色时修改了 `roleCode` | 61012 | 内置角色的编码不可修改 | | 非法创建内置角色 | 尝试设置 `builtin=true` | 61012 | 不允许通过接口创建内置角色 | **新增请求示例**: ``` POST /api/roles/saveOrUpdate Content-Type: application/x-www-form-urlencoded Authorization: Bearer {token} roleName=销售&roleCode=ROLE_SALES&dataScope=2&sort=1&remark=销售团队角色 ``` **编辑请求示例**: ``` POST /api/roles/saveOrUpdate Content-Type: application/x-www-form-urlencoded Authorization: Bearer {token} id=2&roleName=销售&roleCode=ROLE_SALES&dataScope=3&sort=1&remark=改为本部门及子部门 ``` **响应**: ```json { "code": 0, "success": true, "data": null } ``` --- ### 4.3 角色详情 ``` GET /api/roles/detail ``` **权限码**:`crm:role:detail` **请求参数**(URL query): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `roleId` | string | 是 | 角色 ID | **请求示例**: ``` GET /api/roles/detail?roleId=2 Authorization: Bearer {token} ``` **响应示例**: ```json { "code": 0, "success": true, "data": { "id": "2", "roleName": "销售", "roleCode": "ROLE_SALES", "dataScope": 2, "sort": 1, "remark": "销售团队角色", "builtin": false, "resourceIds": ["1", "10", "100", "200"] } } ``` > `resourceIds` 是存储态的完整授权集合(已含祖先补全)。前端在回显权限树勾选状态时,需要用这个集合与资源树做交集——但只需勾选叶子节点,祖先节点会自动包含在集合中但不需要在前端勾选。 --- ### 4.4 分配角色权限资源 ``` POST /api/roles/assign-resources ``` **权限码**:`crm:role:assign` **请求参数**(表单字段): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `roleId` | string | 是 | 角色 ID | | `resourceIds` | string | 否 | 逗号分隔的资源 ID,不传或空字符串=清空权限 | > ⚠️ **前端只需传用户勾选的叶子节点 ID**。后端会自动沿 parentId 向上遍历补全所有祖先节点(ADR-0005),确保菜单可见性链路完整。 > 例如:前端勾选了叶子节点 `100`,其祖先为 `10` → `1`,后端会自动补全为 `{1, 10, 100}` 写入数据库。 **请求示例**: ``` POST /api/roles/assign-resources Content-Type: application/x-www-form-urlencoded Authorization: Bearer {token} roleId=2&resourceIds=100,200,300 ``` **清空权限示例**: ``` POST /api/roles/assign-resources Content-Type: application/x-www-form-urlencoded Authorization: Bearer {token} roleId=2&resourceIds= ``` **响应**: ```json { "code": 0, "success": true, "data": null } ``` **实现说明**: 1. 后端调用 `getAncestorIds` 补全全部祖先 ID 2. 先删除该角色的所有旧关联(全量替换,非增量) 3. 批量插入补全后的完整 ID 集合 4. 整个操作在 `@Transactional` 事务中执行 --- ### 4.5 删除角色 ``` POST /api/roles/delete ``` **权限码**:`crm:role:delete` **请求参数**(表单字段): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | string | 是 | 角色 ID | **请求示例**: ``` POST /api/roles/delete Content-Type: application/x-www-form-urlencoded Authorization: Bearer {token} id=2 ``` **响应**: ```json { "code": 0, "success": true, "data": null } ``` **级联清理说明**: | 步骤 | 操作 | 说明 | |------|------|------| | 1 | 内置角色检查 | `builtin=true` 的角色拒绝删除,返回错误码 61012 | | 2 | 清理 `sys_role_menu` | 删除该角色与所有菜单/权限点的关联 | | 3 | 清理 `sys_user_role` | 解除该角色与所有用户的绑定 | | 4 | 删除角色记录 | 从 `sys_role` 表删除 | | 5 | 全部在事务中执行 | 任一步骤失败则回滚 | **内置角色删除失败示例**: ```json { "code": 61012, "success": false, "message": "内置角色不允许删除" } ``` --- ## 5. 错误码 | 错误码 | 含义 | 触发场景 | |--------|------|----------| | 61010 | 角色参数非法 | 角色名称/编码为空、数据范围不合法 | | 61011 | 角色编码已存在 | 新建或编辑时 roleCode 与其他角色重复 | | 61012 | 内置角色保护 | 删除内置角色、修改内置角色编码、尝试创建内置角色 | --- ## 6. 权限码与前端按钮控制 角色管理页面的按钮需要根据权限码控制显示: | 权限码 | 对应按钮 | 说明 | |--------|----------|------| | `crm:role:list` | 角色列表页面访问 | 控制菜单可见性 | | `crm:role:detail` | 查看/编辑按钮 | 点击后调用详情接口 | | `crm:role:save` | 新增/编辑保存按钮 | 弹窗保存时调用 | | `crm:role:assign` | 权限分配按钮 | 打开权限树弹窗时调用 | | `crm:role:delete` | 删除按钮 | 列表行操作列 | > 这些权限码在系统启动时由 `DataInitializer` 自动创建为 button 类型权限点,并分配给管理员角色。其他角色需要通过权限分配接口授予。 --- ## 7. 前端典型流程 ### 7.1 角色列表页 ``` 1. 进入页面 → POST /api/roles/page?current=1&size=10 2. 输入关键词搜索 → POST /api/roles/page?current=1&size=10&keyword=销售 3. 点击"新增" → 弹窗填写 → POST /api/roles/saveOrUpdate 4. 点击"编辑" → GET /api/roles/detail?roleId=xxx → 回填表单 → POST /api/roles/saveOrUpdate 5. 点击"删除" → 确认 → POST /api/roles/delete?id=xxx ``` ### 7.2 权限分配弹窗 ``` 1. 打开弹窗 → 调用 /api/resources/list 获取资源树 2. 回显已选 → GET /api/roles/detail?roleId=xxx → 取 resourceIds 与资源树做交集 3. 用户勾选/取消勾选叶子节点 4. 点击"保存" → POST /api/roles/assign-resources?roleId=xxx&resourceIds=100,200,300 ``` > 前端只需提交用户勾选的叶子节点 ID。后端自动补全祖先,无需前端处理。 ### 7.3 内置角色处理 - 管理员角色(`builtin=true`)在列表中展示时,删除按钮应置灰或隐藏 - 编辑内置角色时,角色编码字段应设为只读(后端会拒绝修改)