# 租户主数据模型设计 > 适用范围:当前系统把 `area / region / 入口地区 / 省级公共范围` 混合当成业务隔离边界使用的场景 > 文档定位:定义“地区正式升级为租户主数据”后的核心表结构、字段规则、接口边界和兼容策略,作为后续入口模块、RAG、合同模板、文档、公文、统计、RBAC 改造的底层依据。 --- ## 1. 结论先行 当前系统真正缺的不是“再补几个地区下拉”,而是: 1. 缺少租户主数据表 2. 缺少统一租户编码 3. 缺少“公共租户 / 总部租户 / 普通租户”的类型语义 4. 缺少租户启停、排序、展示名、别名、扩展字段 5. 缺少业务表和租户主数据之间的稳定关联 因此建议正式引入: 1. `sys_tenants` 2. `sys_tenant_aliases` 3. `sys_tenant_feature_flags` 4. 业务表中的 `tenant_code` 同时保留现有 `area / region / tenant_name` 一段时间作为兼容字段,但不再把它们当主键语义使用。 --- ## 2. 当前模型为什么不够 当前系统涉及租户边界的核心字段分散如下: 1. `sso_users.area` 2. `sso_users.tenant_name` 3. `leaudit_documents.region` 4. `contract_templates.region` 5. `rag_dataset.area` 6. `rag_chat_app.area` 7. `leaudit_entry_modules.areas[].area` 8. 前端若干页面里的固定地区常量 这些字段的问题不是“名字不同”这么简单,而是: 1. 没有统一编码 2. 没有主数据约束 3. 没有类型定义 4. 没有别名映射 5. 没有生命周期管理 6. 没有租户能力开关 只要继续沿用字符串直写模式,新增一个租户就必须人工排查: 1. 登录态信息 2. 首页入口 3. RAG 数据集 4. 合同模板 5. 文档上传 6. 交叉评查入口 7. 统计维度 8. RBAC 管理树 这已经不是可维护架构。 --- ## 3. 目标模型 ## 3.1 核心原则 后续统一按下面规则建模: 1. `tenant_code` 是唯一稳定编码,只用于程序匹配和外键引用 2. `tenant_name` 是展示名称,可修改 3. `area / region` 退化为兼容字段,不再承担主键语义 4. “公共资源”不再用 `省级 / 省局 / default / 空串` 表示,而是用明确租户类型或独立作用域字段表达 5. 所有租户候选值都必须来自主数据,不允许前后端各自发明字符串 ## 3.2 推荐主表 `sys_tenants` 建议表结构: ```sql CREATE TABLE IF NOT EXISTS sys_tenants ( id BIGSERIAL PRIMARY KEY, tenant_code VARCHAR(64) NOT NULL UNIQUE, tenant_name VARCHAR(128) NOT NULL, tenant_short_name VARCHAR(64) NULL, tenant_type VARCHAR(32) NOT NULL, parent_tenant_code VARCHAR(64) NULL, display_order INT NOT NULL DEFAULT 0, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, is_builtin BOOLEAN NOT NULL DEFAULT FALSE, is_public BOOLEAN NOT NULL DEFAULT FALSE, can_host_entry_module BOOLEAN NOT NULL DEFAULT TRUE, can_host_documents BOOLEAN NOT NULL DEFAULT TRUE, can_host_rag BOOLEAN NOT NULL DEFAULT TRUE, can_host_templates BOOLEAN NOT NULL DEFAULT TRUE, ext JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), deleted_at TIMESTAMP NULL ); ``` ## 3.3 字段语义 重点字段建议如下: 1. `tenant_code` 用于所有内部匹配,例如 `MZ`、`YF`、`JY`、`CZ`、`PROVINCIAL`、`PUBLIC` 2. `tenant_name` 展示名,例如 `梅州`、`云浮`、`揭阳`、`潮州`、`省局公共` 3. `tenant_type` 枚举建议: - `LOCAL` - `HEADQUARTER` - `PUBLIC` - `PILOT` - `INTERNAL` 4. `parent_tenant_code` 用于表达层级,例如某试点租户挂在省级租户下 5. `is_public` 表示该租户是否代表跨租户公共资源域,不再让业务侧自行解释 `省级 / default / 空串` 6. `is_builtin` 标识是否系统内置。当前固定地区和公共租户可以先作为内置租户落地 7. `can_host_entry_module / can_host_documents / can_host_rag / can_host_templates` 表示这个租户是否允许承载某类业务配置,避免把“公共租户”误用于不该投放的模块 8. `ext` 用于保留显示标签、颜色、旧编码映射、第三方系统标识等扩展信息 --- ## 4. 别名表设计 ## 4.1 为什么必须有别名表 当前系统已存在以下同义值混用: 1. `省局` 2. `省级` 3. `default` 4. `''` 5. 未来可能出现的 `广东省局` 如果不显式建别名表,只靠 if/else 兼容,后续每个模块都会各写一遍归一逻辑。 ## 4.2 推荐表 `sys_tenant_aliases` ```sql CREATE TABLE IF NOT EXISTS sys_tenant_aliases ( id BIGSERIAL PRIMARY KEY, tenant_code VARCHAR(64) NOT NULL, alias_value VARCHAR(128) NOT NULL, alias_type VARCHAR(32) NOT NULL, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), deleted_at TIMESTAMP NULL, UNIQUE (tenant_code, alias_value) ); ``` ## 4.3 `alias_type` 建议 建议最少支持: 1. `LEGACY_AREA` 2. `LEGACY_REGION` 3. `DISPLAY` 4. `IMPORT` 5. `EXTERNAL_SYSTEM` 这样可以区分: 1. 历史库里的旧值 2. 前端展示名 3. 导入脚本映射值 4. 第三方接口传入值 --- ## 5. 租户能力开关表设计 ## 5.1 为什么需要功能级能力开关 用户这次点出来的“入口模块无法给新租户分配”只是第一层问题。更深一层是: 新增租户后,不一定所有模块都同时开放。 例如某新租户可能只开: 1. 首页入口 2. 文档上传 但暂时不开: 1. 合同模板 2. RAG 知识库管理 3. 交叉评查 所以需要功能级能力模型,而不是只维护一个名字列表。 ## 5.2 推荐表 `sys_tenant_feature_flags` ```sql CREATE TABLE IF NOT EXISTS sys_tenant_feature_flags ( id BIGSERIAL PRIMARY KEY, tenant_code VARCHAR(64) NOT NULL, feature_key VARCHAR(64) NOT NULL, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, ext JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), deleted_at TIMESTAMP NULL, UNIQUE (tenant_code, feature_key) ); ``` ## 5.3 `feature_key` 建议 建议按当前系统模块先固化: 1. `home.entry_module` 2. `documents.upload` 3. `documents.list` 4. `govdoc.audit` 5. `rag.dataset` 6. `rag.chat` 7. `contract.template` 8. `cross.review` 9. `usage.stats` --- ## 6. 业务表接入规范 ## 6.1 用户表 当前: 1. `sso_users.area` 2. `sso_users.tenant_name` 建议新增: 1. `sso_users.tenant_code` 规则: 1. `tenant_code` 为主归属租户 2. `area` 先保留,作为历史兼容展示字段 3. `tenant_name` 保留,但展示应优先来自 `sys_tenants.tenant_name` ## 6.2 文档表 当前: 1. `leaudit_documents.region` 建议新增: 1. `leaudit_documents.tenant_code` 兼容策略: 1. 老代码读取 `region` 时,通过别名表映射为 `tenant_code` 2. 新代码写入时同时写 `tenant_code` 和兼容 `region` ## 6.3 合同模板 当前: 1. `contract_templates.region` 建议新增: 1. `contract_templates.tenant_code` 2. 可选新增 `scope_type` 原因: 当前 `省级` 既像租户,又像公共范围,语义混乱。建议拆成: 1. `tenant_code` 2. `scope_type = TENANT / PUBLIC` ## 6.4 RAG 当前: 1. `rag_dataset.area` 2. `rag_chat_app.area` 3. `is_public` 4. `is_default` 建议新增: 1. `tenant_code` 2. `scope_type` 说明: `is_public` 可以保留,但应由 `scope_type` 和租户类型统一解释,避免 `省级 + is_public` 双重表达。 ## 6.5 入口模块 当前: 1. `leaudit_entry_modules.areas JSONB` 建议: 1. 保留 `leaudit_entry_modules` 2. 新增 `leaudit_entry_module_tenants` 3. 由关系表表达模块与租户的分配关系 详见文档: - [入口模块租户配置表迁移方案.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/入口模块租户配置表迁移方案.md) --- ## 7. 租户类型与公共范围语义 ## 7.1 当前问题 现在公共范围至少存在 4 种写法: 1. `省局` 2. `省级` 3. `default` 4. `''` 这 4 种值不能继续并存。 ## 7.2 推荐统一语义 建议统一为两层: 1. 租户归属层 2. 数据作用域层 推荐枚举: 1. `scope_type = TENANT` 2. `scope_type = PUBLIC` 3. `scope_type = CROSS_TENANT` 说明: 1. `tenant_code` 决定“归属” 2. `scope_type` 决定“谁可以看” 例如: 1. 某模板属于 `PROVINCIAL_PUBLIC`,`scope_type=PUBLIC` 2. 某知识库属于 `MZ`,`scope_type=TENANT` 这样比单纯写 `region='省级'` 清晰得多。 --- ## 8. 后端接口建议 ## 8.1 新增租户主数据接口 建议最少提供: 1. `GET /api/v3/tenants` 返回启用中的租户列表 2. `GET /api/v3/tenants/{tenantCode}` 返回租户详情 3. `POST /api/v3/tenants` 创建租户 4. `PUT /api/v3/tenants/{tenantCode}` 更新租户 5. `GET /api/v3/tenants/options` 返回前端下拉所需精简结构 6. `GET /api/v3/tenants/features` 查询租户能力矩阵 ## 8.2 返回结构建议 建议前端统一使用: ```json { "tenant_code": "MZ", "tenant_name": "梅州", "tenant_type": "LOCAL", "is_public": false, "display_order": 10, "is_enabled": true, "features": [ "home.entry_module", "documents.upload", "rag.chat" ] } ``` --- ## 9. 前端接入规范 所有原来写死地区列表的页面,后续都不应该再维护常量数组,而应读取租户主数据接口。 重点包括: 1. 入口模块新建页 2. 入口模块列表页筛选 3. 交叉评查入口可见性计算 4. RAG 地区配置页 5. 合同模板地区筛选 6. 使用统计地区维度筛选 前端要遵守两条规则: 1. 存储和提交用 `tenant_code` 2. 展示才用 `tenant_name` --- ## 10. 不建议复用 `tenant_name` 当主键 虽然系统里已经有 `sso_users.tenant_name`,但不建议直接把它升级为租户主键,原因有 5 个: 1. 它本质更像组织展示字段 2. 历史数据可能存在空值和重复值 3. 它不一定稳定 4. 它可能包含业务展示修饰词 5. 它不能稳定承载外键关系 因此: 1. `tenant_name` 可保留 2. 但必须引入新的 `tenant_code` --- ## 11. 与权限系统的关系 租户主数据模型不是替代 RBAC,而是给 RBAC 提供更稳定的数据边界基座。 关系应为: 1. RBAC 决定“你能做什么” 2. 租户主数据决定“你的租户边界是什么” 3. 数据范围执行器把两者合并成最终访问决策 也就是: 1. `permission_key` 2. `data_scope` 3. `tenant_code` 这三者必须同时成立。 --- ## 12. 推荐实施顺序 建议顺序如下: 1. 建 `sys_tenants`、`sys_tenant_aliases`、`sys_tenant_feature_flags` 2. 先把当前固定地区和公共范围落成主数据 3. 给 `sso_users` 增加 `tenant_code` 4. 先改入口模块为租户接口驱动 5. 再改 RAG、合同模板、文档上传 6. 最后将统一数据范围执行器接到 `tenant_code` --- ## 13. 本文档解决什么问题 本文档主要解决: 1. “地区”到底该怎么升级成“租户” 2. 新租户为什么不能只改前端下拉 3. 为什么必须引入 `tenant_code` 4. 为什么必须有别名表和能力开关 5. 业务表后续应该如何统一接租户主数据 下一步建议继续阅读: 1. [地区租户化与自定义租户扩展改造方案.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/地区租户化与自定义租户扩展改造方案.md) 2. [地区到租户编码映射清洗清单.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/地区到租户编码映射清洗清单.md) 3. [入口模块租户配置表迁移方案.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/入口模块租户配置表迁移方案.md) 4. [自定义租户功能连带影响深度补充.md](/home/wren-dev/Porject/leaudit-platform/docs/权限与地区隔离/自定义租户功能连带影响深度补充.md)