用户和组织体系
1. 组织(Org):一棵树,两个维度
组织表 mdc_org 是一棵用层级路径(treePath,形如 /687106879444787203/107787813521504256/)维护的树,每个节点有两个维度的属性:
| 维度 | 字段 | 取值 | 决定什么 |
|---|---|---|---|
| 类型维度 | orgType(OrgTypeEnum) | 10-公司 / 20-部门 | 挂载规则(公司不能挂到部门下) |
| 身份维度 | nature(OrgNatureEnum) | 1-总公司 / 90-开发者 / 99-运营 | 可见性、角色归属、身份判定、统计数据域 |
组织性质是MDP平台的重要概念:
| 性质 | 枚举 | 业务角色 |
|---|---|---|
1 | HEAD_COMPANY(总公司) | 普通用户体系,可建任意公司/部门子树 |
90 | DEVELOPER(开发者) | 应用申请/对接体系,开发者注册时自动属于「开发者平台」公司 |
99 | OPERATIONS(运营) | 平台运营体系,最高权限,可见全部三棵树 |
性质的维护规则:
- 一个组织有且只有一个性质;(基础平台不做1个组织有多个性质的复杂业务,二开时,子应用可以单独做)
- 性质只在公司层级维护,部门行冗余同步所属公司的性质,读取时以顶级公司为准;
- 创建机构时
OrgDto没有 nature 字段——OrgServiceImpl.saveBefore自动继承父节点性质,根节点默认总公司。前端机构表单中「组织性质」为只读展示,不可编辑。 - 组织表内置3个根节点(运营中心、开发者平台、总公司),任何用户不可新增其他根节点。
整棵组织树的内置骨架如下:
- 三个根节点各自是一棵独立的身份体系树(
treePath以各自根 id 开头),互不包含; - 「默认部门」是总公司树下唯一的内置部门,普通用户注册后落入其中;
- 业务扩展只能在「总公司」树下新增公司/部门子树(性质随父继承),运营树与开发者树不开放业务自建。
- 新增、修改、移动、删除 组织节点时,请务必注意treePath的正确性,若该字段存储数据错误,将会影响整个系统的数据权限。
2. 内置组织与硬保护
四个内置组织(BuiltInOrgId,与初始化 SQL 一一对应,属跨环境契约,禁止修改):
| 常量 | 组织 | 性质 | 角色 |
|---|---|---|---|
OPERATIONS_CENTER | 运营中心 | 99 | 运营者账号所在公司 |
DEVELOPER_PLATFORM | 开发者平台 | 90 | 注册开发者及其子账号时挂载在此节点 |
HEAD_COMPANY | 总公司 | 1 | 普通业务公司树的根 |
DEFAULT_DEPT | 默认部门 | 1(继承总公司) | 普通用户注册时挂载在此节点 |
三棵内置根树把三套身份体系物理隔离:运营树、开发者树、总公司业务树互不包含。
内置组织受 service 层写死的代码硬保护(OrgServiceImpl):不可删除/移动,不可改类型/状态/层级,根节点性质不允许经控制台编辑变更。前端通过 GET /organization/org/findBuiltInIds 拿到内置 ID 列表,禁用对应操作按钮。
3. 用户(User)与组织的关系
- 关联表
mdc_user_org_rel仅userId + orgId两字段,一个用户可属多个组织(User.orgIdList经@RelationOneToMany映射,保存时先删后插); User上的lastDeptId/lastCompanyId/lastTopCompanyId记录最近登录的组织上下文:登录时逐个校验有效性,失效则默认分配(AuthServiceImpl.fillTokenSession),最终写入会话的CurrentDeptId / CurrentCompanyId / CurrentCompanyNature / CurrentTopCompanyId / CurrentTopCompanyNature,业务代码经ContextUtil读取;- 岗位(
positionId)为单值,岗位表mdc_position独立维护。
4. 机构性质与角色性质的关联(重点)
角色表 mdc_role 同样携带组织性质——orgNature 存性质编码(1/90/99),不存 org_id,即角色分层是「组织性质级」而非「公司级」。配上 roleCategory(角色分类)构成双维度:
| roleCategory | 含义 | 维护入口 | 数量约束 |
|---|---|---|---|
10 普通角色 | 各管理员自建的业务角色 | 角色管理页 | 不限 |
20 管理员角色 | 该性质下最高权限 | 角色模板页(运营者专属) | 每个性质最多 1 个 |
30 权限集合 | 控制该性质下「角色管理」能分配的应用/菜单/数据/字段权限范围 | 角色模板页(运营者专属) | 每个性质最多 1 个 |
机构性质与角色性质的关联,落在四条具体逻辑上:
① 身份判定(角色性质决定用户身份)
UserIdentityService.resolve 按用户持有的角色判定身份(UserIdentityEnum):
有「性质99 + 管理员角色」的启用角色 → 运营者(OPERATIONS_ADMIN)
有「性质90 + 管理员角色」 → 开发者管理员
有「性质90 其他角色」 → 开发者
其余 → 普通用户② 可见性过滤(通过身份决定可见的组织树)
OrgVisibilityService.visibleRootOrgIds:运营者 → 不限制(三棵树全见);开发者管理员 → 仅开发者平台树;普通用户 → 仅总公司树;开发者 → 不可见组织管理。数据过滤实现方式为在sql拼接 treePath like '/根id/'。
③ 授权范围(通过组织性质决定可分配什么)
角色管理页加载「可分配应用/菜单/数据/字段权限」时,一律从同性质的权限集合角色(roleCategory=30)已授权的内容中读取(RoleServiceImpl.getPermSetRoleOfCurrentOperator),角色管理页面该操作人能授权的权限与操作人自身权限无关。
- 操作人能分配的权限取决于同性质的权限集合角色拥有什么权限。
- 操作人自身的权限取决于操作人拥有什么角色(普通角色/管理员角色)。
④ 绑定校验(性质一致性 决定 能不能绑)
角色绑定用户时逐个校验(UserRoleRelServiceImpl.checkRoleUserNatureMatch):用户所属每个组织的 nature 必须等于角色的 orgNature,否则报错「用户所属组织性质与角色的组织性质不一致」。「角色用户」候选人也按角色性质圈定组织树。
5. 注册与身份选择
注册入口(POST /organization/user/registerBy{Email,Phone,Username})中的身份选择决定了注册后用户所属的组织性质:
- 校验
checkRegisterNature:仅允许 1(总公司)/ 90(开发者),运营身份不开放注册; - 绑定用户和组织关系:90 → 开发者平台公司,1 → 默认部门;
- 绑定默认角色:90 →
DEFAULT_DEVELOPER,1 →DEFAULT_USER。
6. 控制台操作指南
| 页面 | 操作 | 接口 |
|---|---|---|
| 机构管理 | 新增机构(点树节点 +,性质自动继承父节点) | POST /organization/org/save |
| 编辑/删除/移动机构(内置机构按钮禁用) | POST /organization/org/update、/delete、/move | |
| 用户管理 | 新增用户(表单多选机构 orgIdList、单选岗位) | POST /organization/user/save |
| 重置密码 / 解锁(密码错误锁定) | POST /organization/user/resetPassword、/unlock | |
| 岗位管理 | 岗位 CRUD、状态开关 | /organization/position/* |
| 角色管理 → 角色用户 Tab | 用户-角色绑定/解绑 | POST /organization/userRoleRel/save、/delete |
用户表单里没有角色字段
用户-角色绑定不在用户表单中做,而是到「角色管理」页选中角色后的「角色用户」Tab 反向勾选——因为绑定前要做性质一致性校验(见第 4 节④),以角色为操作主体更自然。
7. 二次开发:新增一套身份体系
扩展新身份(如"供应商"体系)的标准路径:
OrgNatureEnum新增枚举值(如40-供应商);- 初始化脚本新增一棵根公司(性质=40);
- 视需要新增内置角色编码(管理员角色 + 权限集合,
(roleCategory, orgNature)唯一); - 注册/身份判定/可见性映射中接入新性质(
UserIdentityService、OrgVisibilityService)。