主题
租户管理 PRD
文档信息
| 项目 | 内容 |
|---|---|
| 产品名称 | PMForge - 租户管理(SaaS 多租户) |
| 文档版本 | v2.0 |
| 创建日期 | 2026-09-13 |
| 最后更新 | 2026-09-24 |
| 文档状态 | 评审中 |
| 优先级 | P0 |
一、功能概述
1.1 功能定位
如果把 PMForge 比作一栋写字楼,那么"租户管理"就是这栋楼的物业管理系统——每家公司(租户)租下独立的办公区域,各自管理自己的员工、文件和门禁,互不干扰。
租户管理是 PMForge 作为 SaaS 平台的基石模块,负责管理平台上所有客户(即"租户")从入驻到退出的完整生命周期。核心能力包括:
- 租户入驻:创建租户、自动初始化管理员账号和权限体系,让新客户"开箱即用"
- 套餐管理:定义不同等级的功能套餐(如基础版、专业版、旗舰版),控制每个租户能使用哪些功能
- 到期管控:设置有效期,到期自动停用,支持续期
- 额度管理:限制每个租户可创建的用户数量上限,为商业化计费提供基础
- 域名绑定:支持客户使用自己的域名访问,提升品牌独立感
- 数据隔离:确保不同租户之间的数据严格隔离,互不可见
1.2 目标用户
| 用户类型 | 核心诉求 | 使用频率 |
|---|---|---|
| 平台超级管理员 | 创建租户、配置套餐、处理异常,是平台运营的"总控台" | 日常高频 |
| 平台运营人员 | 查看租户状态、处理到期续期、导出运营报表 | 日常中频 |
| 租户管理员 | 通过绑定域名登录,管理自己公司内的用户和资源 | 入驻后自行管理 |
1.3 业务价值
- 商业化基础:套餐 + 额度 + 有效期的组合设计,直接支撑 SaaS 按版本收费、按量计费的商业模式
- 快速交付:创建租户时自动初始化完整的管理员账号、角色、菜单权限体系,签约后分钟级交付
- 安全合规:多层数据隔离确保客户数据安全,是 B2B SaaS 赢得客户信任的前提
- 灵活运营:域名绑定让客户拥有独立入口,套餐升降级让客户按需付费,降低流失率
1.4 功能范围
| 功能分类 | 后台管理端 | 说明 |
|---|---|---|
| 租户 CRUD | ✅ | 租户的创建、查询、编辑、删除 |
| 租户状态管理 | ✅ | 启用/停用租户,停用后租户内所有用户无法登录 |
| 租户有效期管理 | ✅ | 设置过期时间,到期自动停用,支持续期 |
| 租户套餐管理 | ✅ | 套餐 CRUD + 菜单权限树形勾选 |
| 租户管理员自动初始化 | ✅ | 创建租户时自动创建管理员用户、角色、菜单权限 |
| 租户额度管理 | ✅ | 账号数量上限控制 |
| 租户域名绑定 | ✅ | 支持多域名绑定与域名自动识别 |
| 租户数据隔离 | ✅ | DB / MQ / Redis / Cache 多维度自动隔离 |
| 租户导出 | ✅ | 导出租户列表 Excel |
二、用户场景
2.1 用户角色
| 角色 | 描述 | 核心诉求 |
|---|---|---|
| 平台超级管理员 | 系统最高权限,不受租户限制 | 管理所有租户,配置套餐,监控平台运营状态 |
| 平台运营人员 | 日常运营管理 | 查看租户信息,管理租户状态,处理到期续期 |
| 租户管理员 | 租户内最高权限 | 通过绑定域名登录,管理本租户内的用户与资源 |
2.2 使用场景
场景1:签约新客户,平台管理员创建租户
- 用户:平台超级管理员小李
- 场景:销售团队签约了"明远科技" -> 小李登录后台 -> 进入"租户管理" -> 点击"新增租户" -> 填写租户名称"明远科技"、联系人"张总"、手机"138xxxx5678" -> 选择"专业版"套餐 -> 设置有效期为 1 年、账号额度 50 人 -> 设置管理员用户名"zhangsan"和初始密码 -> 点击保存
- 期望:系统自动完成以下动作——①创建租户记录 ②创建管理员用户"zhangsan" ③创建租户管理员角色 ④按"专业版"套餐勾选的菜单分配权限 ⑤将管理员用户与租户关联。整个过程 < 2 秒,"明远科技"即刻可用
- 异常处理:如果管理员用户名已存在,提示"该用户名已被使用,请更换";如果租户名称重复,提示"租户名称已存在"
场景2:租户即将到期,运营人员续期
- 用户:平台运营人员小王
- 场景:每天早上 9 点收到系统推送的"即将到期租户列表" -> 发现"明远科技"7 天后到期 -> 联系客户确认续费 -> 进入租户管理 -> 编辑"明远科技" -> 将过期时间延长 1 年 -> 保存
- 期望:续期后立即生效,租户无感知;如果客户未续费,到期后系统自动停用该租户
场景3:客户升级套餐
- 用户:平台超级管理员小李
- 场景:"明远科技"从基础版升级到旗舰版 -> 小李编辑租户 -> 切换套餐为"旗舰版" -> 保存
- 期望:切换后,"明远科技"的管理员登录后,立即可以看到旗舰版新增的菜单和功能。原来基础版的菜单不受影响,只是新增了更多功能
- 异常处理:如果新套餐的菜单范围比旧套餐小(降级),被移除的菜单对应的功能将不可访问,但已有数据不丢失
场景4:客户通过独立域名登录
- 用户:明远科技的管理员张三
- 场景:张三访问公司专属域名
pm.mingyuan.com-> 系统自动识别该域名绑定了"明远科技"租户 -> 展示登录页面 -> 张三输入账号密码登录 -> 进入系统 - 期望:无需手动选择租户,域名自动关联,登录体验与独立系统无异
- 异常处理:如果域名未绑定任何租户,提示"未找到对应的租户信息";如果租户已停用,提示"该租户已停用,请联系平台管理员"
场景5:平台管理员设计新套餐
- 用户:平台超级管理员小李
- 场景:产品团队决定推出"企业定制版"套餐 -> 小李进入"租户套餐管理" -> 新增套餐 -> 填写名称"企业定制版" -> 在菜单权限树中勾选所有菜单(包括高级报表、API 开放平台等旗舰版没有的模块) -> 保存
- 期望:新套餐创建后,可以在创建/编辑租户时被选择;菜单权限树支持按模块展开/折叠,方便勾选
场景6:租户用户数达到额度上限
- 用户:明远科技的管理员张三
- 场景:张三想邀请新同事加入 -> 进入用户管理 -> 新增用户 -> 系统提示"该租户账号数量已达上限(50/50),请联系平台管理员扩容"
- 期望:明确的提示信息,告知解决路径(联系平台管理员);平台管理员可以通过编辑租户调整额度
场景7:租户到期自动停用
- 用户:系统定时任务
- 场景:每小时执行一次到期检查 -> 发现"测试公司"的过期时间已过 -> 自动将其状态改为"停用" -> "测试公司"的所有用户再次登录时被拒绝,提示"租户已过期,请联系平台管理员"
- 期望:到期停用全自动,无需人工干预;停用后数据保留,续期后可恢复使用
2.3 用户故事
| 编号 | 用户故事 | 优先级 | 验收标准 |
|---|---|---|---|
| US-01 | 作为平台管理员,我希望创建新租户时自动初始化管理员账号和权限,以便快速交付客户使用 | P0 | ①创建租户后,管理员用户可立即登录 ②管理员登录后能看到所选套餐对应的全部菜单 ③创建过程 < 2 秒 |
| US-02 | 作为平台管理员,我希望查看所有租户列表并按条件筛选,以便掌握平台运营状况 | P0 | ①支持按租户名、联系人、手机、状态、创建时间筛选 ②列表展示关联的套餐名称 ③分页查询响应 < 500ms |
| US-03 | 作为平台管理员,我希望编辑租户信息(名称、联系人、套餐、有效期等),以便响应客户变更需求 | P0 | ①切换套餐后菜单权限即时更新 ②延长有效期后租户立即可用 ③编辑不影响已有管理员账号 |
| US-04 | 作为平台管理员,我希望停用/启用租户,以便控制租户的访问权限 | P0 | ①停用后租户内所有用户 Token 失效 ②启用后租户可正常使用 ③系统内置租户不可停用 |
| US-05 | 作为平台管理员,我希望创建和编辑租户套餐并关联菜单,以便灵活控制不同租户的功能范围 | P0 | ①菜单权限支持树形勾选 ②修改套餐后使用该套餐的所有租户权限同步更新 ③套餐名不可重复 |
| US-06 | 作为平台管理员,我希望设置租户的账号数量额度,以便控制租户的用户规模 | P1 | ①创建租户时必须指定额度 ②达到上限后阻止新增用户并给出明确提示 ③管理员可随时调整额度 |
| US-07 | 作为平台管理员,我希望导出租户列表,以便进行运营数据分析 | P1 | ①导出当前筛选条件下的全部租户 ②包含租户名、联系人、手机、状态、创建时间 ③10 万条以内导出 < 30 秒 |
| US-08 | 作为平台管理员,我希望通过域名识别租户,以便支持客户使用独立域名访问 | P1 | ①域名格式校验合法 ②域名未绑定时返回空 ③域名绑定后用户访问该域名自动关联租户 |
| US-09 | 作为平台管理员,我希望能批量删除租户,以便高效处理废弃租户 | P1 | ①批量选择后二次确认 ②逻辑删除,数据可恢复 ③系统内置租户不可删除 |
| US-10 | 作为系统,我需要保证所有业务数据按租户隔离,确保数据安全 | P0 | ①任何 SQL 查询自动拼接租户条件 ②Redis 缓存 key 自动按租户隔离 ③不同租户不可互相访问任何数据 |
三、功能需求
3.1 后台管理端 - 租户管理
3.1.1 功能清单
| 功能 | 优先级 | 权限标识 | 说明 |
|---|---|---|---|
| 租户列表(分页) | P0 | system:tenant:query | 分页展示租户,支持搜索筛选 |
| 租户详情 | P0 | system:tenant:query | 查看租户详细信息 |
| 创建租户 | P0 | system:tenant:create | 创建租户并自动初始化管理员 |
| 编辑租户 | P0 | system:tenant:update | 修改租户基本信息、套餐、有效期等 |
| 删除租户 | P0 | system:tenant:delete | 逻辑删除租户 |
| 批量删除租户 | P1 | system:tenant:delete | 批量逻辑删除 |
| 租户导出 | P1 | system:tenant:export | 导出租户列表 Excel |
| 租户精简列表 | P0 | 免鉴权 | 获取已启用租户列表,用于下拉选择 |
| 按租户名获取 ID | P0 | 免鉴权 | 登录页根据租户名获取租户编号 |
| 按域名获取租户 | P1 | 免鉴权 | 根据域名识别租户信息 |
3.1.2 租户列表(分页查询)
页面描述:

业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 默认按创建时间倒序排列 | 最新创建的租户排在前面,方便运营人员关注 |
| R-02 | 支持按租户名模糊搜索 | 运营人员通常只记得租户名的部分关键字 |
| R-03 | 支持按联系人、联系手机模糊搜索 | 商务场景中常通过联系人信息查找租户 |
| R-04 | 支持按状态筛选(正常/停用) | 快速定位停用租户,处理续期或清理 |
| R-05 | 仅平台超级管理员可访问本页面 | 租户管理是平台级操作,租户内用户不可访问 |
| R-06 | 租户名全局唯一 | 避免混淆,确保每个租户有唯一标识 |
| R-07 | 列表展示关联的套餐名称 | 通过 packageId 关联查询,运营人员直观了解租户版本 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 获取租户分页 | GET | /admin-api/system/tenant/page | system:tenant:query | 分页查询 |
| 获取租户详情 | GET | /admin-api/system/tenant/get | system:tenant:query | 查看详情 |
分页查询请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | String | 否 | 租户名称(模糊匹配) |
| contactName | String | 否 | 联系人(模糊匹配) |
| contactMobile | String | 否 | 联系手机(模糊匹配) |
| status | Integer | 否 | 状态(0-正常 1-停用) |
| createTime | DateTime[] | 否 | 创建时间范围 |
| pageNo | Integer | 是 | 页码(从 1 开始) |
| pageSize | Integer | 是 | 每页条数(默认 10) |
分页返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | Long | 租户编号 |
| name | String | 租户名称 |
| contactName | String | 联系人 |
| contactMobile | String | 联系手机 |
| status | Integer | 状态 |
| packageId | Long | 租户套餐编号 |
| packageName | String | 套餐名称(关联翻译) |
| expireTime | DateTime | 过期时间 |
| accountCount | Integer | 账号数量额度 |
| createTime | DateTime | 创建时间 |
租户详情返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | Long | 租户编号 |
| name | String | 租户名称 |
| contactName | String | 联系人 |
| contactMobile | String | 联系手机 |
| contactUserId | Long | 管理员用户编号 |
| status | Integer | 状态 |
| websites | List<String> | 绑定域名列表 |
| packageId | Long | 租户套餐编号 |
| expireTime | DateTime | 过期时间 |
| accountCount | Integer | 账号数量额度 |
| createTime | DateTime | 创建时间 |
3.1.3 创建租户
页面描述:

业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 租户名称全局唯一,创建时校验 | 避免租户标识混淆 |
| R-02 | 创建租户时同步创建管理员用户 | "开箱即用",不需要额外步骤去配置管理员 |
| R-03 | 管理员用户名 4-30 位字母数字 | 兼顾简短好记和足够区分 |
| R-04 | 管理员密码 4-16 位 | 初始密码不宜过于复杂,首次登录后建议修改 |
| R-05 | 自动初始化租户角色(含管理员角色) | 让租户创建后即拥有完整的权限体系 |
| R-06 | 根据所选套餐自动分配菜单权限 | 套餐决定了租户能用哪些功能,自动关联减少手动操作 |
| R-07 | 管理员用户名不可与系统中已有用户名重复 | 用户名全局唯一,防止登录冲突 |
| R-08 | 编辑租户时不需要再填管理员用户名和密码 | 管理员账号已在创建时生成,编辑不影响已有账号 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 创建租户 | POST | /admin-api/system/tenant/create | system:tenant:create | 创建租户并初始化管理员 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | String | 是 | 租户名称,最大 100 字符 |
| contactName | String | 是 | 联系人姓名 |
| contactMobile | String | 否 | 联系手机 |
| status | Integer | 是 | 状态(0-正常 1-停用) |
| websites | List<String> | 否 | 绑定域名列表 |
| packageId | Long | 是 | 租户套餐编号 |
| expireTime | DateTime | 是 | 过期时间 |
| accountCount | Integer | 是 | 账号数量额度 |
| username | String | 是 | 管理员用户名(仅创建时必填) |
| password | String | 是 | 管理员密码(仅创建时必填) |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | Long | 新创建的租户编号 |
创建租户完整流程:

3.1.4 编辑租户
页面描述:
弹窗/抽屉形式,预填现有数据。与创建表单相比,不显示管理员用户名和密码字段。
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 编辑时不传 username/password,不影响已有管理员账号 | 管理员账号独立管理,编辑租户信息不应影响登录 |
| R-02 | 切换套餐后,租户可访问的菜单权限即时更新 | 通过同步更新角色菜单实现,确保功能范围与套餐一致 |
| R-03 | 延长过期时间相当于续期,缩短相当于临时限制 | 灵活支持续费和临时限制两种运营场景 |
| R-04 | 减少账号数量时,若当前用户数已超过新额度,给出警告但不阻止保存 | 不强制阻断运营操作,但提醒风险 |
| R-05 | 停用租户后,该租户所有用户 Token 失效 | 安全考虑,停用即断所有会话 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 更新租户 | PUT | /admin-api/system/tenant/update | system:tenant:update | 更新租户信息 |
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 租户编号 |
| name | String | 是 | 租户名称 |
| contactName | String | 是 | 联系人 |
| contactMobile | String | 否 | 联系手机 |
| status | Integer | 是 | 状态 |
| websites | List<String> | 否 | 绑定域名列表 |
| packageId | Long | 是 | 租户套餐编号 |
| expireTime | DateTime | 是 | 过期时间 |
| accountCount | Integer | 是 | 账号数量额度 |
返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| data | Boolean | 是否成功 |
3.1.5 删除租户
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 逻辑删除,不物理删除 | 数据可恢复,避免误删造成不可逆损失 |
| R-02 | 删除前需二次确认 | 删除操作影响大,需防止误操作 |
| R-03 | 删除后该租户所有用户 Token 失效 | 安全考虑,删除即断所有会话 |
| R-04 | 系统内置租户(packageId = 0)不可删除 | 系统内置租户是平台运行的基础,不可删除 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 删除租户 | DELETE | /admin-api/system/tenant/delete | system:tenant:delete | 删除单个租户 |
| 批量删除租户 | DELETE | /admin-api/system/tenant/delete-list | system:tenant:delete | 批量删除租户 |
请求参数(删除):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 是 | 租户编号 |
请求参数(批量删除):
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | List<Long> | 是 | 租户编号列表 |
3.1.6 租户导出
业务规则:
| 规则编号 | 规则描述 |
|---|---|
| R-01 | 支持 xlsx 格式 |
| R-02 | 导出当前筛选条件下的全部租户 |
| R-03 | 导出字段包含:租户编号、租户名、联系人、联系手机、状态、创建时间 |
| R-04 | 状态字段使用字典转换显示(正常/停用) |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 导出租户 Excel | GET | /admin-api/system/tenant/export-excel | system:tenant:export | 导出租户列表 |
3.1.7 辅助接口
| 接口名称 | 请求方式 | 接口路径 | 鉴权 | 说明 |
|---|---|---|---|---|
| 按租户名获取 ID | GET | /admin-api/system/tenant/get-id-by-name | 免鉴权 | 登录页根据租户名获取编号 |
| 租户精简列表 | GET | /admin-api/system/tenant/simple-list | 免鉴权 | 获取已启用租户列表(仅 id + name),用于下拉选择 |
| 按域名获取租户 | GET | /admin-api/system/tenant/get-by-website | 免鉴权 | 根据域名识别租户信息 |
按域名获取租户 - 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| website | String | 是 | 域名,格式校验:^[a-zA-Z0-9.-]+(:\d{1,5})?$ |
按域名获取租户 - 返回结果:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | Long | 租户编号 |
| name | String | 租户名称 |
说明:如果域名未匹配到租户或租户已停用,返回 null。
3.2 后台管理端 - 租户套餐管理
3.2.1 功能清单
| 功能 | 优先级 | 权限标识 | 说明 |
|---|---|---|---|
| 套餐列表(分页) | P0 | system:tenant-package:query | 分页展示套餐列表 |
| 套餐详情 | P0 | system:tenant-package:query | 查看套餐详细信息(含关联菜单) |
| 创建套餐 | P0 | system:tenant-package:create | 创建新套餐并关联菜单 |
| 编辑套餐 | P0 | system:tenant-package:update | 修改套餐信息及关联菜单 |
| 删除套餐 | P0 | system:tenant-package:delete | 逻辑删除套餐 |
| 批量删除套餐 | P1 | system:tenant-package:delete | 批量逻辑删除 |
| 套餐精简列表 | P0 | 登录即可 | 获取已启用套餐列表,用于下拉选择 |
3.2.2 套餐列表(分页查询)
页面描述:

业务规则:
| 规则编号 | 规则描述 |
|---|---|
| R-01 | 默认按创建时间倒序排列 |
| R-02 | 支持按套餐名模糊搜索 |
| R-03 | 支持按状态筛选 |
| R-04 | 套餐名全局唯一 |
| R-05 | 列表不直接展示关联菜单(菜单数量可能很多),需查看详情时展开 |
3.2.3 创建/编辑套餐
页面描述:

业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 套餐名称全局唯一 | 避免运营人员混淆套餐 |
| R-02 | 关联菜单不能为空集合 | 至少需要一个菜单,否则租户无法使用任何功能 |
| R-03 | 修改菜单后,使用该套餐的所有租户的菜单权限同步更新 | 保证套餐与租户权限的一致性 |
| R-04 | 删除的菜单项从租户权限中移除时,不影响租户内已配置的业务数据 | 权限变更不影响数据完整性 |
| R-05 | 停用套餐不影响已使用该套餐的租户,但租户菜单权限会同步失效 | 停用是渐进操作,不直接中断租户使用 |
接口设计:
| 接口名称 | 请求方式 | 接口路径 | 权限标识 | 说明 |
|---|---|---|---|---|
| 创建套餐 | POST | /admin-api/system/tenant-package/create | system:tenant-package:create | 创建租户套餐 |
| 更新套餐 | PUT | /admin-api/system/tenant-package/update | system:tenant-package:update | 更新租户套餐 |
| 获取套餐详情 | GET | /admin-api/system/tenant-package/get | system:tenant-package:query | 获取套餐详情 |
| 获取套餐分页 | GET | /admin-api/system/tenant-package/page | system:tenant-package:query | 分页查询 |
| 获取套餐精简列表 | GET | /admin-api/system/tenant-package/simple-list | 登录即可 | 已启用套餐下拉选择 |
创建/更新请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | Long | 编辑时必填 | 套餐编号 |
| name | String | 是 | 套餐名称 |
| status | Integer | 是 | 状态(0-正常 1-停用) |
| remark | String | 否 | 备注 |
| menuIds | Set<Long> | 是 | 关联的菜单编号集合 |
3.2.4 删除套餐
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 逻辑删除 | 数据可恢复 |
| R-02 | 删除前检查是否有租户正在使用该套餐,若有则阻止删除 | 避免租户变成"孤儿",无法关联套餐信息 |
| R-03 | 提示信息:"该套餐下有 N 个租户正在使用,请先迁移后再删除" | 引导运营人员先处理关联租户 |
3.3 租户管理员自动初始化
3.3.1 功能说明
租户管理员在创建租户时自动创建,不需要独立的 CRUD 页面。
创建流程:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 创建租户记录 | 写入 system_tenant 表 |
| 2 | 创建管理员用户 | 在该租户下创建用户,写入 system_users 表 |
| 3 | 创建租户管理员角色 | 在该租户下创建管理员角色,写入 system_role 表 |
| 4 | 分配角色菜单 | 根据所选套餐的 menuIds 为管理员角色分配菜单权限 |
| 5 | 关联联系人 | 将管理员用户的 ID 写入 system_tenant.contact_user_id |
业务规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 每个租户有且仅有一个管理员用户(contactUserId) | 确保租户有唯一的管理入口 |
| R-02 | 管理员用户默认为该租户的第一个用户 | 创建顺序保证管理员先于其他用户存在 |
| R-03 | 管理员用户自动拥有该套餐下的全部菜单权限 | 管理员需要管理所有功能 |
| R-04 | 管理员用户名和密码仅在创建租户时设置 | 后续通过"用户管理-重置密码"修改 |
3.4 租户额度管理
3.4.1 功能说明
租户额度管理通过 accountCount 字段实现,控制每个租户允许创建的最大用户数量。这是 SaaS 按量计费的核心机制之一。
额度控制规则:
| 规则编号 | 规则描述 | 为什么这样设计 |
|---|---|---|
| R-01 | 创建租户时必须指定账号数量额度 | 防止无限创建用户,保护平台资源 |
| R-02 | 租户内新增用户时校验当前用户数是否达到上限 | 实时控制,不超卖 |
| R-03 | 达到上限后阻止新增用户,提示"该租户账号数量已达上限,请联系平台管理员扩容" | 明确告知解决路径 |
| R-04 | 平台管理员可随时通过编辑租户调整额度 | 灵活响应客户需求 |
| R-05 | 减少额度时,若当前用户数已超过新额度,给出警告但不阻止保存 | 不强制阻断运营操作 |
额度校验流程:

3.5 租户数据隔离机制
3.5.1 隔离策略概述
PMForge 采用共享数据库、共享表、按 tenant_id 字段隔离的策略。通俗地说:所有租户的数据存在同一张表里,但每条数据都打上了"租户标签",系统自动确保每个租户只能看到自己的数据。
| 隔离维度 | 实现方式 | 通俗解释 |
|---|---|---|
| 数据库层 | 自动拼接 tenant_id 条件 | 每条 SQL 查询自动加上"属于哪个租户"的过滤条件 |
| 消息队列层 | 消息体自动携带 tenant_id | 异步消息也不会串租户 |
| Redis 缓存层 | 缓存 key 自动加租户前缀 | 缓存数据也按租户分开存放 |
| Spring Cache 层 | 缓存管理器自动隔离 | 框架级缓存也遵循租户隔离 |
| 安全层 | 从请求头提取 tenant-id | 每个请求都携带租户身份标识 |
| 上下文层 | ThreadLocal 维护租户 ID | 整个请求链路中租户身份不丢失 |
3.5.2 数据库隔离规则
| 规则 | 说明 |
|---|---|
| 默认隔离 | 所有继承 TenantBaseDO 的实体类自动进行租户隔离 |
| 全局忽略 | 标注 @TenantIgnore 注解的实体/方法跳过租户过滤 |
| 表级忽略 | 通过配置指定不需要租户过滤的表(如租户表本身、字典表等全局共享数据) |
| 租户标识 | 请求头 tenant-id 携带租户编号 |
3.5.3 租户上下文传播

对开发者的意义:新增业务实体时,只要继承了 TenantBaseDO 基类,就自动具备租户隔离能力,无需手动处理。如果是全局共享的数据(如字典),标注 @TenantIgnore 即可跳过隔离。
四、非功能需求
4.1 性能要求
| 指标 | 要求 | 说明 |
|---|---|---|
| 租户列表查询 | < 500ms | 含套餐名称关联翻译 |
| 租户详情查询 | < 200ms | 单条查询 |
| 创建租户(含初始化管理员) | < 2s | 涉及多表写入 |
| 套餐列表查询 | < 300ms | - |
| 租户拦截器 SQL 拼接额外耗时 | < 5ms | 对业务接口几乎无感 |
| 租户上下文切换 | < 1ms | ThreadLocal 操作极快 |
4.2 安全要求
| 要求 | 说明 |
|---|---|
| 数据隔离 | 所有业务数据严格按租户隔离,不同租户不可互相访问 |
| 权限控制 | 租户管理相关接口仅平台超级管理员可操作 |
| 操作日志 | 所有租户管理操作记录操作日志 |
| 租户校验 | 每次请求校验 tenant-id 合法性 |
| 域名校验 | 域名格式严格校验,防止非法域名注入 |
| 密码安全 | 管理员密码加密存储,创建后不可明文查看 |
4.3 兼容性要求
| 端 | 要求 |
|---|---|
| PC 浏览器 | Chrome 80+、Firefox 75+、Safari 13+ |
| 数据库 | MySQL 5.7+、PostgreSQL 12+ |
五、数据设计
5.1 数据模型
5.1.1 租户表(system_tenant)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGINT | PK, AUTO_INCREMENT | 租户编号 |
| name | VARCHAR(100) | NOT NULL, UNIQUE | 租户名称 |
| contact_user_id | BIGINT | - | 管理员用户编号,关联 system_users.id |
| contact_name | VARCHAR(100) | NOT NULL | 联系人姓名 |
| contact_mobile | VARCHAR(20) | - | 联系手机 |
| status | TINYINT | NOT NULL, DEFAULT 0 | 租户状态(0-正常 1-停用) |
| package_id | BIGINT | NOT NULL | 租户套餐编号,关联 system_tenant_package.id |
| expire_time | DATETIME | NOT NULL | 过期时间 |
| account_count | INT | NOT NULL | 账号数量额度 |
| websites | VARCHAR(2000) | - | 绑定域名列表(JSON 数组) |
| creator | VARCHAR(64) | - | 创建者 |
| create_time | DATETIME | NOT NULL | 创建时间 |
| updater | VARCHAR(64) | - | 更新者 |
| update_time | DATETIME | NOT NULL | 更新时间 |
| deleted | BIT(1) | NOT NULL, DEFAULT 0 | 删除标记 |
| tenant_id | BIGINT | - | 该表标注 @TenantIgnore,不做租户隔离 |
索引设计:
| 索引名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| uk_name | name | UNIQUE | 租户名唯一索引 |
| idx_package_id | package_id | NORMAL | 按套餐查询租户 |
| idx_status | status | NORMAL | 按状态筛选 |
5.1.2 租户套餐表(system_tenant_package)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGINT | PK, AUTO_INCREMENT | 套餐编号 |
| name | VARCHAR(100) | NOT NULL, UNIQUE | 套餐名称 |
| status | TINYINT | NOT NULL, DEFAULT 0 | 状态(0-正常 1-停用) |
| remark | VARCHAR(500) | - | 备注 |
| menu_ids | TEXT | NOT NULL | 关联菜单编号集合(JSON 数组) |
| creator | VARCHAR(64) | - | 创建者 |
| create_time | DATETIME | NOT NULL | 创建时间 |
| updater | VARCHAR(64) | - | 更新者 |
| update_time | DATETIME | NOT NULL | 更新时间 |
| deleted | BIT(1) | NOT NULL, DEFAULT 0 | 删除标记 |
索引设计:
| 索引名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| uk_name | name | UNIQUE | 套餐名唯一索引 |
| idx_status | status | NORMAL | 按状态筛选 |
5.1.3 数据模型 ER 关系

5.2 数据字典
| 字典类型 | 字典值 | 说明 |
|---|---|---|
| common_status | 0-正常, 1-停用 | 通用状态枚举,用于租户状态、套餐状态 |
5.3 数据流转关系
| 操作 | 涉及的表 | 数据流转 |
|---|---|---|
| 创建租户 | system_tenant + system_users + system_role + system_role_menu | 创建租户 → 创建管理员用户 → 创建管理员角色 → 按套餐分配菜单 → 关联 contactUserId |
| 编辑租户-切换套餐 | system_tenant + system_role_menu | 更新套餐编号 → 同步更新该租户管理员角色的菜单权限 |
| 编辑套餐-修改菜单 | system_tenant_package + system_role_menu | 更新套餐 menuIds → 遍历使用该套餐的所有租户 → 同步更新各租户管理员角色的菜单权限 |
| 删除租户 | system_tenant | 逻辑删除租户记录,租户下数据保留但不可访问 |
| 租户过期 | system_tenant | 定时任务检查 expire_time,到期自动将 status 设为停用 |
六、跨模块联动
6.1 联动关系总览
租户管理作为 SaaS 平台的基石,与以下模块存在直接联动:
| 关联模块 | 联动方式 | 联动时机 | 影响范围 |
|---|---|---|---|
| 用户管理 | 创建租户时自动创建管理员用户 | 创建租户 | 用户表新增记录 |
| 角色权限 | 创建租户时自动创建管理员角色并分配菜单 | 创建租户、编辑套餐 | 角色表和角色菜单关联表 |
| 菜单管理 | 套餐通过 menuIds 关联菜单,控制功能范围 | 编辑套餐 | 所有使用该套餐的租户的权限 |
| 认证授权 | 停用/删除租户后所有用户 Token 失效 | 停用/删除租户 | 认证 Token 缓存 |
| 日志审计 | 租户管理操作记录操作日志 | 所有写操作 | 操作日志表 |
| 数据字典 | 租户状态使用通用状态字典 | 展示层 | 列表和表单的状态显示 |
6.2 关键联动流程
编辑套餐 → 同步租户权限

停用租户 → Token 失效

6.3 数据一致性要求
| 场景 | 一致性要求 | 实现方式 |
|---|---|---|
| 创建租户 | 租户、管理员用户、角色、菜单权限必须同时成功 | 事务控制,任一步骤失败则全部回滚 |
| 编辑套餐 | 套餐菜单更新后,关联租户的角色菜单必须同步 | 事务控制 + 遍历更新 |
| 停用租户 | 租户状态更新与 Token 清除需保证最终一致 | 先更新状态,再清除缓存 |
七、附录
7.1 名词解释
| 术语 | 解释 | 类比 |
|---|---|---|
| 租户(Tenant) | SaaS 平台中的独立客户/组织,拥有独立的用户、角色、数据空间 | 写字楼里的一家公司 |
| 租户套餐(Tenant Package) | 定义租户可访问的功能菜单集合,不同套餐对应不同功能权限 | 手机套餐——基础版只能打电话,旗舰版还能上网+国际漫游 |
| 租户隔离 | 不同租户的业务数据互相不可见、不可访问 | 每个公司有独立的办公室和文件柜,互相看不到 |
| 租户管理员(contactUserId) | 创建租户时自动创建的管理员用户,是该租户的第一个用户 | 公司的 IT 管理员,第一个拿到系统账号的人 |
| 账号额度(accountCount) | 限制租户可创建的最大用户数量 | 公司租了 50 个工位,最多只能坐 50 个人 |
| 绑定域名(websites) | 租户可绑定的独立访问域名 | 公司的门牌号——客户通过这个地址找到你 |
| 数据隔离(tenant_id) | 每条数据都带有的"租户标签",系统自动按此过滤 | 每份文件上都盖了公司章,只能看自己公司的 |
| TenantBaseDO | 实体基类,继承它的数据自动参与租户隔离 | 入职即自动获得工牌,不需要额外申请 |
| @TenantIgnore | 标注此注解的数据跳过租户过滤,用于全局共享数据 | VIP 通道——不需要检查工牌 |
| TenantContextHolder | 存储当前请求属于哪个租户的上下文 | 前台登记本——记录当前来访的是哪家公司 |
| 系统内置租户 | packageId = 0 的特殊租户,即平台自身,拥有全部权限 | 物业公司自己——不受任何套餐限制 |
7.2 权限标识汇总
| 权限标识 | 说明 |
|---|---|
| system:tenant:create | 创建租户 |
| system:tenant:update | 编辑租户 |
| system:tenant:delete | 删除租户 |
| system:tenant:query | 查询租户 |
| system:tenant:export | 导出租户 |
| system:tenant-package:create | 创建租户套餐 |
| system:tenant-package:update | 编辑租户套餐 |
| system:tenant-package:delete | 删除租户套餐 |
| system:tenant-package:query | 查询租户套餐 |
7.3 接口汇总
| 序号 | 接口名称 | 方法 | 路径 | 权限 | 优先级 |
|---|---|---|---|---|---|
| 1 | 按租户名获取 ID | GET | /admin-api/system/tenant/get-id-by-name | 免鉴权 | P0 |
| 2 | 租户精简列表 | GET | /admin-api/system/tenant/simple-list | 免鉴权 | P0 |
| 3 | 按域名获取租户 | GET | /admin-api/system/tenant/get-by-website | 免鉴权 | P1 |
| 4 | 创建租户 | POST | /admin-api/system/tenant/create | system:tenant:create | P0 |
| 5 | 更新租户 | PUT | /admin-api/system/tenant/update | system:tenant:update | P0 |
| 6 | 删除租户 | DELETE | /admin-api/system/tenant/delete | system:tenant:delete | P0 |
| 7 | 批量删除租户 | DELETE | /admin-api/system/tenant/delete-list | system:tenant:delete | P1 |
| 8 | 获取租户详情 | GET | /admin-api/system/tenant/get | system:tenant:query | P0 |
| 9 | 获取租户分页 | GET | /admin-api/system/tenant/page | system:tenant:query | P0 |
| 10 | 导出租户 Excel | GET | /admin-api/system/tenant/export-excel | system:tenant:export | P1 |
| 11 | 创建套餐 | POST | /admin-api/system/tenant-package/create | system:tenant-package:create | P0 |
| 12 | 更新套餐 | PUT | /admin-api/system/tenant-package/update | system:tenant-package:update | P0 |
| 13 | 删除套餐 | DELETE | /admin-api/system/tenant-package/delete | system:tenant-package:delete | P0 |
| 14 | 批量删除套餐 | DELETE | /admin-api/system/tenant-package/delete-list | system:tenant-package:delete | P1 |
| 15 | 获取套餐详情 | GET | /admin-api/system/tenant-package/get | system:tenant-package:query | P0 |
| 16 | 获取套餐分页 | GET | /admin-api/system/tenant-package/page | system:tenant-package:query | P0 |
| 17 | 获取套餐精简列表 | GET | /admin-api/system/tenant-package/simple-list | 登录即可 | P0 |
| 18 | App 端按域名获取租户 | GET | /app-api/system/tenant/get-by-website | 免鉴权 | P1 |
7.4 变更记录
| 版本 | 日期 | 修改内容 | 修改人 |
|---|---|---|---|
| v1.0 | 2026-09-13 | 初始版本 | PM Team |
| v2.0 | 2026-09-19 | 增强用户场景(7 个含异常处理)、验收标准、ASCII 页面原型、跨模块联动、名词解释 | PM Team |
本文档为租户管理模块 PRD v2.0,如有问题请联系产品负责人。