2. 租户模型设计:租户的定义、租户属性设计、租户状态管理、租户生命周期

好,咱们进入第二个核心话题——租户模型设计。

说实话,很多SaaS新手容易犯一个错误:把租户当成一个简单的「公司名称」字段来存。嗯,我早期也这么干过,结果后来业务一复杂,改得我头皮发麻。租户模型,说白了就是你的多租户系统的「户口本」。户口本设计得不好,后面查人、管人、分资源全乱套。

2.1 租户的定义:它到底是个啥?

先搞清楚一个基本问题:租户是什么?

我个人习惯这样定义:租户是一个独立的业务运营单元,拥有隔离的数据和配置。它不一定是公司,也可能是部门、项目组,甚至是一个个人用户(比如个人版SaaS)。

举个例子。我做过一个项目管理SaaS,有的客户是500人的公司,有的客户是3个人的小团队。对于系统来说,它们都是租户。区别在于,大租户可能买了100个席位,小租户只买了3个。但底层逻辑一样——各自的数据互不可见。

核心要点:

  • 租户 = 隔离单元
  • 租户 ≠ 用户(一个租户下有多个用户)
  • 租户 ≠ 组织(组织是租户的一种常见形式)

你想想看,如果连租户的定义都模糊,后面做权限、做计费、做数据迁移,每一步都会踩坑。

2.2 租户属性设计:该存哪些字段?

租户属性设计,我建议遵循一个原则:够用就好,别过度设计。但「够用」到底是多少?

我一般把租户属性分成三类:

分类 字段示例 说明
基础属性 tenant_id, tenant_name, tenant_type, domain 唯一标识、名称、类型(企业/个人)、子域名
业务属性 plan_id, max_users, storage_quota, features 套餐、用户上限、存储配额、功能开关
管理属性 status, created_at, updated_at, expired_at 状态、时间戳、过期时间

这里我特别想强调一下 tenant_type 这个字段。我在项目中遇到过,一开始只支持企业租户,后来个人用户也要接入。如果没有预留类型字段,就得加一张新表,或者硬改原有逻辑,非常痛苦。所以,哪怕你现在只有一种租户类型,也建议加上这个字段,默认值设好就行。

小技巧: 用 JSON 字段存储可扩展的属性,比如 extra_config。这样后续加字段不用改表结构。但别滥用,核心字段还是建议单独列出来,方便索引和查询。

2.3 租户状态管理:别让租户「死」得不明不白

租户状态管理,说白了就是管租户的「死活」。我见过最简陋的设计,租户只有两个状态:启用和禁用。结果呢?租户欠费了、试用到期了、被管理员冻结了,全混在一起处理,代码里一堆 if-else。

我建议至少设计以下状态:

  • ACTIVE(活跃):正常使用中
  • TRIAL(试用):试用期,可能有功能限制
  • SUSPENDED(暂停):欠费或违规,暂时停用
  • EXPIRED(过期):套餐到期,数据保留但不可用
  • DISABLED(禁用):管理员手动关闭
  • DELETED(删除):逻辑删除,数据保留一段时间

为什么会这样设计?因为不同状态触发的逻辑不一样。比如 SUSPENDED 状态,系统要发催费通知,但数据还能读;EXPIRED 状态,数据只保留30天,之后自动清理。如果只用「启用/禁用」,这些逻辑根本没法优雅实现。

避坑指南: 我曾经犯过一个错——把状态存在业务表里,结果每次查询租户状态都要 JOIN 好几张表,性能惨不忍睹。后来我把状态直接放在租户主表里,加个索引,查询速度直接起飞。记住:状态字段一定要放在租户主表,别分散。

2.4 租户生命周期:从生到死的完整旅程

租户生命周期,就是租户从注册到注销的全过程。我习惯把它画成一条时间线:

  1. 注册(Registration):租户提交信息,系统创建租户记录,状态设为 TRIALACTIVE
  2. 激活(Activation):租户完成邮箱验证或支付首笔费用,正式启用。
  3. 使用(Usage):租户正常使用,期间可能升级套餐、增加用户、修改配置。
  4. 暂停(Suspension):欠费或违规,系统自动或手动暂停服务。
  5. 恢复(Resumption):租户补缴费用或解决问题,状态恢复为 ACTIVE
  6. 过期(Expiration):套餐到期,进入数据保留期。
  7. 删除(Deletion):数据保留期结束,彻底清理或归档。

每个阶段,系统都要有对应的处理逻辑。比如注册阶段,要初始化租户的默认配置(角色、权限、存储空间)。暂停阶段,要停止所有定时任务和API调用。删除阶段,要确保数据彻底清除,避免合规风险。

嗯,这里有个细节:删除不一定是真删。我建议做逻辑删除,保留数据30-90天,给租户一个反悔的机会。我遇到过客户误操作删了租户,结果第二天打电话来求恢复。幸好我们做了逻辑删除,不然就凉了。

生命周期状态机示例(伪代码):

// 租户状态转换规则
state_machine = {
  'TRIAL':    ['ACTIVE', 'EXPIRED'],
  'ACTIVE':   ['SUSPENDED', 'EXPIRED', 'DISABLED'],
  'SUSPENDED': ['ACTIVE', 'DISABLED'],
  'EXPIRED':  ['ACTIVE', 'DELETED'],
  'DISABLED': ['ACTIVE', 'DELETED'],
  'DELETED':  []  // 终态,不可逆
}

你想想看,有了这个状态机,代码里就不会出现「从 DELETED 直接跳到 ACTIVE」这种荒唐事了。每个状态转换都清晰可控,审计日志也好查。

2.5 总结一下

租户模型设计,看似简单,实则暗藏玄机。我个人的经验是:先把租户的定义想清楚,再把属性分好类,然后设计一套完整的状态和生命周期。这三步走稳了,后面的多租户架构才能站得住脚。

下一章,咱们聊聊租户数据的隔离策略——到底是共享数据库还是独立数据库?到时候我会把两种方案的优缺点和适用场景掰开揉碎了讲。敬请期待。