Files
starRiverProperty/docs/business/租户访客默认楼层-数据库配置阶段技术设计.md
T
反编译工作区 8b15445328 feat: add service config templates and extraction script
Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
2026-05-01 19:38:01 +08:00

294 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 租户访客默认楼层:数据库配置阶段 — 详细技术设计
> **文档性质**:实施级技术设计(TDD 范围:本阶段仅 **数据库表 + 电梯服务侧读表求交**;**不包含** 物业管理端前端页面与开放管理 API)。
> **产品依据**:[租户访客默认楼层技术产品方案](租户访客默认楼层技术产品方案.md)(策略类型、安全底线、AC 验收)。
> **流程依据**:[访客注册与派梯楼层业务流程走查](访客注册与派梯楼层业务流程走查.md)UC-01 / UC-02、`PersonRuleServiceImpl.addVisitor`)。
| 项目 | 内容 |
|------|------|
| 版本 | v0.1 草案(数据库阶段) |
| 适用工程 | `maven-cw-elevator-application`(电梯应用库;落库表位于**电梯库**或与电梯同数据源的业务库,以现网数据源划分为准) |
| 读者 | 后端开发、DBA、集成测试、运维 |
| **模型索引** | [租户组织人员访客-数据模型与用例](../architecture/租户组织人员访客-数据模型与用例.md)(仓库级 ER、`business_id` 与组织主键辨析) |
---
## 1. 背景与阶段目标
### 1.1 问题复述
多租户场景下,部分机构要求:在调用方**不传** `floorIds`(走 **UC-01**)时,访客派梯生效楼层**不得**简单等同于被访人组织侧 `floorList` 全集,而应**收敛**为机构允许的若干 `zoneId`(如固定接待层)。未做特殊要求的租户须与现网行为**完全一致**。
### 1.2 本阶段目标(范围边界)
| 目标 | 说明 |
|------|------|
| **配置方式** | 通过 **数据库表** 维护「哪些租户启用访客楼层策略及允许列表」;由实施/DBA/SQL 脚本录入,**不提供** 物业/客服可视化管理界面(后续阶段再做)。 |
| **行为** | 仅对**存在有效策略行**的 `businessId`:在 **UC-01** 路径上,在取得被访人 `floorList` 后执行 **`allow_zone_ids ∩ floorList`**(顺序建议保持 **`floorList` 顺序** 过滤)。 |
| **兼容** | 表中**无**该租户配置、或 `enabled=0`、或 `allow_zone_ids` 为空:与现网 **UC-01** 一致(仍使用组织返回的 `floorList` 全集)。 |
| **显式楼层** | 请求体已带**非空** `floorIds`**UC-02**):本阶段 **不改变** 现网语义,**不**读策略表、**不**对传入列表做求交(与产品方案 **AC-4** 一致;若未来合同要求「显式也求交」须另立变更)。 |
| **访客业务系统 / 登记页** | 本阶段**不要求**改第三方 BFF;但若登记页仍只拉组织 `floorList` 展示,则**展示可能与电梯最终开通楼层不一致** —— 见 **§8 风险与后续工作**。 |
### 1.3 非目标(明确排除)
- 物业管理端 CRUD 页面、审计日志界面、策略版本与登记单快照联动(产品方案 §2.7、§3.4 完整能力)。
- 组织侧收窄 `floorList`、Nacos 配置中心等其它路径(参见产品方案 §4.1 方案族)。
- 按楼栋 `building_id` 的多套策略并行(表结构可预留字段,本阶段查询规则见 **§4.3**)。
---
## 2. 设计原则与安全约束
1. **权限上界(不变量)**
租户策略仅表达**允许集合**;在 **UC-01** 路径下,最终写入电梯规则的 `zoneId` 列表必须是 **`allow_zone_ids``PersonResult.floorList` 的交集** 的子集(实现上等价于交集本身)。**禁止**在交集为空时静默落到其它楼层。
2. **交集为空**
租户已配置非空允许列表,但与被访人 `floorList` **无交集**:返回**明确业务错误**(禁止继续 `zone/page``get(0)` 等,避免走查文档 **UC-04** 类 NPE)。
3. **被访人无楼层**
组织 `detail` 返回的 `floorList`**null 或空列表**:在应用层**短路失败**(与产品方案建议的空集校验一致),错误信息区别于「策略求交为空」。
4. **数据源**
策略表以 **`businessId`(机构 ID** 与调用上下文 `CloudwalkCallContext.company.companyId` 对齐;所有查询必须带 `business_id` 条件,避免串租户。
---
## 3. 总体架构(本阶段)
```mermaid
flowchart LR
subgraph callers [调用方不变]
T[第三方 / intelligent]
end
subgraph elev [cw-elevator-application]
API["POST /elevator/person/add/visitor"]
SVC["PersonRuleServiceImpl.addVisitor"]
ORG["Feign PersonService.detail"]
DB[("tenant_visitor_floor_policy")]
end
subgraph orgsvc [组织服务]
DETAIL["POST /component/person/detail"]
end
T --> API --> SVC
SVC -->|floorIds 为空| ORG --> DETAIL
SVC -->|读策略| DB
SVC -->|后续现有逻辑| Z["zoneService.page 等"]
```
**职责划分**
- **策略存储与读取**:电梯应用访问本表(与现网 `image_rule_ref` 等表同一数据源即可,减少分布式事务)。
- **「访客系统只获取对应楼层」**:若「访客系统」指**登记页/第三方 BFF**,本阶段**不强制**其改代码;电梯侧已保证**开通结果**收敛。展示一致需后续 **预览接口或 BFF 同源计算**(产品方案 §2.3、§2.5)。
---
## 4. 数据模型设计
### 4.1 逻辑模型
与产品方案 §4.3 对齐,本阶段至少使用下列语义字段:
| 字段 | 类型(建议) | 必填 | 说明 |
|------|----------------|------|------|
| `id` | `VARCHAR(32)` PK | 是 | 主键,UUID |
| `business_id` | `VARCHAR(64)` | 是 | 机构/租户 ID,与 `businessId` 一致 |
| `policy_type` | `VARCHAR(32)` | 是 | 本阶段仅使用 **`INTERSECT_ALLOWLIST`**;预留 `HOST_FLOOR_LIST` 等枚举便于扩展 |
| `allow_zone_ids` | `TEXT` | 条件 | **JSON 数组**,元素为 `zoneId` 字符串;策略生效时须非空(见 §4.4) |
| `building_id` | `VARCHAR(64)` NULL | 否 | **租户级默认**:本阶段固定 **`NULL`** 表示全机构默认一条;非 NULL 预留给「按楼栋」扩展 |
| `enabled` | `TINYINT(1)` | 是 | `1` 启用,`0` 停用(等价于未配置) |
| `policy_version` | `BIGINT` | 是 | 每次配置变更递增;本阶段**可不**接入登记快照,但建议表结构一次到位 |
| `remark` | `VARCHAR(256)` | 否 | 实施备注(如「广发基金接待层」) |
| `created_by` / `updated_by` | `VARCHAR(64)` | 否 | 本阶段手工 SQL 可填运维账号 |
| `created_at` / `updated_at` | `BIGINT` | 否 | Unix 毫秒时间戳,与项目内其它表风格一致 |
**唯一约束建议**`UNIQUE KEY uk_biz_building (business_id, building_id)`
MySQL 中 `building_id` 为 NULL 时多行 `(biz, NULL)` 在部分版本下**可能**被唯一索引允许多条 —— **实施约束**:应用层查询 `WHERE business_id = ? AND building_id IS NULL ... LIMIT 1`,DBA 规范**每个租户仅一行**租户级策略。
### 4.2 DDL 草案(MySQL
```sql
CREATE TABLE tenant_visitor_floor_policy (
id VARCHAR(32) NOT NULL COMMENT '主键',
business_id VARCHAR(64) NOT NULL COMMENT '机构/租户 ID',
policy_type VARCHAR(32) NOT NULL DEFAULT 'INTERSECT_ALLOWLIST' COMMENT '策略类型',
allow_zone_ids TEXT NULL COMMENT 'JSON 数组,zoneId 列表',
building_id VARCHAR(64) NULL COMMENT '预留:楼栋维度;租户默认填 NULL',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1 启用 0 停用',
policy_version BIGINT NOT NULL DEFAULT 1 COMMENT '配置版本号',
remark VARCHAR(256) NULL,
created_by VARCHAR(64) NULL,
created_at BIGINT NULL,
updated_by VARCHAR(64) NULL,
updated_at BIGINT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_biz_building (business_id, building_id),
KEY idx_business_enabled (business_id, enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户访客默认楼层策略(与组织 floorList 求交)';
```
脚本落地路径:**`docs/sql/tenant_visitor_floor_policy.sql`**(本仓库已提供);亦可复制到各环境 Flyway/Liquibase 目录(以现网规范为准)。
**实现分支**`feature/tenant-visitor-floor-policy-db`(电梯 `PersonRuleServiceImpl.addVisitor` + `TenantVisitorFloorPolicyDao`)。
### 4.3 查询语义(应用层)
单条加载(租户级、启用、默认楼栋):
```text
SELECT ... FROM tenant_visitor_floor_policy
WHERE business_id = ?
AND enabled = 1
AND policy_type = 'INTERSECT_ALLOWLIST'
AND (building_id IS NULL OR building_id = '')
ORDER BY updated_at DESC, policy_version DESC
LIMIT 1;
```
若无行 → 不应用策略。
`allow_zone_ids` 为 NULL、空串、或 JSON 解析为空数组 → **本设计建议**:视为**未配置有效允许列表**,行为与「无策略」一致(避免误配导致全员无法开通);**可选变体**(需产品签字):解析为空则判为配置错误并拒绝 UC-01 —— 实施前二选一写死。
### 4.4 `allow_zone_ids` JSON 约定
- 格式:`["zoneId1","zoneId2"]`UTF-8,无 BOM。
- 元素与组织 `floorList`、空间服务 `zoneId` **同一套 ID**
- 实施前在**空间服务**或台账中核对「28 楼」等业务语言与 `zoneId` 映射(产品方案 §4.6)。
### 4.5 配置示例(实施 SQL
```sql
-- 示例:某租户仅允许访客到达 zone A、B(需替换为真实 business_id / zoneId
INSERT INTO tenant_visitor_floor_policy
(id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, remark, created_at, updated_at)
VALUES
(REPLACE(UUID(),'-',''), 'REPLACE_WITH_BUSINESS_ID', 'INTERSECT_ALLOWLIST',
'["REPLACE_ZONE_A","REPLACE_ZONE_B"]', NULL, 1, 1, '实施录入:接待层', UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
```
停用某租户策略:`UPDATE ... SET enabled = 0, policy_version = policy_version + 1, updated_at = ...`
---
## 5. 应用层设计(电梯服务)
### 5.1 改造锚点
| 组件 | 路径 | 说明 |
|------|------|------|
| HTTP | `AcsPersonController#addVisitor` | 入参不变 |
| 服务 | `PersonRuleServiceImpl#addVisitor` | 在 **仅当** `floorIds` 为空分支内,于 `personService.detail` 取回 `floorList` 之后,插入策略加载与求交 |
| 数据访问 | 新建 `TenantVisitorFloorPolicyDao` + MyBatis `Mapper` | 仅 `select`;本阶段无写接口 |
### 5.2 控制流(伪代码)
```text
函数 addVisitor(param, context):
callerProvidedFloors = (param.floorIds 非空)
若 NOT callerProvidedFloors:
调用组织 detail(personId, businessId)
若 detail 失败: 返回 detail 的 code/message
hostFloors = PersonResult.floorList;若 null 则 []
若 hostFloors 为空:
返回业务错误「被访人无派梯楼层」
policy = DAO.selectEnabledTenantDefault(businessId) // building_id IS NULL
若 policy 存在且 allow_zone_ids 解析为非空列表 allow:
effective = [ z for z in hostFloors if z in allowSet ] // 保持 hostFloors 顺序
若 effective 为空:
返回业务错误「租户访客楼层与被访人授权无交集」
param.floorIds = effective
否则:
param.floorIds = hostFloors
// callerProvidedFloors: 不读策略表,param.floorIds 保持调用方原值
若 param.floorIds 为空:
返回业务错误「无可用派梯楼层」
后续沿用现有: zoneService.page、image_rule_ref、batchBind ...
```
### 5.3 与 UC 对照矩阵
| 场景 | `floorIds` 请求 | 表中是否有启用策略 | 期望生效列表 |
|------|-----------------|---------------------|--------------|
| UC-01 现网 | 空 | 否 | `floorList`(与现网一致) |
| UC-01 + 策略 | 空 | 是且 allow 非空 | `allow ∩ floorList` |
| UC-01 + 策略无交集 | 空 | 是且 allow 非空 | **失败**,明确错误码 |
| UC-01 被访人无层 | 空 | 任意 | **失败**(无 floorList |
| UC-02 | 非空 | 任意 | **以请求为准**(本阶段不读表) |
### 5.4 错误码与文案(建议)
| 错误码 | 场景 | 用户/集成方可见文案(中文示例) |
|--------|------|----------------------------------|
| 沿用组织失败码 | `detail` 失败 | 透传组织返回 |
| **新增** `76260531`(示例) | `floorList` 为空或求交后仍无可用楼层(可归并细分) | 无法为访客开通派梯:被访人无授权楼层或无可生效楼层 |
| **新增** `76260532`(示例) | 租户允许列表与被访人 `floorList` 无交集 | 无法为访客开通派梯:租户访客楼层策略与被访人授权楼层不一致,请联系管理员 |
> 注:具体码段需与现网 `762605xx` 资源文件及网关错误码规范对齐;实施时在 `messages` 或统一错误表中登记。
### 5.5 异常与日志
- `addVisitor` 外层 `catch (Exception)` 若吞掉 `ServiceException`,会导致业务错误码丢失;**须**对 `ServiceException` 单独 `rethrow` 或改为**提前返回** `CloudwalkResult.fail`,避免一律映射为 `76260530`
- 日志:在求交前后打 **INFO**`businessId``personId``visitorId`、策略 `id``policy_version`、**脱敏后的** `effective` 楼层数量或 zoneId 列表 —— 按合规要求决定是否打全量 ID)。
### 5.6 事务与性能
- 策略查询为只读,可与现有 `addVisitor` 事务边界一致;**无**跨服务写。
- QPS 不高场景可不加缓存;若加缓存:key=`businessId`+`building_id`,失效条件为策略 `UPDATE`(或 TTL + 版本号)。本阶段可省略。
---
## 6. 测试设计
### 6.1 单元测试
| 用例 ID | 输入 | 期望 |
|---------|------|------|
| T-U01 | 无策略,`floorList`=[a,b],不传 `floorIds` | 生效 [a,b] |
| T-U02 | 策略 allow=[b,c]`floorList`=[a,b] | 生效 [b] |
| T-U03 | 策略 allow=[x]`floorList`=[a,b] | 失败,码 76260532 类 |
| T-U04 | `floorList`=[] | 失败 |
| T-U05 | 调用方传 `floorIds`=[x] | 不读表,生效 [x] |
| T-U06 | `enabled=0` | 等同无策略 |
| T-U07 | `allow_zone_ids` 空或解析为空 | 等同无策略(若采用 §4.3 建议) |
### 6.2 集成 / 回归
- 未插表租户:全量回归 **UC-01 / UC-02**
- 插表租户:组织造数 `floorList` 与策略 allow 多种组合,验证 **AC-1AC-3**(产品方案 §3.5)。
---
## 7. 发布与运维
1. **发布顺序**:先 **DDL 上线**(表空不影响行为)→ 再发 **应用包**(读表逻辑)。
2. **配置变更**:仅 `INSERT/UPDATE`;重大变更递增 `policy_version` 并记录 `remark`
3. **回滚**:应用回滚后行为恢复现网;表数据可保留。
4. **监控**:对新增错误码计数;对「求交为空」单独告警便于实施核对 zoneId。
---
## 8. 风险、依赖与后续阶段
| 项 | 说明 |
|----|------|
| **登记页与开通不一致** | 第三方若仍只展示 `floorList` 全集,而电梯已求交,易产生客诉。缓解:**阶段 2** 提供管理 API + 预览接口,或 BFF 读**同一策略源**(若表在电梯库,可通过只读副本或同步配置解决跨库)。 |
| **直连电梯绕过 BFF** | 本阶段在电梯侧兜底,**直连仍受策略约束**(产品方案 §6.2 中「B+C」思路的部分收益)。 |
| **多楼栋** | 预留 `building_id`;待产品定义「按楼栋策略」后再扩展查询与 UI。 |
| **阶段 2(产品已规划)** | 物业管理端页面:策略维护、空间树多选、`policy_version` 审计、可选 **preview-floors** 与登记快照对齐(产品方案 §2.5、§4.4)。 |
---
## 9. 文档维护
| 项目 | 内容 |
|------|------|
| 路径 | `docs/business/租户访客默认楼层-数据库配置阶段技术设计.md` |
| **变更记录** | [租户访客默认楼层-数据库阶段变更记录](租户访客默认楼层-数据库阶段变更记录.md)(发版/评审用) |
| 修订触发 | `add/visitor` 契约变更;策略表字段变更;决定 UC-02 是否参与求交 |
| 关联 PR | 实现类、`Mapper.xml`、DDL 脚本与错误码资源文件 |
---
*本文档为数据库阶段详细设计;实施以现网分支、代码评审与安全评审结论为准。*