Files
starRiverProperty/docs/superpowers/walkthroughs/2026-05-09-guangfa-hardcoded-feasibility.md
T
hpd840321 42c4a9fd6b docs: mark elevator-side tenant policy SQL as deprecated, add guangfa visitor floor design
- Deprecate elevator-side tenant_visitor_floor_policy SQL files
  (V2 queries only component-organization library)
- Add guangfa 28F visitor floor design spec (table-driven approach A)
- Add complete database ER diagram (14 DBs, 537 tables)
- Add implementation plan for guangfa visitor floor policy
- Code walkthrough docs for visitor floor policy analysis
2026-05-09 23:56:12 +08:00

9.5 KiB

广发基金 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() 的楼层来源

// 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() 的当前楼层组装流程

// ImgPersonServiceImpl#detail() 第 630-651 行
// Step A: listByImageId 获取被访人全部楼层
CloudwalkResult<List<AcsPassRuleImageResultDto>> images = elevatorFeignClient.listByImageId(...);

// Step B: 遍历组装 floorList (原始全量)
for (AcsPassRuleImageResultDto dto : acsPassRuleImageResultDtoList) {
    floorList.add(dto.getZoneId());
}

// Step C: ★ 策略替代 — 这是 40F/6F 模式完全没有的
Optional<List<String>> 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 代码膨胀预估

// 需要在 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<String> floorIds = Arrays.asList(gfDefaultFloorList.split(","));
    buildFloorInfoListFromOrderedZoneIds(businessId, floorIds);
    imgStorePersonResult.setDefaultChooseFloor(floorIds.get(0));
}

3.2 问题清单

# 问题 严重度
1 每新增一个租户需要加 2 个 @Value + 2 处 if/else 🔴
2 application.propertiesgfDefaultFloorList 需要维护 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 表:

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 也迁移到表驱动 (统一架构)