# 广发基金 28F 策略 — 参照 40F/6F 硬编码模式重实现可行性评估 **日期**: 2026-05-09 **状态**: 评估完成 — **不可行 (推荐保留表驱动方案)** --- ## 1. 两种模式的本质差异 ### 1.1 40F/6F 硬编码模式 ``` ┌─ @Value("${xhwId}") 注入配置 ├─ @Value("${xhwDefaultFloorId}") 注入配置 ├─ @Value("${xhwSixFloorId}") 注入配置 │ └─ ImgPersonServiceImpl#listByPage (仅此一处) └─ if (orgIds.contains(xhwId)) └─ defaultChooseFloor = xhwDefaultFloorId, zoneName = "40F" else └─ defaultChooseFloor = xhwSixFloorId, zoneName = "6F" ``` **核心特征**: 只影响 `listByPage` 访客列表展示层, **不涉及 `detail()` / `addVisitor()`**。 ### 1.2 当前广发基金表驱动模式 ``` ┌─ tenant_visitor_floor_policy 表 数据源 ├─ TenantVisitorFloorPolicyMapper DAO ├─ TenantVisitorFloorPolicyService 策略引擎 │ ├─ ImgPersonServiceImpl#listByPage P1 分支 (列表展示) └─ ImgPersonServiceImpl#detail() 核心路径 (UC-01 派梯) └─ replacementZoneIdsIfPolicyActive → floorList 替代 └─ PersonRuleServiceImpl#addVisitor() → 写入 image_rule_ref ``` **核心特征**: 同时影响 `listByPage` (列表) 和 `detail()` (派梯)。 ### 1.3 关键差异对比 | 维度 | 40F/6F 硬编码 | 广发表驱动 | 差异 | |------|-------------|-----------|------| | 数据存储 | `application.properties` | `tenant_visitor_floor_policy` 表 | 文件 vs DB | | 变更方式 | 改配置文件 + 重启 | UPDATE SQL (立即生效) | 重启 vs 实时 | | 生效范围 | `listByPage` 仅列表 | `listByPage` + `detail()` | 单路径 vs 双路径 | | 多 zone 支持 | 否 (单 zone) | 是 (JSON 数组) | 1 vs N | | zone 名称 | 硬编码字符串 `"40F"` | 动态解析 (zone 表) | 硬编码 vs 动态 | | 组织匹配 | contains(xhwId) | 按 org_id 精确匹配 | 包含 vs 精确 | | 多组织策略 | 无概念 | 按 orgIds 顺序依次命中 | N/A | | 启用/禁用 | 无开关 | `enabled` 字段 | 需删配置 vs 一行 SQL | | 策略版本 | 无 | `policy_version` 自增 | 无追踪 vs 有追踪 | | addVisitor 影响 | ❌ 无 (不走 detail) | ✅ 核心路径 | **致命差异** | --- ## 2. 致命问题: `detail()` 路径缺失 ### 2.1 `addVisitor()` 的楼层来源 ```java // PersonRuleServiceImpl#addVisitor() 第 187-194 行 boolean callerProvidedFloors = !CollectionUtils.isEmpty(param.getFloorIds()); if (callerProvidedFloors) { effective = param.getFloorIds(); // UC-02 } else { effective = personResult.getFloorList(); // UC-01 ← 来自 detail() } ``` `detail()` 返回的 `floorList` 是 UC-01 派梯的**唯一数据源**。 ### 2.2 `detail()` 的当前楼层组装流程 ```java // ImgPersonServiceImpl#detail() 第 630-651 行 // Step A: listByImageId 获取被访人全部楼层 CloudwalkResult> images = elevatorFeignClient.listByImageId(...); // Step B: 遍历组装 floorList (原始全量) for (AcsPassRuleImageResultDto dto : acsPassRuleImageResultDtoList) { floorList.add(dto.getZoneId()); } // Step C: ★ 策略替代 — 这是 40F/6F 模式完全没有的 Optional> replacementFloors = tenantVisitorFloorPolicyService.replacementZoneIdsIfPolicyActive(organizationIds); if (replacementFloors.isPresent()) { floorList = new ArrayList<>(replacementFloors.get()); // ← 替代 } // Step D: 写入 PersonResult result.setFloorList(floorList); ``` ### 2.3 如果搬到 40F/6F 模式会发生什么 广发基金访客 `addVisitor` 的 UC-01 路径: ``` // 改造后 detail() 不再查策略表 detail() → listByImageId() → floorList = [28F, 29F, ..., 38F] 全部楼层 → 无策略替代 → floorList = 11 个 zone (被访人的全部楼层) → addVisitor UC-01: effective = 11 个 zone → 写入 11 行 image_rule_ref → ❌ 访客获得了不应该有的 29-38F 权限 ``` **结论**: 40F/6F 模式的核心缺陷是**不接入 `detail()` 路径**。广发基金策略如果只套用 `listByPage` 硬编码, 将导致 `addVisitor` 派梯**完全绕过策略限制**, 访客获得被访人的全部楼层权限而非仅限 20 层。 --- ## 3. 如果强行修补 (在 detail() 中也加硬编码) ### 3.1 代码膨胀预估 ```java // 需要在 ImgPersonServiceImpl 中新增: @Value("${gfOrgId}") private String gfOrgId; @Value("${gfDefaultFloorList}") private String gfDefaultFloorList; // 逗号分隔 20 个 zoneId // detail() 中新增 (第 646 行附近): if (result.getOrganizationIds().contains(this.gfOrgId)) { floorList = Arrays.asList(gfDefaultFloorList.split(",")); zoneNames = buildCommaSeparatedFloorNames(businessId, floorList); } // listByPage 中新增 (第 354 行附近): if (imgStorePersonResult.getOrganizationIds().contains(this.gfOrgId)) { List floorIds = Arrays.asList(gfDefaultFloorList.split(",")); buildFloorInfoListFromOrderedZoneIds(businessId, floorIds); imgStorePersonResult.setDefaultChooseFloor(floorIds.get(0)); } ``` ### 3.2 问题清单 | # | 问题 | 严重度 | |---|------|--------| | 1 | 每新增一个租户需要加 2 个 `@Value` + 2 处 if/else | 🔴 | | 2 | `application.properties` 中 `gfDefaultFloorList` 需要维护 20 个 zone_id (长字符串) | 🔴 | | 3 | 20 个 zone_id 在配置文件中不可读,容易出错 | 🟡 | | 4 | 修改策略需改配置文件 + 重启服务 | 🟡 | | 5 | 无法动态启用/禁用 (需注释掉 @Value 或删配置) | 🟡 | | 6 | 无策略版本追踪 | 🟢 | | 7 | 与现有 `tenant_visitor_floor_policy` 表形成**双重逻辑源** | 🔴 | | 8 | `detail()` 中硬编码逻辑与表驱动逻辑并存,维护混乱 | 🔴 | ### 3.3 架构退化示意 ``` 改造前 (表驱动, 单一逻辑源): detail() ─→ TenantVisitorFloorPolicyService ─→ DB listByPage ─→ TenantVisitorFloorPolicyService ─→ DB 改造后 (混合模式, 双逻辑源): detail() ─┬→ TenantVisitorFloorPolicyService ─→ DB (其他租户) └→ if (contains(gfOrgId)) { split(config) } (广发特例) listByPage ─┬→ TenantVisitorFloorPolicyService ─→ DB (其他租户) ├→ if (contains(xhwId)) { "40F" } (物业) ├→ else { "6F" } (非物业) └→ if (contains(gfOrgId)) { split(config) } (广发特例 ← 新增) ``` **每增加一个租户策略, 代码分支呈线性增长**, 最终成为无法维护的 if/else 链。 --- ## 4. 对比结论 | 评估维度 | 40F/6F 模式 | 表驱动模式 | 胜出 | |----------|-----------|-----------|------| | 代码简洁性 | 简单 (3 个 @Value + 1 个 if/else) | 中等 (5 个类) | 40F | | 扩展性 (新增租户) | 差 (每次加代码) | 优 (INSERT 一行) | 表驱动 | | 多 zone 支持 | 无 (单 zone) | 优 (JSON 数组) | 表驱动 | | 变更生效 | 需重启 | 实时 | 表驱动 | | 启用/禁用 | 改配置重启 | UPDATE SQL | 表驱动 | | 版本追踪 | 无 | policy_version | 表驱动 | | **addVisitor 覆盖** | **❌ 不覆盖** | **✅ 覆盖** | **表驱动** | | 双重逻辑源风险 | 无 (仅 listByPage) | 无 (单一策略引擎) | 平 | | 安全审计 | 无 | DB 行可审计 | 表驱动 | ### 4.1 40F/6F 模式为什么可以存在 40F/6F 逻辑是**星河湾物业管理的历史遗留**, 且: 1. 其职责仅限于 `listByPage` 访客列表的**展示层默认值** 2. 不影响 `detail()` / `addVisitor()` 的派梯权限 3. 是"全物业" vs "非物业"的二分类, 不需要细粒度组织匹配 4. 只有 2 个 zone, 不需要多 zone 支持 因此 40F/6F 的正确理解是: **这不是一个"楼层策略", 而是列表展示的 fallback 默认值**。 ### 4.2 广发基金不能套用的根本原因 广发基金 20 层是**真正的访问控制策略** — 它必须限制 `addVisitor` 写入的 `image_rule_ref` 行。40F/6F 模式完全不接入这条路径, 强行接入会导致代码膨胀和双重逻辑源。 --- ## 5. 推荐方案 ### 方案 A: 保持表驱动不改 (★★★★★ 强烈推荐) ``` 当前架构已经正确: tenant_visitor_floor_policy 表 ← 数据 TenantVisitorFloorPolicyService ← 引擎 ImgPersonServiceImpl#detail() ← 替代 floorList ImgPersonServiceImpl#listByPage ← P1 展示 PersonRuleServiceImpl#addVisitor ← UC-01 透传 ``` **唯一需要的变更**: UPDATE `allow_zone_ids` 从 1 个 zone 到 20 个 zone。 ### 方案 B: 重构 40F/6F 到表驱动 (★★★ 锦上添花, 非必须) 将 40F/6F 的硬编码逻辑也迁移到 `tenant_visitor_floor_policy` 表: ```sql INSERT INTO tenant_visitor_floor_policy (...) VALUES ( 'pm_40f_hardcoded_migration', '21474e012cd14e26bc62771873b22562', -- xhwId '2524639890ba4f2cba9ba1a4eeaa4015', 'INTERSECT_ALLOWLIST', '["605560547135455232"]', -- 40F NULL, 1, 1, '物业公司:列表默认展示 40F (迁移自硬编码)' ); ``` 然后在 `listByPage` 中删除 40F/6F 硬编码分支, 统一走策略引擎。此项非当前必须, 可作为技术债务清理。 --- ## 6. 总结 | 问题 | 答案 | |------|------| | 能否参照 40F/6F 模式重实现广发 28F? | **技术上可以, 产出上不可行** | | 根本原因 | 40F/6F 模式不接入 `detail()`, 导致 `addVisitor` 绕过策略 | | 如果强行修补 | 双逻辑源、代码膨胀、维护灾难 | | 正确做法 | 保持表驱动, 扩展 `allow_zone_ids` 即可 (零代码变更) | | 额外收益 | 后续可将 40F/6F 也迁移到表驱动 (统一架构) |