- backend/: 13 Maven modules (cw-elevator-application, cloudwalk-cloud, intelligent-cwoscomponent, ninca-crk, etc.) - frontend/: 4 Vue projects (elevator-front, cwos-portal, alarm-front, front_acs) + decompiled + scripts - scripts/: build, test-env, tools (Docker Compose, service templates, API parity) - docs/: AGENTS.md, superpowers specs, architecture docs - .gitignore: standard Java/Maven exclusions Moved from legacy maven-*/ root layout to backend/ organized structure.
13 KiB
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": "<visitorId>" }
→ 返回该访客可访问的 zoneId 列表
→ 与期望楼层比对
2.2 接口调用细节
POST /elevator/person/add/visitor
| 项目 | 值 |
|---|---|
| Method | POST |
| Path | /elevator/person/add/visitor |
| Content-Type | application/json |
| 鉴权模式 | noauth-probe(仅 businessid 头) |
请求体:
{
"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 毫秒,访客有效期结束 |
响应体(成功):
{ "success": true, "code": "0", "data": true }
响应体(策略拒绝):
{ "success": false, "code": "76260533", "message": "策略配置了被访人无权访问的楼层,请联系管理员" }
POST /elevator/passRule/image(回读)
| 项目 | 值 |
|---|---|
| Method | POST |
| Path | /elevator/passRule/image |
| Body | { "personId": "<visitorId>" } |
响应体:
{
"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)
headers = {
"Content-Type": "application/json",
"businessid": "2524639890ba4f2cba9ba1a4eeaa4015"
}
# add/visitor
POST /elevator/person/add/visitor
{
"personId": "1060601019894960128",
"visitorId": "<轮换访客ID>",
"floorIds": [],
"begVisitorTime": <now_ms>,
"endVisitorTime": <now_ms + 86400000>
}
# passRule/image(回读)
POST /elevator/passRule/image
{
"personId": "<visitorId>"
}
6. 数据准备与清理
6.1 准备 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
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 执行顺序
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。