Files
starRiverProperty/docs/visitor-floor-policy-user-guide.md
hpd840321 db2cb1966c docs: add visitor floor policy user guide and implementation log
- 功能清单与使用手册:23项功能清单、策略配置说明、部署运维、22项验收检查表

- 实施操作日志:9阶段操作流水、commit清单、关键文件索引、103服务器全库备份记录

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 09:27:57 +08:00

357 lines
16 KiB
Markdown
Raw Permalink 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.
# 租户访客楼层策略 — 功能清单 & 使用手册
> **版本**: v2.0.17 增量发布
> **分支**: feature/guangfa-28f-hardcoded
> **日期**: 2026-05-10
> **适用对象**: 运维人员、业务管理人员(业主)
---
## 一、功能概述
### 1.1 背景
星中心(星河湾)电梯门禁系统中,不同组织的访客应被限制在特定楼层活动。例如:
- **广发基金**(28-38F)的访客 → 仅可到达 **28F**
- **物业管理公司**的访客 → 可到达 **28F / 6F**
本功能通过**租户访客楼层策略**实现:当人员属于某个组织且有访客身份时,系统自动将其可到达楼层替换为策略指定的楼层集合。
### 1.2 策略语义变更
| 版本 | 语义 | 说明 |
|------|------|------|
| **旧 (V1)** | `INTERSECT_ALLOWLIST` | 策略楼层与人员楼层**求交集** |
| **新 (V2)** | `REPLACE_ALLOWLIST` | 策略楼层**整表替换**人员楼层 |
**变更原因**:原求交语义会导致 40F 人员即使有策略也因求交结果为空而无法到达任意楼层;替换语义直接赋予目标楼层,逻辑清晰且满足业务需求。
### 1.3 架构变化
```
V1(旧):
┌─ elevator-app ─────────────────────┐
│ TenantVisitorFloorPolicyService │ ← 电梯侧自身查策略表
│ (查询同库 tenant_visitor_floor_policy) │
└────────────────────────────────────┘
V2(新):
┌─ elevator-app ──┐ Feign HTTP ┌─ component-organization ────┐
│ detail() │ ─────────────────→ │ TenantVisitorFloorPolicy │
│ listByPage() │ ←───────────────── │ .replacementZoneIds... │
└─────────────────┘ 返回替换楼层 │ (唯一策略维护点) │
└────────────────────────────┘
```
**V2 唯一策略维护点**: 组织服务库 `component-organization`,电梯侧策略表已废弃(保留仅作历史参考)。
---
## 二、功能清单
### 2.1 策略数据管理
| # | 功能项 | 说明 | 验收标准 |
|---|--------|------|---------|
| F1 | 策略表创建 | component-organization 库建表 `tenant_visitor_floor_policy` | 表结构正确,主键/唯一键/索引齐全 |
| F2 | 广发基金 28F 策略 | 广发基金管理有限公司 org 配置:访客默认 28F | 广发员工邀约访客 → 访客楼层为 28F |
| F3 | 物业管理 28F+6F 策略 | 7 个物业相关组织节点配置:访客默认 28F+6F | 物业员工邀约访客 → 访客楼层为 28F+6F |
| F4 | 策略类型归一化 | 所有策略 `policy_type = REPLACE_ALLOWLIST` | 无 INTERSECT_ALLOWLIST 残留 |
| F5 | 幂等初始化 | 种子 SQL 支持 `ON DUPLICATE KEY UPDATE` | 重复执行不报错,数据一致 |
### 2.2 策略调用(服务端)
| # | 功能项 | 说明 | 验收标准 |
|---|--------|------|---------|
| F6 | detail() 策略替换 | 人员详情接口按 org_id 逐级命中策略,替换 floorList | 有策略的组织 → floorList 被替换;无策略 → 保留原始 |
| F7 | listByPage() 策略替换 | 分页查询接口同步执行策略替换 | 分页结果中访客记录楼层被替换 |
| F8 | addVisitor 策略应用 | 添加访客时校验并应用策略楼层 | 访客创建后楼层为策略指定值 |
| F9 | 按 org_id 顺序匹配 | 多 org_id 时按传入顺序遍历,首个命中即返回 | 命中顺序与预期一致 |
### 2.3 日志与可观测性
| # | 功能项 | 说明 | 验收标准 |
|---|--------|------|---------|
| F10 | POLICY-HIT 日志 | 策略命中时记录 orgId、policyId、allowZones | INFO 级别,可追溯 |
| F11 | POLICY-MISS 日志 | 无策略时 DEBUG 记录 | 不产生 WARN/ERROR |
| F12 | POLICY-EMPTY 日志 | 策略 allow_zone_ids 为空时 WARN 记录 | 触发时明确标记 |
| F13 | POLICY-FALLBACK 日志 | 查询异常时 WARN 记录,不影响 detail | 异常不回抛,fallback 到原始 floorList |
| F14 | DETAIL 日志增强 | detail() 入口/出口日志含 personId、businessId | 可追踪每次 detail 调用 |
| F15 | 日志级别优化 | [POLICY] 入口从 INFO→DEBUG,减少冗馀 | 正常运行不产生 POLICY 入口日志 |
### 2.4 发布与部署
| # | 功能项 | 说明 | 验收标准 |
|---|--------|------|---------|
| F16 | component-org 发布脚本 | 自动构建、打包 JAR + DDL + 配置 + start.sh | 产物完整可部署 |
| F17 | 电梯应用发布脚本 | 自动构建 V2 fat JAR + 含策略 DDL 的完整发布包 | 产物完整可部署 |
| F18 | DDL 执行说明 | 发布包内含 DDL README,标明执行顺序和目标库 | 运维按说明执行无歧义 |
| F19 | 版本升级说明书 | 含升级步骤、回滚方案、配置变更清单 | 运维可独立执行升级 |
### 2.5 代码健壮性
| # | 功能项 | 说明 | 验收标准 |
|---|--------|------|---------|
| F20 | Graceful Shutdown | 30s 排空等待,在途请求完成后关闭 | 发布期无请求中断 |
| F21 | Bean 冲突修复 | TypeFilter 排除 GroupPersonSyn* 避免重复 bean | 服务启动正常,无 bean 冲突 |
| F22 | Logback 配置内嵌 | recognition-logbox.xml 打包至 fat JAR classpath | 无需外部 --logging.config 参数 |
| F23 | 消息源修复 | 去掉 messages basename 的 _zh_CN 后缀 | 无 ResourceBundle WARN |
---
## 三、策略配置说明
### 3.1 策略表结构(component-organization 库)
```sql
tenant_visitor_floor_policy (
id VARCHAR(32) PRIMARY KEY, -- 策略ID(业务唯一)
org_id VARCHAR(32), -- 组织节点ID(策略匹配键)
policy_type VARCHAR(32) DEFAULT 'REPLACE_ALLOWLIST', -- 策略类型
allow_zone_ids TEXT, -- JSON 数组,允许的 zoneId 列表
building_id VARCHAR(64) NULL, -- 楼栋(预留)
enabled TINYINT(1) DEFAULT 1, -- 启用标志
policy_version BIGINT DEFAULT 1, -- 版本号
remark VARCHAR(256), -- 备注
...
UNIQUE KEY uk_org_building (org_id, building_id) -- 每组织每楼栋一条策略
)
```
**关键约束**:
- 每组织节点(`org_id`)最多一条启用策略
- 策略匹配按 `org_id` 精确匹配(不向上回溯组织树)
- `allow_zone_ids` 为标准 JSON 字符串数组:`["zoneId1","zoneId2"]`
### 3.2 楼层编码对照
| Zone ID | 楼层 | 说明 |
|---------|------|------|
| `605560540432957440` | 1F | 首层(默认所有人员可达) |
| `605560541473144832` | 6F | 物业管理办公层 |
| `605560545117995008` | 28F | 广发基金办公层 |
### 3.3 策略执行逻辑
```
detail(personId):
1. 通过 personId 查询人员详情(含 floorList、organizationIds
2. 遍历 organizationIds 列表:
a. 按 org_id 查 tenant_visitor_floor_policyenabled=1
b. 找到 → 解析 allow_zone_ids → 替换 floorList → 返回
c. 未找到 → 继续下一个 org_id
3. 全部未命中 → 保留 listByImageId 原始 floorList
```
### 3.4 策略配置方法
**新增策略**:
```sql
INSERT INTO tenant_visitor_floor_policy (
id, org_id, policy_type, allow_zone_ids,
enabled, policy_version, remark, created_at, updated_at
) VALUES (
'custom_policy_001', -- 策略ID(唯一)
'org_id_value', -- 组织节点ID
'REPLACE_ALLOWLIST', -- 策略类型
'["605560545117995008"]', -- 目标楼层列表
1, 1, '备注说明',
UNIX_TIMESTAMP(NOW()) * 1000,
UNIX_TIMESTAMP(NOW()) * 1000
) ON DUPLICATE KEY UPDATE ...;
```
**停用策略**: 设置 `enabled = 0`
**修改策略**: 更新 `allow_zone_ids` + `policy_version = policy_version + 1`
**删除策略**: 物理删除行(不建议,建议停用)
> **注意**: 所有策略变更需在 **component-organization** 库执行,电梯侧策略表已废弃。
---
## 四、部署运维说明
### 4.1 服务架构
```
Nginx :8090
┌────────────┼────────────┐
▼ ▼ ▼
elevator-app cwos-portal frontend SPAs
:18081 :18080
│ │
▼ ▼
component-org CRK 人脸识别
:17016 (GPU 后端)
```
**策略查询路径**: `elevator-app` → Feign HTTP → `component-org` → MySQL `component-organization`
### 4.2 数据库变更
**执行目标库**: `component-organization`
**执行顺序(2 步)**:
1. 建表:`organization_tenant_visitor_floor_policy.sql`
2. 种子数据:`organization_tenant_visitor_floor_policy_init_tenants.sql`(幂等,可重复执行)
**验证**:
```sql
-- 检查策略表已创建
SHOW TABLES LIKE 'tenant_visitor_floor_policy';
-- 检查种子数据已写入
SELECT id, org_id, policy_type, allow_zone_ids, remark
FROM tenant_visitor_floor_policy WHERE enabled=1;
-- 检查索引
SHOW INDEX FROM tenant_visitor_floor_policy;
```
### 4.3 发布步骤
**component-organization 服务**:
```bash
# 1. 构建发布包
./source/scripts/build/release-component-organization.sh 2.9.5
# 2. 产物位置
# source/backend/ninca-common-component-organization/releases/
# ninca-common-component-organization-2.9.5-xinghewan-YYYYMMDD.zip
# 3. 部署 JAR + 配置 + DDL → 目标服务器
# 4. 执行 DDL(见 4.2
# 5. 启动服务
cd /path/to/deploy && bash start.sh
```
**电梯应用**:
```bash
# 1. 构建发布包
./source/scripts/build/release-cw-elevator-application.sh 2.0.x
# 2. 产物位置
# source/backend/cw-elevator-application/releases/
# cw-elevator-application-V2.0.x.YYYYMMDD.zip
# 3. 部署 JAR + 配置 + DDL → 目标服务器
# 4. 执行 DDL(见 4.2
# 5. 启动服务
cd /path/to/deploy && bash start.sh
```
### 4.4 配置变更
**component-organization**`bootstrap.properties`:
```properties
# 无新增配置项,复用现有 datasource 连接 component-organization 库
spring.datasource.url=jdbc:mysql://.../component-organization
```
**电梯应用**`bootstrap.properties`:
```properties
# 无新增配置项,策略查询通过 Feign 调用 component-org 完成
# logging 配置(策略追踪日志已编码在代码中)
logging.level.cn.cloudwalk.service.organization.policy=INFO
```
### 4.5 日志说明
| 日志标识 | 级别 | 含义 |
|----------|------|------|
| `[POLICY-HIT]` | INFO | 策略命中,记录 orgId、policyId、替换楼层列表 |
| `[POLICY-MISS]` | DEBUG | 未找到组织策略(正常运行) |
| `[POLICY-EMPTY]` | WARN | 策略存在但 allow_zone_ids 解析为空 |
| `[POLICY-FALLBACK]` | WARN | 策略查询异常,已回退到原始 floorList |
| `[POLICY-RESULT]` | INFO | batch 接口命中结果 |
| `[POLICY-ERR]` | WARN | allow_zone_ids JSON 解析异常 |
| `[DETAIL]` | INFO | detail() 调用入口/出口日志 |
| `[DETAIL-POLICY]` | DEBUG | detail() 内策略替换前后 floorList 对比 |
| `[LIST-PAGE-POLICY]` | DEBUG | listByPage() 策略命中详情 |
| `[ADDV-DETAIL]` | DEBUG | addVisitor 中 detail floorList 和 orgIds |
### 4.6 回滚方案
如果发布后出现问题:
**方案 A:停用策略(快速回滚,推荐)**
```sql
UPDATE tenant_visitor_floor_policy SET enabled=0;
-- 恢复方式:SET enabled=1
```
**方案 B:回退 JAR 版本**
1. 停止服务
2. 替换为上一版本 JAR
3. 重启服务
4. DDL 无需回滚(建表语句幂等)
---
## 五、验收检查表
### 5.1 功能验收
| # | 验收项 | 验收方法 | 结果 |
|---|--------|---------|------|
| 1 | 广发组织访客 → 楼层被替换为 28F | 创建广发员工访客 → 查访客楼层是否为 `[28F]` | □ 通过 □ 不通过 |
| 2 | 物业组织访客 → 楼层被替换为 28F+6F | 创建物业员工访客 → 查访客楼层是否为 `[28F, 6F]` | □ 通过 □ 不通过 |
| 3 | 无策略组织 → 楼层保持原始值 | 创建无策略组织访客 → 楼层与 listByImageId 一致 | □ 通过 □ 不通过 |
| 4 | 策略停用 (enabled=0) → 不生效 | 停用策略后创建访客 → 楼层不被替换 | □ 通过 □ 不通过 |
| 5 | 多 org_id 按顺序匹配 | 人员归属多个 org,首个有政策的应命中 | □ 通过 □ 不通过 |
| 6 | detail() 接口正常 | 调用 detail() → 返回符合策略的 floorList | □ 通过 □ 不通过 |
| 7 | listByPage() 分页正常 | 翻页查询 → 访客记录楼层正确 | □ 通过 □ 不通过 |
| 8 | 策略查询失败 → 不阻断业务 | 模拟策略表不可用 → detail() 仍正常返回原始数据 | □ 通过 □ 不通过 |
### 5.2 数据验收
| # | 验收项 | 验收方法 | 结果 |
|---|--------|---------|------|
| 9 | 策略表已创建 | `SHOW TABLES LIKE 'tenant_visitor_floor_policy'` | □ 通过 □ 不通过 |
| 10 | 广发基金策略已配置 | `SELECT * FROM tenant_visitor_floor_policy WHERE id LIKE 'gf_%'` | □ 通过 □ 不通过 |
| 11 | 物业 7 条策略已配置 | `SELECT COUNT(*) FROM tenant_visitor_floor_policy WHERE id LIKE 'pm_%'` | □ 通过 □ 不通过 |
| 12 | 所有策略类型为 REPLACE_ALLOWLIST | `SELECT DISTINCT policy_type FROM tenant_visitor_floor_policy` | □ 通过 □ 不通过 |
| 13 | 索引完整 | `SHOW INDEX FROM tenant_visitor_floor_policy` → idx_org_enabled 存在 | □ 通过 □ 不通过 |
### 5.3 日志验收
| # | 验收项 | 验收方法 | 结果 |
|---|--------|---------|------|
| 14 | 策略命中有 INFO 日志 | `grep 'POLICY-HIT' info.log` | □ 通过 □ 不通过 |
| 15 | 策略未命中仅有 DEBUG | `grep 'POLICY-MISS' info.log` → 无输出 | □ 通过 □ 不通过 |
| 16 | 无 ResourceBundle WARN | `grep 'ResourceBundle' info.log` → 无 (messages basename 修复) | □ 通过 □ 不通过 |
| 17 | detail() 可追踪 | `grep 'DETAIL' info.log` → 含 personId 上下文 | □ 通过 □ 不通过 |
### 5.4 部署验收
| # | 验收项 | 验收方法 | 结果 |
|---|--------|---------|------|
| 18 | component-org 启动正常 | `bash start.sh` → 端口监听 17016 | □ 通过 □ 不通过 |
| 19 | 电梯应用启动正常 | `bash run.sh` → 端口监听 18081 | □ 通过 □ 不通过 |
| 20 | 服务间 Feign 调用正常 | curl detail() → 返回数据含策略楼层 | □ 通过 □ 不通过 |
| 21 | Graceful Shutdown 生效 | 停止服务 → 日志显示 drain 30s | □ 通过 □ 不通过 |
| 22 | 无 Bean 冲突 | 启动日志无 `GroupPersonSyn` 相关冲突 | □ 通过 □ 不通过 |
---
## 六、常见问题
**Q: 为什么新建了策略但访客楼层没变?**
A: 检查 `enabled=1`;确认人员所属 `org_id` 与策略 `org_id` 精确匹配;检查策略 `policy_type` 是否为 `REPLACE_ALLOWLIST`
**Q: 电梯侧策略表还起作用吗?**
A: V2 不再查询电梯侧 `tenant_visitor_floor_policy` 表。该表仅保留作历史参考。所有策略需在 `component-organization` 库维护。
**Q: 一个组织可以配置多条策略吗?**
A: 唯一键 `uk_org_building` 限制每组织每楼栋一条策略。如需多策略,需通过不同的 `org_id`(子组织节点)实现。
**Q: 如何确认当前生效的策略?**
A: 查询 `component-organization``tenant_visitor_floor_policy WHERE enabled=1`,并查看日志中的 `[POLICY-HIT]` 记录。
**Q: 发布时需要停服吗?**
A: 组件服务(component-org / elevator-app)需要重启;使用 Graceful Shutdown30s 排空),在途请求不会中断。