推荐
你问到点子上了,而且这里有个**关键细节需要先纠正**:`member_id` 指向的是 `auth_subject.id`,不永远是 `sys_user.id`。让我先把"它到底怎么落到用户"的代码读准,再讲 `member_type` / `is_direct` 的用途——尤其 `is_direct` 大概率是个"冗余闭包"设计,我得确认下代码里谁在维护它。
ServiceImpl 是空的(只是个 CRUD 壳)。那 `is_direct` 的"冗余闭包"逻辑应该在别处维护。让我全局搜一下 `isDirect` 和 `setIsDirect` 的写入点。
已经定位到关键信息了。先纠正你理解里的一个**精度问题**,再讲 `member_type` / `is_direct` 各自的用途——`is_direct` 的注释 "0继承冗余" 已经说明它是个"闭包冗余"设计。我再看一眼种子数据,给你一个真实例子证明 `member_type` 不总是 1。
读完种子数据,可以给你一个**带实证的精确回答**了。
## 先纠正一个精度问题
你的理解**只对了一半**。`member_id` 指向的是 **`auth_subject.id`,不是 `sys_user.id`**。它只在这个成员是"用户类型"时才最终落到用户表。完整的落地链是:
```
auth_subject_member.member_id → auth_subject.id
├─ 若 subject_type = 1(用户) → auth_subject.subject_id = sys_user.id ✅ 终点是用户
└─ 若 subject_type = 2/3/6/8(科室/组/角色/医疗组) → member_id 指向另一个"组"主体,继续向下展开
```
所以准确说法是:**只有叶子节点 `member_type=1` 时,它背后的 `auth_subject.subject_id` 才是 `sys_user.id`;中间层的 `member_type` 可能是 3/6/8,指向的是"组/角色/医疗组"这种非用户主体。**
## 实证:种子数据里就有 `member_type≠1` 的行
`seed_demo.sql` 第 481 行:
```sql
-- group_subject_id=...4028 (演示角色-含安全组, 角色) member_type=3 member_id=...4017
(9000000000005011, ..., 9000000000001003, 9000000000004028, 3, 9000000000004017, ...);
```
而 `...4017` 本身又是一个"安全组"主体,它的成员是 `member_type=1` 的用户(第 471–473 行)。所以真实链条是:
**角色 R5 → 含安全组(4017) → 含用户(4001/4002/4003) → sys_user**
这正好证明:`member_id` 不永远连 `sys_user`,中间层是"组套组"的递归嵌套。
## `member_type` 有什么用
它是**被引用主体的类型**的冗余副本(跟 `auth_subject.subject_type` 镜像),本表里所有 `member_type` 都和它所指向的 subject 的类型一致。冗余出来的原因:
1. **查询/展示免 join**:成员列表要显示"成员类型:用户/科室/角色/组…",直接读这一列即可,不用每次 JOIN `auth_subject`。
2. **约束"谁能被加为成员"**:配合 `auth_subject_type.member_allowed`——机构(5)、岗位(7) 不允许作为成员,只有 user/dept/group/campus/role/medical_group 能进组。这一列就是校验闸门。
## `is_direct` 有什么用(关键:它是"闭包冗余"标记)
注释写得很明确:`1直接 0继承冗余`。这是一个**闭包表(closure table)式的冗余设计**:
- `is_direct=1`:**你手动显式加的边**("我把张三直接加进这个安全组")。
- `is_direct=0`:**继承来的冗余记录**("张三是因为属于子组 A,而 A 属于本组,所以被冗余记一笔")。
它的价值:
- **查询区分**:"只看直接成员" vs "看全部生效成员(含继承下来的)"——一条 `WHERE is_direct=1` 就能切。
- **删除语义清晰**:删一个直接边即可,继承边不需要、也不应该手工维护。
- **UI 标注**:前端 `SelectDict` 里直接显示"直接 / 继承冗余",让用户知道某人是"直接加进来的"还是"跟着子组带进来的"。
⚠️ **一个现状提醒**:当前 `seed_demo` 里 23 行 `auth_subject_member` 的 `is_direct` **全部是 1**(没有 0 的继承冗余行),而且后端 `AuthSubjectMemberServiceImpl` 是空的 CRUD 壳,**没有自动计算并写入 `is_direct=0` 闭包的代码**。也就是说:这个字段的语义和 UI/筛选都预留好了,但"自动展开闭包并落冗余行"这一步目前要么没实现、要么靠外部脚本/触发器维护。真正做成员递归展开的地方是 `SnapshotBuilder.resolveToUsers`(构建期实时递归,不依赖 `is_direct`)。
## 顺带其它几个列(你可能也会问)
| 列 | 作用 |
|----|------|
| `is_default` | 是否默认成员(种子里管理员指派行是 1,demo 行是 0) |
| `effective_time` / `expire_time` | **成员级时间窗**:某人临时借调进组 3 个月,到期自动失效 |
| `status` | 0停用 1启用(单条成员关系可单独停用) |
| `org_id` | 机构隔离,多院区/多机构共用模型 |
**一句话结论**:`member_id` 不是直接连 `sys_user`,而是连 `auth_subject`;`member_type` 是成员类型的冗余副本(用于免 join 展示 + 成员资格约束),`is_direct` 是用来区分"我亲手加的"和"跟着子组继承来的"的闭包冗余标记。只有在成员树叶子(`member_type=1`)处,才真正关联到 `sys_user.id`。
相关推荐
全部回复(0)