2. 用户与组织管理:用户表设计、组织架构树、用户-组织关联、LDAP集成思路

权限系统说到底,核心就两件事:谁能访问什么。而「谁」这个字,落地到数据库里,就是用户和组织架构。这一章我们就把这个基础打牢。

我见过不少团队,一上来就设计复杂的角色和权限模型,结果用户和组织的关系没理清,后面改得痛不欲生。嗯,咱们先把地基夯实。

2.1 用户表设计:别只想着存账号密码

用户表是权限系统的起点。很多人觉得用户表不就是 id、username、password 嘛?其实不然。在企业级场景下,用户表要承载的信息远不止这些。

我个人习惯把用户表分为三个区域:身份区、认证区、状态区。

区域 字段 说明
身份区 user_id, username, real_name, email, phone 唯一标识用户,不可变或极少变
认证区 password_hash, salt, mfa_secret, last_login_ip 密码存储必须加盐哈希,别存明文
状态区 status, locked_until, failed_attempts, last_login_at 控制账号是否可用、是否被锁定
我曾经接手过一个项目,用户表里直接存了明文密码。当时我整个人都不好了。记住:密码必须用 bcrypt 或 argon2 哈希,加盐是底线。

另外,我建议在用户表里加一个 user_type 字段。为什么?因为企业里除了「自然人」用户,还可能有「机器人」账号、服务账号。不同类型的账号,认证方式和权限策略完全不同。

-- 用户表核心结构(简化版)
CREATE TABLE sys_user (
    user_id        BIGINT       PRIMARY KEY AUTO_INCREMENT,
    username       VARCHAR(64)  NOT NULL UNIQUE,
    password_hash  VARCHAR(256) NOT NULL,
    real_name      VARCHAR(64),
    email          VARCHAR(128),
    phone          VARCHAR(20),
    user_type      TINYINT      NOT NULL DEFAULT 0 COMMENT '0:自然人 1:机器人 2:服务账号',
    status         TINYINT      NOT NULL DEFAULT 1 COMMENT '0:禁用 1:启用 2:锁定',
    failed_attempts INT         DEFAULT 0,
    locked_until   DATETIME,
    created_at     DATETIME     NOT NULL,
    updated_at     DATETIME     NOT NULL
);

2.2 组织架构树:无限层级怎么存?

组织架构是企业权限绕不开的坎。部门、子公司、项目组……层级关系复杂。怎么存?我见过三种主流方案。

方案一:邻接表(Adjacency List)

最简单,每个节点存一个 parent_id。查询子树要递归,MySQL 8.0 以上可以用 CTE。

-- 邻接表结构
CREATE TABLE sys_org (
    org_id    BIGINT PRIMARY KEY,
    org_name  VARCHAR(128),
    parent_id BIGINT DEFAULT NULL,
    sort_order INT DEFAULT 0
);

-- 递归查询所有子节点(MySQL 8.0+)
WITH RECURSIVE org_tree AS (
    SELECT * FROM sys_org WHERE org_id = 1
    UNION ALL
    SELECT o.* FROM sys_org o JOIN org_tree t ON o.parent_id = t.org_id
)
SELECT * FROM org_tree;

方案二:物化路径(Materialized Path)

每个节点存一个路径字符串,比如 /1/2/5/。查询子树用 LIKE,写起来简单,但移动节点时要更新所有子节点的路径。

我个人习惯用物化路径。虽然移动节点麻烦,但企业里组织架构变动不频繁,而查询性能比邻接表好太多。你想想看,一个 LIKE '/1/2/%' 就能拿到整个子树,多痛快。

方案三:闭包表(Closure Table)

用一张单独的表存所有祖先-后代关系。查询快,但数据量大,插入和删除要维护多行记录。

方案 查询子树 移动节点 存储空间
邻接表 需要递归,慢 改 parent_id 即可 小
物化路径 LIKE 查询,快 要更新所有子路径 中等
闭包表 JOIN 查询,极快 要维护多行 大

我的建议是:中小规模用物化路径,大规模用闭包表。邻接表?除非你确定层级不超过三层,否则别碰。

2.3 用户-组织关联:多对多还是多对一?

一个用户只能属于一个部门吗?现实情况是:一个人可能同时在多个项目组,或者兼任两个部门的职务。所以,用户和组织的关系,绝大多数场景是多对多。

关联表的设计很简单,但有一个坑:主岗和兼岗。

-- 用户-组织关联表
CREATE TABLE sys_user_org (
    id         BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id    BIGINT NOT NULL,
    org_id     BIGINT NOT NULL,
    is_primary TINYINT DEFAULT 0 COMMENT '1:主岗 0:兼岗',
    job_title  VARCHAR(64) COMMENT '岗位名称',
    entered_at DATE COMMENT '入职/调入日期',
    UNIQUE KEY uk_user_org (user_id, org_id)
);

为什么要区分主岗?因为权限计算时,主岗往往决定了用户的默认权限范围。比如一个用户主岗在「研发部」,兼岗在「架构委员会」,那他的默认菜单权限应该按研发部来,而不是按委员会来。

我在项目中遇到过一个案例:用户有 5 个兼岗,结果权限计算时把所有岗位的权限都叠加了,导致一个普通员工能看到 CEO 的报表。后来我们加上了「主岗优先」策略,才把问题解决。

2.4 LDAP 集成思路:别重复造轮子

中大型企业一般都有 LDAP(比如 Active Directory、OpenLDAP)。如果你的权限系统要对接 LDAP,记住一个原则:LDAP 负责认证,你的系统负责授权。

说白了,LDAP 就是告诉你「这个人是不是我们公司的员工」,至于他能干什么,还是你的权限系统说了算。

集成 LDAP 的核心步骤,我总结为三步:

  1. 配置连接:LDAP 服务器地址、端口、Base DN、管理员账号。
  2. 认证流程:用户输入密码,系统用 LDAP 的 bind 操作验证。验证通过后,从 LDAP 拉取用户属性(姓名、邮箱、部门等)。
  3. 同步机制:定时或实时同步 LDAP 中的用户和组织到本地数据库。
// LDAP 认证核心逻辑(伪代码)
public LdapUser authenticate(String username, String password) {
    // 1. 连接 LDAP
    LdapContext ctx = new InitialLdapContext(env, null);
    
    // 2. 用用户账号 bind
    ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, "uid=" + username + ",ou=people,dc=example,dc=com");
    ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
    ctx.reconnect(null);  // 验证失败会抛异常
    
    // 3. 拉取用户属性
    Attributes attrs = ctx.getAttributes("uid=" + username + ",ou=people,dc=example,dc=com");
    LdapUser user = new LdapUser();
    user.setUsername(attrs.get("uid").get());
    user.setEmail(attrs.get("mail").get());
    user.setDepartment(attrs.get("department").get());
    
    return user;
}
注意:LDAP 同步时,千万不要把 LDAP 的密码哈希值同步到本地。LDAP 的密码存储方式五花八门,你同步过来也没法用。正确的做法是:本地只存用户基本信息,认证时实时走 LDAP。

关于同步策略,我建议用增量同步 + 定时全量对账。平时通过 webhook 或消息队列处理变更,每天凌晨跑一次全量对账,把 LDAP 中已离职的用户自动禁用。

嗯,用户和组织这块,说白了就是搞清楚「谁是谁」和「谁归谁管」。基础打好了,后面做角色和权限才会顺手。下面这张图可以帮你理清整体关系。

用户与组织管理核心关系图 用户表 (sys_user) 组织架构 (sys_org) LDAP / AD 用户-组织关联表 角色 (下一章) 说明: 1. 用户和组织通过关联表建立多对多关系,并区分主岗/兼岗 2. LDAP 负责认证,同步用户基本信息到本地用户表 3. 用户-组织关联是后续角色分配和权限计算的基础

这张图把用户、组织、LDAP 和关联表的关系串起来了。下一层就是角色,但那是后面的事。先把今天的内容消化掉,尤其是用户-组织关联表的设计,我建议你动手建个表试试,跑几个查询感受一下。