PAYATHON 2026

如何为支持多角色的用户设计 class diagram?

技术老齐

用户与角色的多对多关系

User 与 Role 可以采用多对多关系:每个用户拥有一个或多个角色,同一个角色也可以分配给多个用户。这样就能表示用户只有 listener、只有 musician,或同时拥有两种角色的情况。

如果用 UserRole 显式表示这层关联,它必须同时关联 User 和 Role。单独一个 roleId 无法说明角色属于哪个用户。映射到关系型数据库时,关联表通常至少需要 user_id 和 role_id 两个字段。

UML 类图和数据库表结构要分开考虑。概念类图可以直接画多对多关联,数据库实现通常需要关联表。

先明确角色的含义

如果 listener 和 musician 只是用户身份或权限分类,可以这样建模:

  • User 保存账号的共同信息。
  • Role 定义可分配的角色,例如 listener、musician。
  • UserRole 记录某个用户拥有某个角色。

用户同时拥有两个角色时,建立两条关联即可,无须再定义 both 角色。把角色组合也定义成角色,会导致每增加一种角色,都可能需要补充新的组合。

如果 musician 还有艺名、个人介绍等专属数据,可以另建 MusicianProfile。这些属性是具体用户的音乐人资料,不适合放在所有音乐人共用的 Role 对象中。

类图如何画

显式使用 UserRole 类时,可以这样画:

classDiagram
    class User {
        +userId: Long
        +displayName: String
    }

    class Role {
        +roleId: Long
        +code: String
    }

    class UserRole {
        +userId: Long
        +roleId: Long
    }

    User "1" -- "1..*" UserRole : has
    Role "1" -- "0..*" UserRole : assigned through

图中的多重性表示:

  • 每条 UserRole 必须关联一个 User 和一个 Role。
  • 每个用户至少有一条角色关联。
  • 一个角色可以暂时没有用户,也可以分配给多个用户。

userId、roleId 用于表示数据库映射。纯领域类图可以直接用关联表示对象之间的引用,省略这些外键属性。

还需要约束同一组 (userId, roleId) 只能出现一次。如果角色目录中只有 listener 和 musician,每个用户最多就只能拥有这两种不同角色。

如果注册流程允许用户先创建账号、稍后选择角色,可以把 UserRole 一端的 1..* 改为 0..*,同时明确用户必须在什么时候完成角色选择。

什么时候可以省略第三个类

如果只需要表示用户拥有哪些角色,概念类图可以直接画成:

classDiagram
    User "0..*" -- "1..*" Role : has

一个用户对应一个或多个角色,一个角色对应零个或多个用户。

如果关联本身也有数据,例如角色分配时间、有效期,显式建立 UserRole 会更清楚。UML 也可以用关联类(association class)表达这种关系。前面的三类方案使用普通类和两条关联,便于进一步映射到数据库。

数据库实现示例

下面的 SQL 不依赖自动生成 ID 等数据库特定功能:

CREATE TABLE users (
    user_id BIGINT PRIMARY KEY,
    display_name VARCHAR(100) NOT NULL
);

CREATE TABLE roles (
    role_id BIGINT PRIMARY KEY,
    code VARCHAR(20) NOT NULL UNIQUE
);

CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    FOREIGN KEY (role_id) REFERENCES roles(role_id)
);

INSERT INTO roles (role_id, code)
VALUES
    (1, 'listener'),
    (2, 'musician');

INSERT INTO users (user_id, display_name)
VALUES
    (101, '用户 A'),
    (102, '用户 B'),
    (103, '用户 C');

INSERT INTO user_roles (user_id, role_id)
VALUES
    (101, 1),
    (102, 2),
    (103, 1),
    (103, 2);
用户角色
101listener
102musician
103listener、musician

复合主键 (user_id, role_id) 可以防止重复分配角色。即使为了 ORM 映射增加独立的 user_role_id,也应保留这两个字段的联合唯一约束。

这种表结构允许以后添加其他角色。如果业务明确限定只有两种角色,还需要限制角色目录的维护。

其他方案如何取舍

如果系统始终只有这两种身份,也没有角色配置需求,可以在 User 中保存 isListener、isMusician 两个布尔值。两者可以同时为真;要满足“用户至少选择一种身份”的要求,就必须禁止两者同时为假。以后增加角色时,需要修改用户结构。

如果两种角色各自有较多数据和行为,可以采用组合:

User 1 —— 0..1 ListenerProfile
User 1 —— 0..1 MusicianProfile

同一个 User 可以同时关联两种资料。如果资料是否存在就足以表示角色归属,可以直接据此判断身份。如果仍然保留 UserRole,则需要明确角色记录与资料记录之间的一致性规则。

继承也能表达重叠分类,但必须明确允许一个用户同时属于 Listener 和 Musician。对于可以随时开通或取消的角色,关联或组合通常更容易实现,也便于保留同一个账号身份。

实现时需要注意的约束

上述外键和复合主键无法自动保证每个用户至少有一个角色,外键只能保证关联指向有效记录。通常需要在创建用户、修改角色的事务中检查,并防止移除用户的最后一个角色。如果要在数据库层强制执行,具体方式取决于数据库及其约束机制。

还要确认业务是否允许用户只有 musician 身份。如果所有账号都可以听歌,listener 可能只是默认能力,无须单独存成角色。它影响身份选择、资料或权限时,才有必要独立建模。

如果角色用于授权,后端也必须检查对应权限。前端根据角色隐藏按钮,只能控制界面展示。

备注:内容仅供参考。