mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
8b15445328
Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
357 lines
13 KiB
Markdown
357 lines
13 KiB
Markdown
# 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_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。*
|