Initial commit: reorganized source tree

- 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.
This commit is contained in:
hpd840321
2026-05-09 09:00:12 +08:00
commit 7b2bd307f1
7260 changed files with 612980 additions and 0 deletions
@@ -0,0 +1,19 @@
release-visitor-noauth-verify-v20260430/
├── README.md
├── 操作手册.md
├── 执行流程设计-简化版.md
├── 打包说明.txt
├── MANIFEST.txt
├── python/
│ ├── quick_verify_visitor_floor_policy.py
│ ├── requirements.txt
│ └── requirements-min.txt
├── report/
│ └── .gitkeep
└── sql/
├── 黄平访客ID手工查询.sql
├── 租户访客策略基线核查.sql
├── 批量样本筛选-多公司多部门.sql
├── 人工核查最终楼层结果.sql
├── 标准批量25访客-执行后核验.sql
└── batch_cases.csv
@@ -0,0 +1,105 @@
# 访客无鉴权策略验证发布包(v20260430)
## 1. 发布目标
本目录为“黄平访问蒙海文”无鉴权策略验证的**版本化发布包**,用于生产执行、结果留痕与归档。
当前对应代码发布版本:`cw-elevator-application-2.0.9`
## 2. 包内文件
- `README.md`:发布说明(本文件)
- `操作手册.md`:完整操作步骤与判定规则
- `执行流程设计-简化版.md`:先明确流程再执行的最小闭环方案(推荐先读)
- `sql/黄平访客ID手工查询.sql`:生产手工查询黄平访客 ID 的 SQL
- `sql/批量样本筛选-多公司多部门.sql`:筛选多个公司/多个部门被访人样本 SQL
- `sql/人工核查最终楼层结果.sql`:自定义访客 ID 集合时执行后核验
- `sql/标准批量25访客-执行后核验.sql`:§4.0 单行命令固定 25 名访客号段后的执行后核验
- `python/quick_verify_visitor_floor_policy.py`:执行脚本快照(支持纯 HTTP;可选连组织库/电梯库)
- `python/requirements.txt``requests` + `pymysql`(预置样本拉取、`--verify-local-db`
- `python/requirements-min.txt`:仅 `requests`(不连库时)
- `report/`:脚本输出 JSON 汇总(打包时排除 `*.json` 历史文件)
- `打包说明.txt`:生产机 `pip install`、一键 zip、全量预置命令示例
- `MANIFEST.txt`:交付清单
## 3. 关键基线参数
- 被访人(蒙海文)`personId=964454497399468032`
- 访客(黄平)`personId=1102270499947507712`
- 租户 `businessId=2524639890ba4f2cba9ba1a4eeaa4015`
- 策略语义(**规范**):未传 **`floorIds`** 时,生效楼层为 **`PersonService.detail``floorList`**;租户策略须已在组织 **`detail`** 中以 **`allow_zone_ids` 替代**写入(**非** ∩)。详见 [`visitor-registration-business-flow.md`](../visitor-registration-business-flow.md)、[`租户组织人员访客-数据模型与用例.md`](../../architecture/租户组织人员访客-数据模型与用例.md)、[`2026-05-06` 规格](../../superpowers/specs/2026-05-06-tenant-visitor-policy-organization-implementation.md)。
## 4. 测试执行(一行命令)
以下参数已由交付侧与现场环境对齐:**组织** `http://10.0.22.207:8089`**电梯** `http://10.0.22.207:16112`**25 名测试访客** `personId` 为连续号段 `9199000100000000001``9199000100000000025`(预置脚本内置 25 个被访人样本,本命令**不访问 MySQL**)。
**操作:** 打开解压后的发布包目录(能看到子目录 `python/`),**只粘贴下面整行**,回车执行(自动安装缺失的 `requests` 并跑预置 25 人批量无鉴权探测):
```bash
python3 -m pip install -q -r python/requirements-min.txt && python3 python/quick_verify_visitor_floor_policy.py --mode noauth-probe --org-base-url "http://10.0.22.207:8089" --elevator-base-url "http://10.0.22.207:16112" --use-preset-cases --visitor-person-ids "$(python3 -c 'print(",".join(str(9199000100000000000+i) for i in range(1,26)))')" --probe-with-businessid --batch-output-summary "batch-preset-static-$(date +%Y%m%d-%H%M%S).json"
```
汇总 JSON`report/` 目录下文件名形如 `batch-preset-static-<时间戳>.json`
**执行后须查库分析:** 在目标环境 **`cw-elevator-application`** 库运行 `sql/标准批量25访客-执行后核验.sql`,将每名访客的 **`active_row_count``zone_id`** 与 JSON 中的 `derived.floor_count``derived.floors` 对齐;具体操作与判定要点见 **`操作手册.md` §4.0.1、§4.6**。
---
### 附录 A:单样本(广发基金基线)
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--business-id "2524639890ba4f2cba9ba1a4eeaa4015" \
--meng-person-id "964454497399468032" \
--visitor-person-id "1102270499947507712" \
--probe-with-businessid
```
该访客为存量人员:库表核验易与历史 `image_rule_ref` 混在一起;若以「本轮新增访客 + 证据」为目标,请以 **§4 一行批量号段 `919900…`** 为主。**操作手册 §4.2** 对上述边界有全文说明。
### 附录 B:连 MySQL 拉样本 / 库表比对
需与 HTTP 同属一套库的地址与账号由运维另行提供,不由本 README 承载;禁止使用与目标环境不一致的数据库串联验证。
### 附录 C`sql/batch_cases.csv` 批量
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--batch-input-csv "sql/batch_cases.csv" \
--visitor-person-ids "1102270499947507712,1102270499947507713,1102270499947507714" \
--visitor-names "黄志平A,黄志平B,黄志平C" \
--probe-with-businessid \
--batch-output-summary "batch-from-csv-$(date +%Y%m%d-%H%M%S).json"
```
### 附录 D:人员导出 CSV 自动筛选
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--batch-source-person-csv "../_cw_is_person__202604302030.csv" \
--exclude-business-ids "2524639890ba4f2cba9ba1a4eeaa4015" \
--sample-companies 3 \
--sample-persons-per-company 2 \
--visitor-person-ids "1102270499947507712,1102270499947507713,1102270499947507714" \
--visitor-names "黄志平A,黄志平B,黄志平C" \
--probe-with-businessid \
--batch-output-summary "batch-from-export-$(date +%Y%m%d-%H%M%S).json"
```
说明:导出 CSV 需含列 `BUSINESS_ID`,`ID`,`IS_DEL`,`NAME`(等);无部门列时汇总中 `department` 可能为空。
## 5. 输出位置
脚本输出(均在发布包下 `report/` 目录):
- 单样本:`quick-verify-<timestamp>.json`
- 标准批量:见 §4`batch-preset-static-*.json`
- 其他批量:`--batch-output-summary` 指定名;默认 `batch-summary.json`
@@ -0,0 +1,2 @@
# 仅调用组织/电梯 HTTP,不连 MySQL
requests>=2.28.0,<3
@@ -0,0 +1,5 @@
# 生产验证机:Python 3.8+
# 仅 HTTP 探测:pip install -r requirements-min.txt
requests>=2.28.0,<3
# 可选(--fetch-preset-from-org-db / --verify-local-db
pymysql>=1.0.0,<2
@@ -0,0 +1,4 @@
business_id,meng_person_id,company,department
2524639890ba4f2cba9ba1a4eeaa4015,964454497399468032,广发基金,示例部门A
replace-business-id-2,replace-person-id-2,示例公司2,示例部门B
replace-business-id-3,replace-person-id-3,示例公司3,示例部门C
1 business_id meng_person_id company department
2 2524639890ba4f2cba9ba1a4eeaa4015 964454497399468032 广发基金 示例部门A
3 replace-business-id-2 replace-person-id-2 示例公司2 示例部门B
4 replace-business-id-3 replace-person-id-3 示例公司3 示例部门C
@@ -0,0 +1,49 @@
-- 用途:脚本执行后,人工核查访客最终楼层权限
-- 说明:
-- * 「已授权楼层」= image_rule_ref 中 person_delete = 0 的行(与 passRule/image 可查到的楼层一致)
-- * person_delete = 1 为撤销/作废,不参与当前授权
-- 将 IN (...) 中的访客 person_id 替换为本次执行的访客集合
-- 主查:仅已授权楼层(推荐与接口 / JSON derived.floors 对拍)
SELECT
irr.person_id,
irr.business_id,
irr.zone_id,
irr.zone_name,
irr.last_update_time
FROM `cw-elevator-application`.`image_rule_ref` irr
WHERE irr.person_id IN (
'1102270499947507712'
-- ,'替换为其他访客personId'
)
AND irr.person_delete = 0
ORDER BY irr.person_id, irr.zone_id;
-- 明细(含已撤销历史行,核对 person_delete / 时间线时用)
SELECT
irr.person_id,
irr.business_id,
irr.zone_id,
irr.zone_name,
irr.person_delete,
irr.create_time,
irr.last_update_time
FROM `cw-elevator-application`.`image_rule_ref` irr
WHERE irr.person_id IN (
'1102270499947507712'
-- ,'替换为其他访客personId'
)
ORDER BY irr.person_id, irr.last_update_time DESC, irr.zone_id;
-- 可选:统计每个访客当前「已授权」楼层数量(person_delete = 0
SELECT
irr.person_id,
COUNT(1) AS floor_count
FROM `cw-elevator-application`.`image_rule_ref` irr
WHERE irr.person_id IN (
'1102270499947507712'
-- ,'替换为其他访客personId'
)
AND irr.person_delete = 0
GROUP BY irr.person_id
ORDER BY irr.person_id;
@@ -0,0 +1,35 @@
-- 用途:从组织库筛选“多个公司 + 多个部门”的被访人样本
-- MySQL:连接主机/端口/账号按**当前环境**填写(开发与生产分离;不要用开发库导出的样本去打生产)
--
-- 已与 component-organization 典型结构对齐(2026-04-30 开发库 INFORMATION_SCHEMA 核对):
-- cw_is_person 人员主键列为 ID(无 person_id);删除标记 IS_DEL(无 deleted);
-- 无 labels/business_name/organization_name 列;访客身份通过 cw_is_person_label_ref + cw_is_label 识别。
-- 连接参数可复制 maven-cw-elevator-application/tools/visitor_floor_verification/.env.visitor_verify(勿提交密钥)。
SELECT
p.BUSINESS_ID AS business_id,
p.ID AS meng_person_id,
COALESCE(parent_org.NAME, '') AS company,
COALESCE(o.NAME, '') AS department
FROM `component-organization`.`cw_is_person` p
INNER JOIN `component-organization`.`cw_is_person_organization_ref` r ON r.PERSON_ID = p.ID
LEFT JOIN `component-organization`.`cw_is_organization` o ON o.ID = r.ORG_ID
LEFT JOIN `component-organization`.`cw_is_organization` parent_org ON parent_org.ID = o.PARENT_ID
WHERE (p.IS_DEL = 0 OR p.IS_DEL IS NULL)
AND p.BUSINESS_ID IS NOT NULL
AND p.BUSINESS_ID <> ''
AND NOT EXISTS (
SELECT 1
FROM `component-organization`.`cw_is_person_label_ref` lr
INNER JOIN `component-organization`.`cw_is_label` lb ON lb.ID = lr.LABEL_ID
WHERE lr.PERSON_ID = p.ID
AND lb.BUSINESS_ID = p.BUSINESS_ID
AND lb.NAME = '访客'
)
ORDER BY p.BUSINESS_ID, department, p.LAST_UPDATE_TIME DESC;
-- 建议导出后人工抽样:
-- 1) 至少 3 个 business_id
-- 2) 每个 business_id 至少 2 个不同 department
-- 3) 生成 batch_cases.csv,列头固定为:
-- business_id,meng_person_id,company,department
@@ -0,0 +1,58 @@
-- 用途:`README`/操作手册 §4.0 标准单行批量跑完后,在电梯库核查写库是否与接口侧一致
-- 库:`cw-elevator-application`
-- 表:`image_rule_ref`(访客 person_id 维度楼层规则)
-- 访客清单:与本包约定的 91990001000000000019199000100000000025 完全一致;若现场改用其它 personId,请改下方 expected 子查询或直接用 `sql/人工核查最终楼层结果.sql`。
-- ------------------------------------------------------------------
-- A) 每名访客当前「有效」规则行数 + 最近一次更新时间(核对 JSON 里的 floor_count / 时间点)
-- ------------------------------------------------------------------
SELECT
e.visitor_person_id,
SUM(CASE WHEN irr.person_delete = 0 THEN 1 ELSE 0 END) AS active_row_count,
MAX(CASE WHEN irr.person_delete = 0 THEN irr.last_update_time END) AS latest_active_update_ms
FROM (
SELECT '9199000100000000001' AS visitor_person_id
UNION ALL SELECT '9199000100000000002'
UNION ALL SELECT '9199000100000000003'
UNION ALL SELECT '9199000100000000004'
UNION ALL SELECT '9199000100000000005'
UNION ALL SELECT '9199000100000000006'
UNION ALL SELECT '9199000100000000007'
UNION ALL SELECT '9199000100000000008'
UNION ALL SELECT '9199000100000000009'
UNION ALL SELECT '9199000100000000010'
UNION ALL SELECT '9199000100000000011'
UNION ALL SELECT '9199000100000000012'
UNION ALL SELECT '9199000100000000013'
UNION ALL SELECT '9199000100000000014'
UNION ALL SELECT '9199000100000000015'
UNION ALL SELECT '9199000100000000016'
UNION ALL SELECT '9199000100000000017'
UNION ALL SELECT '9199000100000000018'
UNION ALL SELECT '9199000100000000019'
UNION ALL SELECT '9199000100000000020'
UNION ALL SELECT '9199000100000000021'
UNION ALL SELECT '9199000100000000022'
UNION ALL SELECT '9199000100000000023'
UNION ALL SELECT '9199000100000000024'
UNION ALL SELECT '9199000100000000025'
) e
LEFT JOIN `cw-elevator-application`.`image_rule_ref` irr
ON irr.person_id = e.visitor_person_id
GROUP BY e.visitor_person_id
ORDER BY e.visitor_person_id;
-- ------------------------------------------------------------------
-- B) 有效规则明细(与报告 `derived.floors` 比对 zone_id;按人+时间排序)
-- ------------------------------------------------------------------
SELECT
irr.person_id,
irr.zone_id,
irr.zone_name,
irr.person_delete,
irr.business_id,
irr.create_time,
irr.last_update_time
FROM `cw-elevator-application`.`image_rule_ref` irr
WHERE irr.person_id BETWEEN '9199000100000000001' AND '9199000100000000025'
ORDER BY irr.person_id, irr.last_update_time DESC, irr.zone_id;
@@ -0,0 +1,43 @@
-- 租户访客楼层策略最小核查 SQL(执行前确认数据库实例为电梯业务库)
-- 目标租户:广发基金 business_id = 2524639890ba4f2cba9ba1a4eeaa4015
-- 1) 表结构与索引确认
SHOW CREATE TABLE tenant_visitor_floor_policy;
-- 2) 广发租户策略主检查
SELECT id,
business_id,
policy_type,
allow_zone_ids,
building_id,
enabled,
policy_version,
updated_at,
remark
FROM tenant_visitor_floor_policy
WHERE business_id = '2524639890ba4f2cba9ba1a4eeaa4015'
ORDER BY updated_at DESC, policy_version DESC;
-- 3) 同租户是否存在多条默认策略(building_id 为空)
SELECT business_id, COUNT(*) AS cnt
FROM tenant_visitor_floor_policy
WHERE business_id = '2524639890ba4f2cba9ba1a4eeaa4015'
AND (building_id IS NULL OR building_id = '')
GROUP BY business_id
HAVING COUNT(*) > 1;
-- 4) enabled 与 policy_type 分布
SELECT policy_type, enabled, COUNT(*) AS cnt
FROM tenant_visitor_floor_policy
WHERE business_id = '2524639890ba4f2cba9ba1a4eeaa4015'
GROUP BY policy_type, enabled
ORDER BY cnt DESC;
-- 5) allow_zone_ids 是否包含 28F zone_id
SELECT id, allow_zone_ids
FROM tenant_visitor_floor_policy
WHERE business_id = '2524639890ba4f2cba9ba1a4eeaa4015'
AND enabled = 1
AND policy_type = 'INTERSECT_ALLOWLIST'
AND allow_zone_ids LIKE '%605560545117995008%';
@@ -0,0 +1,18 @@
-- 访客:黄平(手机号 13926442944)手工查询 SQL
-- 已确认库表:component-organization.cw_is_person
SELECT
person_id,
name,
mobile,
business_id,
labels,
deleted,
create_time,
update_time
FROM `component-organization`.`cw_is_person`
WHERE business_id = '2524639890ba4f2cba9ba1a4eeaa4015'
AND name = '黄平'
AND mobile = '13926442944'
ORDER BY update_time DESC;
@@ -0,0 +1,31 @@
发布包目录(源码内):
docs/testing/release-visitor-noauth-verify-v20260430
一、测试执行(单行,与 README「4」一致;在解压后的发布包根目录执行,须有子目录 python/)
python3 -m pip install -q -r python/requirements-min.txt && python3 python/quick_verify_visitor_floor_policy.py --mode noauth-probe --org-base-url "http://10.0.22.207:8089" --elevator-base-url "http://10.0.22.207:16112" --use-preset-cases --visitor-person-ids "$(python3 -c 'print(",".join(str(9199000100000000000+i) for i in range(1,26)))')" --probe-with-businessid --batch-output-summary "batch-preset-static-$(date +%Y%m%d-%H%M%S).json"
二、一键打包(在仓库根「源码」目录执行,排除本地 report 里的 JSON 痕迹与 __pycache__
STAMP=$(date +%Y%m%d-%H%M%S)
OUT="docs/testing/release-visitor-noauth-verify-v20260430-prod-${STAMP}.zip"
zip -r "$OUT" docs/testing/release-visitor-noauth-verify-v20260430 \
-x 'docs/testing/release-visitor-noauth-verify-v20260430/report/*.json' \
-x '*.pyc' -x '*__pycache__*' -x '*.pyo'
unzip -l "$OUT" | tail -5
echo "written: $OUT"
三、解压到测试机后的目录示例
mkdir -p ~/visitor-noauth-verify && cd ~/visitor-noauth-verify
unzip -q release-visitor-noauth-verify-v20260430-prod-*.zip
cd docs/testing/release-visitor-noauth-verify-v20260430
(在此目录执行上文「一行命令」)
四、打包前自检
1) python/quick_verify_visitor_floor_policy.py、requirements*.txt 存在
2) sql/、README.md 与交付地址/访客号段约定一致
3) MANIFEST.txt 与目录一致
五、选配:连 MySQL 等参数由运维另表提供,不写死于此文件;勿用开发与目标不一致的库串联 HTTP。
@@ -0,0 +1,90 @@
# 访客楼层策略验证执行流程(简化版)
## 1. 目标
按固定顺序完成两组验证,并输出汇总结果:
1. **基线组(广发基金)**:新增“黄平访客 -> 被访人蒙海文(`personId=964454497399468032`)”,再查询该访客楼层权限;
2. **对照组(其他公司多样本)**:从数据库筛选多个公司/多个部门被访人,分别新增“黄志平访客”,再查询各自楼层权限;
3. 最终输出“每个样本是否成功、楼层数量与楼层清单”。
> 说明:本流程强调顺序和可复核性,不再依赖复杂自动推断。
## 2. 统一原则
- 只以 `personId` 作为最终查询键,不以姓名直接判定权限。
- 姓名查询仅用于“定位候选人”,不用于最终业务判定。
- 判定顺序固定:**先写(add/visitor),再读(passRule/image**。
## 3. 执行总流程(必须按顺序)
### 步骤A:准备参数与样本
- 访客固定:黄平/黄志平(按现场实际确认对应 `visitorPersonId`
- 先准备两类样本:
- 样本1(广发基金基线):`businessId=252463...`,被访人 `964454497399468032`
- 样本2(多公司多部门对照):在**与目标验证环境一致**的组织库/MySQL 上筛选多个公司和部门被访人(禁止使用开发库数据去打生产接口)
### 步骤B:执行样本1(广发基金基线)
1.`add/visitor` 新增访客;
2. 紧接着调 `passRule/image` 查询楼层;
3. 记录基线组结果(期望仅 `28F`,若策略开启)。
### 步骤C:执行样本2(多公司多部门)
对每个被访人样本重复:
1.`add/visitor` 新增“黄志平访客”;
2.`passRule/image` 查询该访客楼层;
3. 记录结果到汇总表。
### 步骤D:输出总结果
按样本输出字段:
- 公司
- 部门
- 被访人 `personId`
- 访客 `personId`
- `add/visitor` 结果码
- 楼层数量
- 楼层清单
## 4. 异常分流(必须遵守)
### 4.1 add/visitor 失败(任何样本)
结论:该样本不具备“策略生效验证条件”。
动作:
1. 查同时间窗口服务日志;
2. 定位具体失败点(远程超时、依赖服务不可用、SQL异常等);
3. 修复后仅重跑失败样本。
### 4.2 add/visitor 成功但楼层不符
动作:
1. 查策略读取日志(`policyDecision``allowFloorSize``effectiveFloorSize`);
2. 查策略表配置是否匹配租户;
3.`image_rule_ref` 当前 `person_delete=0` 记录是否包含历史残留。
## 5. 为什么采用该顺序
- 先做广发基金基线,可快速判断策略主路径是否正常;
- 再做多公司多部门对照,可评估是否存在租户间差异或历史规则干扰;
- 最后统一汇总,避免单条日志或单个样本误导结论。
## 6. 推荐命令模板(单样本)
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--business-id "2524639890ba4f2cba9ba1a4eeaa4015" \
--meng-person-id "964454497399468032" \
--visitor-person-id "1102270499947507712" \
--probe-with-businessid
```
> 多样本场景建议外层脚本循环调用本命令,并将结果JSON汇总为一张表。
@@ -0,0 +1,194 @@
# 访客无鉴权策略验证-操作手册(发布版)
## 1. 适用范围
- 目标:验证无鉴权模式下访客接口是否可调用,以及“黄平访问蒙海文”是否触发策略。
- 环境:生产环境(建议低峰时段执行)。
- 对应版本:`cw-elevator-application-2.0.9`
- 建议先读:`执行流程设计-简化版.md`(先定流程,再执行)
## 2. 前置条件
1. Python 3.8+
2. **推荐(与 §4.0 一致)**`python3 -m pip install -r python/requirements-min.txt`(安装 `requests`)。若仅单笔安装亦可:`python3 -m pip install requests`
3. 关键参数确认:
- `businessId=2524639890ba4f2cba9ba1a4eeaa4015`
- `蒙海文personId=964454497399468032`
- `黄平personId=1102270499947507712`
- `黄平姓名=黄平``手机号=13926442944`(仅用于人工核对)
说明:
- 被访者ID参数为 `--meng-person-id`,默认即 `964454497399468032`,可按现场覆盖。
## 3. 手工 SQL(访客 ID 复核)
见:`sql/黄平访客ID手工查询.sql`
## 3.1 策略基线核查 SQL(必须先执行)
见:`sql/租户访客策略基线核查.sql`
重点核验:
- `business_id='2524639890ba4f2cba9ba1a4eeaa4015'`
- `policy_type='INTERSECT_ALLOWLIST'`
- `enabled=1`
- `allow_zone_ids` 包含 `605560545117995008`28F
- 同租户 `building_id` 为空的默认策略仅 1 条
- 策略语义(**规范**):启用策略时 **`allow_zone_ids` 在组织 `detail` 以「替代」写入 `floorList`**;电梯 **`addVisitor`** 仅透传 **`detail.floorList`**UC-01)或请求 **`floorIds`**UC-02),**不与 allow 求交**。失效场景以 **`76260531`** 等为准(历史 **`76260532`** 交集失败路径已废止)。详见 [`visitor-registration-business-flow.md`](../visitor-registration-business-flow.md)、[`租户组织人员访客-数据模型与用例.md`](../../architecture/租户组织人员访客-数据模型与用例.md)。
## 3.2 多公司多部门样本筛选 SQL(第二步)
见:`sql/批量样本筛选-多公司多部门.sql`
筛选结果请保存为 `sql/batch_cases.csv`,列头要求:
`business_id,meng_person_id,company,department`
## 4. 执行步骤
### 4.0 标准预置 25 人批量(推荐,单行命令)
在发布包根目录(见 4.1)下**复制整行**执行即可(与 `README.md` §4 相同;不写占位符:组织/电梯 URL 与访客号段已由交付对齐):
```bash
python3 -m pip install -q -r python/requirements-min.txt && python3 python/quick_verify_visitor_floor_policy.py --mode noauth-probe --org-base-url "http://10.0.22.207:8089" --elevator-base-url "http://10.0.22.207:16112" --use-preset-cases --visitor-person-ids "$(python3 -c 'print(",".join(str(9199000100000000000+i) for i in range(1,26)))')" --probe-with-businessid --batch-output-summary "batch-preset-static-$(date +%Y%m%d-%H%M%S).json"
```
结果见 `report/batch-preset-static-*.json`
### 4.0.1 执行后数据库核验(必须)
在完成 §4.0 且已拿到批量 JSON 后,于**与本环境电梯服务写入同一套库的** MySQL(`cw-elevator-application`)执行:
`sql/标准批量25访客-执行后核验.sql`
**如何对照判断「是否跑成功」**
1. 打开本轮 `report/batch-preset-static-*.json`,对每条样本看 `steps.add_visitor.business_ok``derived.floor_count``grade`
2.**`add_visitor` 业务成功** 且 `passRule/image` HTTP 大体正常的样本,在同一库中期望:查询 **A** 中该行访客的 **`active_row_count` ≥ 1**(租户策略或写库链路正常时,`person_delete=0` 的规则行应与接口返回楼层有对应);**全部为 0** 则重点排查写库延迟、租户/访客 ID 不真实、或网关虽 200 实际未打到目标实例。
3. 查询 **B** 与 JSON 内 `derived.floors[].zoneId` 做逐项比对;若库中 `person_delete=1` 的历史行较多,结合 `last_update_time` 看是否为本次执行产生的新生效行。
4. 若访客号段与本包约定不一致(非 `919900…`),请改用 `sql/人工核查最终楼层结果.sql`,将 `IN (...)` 换为当天 JSON 中全部 `resolved_visitor_person_id`
### 4.1 进入发布目录
```bash
cd docs/testing/release-visitor-noauth-verify-v20260430
```
### 4.2 样本1(广发基金基线)执行
本节「黄平 + 蒙海文」样本用于:**无鉴权下探针是否可走通**,以及 **`passRule/image` 返回是否与租户策略语义一致**。它**不是用来**证明「在本次执行中产出了全新访客主数据」——`add/visitor` 传入的是已在组织/电梯侧建档的访客 `personId` 时,写库常为**续约/补齐规则**`image_rule_ref` 里的行常与**历史登记**混在一起。
若验收目标是「必须先有真实新增访客,再验证派梯」,请:
- **优先**:走 **§4.0** 约定号段 **`9199000100000000001``…0025`** 的批量预置链路(租户内已预置对应用人,`image_rule_ref` 证据与 JSON 对齐);
- 或:业务流程上**新开一名访客**,再将其 `visitorPersonId` 交给本脚本;查库时对 `last_update_time` 与脚本 JSON 顶层 **`started_at`** 做对时(仅能辅助说明「本轮是否写过」)。
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--business-id "2524639890ba4f2cba9ba1a4eeaa4015" \
--meng-person-id "964454497399468032" \
--visitor-person-id "1102270499947507712" \
--probe-with-businessid
```
### 4.3 样本2(其他公司多部门)执行
在与**当前验证目标环境一致**的 MySQL 上执行 `sql/批量样本筛选-多公司多部门.sql`(或等价查询),导出被访人到 `sql/batch_cases.csv`。**禁止**用开发环境数据库导出样本却去打生产 HTTP。**开发环境快照仅可用于开发联调**,生产走查必须使用生产侧数据或手写/审批过的样本 CSV。
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--batch-input-csv "sql/batch_cases.csv" \
--visitor-person-ids "<访客ID1>,<访客ID2>,<访客ID3>" \
--visitor-names "黄志平A,黄志平B,黄志平C" \
--probe-with-businessid
```
建议最少覆盖:
- 3 个不同公司;
- 每公司 2 个不同部门被访人。
若已导出 `docs/testing/_cw_is_person__202604302030.csv`,可直接自动筛选(排除广发基金):
```bash
python3 python/quick_verify_visitor_floor_policy.py \
--mode noauth-probe \
--org-base-url "http://10.0.22.207:8089" \
--elevator-base-url "http://10.0.22.207:16112" \
--batch-source-person-csv "../_cw_is_person__202604302030.csv" \
--exclude-business-ids "2524639890ba4f2cba9ba1a4eeaa4015" \
--sample-companies 3 \
--sample-persons-per-company 2 \
--visitor-person-ids "<访客ID1>,<访客ID2>,<访客ID3>" \
--visitor-names "黄志平A,黄志平B,黄志平C" \
--probe-with-businessid
```
说明:
- 自动筛选依赖列:`BUSINESS_ID`,`ID`,`NAME`;若存在 **`IS_DEL`**,脚本会**跳过已删除行**`IS_DEL≠0` 或与 `true`/`1` 等价)。
- 该 CSV 若无标准部门列,汇总中 `department` 将为空。
### 4.4 结果判定
脚本 `noauth-probe` 对每条样本计算 `grade`(单样本写入 `quick-verify-*.json`;批量写入每条 `results[]`):
- `grade=high_risk`:任一步均未出现 401/403,且 **`add/visitor` 的 HTTP 成功(&lt;300)且接口业务码判定为成功**(`business_ok`),判定高风险放开(安全问题成立)。若仅 HTTP 200 但业务失败,归为 `needs_review`,不是 `high_risk`
- `grade=expected_block`**人员详情、`add/visitor``passRule/image` 任一步**出现 401/403,视为无鉴权被拦截,符合安全预期。
- `grade=needs_review`:未出现 401/403,但未达到上述「放行且业务成功」条件(例如网关超时、业务码失败等),需人工复核。
- `grade=failed`(批量中单条可出现):访客 `personId` 无法解析且无内置兜底等情况,脚本未完成调链;不按上述安全三档解释,需先看 `summary`/JSON 报错原因。
### 4.5 汇总输出(必须)
最终按样本输出表格(建议 CSV/Markdown;字段可从批量 JSON **`results[]`** 各条中取):
- 公司:`sample.company`
- 部门:`sample.department`
- 被访人 `personId``args.meng_person_id`
- 访客 `personId``args.resolved_visitor_person_id`
- **`add/visitor` 业务码**`steps.add_visitor.business_code`(及 `http_status`
- **`passRule/image`**`steps.passrule_image` 内的 `business_code` / `http_status`
- 楼层数量:`derived.floor_count`
- 楼层清单:`derived.floors`(元素含 `zoneId`/`zoneName`
- 安全分档:`grade``summary`
控制台会摘要打印 `company/department``addCode``floorCount`;完整以 JSON 为准。
批量汇总文件:**默认** `report/batch-summary.json`;若使用了 `--batch-output-summary`(如 §4.0 的 `batch-preset-static-*.json`),则路径为 **`report/` + 传入文件名**。
### 4.6 人工 SQL 核查最终结果(最后一步)
- §4.0 固定 25 号段:**优先**执行 `sql/标准批量25访客-执行后核验.sql`
- 其它访客清单或单笔核对:执行 `sql/人工核查最终楼层结果.sql`,自行替换 `IN (...)`
核查要点:
- 按本次访客ID集合过滤,确认 `person_delete=0` 的有效规则;
- 对比 `zone_id` 与脚本输出楼层清单;
- 如存在历史残留,结合 `create_time` / **`last_update_time`**`image_rule_ref`)判定最新记录。
### 4.7 业务侧整改后关键日志检查点
本版整改聚焦业务策略链路,不调整鉴权逻辑。应用日志需出现:
- 策略决策日志(`PersonRuleServiceImpl`)含字段:
- `policyDecision``hostFloorSize``allowFloorSize``effectiveFloorSize`(有一条覆盖生效日志中占位名为 `effectiveSize`,与同次决策中的楼层数量一致)、`policyId``policyVersion`
失败路径上的告警亦多为 `WARN`/`INFO`(如「访客派梯楼层决策失败-…」「allow_zone_ids JSON 无效」),便于在无 DEBUG 前提下排查。**是否仍存在「仅为探针、且仍为 DEBUG」的日志行,请以当前发布包镜像实际 `logback` 与源码为准逐项核对**。
## 5. 证据留档
至少归档以下内容:
1. 执行命令
2. 脚本输出摘要(控制台)
3. 生成的 JSON**单样本** `report/quick-verify-*.json`**标准批量** `report/batch-preset-static-*.json` 或其他 `--batch-output-summary` 指定名
4. **`sql/标准批量25访客-执行后核验.sql`(或等价)查询导出**与其它 SQL 核验结果一并留档
5. 关键接口返回截图/日志