# 房屋租赁 SaaS 平台 · 基础数据梳理

> 版本：v0.1（草案，待调研结论回填后定稿）
> 目标规模：百万级房源 / 千万级租客 / 亿级账单流水
> 技术栈前提：Java 生态，多租户 SaaS
> 落盘位置：`/Volumes/Extreme Pro/Workspace/rental-saas-20261018/`

---

## 一、先把「数据」分成四类，这是整个设计的地基

绝大多数租赁系统设计失败的根因，是把**四类生命周期完全不同的数据混在一张表里**。本设计的
第一原则是**按数据性质分层**，而不是按业务模块分表。

| 类别 | 特征 | 典型数据 | 存储载体 | 变更频率 |
| --- | --- | --- | --- | --- |
| **A. 主数据（Master）** | 有唯一身份、被引用、需强一致 | 租户、楼盘、楼栋、房间、租客、设备 | PostgreSQL（OLTP） | 低 |
| **B. 事务数据（Transactional）** | 有状态机、有流水、需审计与对账 | 合同、账单、收款、工单 | PostgreSQL（OLTP）+ 时间分区 | 高 |
| **C. 时序/事件数据（Time-series）** | 写多读少、按时间窗口聚合、几乎不改 | IoT 设备上报、门锁记录、操作审计、Agent 调用日志 | 时序库（ClickHouse 为主） | 极高 |
| **D. 非结构化数据（Unstructured）** | 大对象、内容寻址 | 房源图片、视频、AR 全景图/glTF 模型、合同扫描件、工单现场照片 | 对象存储（RustFS/S3）+ 元数据表 | 中 |

**关键结论：**

1. **A + B 全部进 PostgreSQL**，不拆库。理由：租赁业务本质是**强事务 + 强关联**（收租要对账、
   合同要留痕），分布式事务带来的复杂度远大于它买到的扩展性。百万房源 / 千万租客这个量级，
   PostgreSQL 单集群配好分区完全扛得住（详见 03 文档容量测算）。
2. **C 绝对不能进 PostgreSQL 主库**。IoT 设备上报按每户 10 个设备、每 5 分钟一次估算，
   百万房源即产生 **每年 10 亿+ 行**写入，压垮 OLTP 主库只是时间问题。必须旁路到时序库。
3. **D 的文件字节一律不进数据库**，库里只存 `media_asset` 元数据 + 对象存储 key。
   这是图片/视频/AR 模型这类大对象的唯一正确做法。

---

## 二、九大业务域的基础数据实体

按你给的范围（数字家庭、工单、物业、长短租、房源含 AR、Agent 能力）拆解。
**每域只列「基础数据」，即需要持久化并有明确生命周期的实体**，不含派生视图与缓存。

### 域 1 · 平台与租户（Platform / Tenancy）

这是 SaaS 的地基，所有业务表都必须挂在租户下。

| 实体 | 说明 | 关键字段 |
| --- | --- | --- |
| `tenant` | 租户（一家运营公司），SaaS 隔离单元 | 租户码、名称、套餐、状态、隔离策略 |
| `tenant_config` | 租户级配置（计费规则、账期、时区、编号规则） | 键值对 + 类型 |
| `user_account` | 平台登录账号（与「租客」是两回事） | 手机号、密码散列、MFA |
| `role` / `permission` | RBAC 权限模型 | 角色、权限码 |
| `user_role_binding` | 账号-角色绑定 | — |
| `user_tenant_binding` | 账号-租户绑定（一人可服务多租户） | — |
| `data_scope_rule` | 数据权限（某账号只看某几个楼盘） | 资源类型 + 范围 |

> ⚠️ **必须区分 `user_account`（系统操作者）与 `tenant_person`（租客自然人）**。
> 二者可能重合（租客也登录 App），但绝不能合并 —— 系统操作者没有租赁合同，
> 租客也不需要后台权限。混表是租赁系统最常见的设计债。

### 域 2 · 资产与房源（Asset / Property）

房源是多层级结构，且长租与短租共用一套物理资产。

| 实体 | 说明 | 层级 |
| --- | --- | --- |
| `property` | 物业项目 / 小区 | 1 |
| `building` | 楼栋 | 2 |
| `unit` | 单元 / 座 | 3 |
| `floor` | 楼层（可虚拟） | 4 |
| `room` | 房间 / 房源（**核心实体**） | 5 |
| `room_attribute` | 房间属性（面积、朝向、户型、装修、配套），支持扩展 | — |
| `room_status_log` | 房间状态变更留痕（空置/预定/在租/维修/停用） | — |
| `bed` | 床位（合租场景，一间房多床） | 6 |
| `parking_space` | 车位（也是可租资产） | 附属 |
| `amenity` | 配套设施字典（健身房、泳池） | 字典 |
| `room_amenity` | 房间-设施关联 | 关联 |

> **设计要点：`room` 是「可出租的最小单元」，一切租赁、账单、设备都挂在 `room_id` 上。**
> 短租与长租的差异不在物理表，而在「库存维度」：长租房间同一时刻只有 0/1 份库存，
> 短租需要**间夜库存日历**（见下）。

| 实体 | 说明 |
| --- | --- |
| `room_calendar` | 间夜库存日历（短租核心），按 `room_id + biz_date` 唯一，记录可售/已售/价格/限制 |
| `rate_plan` | 价格方案（长租月租价、短租日价、阶梯价、会员价） |
| `rate_policy` | 价格策略（连住折扣、提前订优惠、最低入住天数） |

> **防超卖的唯一正确做法**：以 `room_calendar` 的 `(room_id, biz_date)` 唯一约束 +
> 行级锁（或 `UPDATE ... WHERE sold_count < total_count` 条件更新）做原子性扣减。
> 绝不能「先查再写」。

### 域 3 · 客户与租客（Customer / Person）

| 实体 | 说明 |
| --- | --- |
| `tenant_person` | 租客自然人（个人），含证件、手机、实名状态 |
| `tenant_org` | 企业租客（公司名义承租） |
| `tenant_person_org` | 个人-企业归属（员工、联系人） |
| `guarantor` | 担保人 |
| `credit_profile` | 信用档案（欠租记录、黑名单、评分） |
| `tenant_person_identity` | 证件附件与实名认证记录（走对象存储） |
| `contact_record` | 沟通/跟进记录（为 Agent 能力准备语料） |

> **千万级租客的存储要点**：租客表是**跨租户全局实体**还是**租户内实体**？
> 这是本设计最重要的一个判断 —— 同一自然人在两家运营公司都租过房，
> 若做成租户内实体会产生重复身份，风控与黑名单失效。**结论：租客自然人做全局表，
> 通过关联表挂到租户下**（详见 02 文档多租户章节的「混合隔离」）。

### 域 4 · 合同与租赁（Contract / Lease）

| 实体 | 说明 |
| --- | --- |
| `contract` | 合同主表，含类型（长租/短租）、起止、状态机 |
| `contract_party` | 合同当事人（承租方、出租方、担保人、共租人） |
| `contract_room` | 合同-房间关联（一合同可含多间房） |
| `contract_term` | 合同条款（版本化，JSONB 存结构化条款） |
| `contract_change` | 变更单（续租、换房、改价、提前退租），保留原合同不动 |
| `contract_deposit` | 押金记录（应收/实收/退还/扣除） |
| `checkin_record` | 入住/退租交割（水电底数、物品清单、钥匙） |
| `contract_file` | 合同电子档与电子签章记录 |

> **核心原则：合同只增不改。** 任何变更都产生 `contract_change` 新记录 +
> 生成新版本，`contract` 上只维护 `current_version`。否则历史对账必然出错。

### 域 5 · 计费与收租（Billing / Payment）

| 实体 | 说明 |
| --- | --- |
| `bill` | 账单（租金/押金/水电/物业费/违约金），按月/按周期生成 |
| `bill_item` | 账单明细（一张账单可拆多个费用项） |
| `bill_period` | 账期定义 |
| `fee_item` | 费用项字典（租金、水费、电费、物业费、停车费） |
| `meter` | 仪表（水/电/燃气表），关联房间 |
| `meter_reading` | 抄表读数（时序性质，但需精确保留） |
| `payment` | 收款流水（一笔支付） |
| `payment_allocation` | **核销明细**（一笔收款核销多张账单） |
| `refund` | 退款单 |
| `invoice` | 发票（申请、开具、红冲） |
| `dunning_record` | 催缴记录（为 Agent 自动催收准备） |
| `late_fee_rule` | 滞纳金规则 |

> **收租建模的关键：`payment` 与 `bill` 是多对多，必须用 `payment_allocation` 连接。**
> 新人最常犯的错是给 `bill` 加一个 `payment_id` 字段 —— 一次性付清 3 个月房租就无法表达。
> 核销明细表还必须记录**每笔核销金额**，且核销过程幂等（唯一键 = 支付流水号 + 账单号）。

### 域 6 · 物业与工单（Property Service / Work Order）

| 实体 | 说明 |
| --- | --- |
| `service_catalog` | 服务项目录（保洁、维修、代收快递） |
| `work_order` | 工单主表，含类型、优先级、状态机 |
| `work_order_item` | 工单明细（材料、工时、费用） |
| `work_order_flow` | 工单流转记录（谁在何时做了什么） |
| `work_order_attachment` | 现场照片/视频（对象存储引用） |
| `staff` | 服务人员（维修工、管家）与技能标签 |
| `dispatch_record` | 派单记录 |
| `sla_rule` | SLA 规则（响应时限、完工时限） |
| `inspection_task` | 巡检任务与巡检点 |

> 工单是**强状态机**实体，状态流转必须有 `work_order_flow` 全量留痕，
> 不允许只更新 `work_order.status`。否则 SLA 计时与责任判定无从谈起。

### 域 7 · 数字家庭 / IoT（Smart Home）

| 实体 | 说明 |
| --- | --- |
| `device` | 设备主数据（门锁、电表、水表、传感器、摄像头） |
| `device_model` | 设备型号与能力定义（支持的属性/指令） |
| `device_binding` | 设备-房间-租户绑定关系（含生效时间段） |
| `device_credential` | 设备密钥/证书 |
| `device_shadow` | 设备影子（最新状态快照，覆盖写） |
| `device_event` | 设备事件（开锁、告警）→ 时序库 |
| `device_telemetry` | 设备遥测（温度、电量）→ 时序库 |
| `device_command` | 指令下发记录（含下发/回执状态） |
| `scene` / `scene_action` | 场景联动（回家模式、离家模式） |
| `access_grant` | 门锁临时密码/权限（租客、保洁、维修） |

> **`device_shadow`（影子）与 `device_telemetry`（时序）必须分开**：
> 前者是「此刻状态」，一张小表高频覆盖写，查询 O(1)；
> 后者是「历史轨迹」，写入时序库。混在一起会让状态查询变成全表扫描。

### 域 8 · 多媒体与 AR 看房（Media / AR）

| 实体 | 说明 |
| --- | --- |
| `media_asset` | 媒体资源元数据（图片/视频/音频/模型），**不存字节** |
| `media_variant` | 多规格派生（缩略图、不同清晰度），一源多规格 |
| `media_binding` | 媒体-业务对象绑定（房间、合同、工单、设备） |
| `ar_scene` | AR 场景（一个房间可有多套场景/多期拍摄） |
| `ar_scene_node` | 场景节点（全景图球面、三维模型、热点锚点） |
| `ar_hotspot` | 热点（点击跳到某位置/展示某信息/触发某操作） |
| `ar_camera_path` | 相机轨迹（漫游路线） |
| `ar_scene_version` | 场景版本（重新拍摄、模型更新） |
| `vr_session` | 看房会话（谁在何时看了哪个场景、停留时长）→ 行为数据 |

> **AR 看房的数据本质是三块：**
> ① **全景图/立方体贴图**（大文件，对象存储，按瓦片切分）；
> ② **glTF/GLB 三维模型**（含 Draco 压缩，对象存储 + 版本号）；
> ③ **空间锚点元数据**（热点坐标、相机轨迹、房间尺寸），这部分是**小体积强结构数据，
> 必须进 PostgreSQL**，因为它要和房间、设备、账单关联（例如热点上挂「这台空调每月耗电」）。
>
> 千万不要把锚点数据塞进模型文件里 —— 那样就无法做「点热点→查真实设备状态」的联动。

### 域 9 · Agent 能力（AI Agent）

这是最新的一块，也是最容易被设计成「一堆散字段」的一块。

| 实体 | 说明 |
| --- | --- |
| `agent_definition` | Agent 定义（名称、系统提示词、可用工具集、模型配置） |
| `agent_tool` | 工具注册表（工具名、参数 Schema、后端实现、权限要求） |
| `agent_session` | 会话（谁、哪个渠道、关联哪个业务对象） |
| `agent_message` | 消息（角色、内容、token 用量、耗时） |
| `agent_tool_call` | 工具调用记录（入参、出参、是否成功、耗时）→ 审计用 |
| `agent_memory` | 长期记忆（结构化键值 + 向量） |
| `knowledge_doc` | 知识库文档（制度、FAQ、话术） |
| `knowledge_chunk` | 文档切片 + **向量（pgvector）** |
| `prompt_template` | 提示词模板与版本 |

> **Agent 落地的三个数据要点：**
> 1. **工具调用必须可审计**：`agent_tool_call` 要记录入参出参，否则 Agent 误操作无法追责。
> 2. **向量不必上专用库**：本机 PostgreSQL 17 已装 **pgvector 0.8.7**，知识库切片量级
>    （十万~百万级）用 pgvector + HNSW 索引完全够用，省掉一套独立向量库的运维成本。
>    超过千万切片再考虑迁移。
> 3. **Agent 的「记忆」要区分短期（会话上下文）与长期（跨会话画像）**，
>    长期记忆才是让它「越用越懂这个租客」的关键，也是租赁场景最值钱的数据资产。

---

## 三、跨域的关系主干（一张图看懂全局）

```
tenant (租户)
  ├── property (小区) ── building ── unit ── floor ── room ── bed
  │                                                      ├── room_attribute
  │                                                      ├── room_calendar (短租库存)
  │                                                      ├── device_binding ── device ── device_shadow
  │                                                      ├── media_binding ── media_asset
  │                                                      └── ar_scene ── ar_scene_node ── ar_hotspot
  │
  ├── tenant_person (租客，全局实体) ── credit_profile
  │        │
  │        └── contract ── contract_party / contract_room / contract_term
  │                 ├── bill ── bill_item
  │                 │      └── payment_allocation ── payment
  │                 └── contract_change (变更留痕)
  │
  ├── work_order ── work_order_flow / work_order_item / dispatch_record
  │        └── 关联 room / contract / tenant_person
  │
  └── agent_session ── agent_message ── agent_tool_call
           └── 关联任意业务对象 (多态引用)
```

**三条贯穿全局的引用原则：**

1. **`room_id` 是资产轴的唯一锚点** —— 设备、媒体、AR 场景、合同、工单最终都归到房间。
2. **`contract_id` 是履约轴的唯一锚点** —— 账单、押金、交割、催缴都归到合同。
3. **`tenant_person_id` 是人的唯一锚点** —— 但它是**全局实体**，不随租户隔离（见 02 文档）。

---

## 四、决策结论与遗留问题

### 已定案（本轮设计给出结论）

| # | 问题 | 结论 | 依据 |
| --- | --- | --- | --- |
| 1 | 租客自然人是全局实体还是租户私有？ | **全局实体，不做租户隔离** | 若隔离，跨公司黑名单失效，风控形同虚设。用哈希查重 + 证件加密平衡隐私。详见 02 文档 |
| 2 | 短租是否与长租同库同表？ | **同表 `room`，短租靠 `room_calendar` 库存日历** | 物理资产是同一份，差异只在库存维度。分表会导致同一房间两套身份 |
| 4 | 时序库选 ClickHouse 还是 TimescaleDB？ | **ClickHouse** | 遥测是纯追加+聚合场景，正是其主场；TimescaleDB 与主库同实例会抢 OLTP 资源。详见 02 文档 |
| 7 | 向量库用专用还是 pgvector？ | **pgvector（本机已装 0.8.7）** | 百万切片以内 HNSW 足够，省一套中间件运维 |

### 仍需业务方确认（不影响建表，影响上层实现）

| # | 问题 | 影响 | 建议 |
| --- | --- | --- | --- |
| 3 | 是否需要 PostGIS？ | 本机**未安装**，需运维部署 | 若要做「地铁房」「学区房」「地图选址」，建议装。当前用 `lng/lat` 两列 + 应用层算距离，10 万房源内可接受 |
| 5 | 电子签章自建还是第三方？ | 影响 `contract_file.sign_provider` 取值 | 强烈建议接第三方（法大大/e签宝），自建电子签法律效力难保证 |
| 6 | AR 看房自研还是接第三方？ | 影响 `ar_scene*` 表的粒度 | 建议**接第三方拍摄能力**（如贝壳如视/众趣），但**锚点数据必须存自己库**——否则业务联动被厂商锁死。本设计的 `ar_hotspot` 已按此预留 |
| 8 | 短租房源是否与 OTA（携程/美团）打通？ | 影响是否需要「渠道库存同步表」 | 若要打通，需增加 `channel_inventory` 表做多渠道库存映射与超卖防护。**这是一个隐藏的大坑**：多渠道库存同步需分布式锁 + 补偿队列 |

### 下一步建议的实施顺序

1. **先做域 1-5（平台/房源/租客/合同/收租）** —— 这是业务闭环的最小集，
   没有收租就没有商业模式。
2. **再做域 6（工单）** —— 直接决定租客满意度与续租率。
3. **域 7（IoT）与域 8（AR）** —— 差异化竞争力，但依赖硬件与内容生产，
   建议先做单一楼盘试点。
4. **域 9（Agent）最后** —— 它是放大器，没有前面的数据积累，
   Agent 无米下锅。**但 `agent_tool_call` 等审计表建议早期就建**，
   因为工具调用日志越早积累越有价值。

---

*本文档为基础数据梳理草案，详细表结构与 DDL 见 `02-多租户与技术栈选型.md`、`03-数据库设计.md`。*
