# 租户访客楼层策略 — 功能清单 & 使用手册 > **版本**: 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_policy(enabled=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 Shutdown(30s 排空),在途请求不会中断。