Files
starRiverProperty/docs/superpowers/specs/2026-05-01-org-policy-verify-design.md
T
反编译工作区 8b15445328 feat: add service config templates and extraction script
Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
2026-05-01 19:38:01 +08:00

357 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 头) |
**请求体:**
```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": "<visitorId>" }` |
**响应体:**
```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": <now_ms>,
"endVisitorTime": <now_ms + 86400000>
}
# passRule/image(回读)
POST /elevator/passRule/image
{
"personId": "<visitorId>"
}
```
---
## 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_1403T3 后恢复 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。*