用户体系建模:三户模型与三层身份模型

目录

用户系统的难点,往往不是登录接口,而是同一个“用户”字段承担了太多含义:它既代表付款的企业,也代表操作后台的员工,还被当成资金账户和微信身份。员工离职时,系统便很难回答:应该停用登录、撤销授权,还是关闭企业账户?

本文用一家假设的企业服务平台贯穿说明。甲公司购买服务,员工张三和李四分别登录,获准查看或操作公司的账户。张三也可以加入乙公司;离开甲公司时,他在乙公司的身份不受影响。这个场景需要同时区分业务归属与登录身份,三户模型和三层身份模型分别帮助处理这两个问题。

先确定“用户”在当前业务中指什么

“三户”通常指客户、用户和账户,但同名实体在不同行业不一定具有相同语义。电信业务中的“用户”可能指服务订购或产品实例,不能直接等同于网站登录账号。TM Forum 的 SID 信息框架提供业务信息模型,eTOM 则描述业务过程;不应仅凭名称把三张业务表宣称为 eTOM 标准模型。

本文针对企业服务平台,采用以下定义:

实体 本文含义 贯穿案例
客户 Customer 与平台建立业务关系的个人或组织 甲公司、乙公司
用户 User 能登录并发起操作的系统主体 张三、李四各自的登录主体
账户 Account 归属于客户、记录资金或资源权益的业务账户 甲公司的服务额度账户
成员关系 Membership 用户加入客户组织的关系及有效状态 张三加入甲公司、同时加入乙公司
授权 Grant 在特定组织和资源范围内允许的动作 张三可查询甲公司账户,李四可发起使用申请

员工、登录用户和企业客户不能压成一条记录。账户归属不随操作者变化,操作权限也不由归属关系自动推导。

如果平台只有个人收藏功能,未必需要客户、资金账户和组织成员关系。模型的价值在于解释现有业务,不是让每个产品都建齐这些表。

三户分离后,哪些变化能够各自发生

在案例中,甲公司可以开立多个账户,多个员工也可以获得同一账户的不同权限。用户和账户之间是授权关系,客户和账户之间是归属关系。

变化 应变更的对象 应保持的事实
张三更换手机号 登录标识及验证状态 内部用户 ID、业务记录和有效授权
张三离开甲公司 甲公司的成员关系及相关授权 甲公司账户、历史操作记录、张三在乙公司的成员关系
甲公司账户暂停支出 账户的支出控制 经授权的查询或对账能力可按规则保留
李四更换展示昵称 公开资料 登录绑定与历史操作归属
甲公司终止服务 合同、账户和组织状态 按保留要求处理历史记录,不把停用等同于物理删除

分表本身不会自动实现隔离。每次账户操作仍要校验登录主体、组织成员关系、资源所属客户、动作授权和账户控制状态。只检查“用户已经登录”或“请求里带有 customer_id”都不够。

客户 ID 稳定,身份材料可以变化

客户使用系统分配的稳定 ID。证件号、手机号、企业注册号是带有类型、签发地区、验证来源和有效期的身份信息,不应直接作为所有业务表的关联主键。

实名材料相同可以产生归并线索,但不能仅凭字符串相等自动合并账户、余额和权限。需要确认材料可信、主体关系一致,并记录归并依据、历史 ID 映射和后续纠错方式。手机号可能回收,掌握一个号码也不等于证明其持有人拥有该号码曾关联的全部历史数据。

客户对象不必等到实名认证后才存在。例如,组织可以先以待核验状态登记,再完成业务要求的认证。是否需要实名、何时核验,取决于产品和业务规则。

客户信息的领域划分不等于微服务数量

识别信息、联系方式、客户标签和协议记录具有不同的变更与访问要求,可以分模块维护;是否部署为独立服务,需要进一步考虑团队、事务、访问量和隔离需求。不能把每类字段直接拆成一个远程服务。

商户也是具体业务角色:它可能代表一家企业在平台上的经营签约主体、门店或结算关系。应先解释它和客户、组织、账户的关系,再决定是否独立建模;“商户就是资金账户分组”不足以覆盖这些差异。

三层身份:稳定 ID、登录绑定与公开资料

对于登录用户,可以再区分三类信息:

层次 保存什么 变化规则
内部标识 稳定的 user_id 不随昵称、手机号和登录方式变化,不回收给另一个主体
登录绑定 手机号、邮箱或外部身份到 user_id 的映射 经验证后绑定、替换或撤销,并记录作用域
公开资料 昵称、头像、简介等展示信息 按产品规则修改,公开范围由权限和隐私设置决定

内部 ID 不一定必须对用户不可见;关键是它不包含可变业务含义,也不能因为“很难猜”就替代访问控制。登录名也不总是秘密:有些产品允许公开用户名登录。密码、会话令牌和恢复凭证才需要作为认证秘密保护。

三层分离使昵称修改不必改动登录映射,但并不意味着三层互不相关。封禁、注销、账号恢复和身份合并仍需协调会话、绑定与业务权限。

登录标识和认证凭证不是同一个东西

手机号、邮箱或用户名用于定位待认证主体;密码、Passkey 或一次性验证码用于证明当前操作人满足认证条件。不能把一个手机号字段既当标识又当认证成功的证明。

密码应使用适合密码存储的慢哈希方案,如 Argon2id,并按库的规范使用独立盐和成本参数,而不是采用可逆加密或普通快速摘要。OWASP 密码存储指南解释了这些选择。登录绑定与认证器可以分别建模,避免每个手机号或邮箱都复制一份相同密码。

手机号更换需要验证新号码,并对当前账号重新认证。旧号码不可用时应进入独立恢复流程,而不是把“必须同时验证新旧号码”作为唯一入口。成功恢复后还要按策略撤销旧会话和恢复材料,防止旧持有人继续访问。

第三方登录必须保留身份作用域

OAuth 2.0 解决授权委托,不能仅拿到一个 access token 就推导出已验证的用户身份。使用 OpenID Connect 时,应由成熟实现完成令牌签名、签发者、受众、有效期及相关请求绑定校验,再使用可信身份声明。

OIDC 中,稳定外部身份由 iss 与 sub 共同确定;不能只用邮箱或裸 sub 在不同签发者之间归并。其依据见 OpenID Connect Core 的标识符规则。其他平台也应遵循各自的应用作用域和身份规则,不能假设所有 OpenID 都全局通用。

第三方首次登录时,平台可以创建新用户,或在用户证明拥有已有账号后执行绑定。“两个账号的邮箱相同”不能单独作为合并凭据。 绑定操作需要重新认证、冲突检查和唯一约束;解除绑定前,还要确认用户保有可用的登录或恢复路径。

业务账户:归属、账务和权限分别建模

本文的 Account 指内部资金或资源账户,不是登录账号。一个业务账户可以由多个登录用户在授权范围内操作,正如甲公司的服务额度可以由多名员工使用。

外部银行卡或支付渠道账号则属于外部账户引用:平台可记录绑定关系、渠道令牌和本平台交易标识,不能把它当作自己控制的内部账本。通过经授权接口得到的外部余额,也必须注明来源与时间,不能假设本地交易明细能还原全部外部余额。

信息 负责回答的问题
账户归属与计量单位 权益属于谁,以人民币、美元、积分还是服务次数计量
账务流水 哪个业务事件引起多少增减,能否追溯和对账
余额或汇总视图 在约定账务口径下,目前还有多少权益
占用或冻结记录 哪些额度被哪个订单或控制措施占用
授权关系 哪个主体可以执行哪种操作
账户控制 当前是否允许入账、支出、提现等动作

在“无透支、无待结算项、只分可用与冻结”的简化模型中,可以规定总余额等于可用余额加冻结余额。存在授信、在途清算、待入账或多种占用时,需要另行定义,不能把这条等式当作所有账务系统的通用规则。

余额字段也不能代替账务流水。需要业务幂等键、事务内记账与余额更新、并发控制,以及失败后可查询的处理状态。货币用定点数或明确最小单位的整数表示,精度按币种或资产确定;不同计量单位不能直接相加。

银行账户分类、支付账户类型与平台内部资源账户属于不同的产品及规则体系,不能把一张“Ⅰ/Ⅱ/Ⅲ 类限额表”直接套给所有账户。接入时将开户方式、交易方向、渠道限额、统计周期和适用版本放入明确的策略配置,再由对应业务规则校验,而不是把限额硬编码在 user 表中。

涉及银行卡验证信息时,也不能把“加密保存”作为统一答案。对于商户收单场景,PCI DSS 禁止在授权完成后保留 CVV/CVC 等验证码,即使加密也不行,见 PCI SSC 官方说明。

状态机要区分停用对象和影响范围

客户终止业务、用户不能登录、成员离职、账户不能支出,是四件不同的事。它们可以由同一事件触发,但不能共享一个含义模糊的 frozen 字段。

对象 示例状态或控制 生效时需要检查什么
客户业务关系 待核验、有效、终止中、已终止 合同、未结业务与数据保留要求
登录用户 有效、禁止登录、注销中、已注销 现有会话、恢复凭证和第三方绑定
组织成员 待邀请、有效、已撤销 所属组织及全部相关授权
业务账户 开立中、有效、关闭中、已关闭 未结账务、余额、占用和对账
操作控制 禁止支出、禁止提现等 控制对象、依据、范围、生效与解除时间

一项控制措施可以只约束某个账户或某类操作,不应默认冻结客户下所有资源。解除控制也不能直接重置成“全部正常”,因为可能还存在另一个未解除的控制原因。

员工离职通常是撤销成员关系和授权,不是关闭企业账户。个人申请注销登录账号时,也不能顺带关闭其曾获授权操作的第三方或企业账户。真正的业务销户流程应区分本人所有的账户、受托管理资源与历史记录,按各自规则处理。

“逻辑删除”同样不等于可以永久保留全部资料。业务关系终止与数据保留、删除或匿名化是相关但不同的流程,需要按数据类别和适用要求处理。

把关系落到表上:一个可执行的最小模型

下面用 SQLite 说明身份、成员与账户授权之间的关系。它是本地关系约束示例,未实现认证、余额和完整账务;示例数据库不应接收真实凭证。状态值采用 CHECK 约束,以便观察错误如何被拒绝。

PRAGMA foreign_keys = ON;

CREATE TABLE customer (
    customer_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE app_user (
    user_id INTEGER PRIMARY KEY,
    status TEXT NOT NULL CHECK (status IN ('active', 'disabled', 'closed'))
);

CREATE TABLE membership (
    customer_id INTEGER NOT NULL REFERENCES customer(customer_id),
    user_id INTEGER NOT NULL REFERENCES app_user(user_id),
    status TEXT NOT NULL CHECK (status IN ('active', 'revoked')),
    PRIMARY KEY (customer_id, user_id)
);

CREATE TABLE external_identity (
    issuer TEXT NOT NULL,
    subject TEXT NOT NULL,
    user_id INTEGER NOT NULL REFERENCES app_user(user_id),
    PRIMARY KEY (issuer, subject)
);

CREATE TABLE business_account (
    account_id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL REFERENCES customer(customer_id),
    unit TEXT NOT NULL,
    status TEXT NOT NULL CHECK (status IN ('active', 'closed')),
    UNIQUE (customer_id, account_id)
);

CREATE TABLE account_grant (
    customer_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL,
    account_id INTEGER NOT NULL,
    action TEXT NOT NULL CHECK (action IN ('read', 'spend')),
    PRIMARY KEY (customer_id, user_id, account_id, action),
    FOREIGN KEY (customer_id, user_id)
        REFERENCES membership(customer_id, user_id),
    FOREIGN KEY (customer_id, account_id)
        REFERENCES business_account(customer_id, account_id)
);

CREATE TABLE user_profile (
    user_id INTEGER PRIMARY KEY REFERENCES app_user(user_id),
    display_name TEXT NOT NULL
);

这里有两个容易遗漏的约束。第一,外部身份以签发者和主体联合唯一,两个签发者可以各自拥有相同的 subject。第二,授权表使用复合外键,把成员关系与账户限定在同一个客户范围;不能给“甲公司的成员关系”挂上“乙公司的账户”。

外键只能证明关系存在,不能证明状态仍有效。下面的查询返回指定用户目前拥有的有效授权,所有状态都在查询时校验:

SELECT a.account_id, g.action
FROM account_grant AS g
JOIN membership AS m
  ON m.customer_id = g.customer_id AND m.user_id = g.user_id
JOIN app_user AS u ON u.user_id = g.user_id
JOIN business_account AS a
  ON a.customer_id = g.customer_id AND a.account_id = g.account_id
WHERE g.user_id = :user_id
  AND m.status = 'active'
  AND u.status = 'active'
  AND a.status = 'active';

对实际支出操作,还要把授权与账户控制检查纳入能够约束并发变更的执行过程。先查询“有权限”,随后很久才扣账,可能遇到授权已经撤销的竞态。查询缓存也要有明确的失效策略。这里的 SELECT 只演示关系判断,不宣称实现了完整支付安全。

公开资料、登录绑定和组织成员关系可以分别扩展,而不改变内部 user_id。新增租户、替换登录渠道或员工离职时,系统修改的是对应关系,历史账务与操作记录仍能找到当时的主体。

从变化检验模型是否成立

评审用户模型时,可以用四个问题检验:换手机号是否需要改业务主键;离职是否会影响别的组织;新增登录渠道是否可能错误合并用户;关闭登录账号是否会误关企业账户。

三户模型帮助区分业务主体、操作主体和权益归属,三层身份模型帮助区分稳定 ID、登录绑定和展示资料。它们决定的是语义与关系的边界,不是固定的表数量或微服务数量。把这些变化解释清楚,才算完成用户体系建模。

评论