# org_id 策略修复 — 无鉴权验证方案设计 **文档性质**:验证方案设计说明 **设计日期**:2026-05-01 **状态**:待评审 **关联 Spec**:`docs/superpowers/specs/2026-05-01-org-id-policy-fix-design.md` **被测应用**:`cw-elevator-application`(V2, port 18081) --- ## 1. 目标 通过无鉴权 HTTP 调用 `POST /elevator/person/add/visitor` 和 `POST /elevator/passRule/image`,验证 org_id 策略修复的 7 个核心场景,确保业务逻辑满足需求。 **不验证**:鉴权正确性、Feign 调用链、图库绑定、Consul 服务发现。 --- ## 2. 完整业务流程与接口调用链 ### 2.1 业务全景 ``` Step 0: 访客邀约(上游,非电梯侧) ┌─────────────────────────────────────────────────┐ │ 第三方业务系统(BFF / intelligent / 登记页) │ │ ├── 创建访客人员档案(cw_is_person + 访客标签) │ │ └── 确定被访人(host personId) │ └─────────────────────────────────────────────────┘ → 访客已存在于组织库中,personId 可用 → 本方案使用预置测试访客(919900... 号段),跳过此步 Step 1: 设置访客可访问楼层(电梯侧,核心验证目标) POST /elevator/person/add/visitor ┌──────────────────────────────────────────────────────┐ │ ① Feign → /component/person/detail │ │ 取被访人 floorList + organizationIds │ │ │ │ ② 查 tenant_visitor_floor_policy │ │ WHERE org_id = ? (从 organizationIds 取) │ │ │ │ ③ 二选一: │ │ 有策略 → effectiveFloors = allow(直接替换) │ │ 无策略 → effectiveFloors = floorList │ │ allow 含无效值 → fail 76260533 │ │ │ │ ④ Feign → /sysetting/zone/page │ │ 取楼栋 + 图库 imageStoreId │ │ │ │ ⑤ 写 image_rule_ref(访客 personId ← visitorId) │ │ │ │ ⑥ Feign → /component/imagestore/person/batchBind │ │ 访客绑定到楼栋图库 │ └──────────────────────────────────────────────────────┘ Step 2: 回读验证(确认楼层写入正确) POST /elevator/passRule/image Body: { "personId": "" } → 返回该访客可访问的 zoneId 列表 → 与期望楼层比对 ``` ### 2.2 接口调用细节 #### POST /elevator/person/add/visitor | 项目 | 值 | |------|-----| | Method | POST | | Path | `/elevator/person/add/visitor` | | Content-Type | `application/json` | | 鉴权模式 | noauth-probe(仅 `businessid` 头) | **请求体:** ```json { "personId": "1060601019894960128", "visitorId": "9199000100000000001", "floorIds": [], "begVisitorTime": 1751319780000, "endVisitorTime": 1751406180000 } ``` | 字段 | 必填 | 说明 | |------|------|------| | `personId` | 是 | 被访人 ID(决定 floorList 和 org_id 来源) | | `visitorId` | 是 | 访客 ID(落库 image_rule_ref 的 person_id) | | `floorIds` | 否 | **空数组**=UC-01 由电梯补全;**非空**=UC-02 调用方指定 | | `begVisitorTime` | 是 | Unix 毫秒,访客有效期开始 | | `endVisitorTime` | 是 | Unix 毫秒,访客有效期结束 | **响应体(成功):** ```json { "success": true, "code": "0", "data": true } ``` **响应体(策略拒绝):** ```json { "success": false, "code": "76260533", "message": "策略配置了被访人无权访问的楼层,请联系管理员" } ``` #### POST /elevator/passRule/image(回读) | 项目 | 值 | |------|-----| | Method | POST | | Path | `/elevator/passRule/image` | | Body | `{ "personId": "" }` | **响应体:** ```json { "success": true, "data": { "datas": [ { "zoneId": "605560545117995008", "zoneName": "28F" } ] } } ``` ### 2.3 策略在流程中的位置 ``` add/visitor 请求进入 │ ├─ personId → Feign detail() → { floorList, organizationIds } │ ├─ organizationIds[0] → SELECT ... FROM tenant_visitor_floor_policy │ WHERE org_id = ? ← 新:org_id 粒度 │ AND enabled = 1 │ ├─ 有策略行 → effectiveFloors = allow ← 二选一,不求交 │ allow ⊆ floorList 校验 │ └─ 不通过 → 76260533 │ └─ 无策略行 → effectiveFloors = floorList ← 默认行为 ``` --- ## 3. 测试环境 | 项目 | 值 | |------|-----| | 电梯服务 | `http://127.0.0.1:18081` | | MySQL 组织库 | `192.168.3.12:3307 / component-organization` | | MySQL 电梯库 | `192.168.3.12:3307 / cw-elevator-application` | | 租户 | `businessId = 2524639890ba4f2cba9ba1a4eeaa4015` | | 调用方式 | noauth-probe(仅 businessid 头,无鉴权 token) | --- ## 4. 测试矩阵(7 个用例) ### 4.1 测试数据 | 实体 | ID | |------|-----| | 1403艾斯 org | `72fb65ec5de94201b909a98b8bae1892` | | 1405一博环保 org | `2095de3d541f44eba686c78fda68336f` | | 星中心物业 org | `f216235e54ca42bfa0379e69b3754aff` | | 广发基金 org | `488b8ad049bb43408a6fbcc50bcb89ac` | | 陈国辉(1403+星中心) | `1060601019894960128` | | 王姣(1405) | `1090779433129840640` | | 秦夏(广发基金) | `1072908835884208128` | | 28F zone | `605560545117995008` | | 6F zone | `605560541473144832` | ### 4.1.1 访客数据 测试使用开发库中已预置的**专用测试访客**(号段 `9199000100000000001`~`9199000100000000020`,均已标注"访客"标签 `LABEL_ID = ed2dbab6d6234a7287106b80857c819e`)。这些是测试专用人员,非真实业务访客。 `add/visitor` 写入 `image_rule_ref` 时以 `visitorId` 为键,为避免同一访客的多次写入相互干扰,每个用例轮换使用不同测试访客。 | 用例 | visitorId | 访客标识 | |------|-----------|----------| | T1 | `9199000100000000001` | 测试访客AUTO-01 | | T2 | `9199000100000000002` | 测试访客AUTO-02 | | T3 | `9199000100000000003` | 测试访客AUTO-03 | | T4 | `9199000100000000004` | 测试访客AUTO-04 | | T5 | `9199000100000000005` | 测试访客AUTO-05 | | T6 | `9199000100000000006` | 测试访客AUTO-06 | | T7 | `9199000100000000007` | 测试访客AUTO-07 | ### 4.2 用例 | ID | 场景 | 被访人 | 策略 org | allow | enabled | 期望 | |----|------|--------|----------|-------|---------|------| | T1 | 有策略→allow 替换 floorList | 陈国辉 | 1403 | [28F] | 1 | passRule 返回仅 [28F] | | T2 | 无策略→floorList | 王姣 | 1405 | 无 | — | passRule 返回 floorList 全集 | | T3 | allow 含无效zone→拒绝 | 陈国辉 | 1403 | [28F, 99F] | 1 | code=76260533 | | T4 | 多组织命中第一个策略 | 陈国辉 | 1403+星中心 | 1403有,星中心无 | 1 | 命中1403→[28F] | | T5 | enabled=0 等同无策略 | 陈国辉 | 1403 | [28F] | 0 | passRule 返回 floorList 全集 | | T6 | UC-02 策略优先 | 陈国辉 | 1403 | [28F] | 1 | 传floorIds被忽略→[28F] | | T7 | 广发基金迁移验证 | 秦夏 | 广发基金 | [28F] | 1 | 迁移后→仅 [28F] | --- ## 5. 脚本结构 **文件**:`maven-cw-elevator-application/tools/visitor_floor_verification/scripts/verify_org_policy_fix.py` ``` verify_org_policy_fix.py │ ├── Phase 0: health_check() │ GET /actuator/health │ ├── Phase 1: prepare_test_data() │ ① INSERT policy_t1_1403 (org=1403, allow=[28F], enabled=1) │ ② INSERT policy_t3_invalid (org=1403, allow=[28F,99F], enabled=1) │ ③ INSERT policy_t5_disabled (org=1403, allow=[28F], enabled=0) │ ④ UPDATE 广发基金 SET org_id='488b8adb...' WHERE id='gf_vstr_...' │ ├── Phase 2: run_all_cases() │ 每个用例: │ POST add/visitor → 检查 HTTP 200 + code + success │ 成功 → POST passRule/image → 比对 zoneIds │ 失败 → 检查 code == 期望错误码 │ ├── Phase 3: cleanup_test_data() │ ① DELETE 测试策略行 │ ② UPDATE 广发基金 SET org_id=NULL │ └── Phase 4: print_report() 输出 JSON 报告到 report/org-policy-fix-verify-{timestamp}.json ``` ### 5.1 HTTP 请求格式(noauth-probe) ```python headers = { "Content-Type": "application/json", "businessid": "2524639890ba4f2cba9ba1a4eeaa4015" } # add/visitor POST /elevator/person/add/visitor { "personId": "1060601019894960128", "visitorId": "<轮换访客ID>", "floorIds": [], "begVisitorTime": , "endVisitorTime": } # passRule/image(回读) POST /elevator/passRule/image { "personId": "" } ``` --- ## 6. 数据准备与清理 ### 6.1 准备 SQL ```sql -- T1/T4/T6 共用 INSERT INTO tenant_visitor_floor_policy (id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at) VALUES ('policy_t1_1403', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST', '["605560545117995008"]', NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000); -- T3 INSERT INTO tenant_visitor_floor_policy (id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at) VALUES ('policy_t3_invalid', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST', '["605560545117995008","605560540000000000"]', NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000); -- T5 INSERT INTO tenant_visitor_floor_policy (id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at) VALUES ('policy_t5_disabled', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST', '["605560545117995008"]', NULL, 0, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000); -- T7: 广发基金迁移 UPDATE tenant_visitor_floor_policy SET org_id = '488b8ad049bb43408a6fbcc50bcb89ac' WHERE id = 'gf_vstr_policy_guangfa_fund_001x'; ``` ### 6.2 清理 SQL ```sql DELETE FROM tenant_visitor_floor_policy WHERE id IN ('policy_t1_1403','policy_t3_invalid','policy_t5_disabled'); UPDATE tenant_visitor_floor_policy SET org_id = NULL WHERE id = 'gf_vstr_policy_guangfa_fund_001x'; ``` ### 6.3 执行顺序 ```text prepare: INSERT policy_t1_1403 (T1/T4/T6 使用) INSERT policy_t3_invalid (T3 使用) INSERT policy_t5_disabled (T5 使用) UPDATE 广发基金 SET org_id (T7 使用) execute: T1: 陈国辉 + policy_t1_1403 → allow=[28F] T3: 陈国辉 + policy_t3_invalid → 76260533(需先改1403的org_id或先DELETE T1再切换) T4: 陈国辉(多组织) + policy_t1 → allow=[28F] T5: 陈国辉 + policy_t5_disabled → floorList全集 T2: 王姣(无策略) → floorList全集 T6: 陈国辉(UC-02) + policy_t1 → allow=[28F] T7: 秦夏 + 广发基金策略 → allow=[28F] cleanup: DELETE 测试行 UPDATE 广发基金 SET org_id=NULL ``` > T3 执行前需确保 policy_t1_1403 不生效(相同 org_id 只能有一条启用策略)。方案:T3 前 DELETE policy_t1_1403,T3 后恢复 INSERT。 --- ## 7. 验证判定规则 | 判定项 | 通过条件 | |--------|----------| | HTTP 状态 | `add/visitor` 返回 200 | | 业务成功 | `response.success == true` | | 业务失败 | `response.code == "76260533"` | | 楼层匹配 | `passRule/image` 返回的 zoneId 集合 == 期望集合(顺序无关) | | 脱敏 | 报告中不出现 PII(姓名/手机号) | --- ## 8. 输出 ``` report/org-policy-fix-verify-{timestamp}.json ``` 包含:每个用例的请求/响应摘要、passRule 结果、期望 vs 实际对比、通过/失败汇总。 --- ## 9. 文件清单 | 文件 | 操作 | 说明 | |------|------|------| | `tools/visitor_floor_verification/scripts/verify_org_policy_fix.py` | 新增 | 主验证脚本 | | `tools/visitor_floor_verification/report/` | — | 报告输出目录(已有) | --- *本设计说明待评审,通过后转入 implementation。*