mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
feat: add service config templates and extraction script
Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
This commit is contained in:
Binary file not shown.
+116
-116
@@ -1,116 +1,116 @@
|
||||
# 按 V1(门禁 access-control)要求:通行记录分表问题怎么解决
|
||||
|
||||
本文面向运维/实施,用白话说明:**V1 电梯程序按官方配置应该是什么样**,以及**你需要动哪些地方**,才能让「按识别时间查通行记录」这类接口不再报错。
|
||||
|
||||
### 背景说明(与你们现状对齐)
|
||||
|
||||
- **`星河湾星中星/星中心/cw-elevator-application-V1.0.0.20211103`** 下的相关配置,对应**生产环境**使用的参数与规则(以外置 `*.properties` 为准;线上还可能被 **Consul** 覆盖一层)。
|
||||
- **当前正在使用的 MySQL** 若为**从生产还原**的数据,则原则上**库里的表结构、分表应与生产一致**。
|
||||
|
||||
在这一前提下,若仍出现「某年分表不存在」「路由不到表」之类现象,排查方向应转向:
|
||||
|
||||
1. **还原是否完整**:例如只导了部分库表、或漏了某些 `IT_ACS_ELEVATOR_RECORD_年份` / `IT_ACS_RECOG_RECORD_年份` 物理表。
|
||||
2. **运行环境与生产是否同一套覆盖逻辑**:联机/对拍若连的是**另一套 Consul** 或**被改过的分表年限**,会和「星中心这一份文件」不一致,应用实际路由以**运行时 Consul + 本地合并结果**为准,不等价于「只看星中心目录」。
|
||||
3. **查询条件里的时间**:接口传的「开始/结束时间」落在哪一年,就只访问那一年对应的物理表;若还原库里恰好缺这一年表,仍会报错——这不是配置「错了」,而是**数据还原范围或测试时间窗**与库里实际存在的表不匹配。
|
||||
|
||||
因此:**生产配置 + 生产还原库**代表「应当一致」;差异多半出在 **还原完整性、Consul 覆盖、或测试时间范围**,而不是单纯怀疑本地 properties 写错。
|
||||
|
||||
---
|
||||
|
||||
## 一、V1 是怎么读配置的?
|
||||
|
||||
1. **入口**:jar 同目录下的 **`bootstrap.properties`**
|
||||
- 里面写了:`spring.profiles.active=access-control`
|
||||
- 意思是:启动时用 **access-control** 这一套组合配置。
|
||||
|
||||
2. **Consul(配置中心)**:`bootstrap` 里打开了 Consul。
|
||||
- 运行时:**Consul 里下发的配置会叠在本地文件之上**。
|
||||
- 若出现「本地文件写的是 A,日志里却是 B」,多半要以 **Consul 最终生效值** 为准去对齐数据库。
|
||||
|
||||
3. **本地两个关键文件**(与你提供的「星中心」路径一致):
|
||||
- **`application.properties`**:数据源(连哪个库)、Redis、Kafka、序列号等。
|
||||
- **`application-access-control.properties`**:**门禁 / 电梯开门记录相关的分表规则**在这里。
|
||||
|
||||
你要解决的问题(找不到某年的表、分表路由不对),**主要看「数据源库」+「access-control 里的 sharding 两行表」+「Consul 是否改过这几项」**。
|
||||
|
||||
---
|
||||
|
||||
## 二、按「星中心」这套 V1,规则说明书里写什么?
|
||||
|
||||
在你给的目录里,`application-access-control.properties` 中已经写明:
|
||||
|
||||
- 表 **`IT_ACS_ELEVATOR_RECORD`**(电梯通行记录)
|
||||
- 按字段 **`RECOGNITION_TIME`**(识别时间)做**按年分表**。
|
||||
- 逻辑上会路由到:`IT_ACS_ELEVATOR_RECORD_2020`、`…_2021` … 一直到 **`…_2030`**(配置里是 **`2020..2030`** 这一段)。
|
||||
|
||||
- 表 **`IT_ACS_RECOG_RECORD`**(识别流水)
|
||||
- 同样是 **`2020..2030`** 的年表。
|
||||
|
||||
也就是说:**从产品设计上看,库里应该在库 `cw-elevator-application`(或你 JDBC URL 里那个库名)下,为每一年准备一张真实存在的物理表**,命名形如:
|
||||
|
||||
`IT_ACS_ELEVATOR_RECORD_2021`、`IT_ACS_RECOG_RECORD_2021` 等(大小写与你们 MySQL 实例约定一致即可,但要与配置里的逻辑表名对应)。
|
||||
|
||||
若某一年的物理表**根本没建**,程序就会报「表不存在」一类错误——这和程序写得对不对无关,是**库表没跟上配置**。
|
||||
(若库已是生产全量还原仍缺表,则说明**还原脚本/导出范围不完整**,需要 DBA 核对是否漏表。)
|
||||
|
||||
---
|
||||
|
||||
## 三、按 V1 要求,实操上要做哪几件事?
|
||||
|
||||
### 1)搞清楚「当前线上到底认哪一段年份」
|
||||
|
||||
- 打开 **`application-access-control.properties`**(星中心那份为准):看
|
||||
`spring.shardingsphere.sharding.tables.IT_ACS_ELEVATOR_RECORD.actual-data-nodes=`
|
||||
里是 **`2020..2030`** 还是别的。
|
||||
- 再到 **Consul** 里搜同一应用(如 `elevator-app`)的配置,看有没有**同名配置覆盖了这一段**。
|
||||
**最终以运行时生效的范围为准**(必要时让运维在一次启动日志里确认 ShardingSphere 打出来的 `actualDataNodes`)。
|
||||
|
||||
> 说明:仓库里「参考解压包」里的同名文件可能是 **`2020..2022`**,和「星中心 **`2020..2030`**」不一致。**以你们实际部署目录(星中心)和 Consul 为准**,不要混用两套口径。
|
||||
|
||||
### 2)在 MySQL 里把缺的物理表补上
|
||||
|
||||
- 连上 **`application.properties` 里 JDBC 指向的那个库**(用户名密码在同一文件里,此处不写明文)。
|
||||
- 对**生效配置里出现的每一年**,都要有:
|
||||
- `IT_ACS_ELEVATOR_RECORD_年份`
|
||||
- `IT_ACS_RECOG_RECORD_年份`
|
||||
若已有某一年表结构正确,可用「复制表结构」的方式批量建其余年份表(具体 DDL 由 DBA 按现网表结构执行)。
|
||||
- 应用查询时,**请求里的开始/结束时间**落在哪一年,就会打到哪张年表;若该年表不存在,就会失败。
|
||||
|
||||
### 3)保证配置与库一致,不要「配置写了 2030,库里只建了两年」
|
||||
|
||||
- 若短期无法建全 **`2020..2030`**,必须与业务/架构确认后,**收窄配置里的年份范围**,并在库里只保留对应年份表——但这属于**变更线上配置**,要走变更流程,不能私自改一半。
|
||||
|
||||
### 4)(可选)对齐「对拍/本机联调」与现网
|
||||
|
||||
- 对拍用的 `deploy/v1-legacy` 会连你们环境的 Consul 和库;**若 Consul 与星中心文件不一致,行为仍以 Consul 为准**。
|
||||
- 若希望与星中心 V1 完全一致,建议:**把星中心的 `application.properties`、`application-access-control.properties`、`bootstrap.properties` 作为现网真值**;参考仓库里旧包仅作对照。
|
||||
|
||||
---
|
||||
|
||||
## 四、和「新包 V2」为什么有时对不齐?
|
||||
|
||||
新包若走的**不是同一套 profile / 同一套 Consul 分表声明**,可能出现「一边按年表查、一边查单表」的情况。那是**部署形态不一致**,不是单独改一个接口代码就能统一的。
|
||||
要对齐,需要:**同一套库表 + 同一套分表配置(或两边约定都不分表)**,由运维和架构一起定。
|
||||
|
||||
---
|
||||
|
||||
## 五、文件路径速查(你本机)
|
||||
|
||||
| 用途 | 路径(你提供的星中心) |
|
||||
|------|------------------------|
|
||||
| 端口、Consul、注册发现 | `星中心/cw-elevator-application-V1.0.0.20211103/bootstrap.properties` |
|
||||
| 数据源、Redis、Feign 等 | `星中心/cw-elevator-application-V1.0.0.20211103/application.properties` |
|
||||
| **分表规则(电梯记录 / 识别记录)** | `星中心/cw-elevator-application-V1.0.0.20211103/application-access-control.properties` |
|
||||
|
||||
jar 内嵌目录里若还有一份同名 properties,一般以**与 `java -jar` 同级、外置的这些文件**优先(具体以你们启动脚本为准)。
|
||||
|
||||
---
|
||||
|
||||
## 六、一句话记住
|
||||
|
||||
**V1 要求:配置里声明了哪些年份的分表,库里就要有那些年的物理表;Consul 若改了声明,库表也要跟着声明走。**
|
||||
做不到这一点,「通行记录分页」就会在数据库层失败,业务码就不会是成功。
|
||||
|
||||
---
|
||||
|
||||
*本文依据「星中心」目录下 V1 部署配置整理;该目录配置视为生产侧参考;敏感连接信息请勿提交到公开仓库。*
|
||||
# 按 V1(门禁 access-control)要求:通行记录分表问题怎么解决
|
||||
|
||||
本文面向运维/实施,用白话说明:**V1 电梯程序按官方配置应该是什么样**,以及**你需要动哪些地方**,才能让「按识别时间查通行记录」这类接口不再报错。
|
||||
|
||||
### 背景说明(与你们现状对齐)
|
||||
|
||||
- **`星河湾星中星/星中心/cw-elevator-application-V1.0.0.20211103`** 下的相关配置,对应**生产环境**使用的参数与规则(以外置 `*.properties` 为准;线上还可能被 **Consul** 覆盖一层)。
|
||||
- **当前正在使用的 MySQL** 若为**从生产还原**的数据,则原则上**库里的表结构、分表应与生产一致**。
|
||||
|
||||
在这一前提下,若仍出现「某年分表不存在」「路由不到表」之类现象,排查方向应转向:
|
||||
|
||||
1. **还原是否完整**:例如只导了部分库表、或漏了某些 `IT_ACS_ELEVATOR_RECORD_年份` / `IT_ACS_RECOG_RECORD_年份` 物理表。
|
||||
2. **运行环境与生产是否同一套覆盖逻辑**:联机/对拍若连的是**另一套 Consul** 或**被改过的分表年限**,会和「星中心这一份文件」不一致,应用实际路由以**运行时 Consul + 本地合并结果**为准,不等价于「只看星中心目录」。
|
||||
3. **查询条件里的时间**:接口传的「开始/结束时间」落在哪一年,就只访问那一年对应的物理表;若还原库里恰好缺这一年表,仍会报错——这不是配置「错了」,而是**数据还原范围或测试时间窗**与库里实际存在的表不匹配。
|
||||
|
||||
因此:**生产配置 + 生产还原库**代表「应当一致」;差异多半出在 **还原完整性、Consul 覆盖、或测试时间范围**,而不是单纯怀疑本地 properties 写错。
|
||||
|
||||
---
|
||||
|
||||
## 一、V1 是怎么读配置的?
|
||||
|
||||
1. **入口**:jar 同目录下的 **`bootstrap.properties`**
|
||||
- 里面写了:`spring.profiles.active=access-control`
|
||||
- 意思是:启动时用 **access-control** 这一套组合配置。
|
||||
|
||||
2. **Consul(配置中心)**:`bootstrap` 里打开了 Consul。
|
||||
- 运行时:**Consul 里下发的配置会叠在本地文件之上**。
|
||||
- 若出现「本地文件写的是 A,日志里却是 B」,多半要以 **Consul 最终生效值** 为准去对齐数据库。
|
||||
|
||||
3. **本地两个关键文件**(与你提供的「星中心」路径一致):
|
||||
- **`application.properties`**:数据源(连哪个库)、Redis、Kafka、序列号等。
|
||||
- **`application-access-control.properties`**:**门禁 / 电梯开门记录相关的分表规则**在这里。
|
||||
|
||||
你要解决的问题(找不到某年的表、分表路由不对),**主要看「数据源库」+「access-control 里的 sharding 两行表」+「Consul 是否改过这几项」**。
|
||||
|
||||
---
|
||||
|
||||
## 二、按「星中心」这套 V1,规则说明书里写什么?
|
||||
|
||||
在你给的目录里,`application-access-control.properties` 中已经写明:
|
||||
|
||||
- 表 **`IT_ACS_ELEVATOR_RECORD`**(电梯通行记录)
|
||||
- 按字段 **`RECOGNITION_TIME`**(识别时间)做**按年分表**。
|
||||
- 逻辑上会路由到:`IT_ACS_ELEVATOR_RECORD_2020`、`…_2021` … 一直到 **`…_2030`**(配置里是 **`2020..2030`** 这一段)。
|
||||
|
||||
- 表 **`IT_ACS_RECOG_RECORD`**(识别流水)
|
||||
- 同样是 **`2020..2030`** 的年表。
|
||||
|
||||
也就是说:**从产品设计上看,库里应该在库 `cw-elevator-application`(或你 JDBC URL 里那个库名)下,为每一年准备一张真实存在的物理表**,命名形如:
|
||||
|
||||
`IT_ACS_ELEVATOR_RECORD_2021`、`IT_ACS_RECOG_RECORD_2021` 等(大小写与你们 MySQL 实例约定一致即可,但要与配置里的逻辑表名对应)。
|
||||
|
||||
若某一年的物理表**根本没建**,程序就会报「表不存在」一类错误——这和程序写得对不对无关,是**库表没跟上配置**。
|
||||
(若库已是生产全量还原仍缺表,则说明**还原脚本/导出范围不完整**,需要 DBA 核对是否漏表。)
|
||||
|
||||
---
|
||||
|
||||
## 三、按 V1 要求,实操上要做哪几件事?
|
||||
|
||||
### 1)搞清楚「当前线上到底认哪一段年份」
|
||||
|
||||
- 打开 **`application-access-control.properties`**(星中心那份为准):看
|
||||
`spring.shardingsphere.sharding.tables.IT_ACS_ELEVATOR_RECORD.actual-data-nodes=`
|
||||
里是 **`2020..2030`** 还是别的。
|
||||
- 再到 **Consul** 里搜同一应用(如 `elevator-app`)的配置,看有没有**同名配置覆盖了这一段**。
|
||||
**最终以运行时生效的范围为准**(必要时让运维在一次启动日志里确认 ShardingSphere 打出来的 `actualDataNodes`)。
|
||||
|
||||
> 说明:仓库里「参考解压包」里的同名文件可能是 **`2020..2022`**,和「星中心 **`2020..2030`**」不一致。**以你们实际部署目录(星中心)和 Consul 为准**,不要混用两套口径。
|
||||
|
||||
### 2)在 MySQL 里把缺的物理表补上
|
||||
|
||||
- 连上 **`application.properties` 里 JDBC 指向的那个库**(用户名密码在同一文件里,此处不写明文)。
|
||||
- 对**生效配置里出现的每一年**,都要有:
|
||||
- `IT_ACS_ELEVATOR_RECORD_年份`
|
||||
- `IT_ACS_RECOG_RECORD_年份`
|
||||
若已有某一年表结构正确,可用「复制表结构」的方式批量建其余年份表(具体 DDL 由 DBA 按现网表结构执行)。
|
||||
- 应用查询时,**请求里的开始/结束时间**落在哪一年,就会打到哪张年表;若该年表不存在,就会失败。
|
||||
|
||||
### 3)保证配置与库一致,不要「配置写了 2030,库里只建了两年」
|
||||
|
||||
- 若短期无法建全 **`2020..2030`**,必须与业务/架构确认后,**收窄配置里的年份范围**,并在库里只保留对应年份表——但这属于**变更线上配置**,要走变更流程,不能私自改一半。
|
||||
|
||||
### 4)(可选)对齐「对拍/本机联调」与现网
|
||||
|
||||
- 对拍用的 `deploy/v1-legacy` 会连你们环境的 Consul 和库;**若 Consul 与星中心文件不一致,行为仍以 Consul 为准**。
|
||||
- 若希望与星中心 V1 完全一致,建议:**把星中心的 `application.properties`、`application-access-control.properties`、`bootstrap.properties` 作为现网真值**;参考仓库里旧包仅作对照。
|
||||
|
||||
---
|
||||
|
||||
## 四、和「新包 V2」为什么有时对不齐?
|
||||
|
||||
新包若走的**不是同一套 profile / 同一套 Consul 分表声明**,可能出现「一边按年表查、一边查单表」的情况。那是**部署形态不一致**,不是单独改一个接口代码就能统一的。
|
||||
要对齐,需要:**同一套库表 + 同一套分表配置(或两边约定都不分表)**,由运维和架构一起定。
|
||||
|
||||
---
|
||||
|
||||
## 五、文件路径速查(你本机)
|
||||
|
||||
| 用途 | 路径(你提供的星中心) |
|
||||
|------|------------------------|
|
||||
| 端口、Consul、注册发现 | `星中心/cw-elevator-application-V1.0.0.20211103/bootstrap.properties` |
|
||||
| 数据源、Redis、Feign 等 | `星中心/cw-elevator-application-V1.0.0.20211103/application.properties` |
|
||||
| **分表规则(电梯记录 / 识别记录)** | `星中心/cw-elevator-application-V1.0.0.20211103/application-access-control.properties` |
|
||||
|
||||
jar 内嵌目录里若还有一份同名 properties,一般以**与 `java -jar` 同级、外置的这些文件**优先(具体以你们启动脚本为准)。
|
||||
|
||||
---
|
||||
|
||||
## 六、一句话记住
|
||||
|
||||
**V1 要求:配置里声明了哪些年份的分表,库里就要有那些年的物理表;Consul 若改了声明,库表也要跟着声明走。**
|
||||
做不到这一点,「通行记录分页」就会在数据库层失败,业务码就不会是成功。
|
||||
|
||||
---
|
||||
|
||||
*本文依据「星中心」目录下 V1 部署配置整理;该目录配置视为生产侧参考;敏感连接信息请勿提交到公开仓库。*
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
广发基金租户:访客默认仅能派梯至指定楼层(典型验收:28 层)的 HTTP 验证脚本。
|
||||
|
||||
业务依据(电梯应用):
|
||||
PersonRuleServiceImpl.addVisitor — floorIds 为空时取被访人 floorList,
|
||||
若 tenant_visitor_floor_policy 启用 INTERSECT_ALLOWLIST,则与 allow_zone_ids 求交后再写规则。
|
||||
|
||||
验证思路:
|
||||
1. POST /elevator/person/add/visitor — body 中 floorIds 为空列表(不显式传楼层)。
|
||||
2. POST /elevator/passRule/image — personId=visitorId,回读该访客已开通的楼层 zone 列表。
|
||||
3. 断言楼层条数与名称满足预期(默认:仅 1 条,zoneName 含「28」)。
|
||||
|
||||
环境变量(与 api 对拍一致,须指向广发基金租户):
|
||||
ELEVATOR_HEADER_BUSINESSID — 机构/租户 ID(必填)
|
||||
ELEVATOR_HEADER_LOGINID
|
||||
ELEVATOR_HEADER_PLATFORMUSERID
|
||||
ELEVATOR_HEADER_AUTHORIZATION
|
||||
ELEVATOR_HEADER_APPLICATIONID
|
||||
|
||||
用法示例:
|
||||
export ELEVATOR_HEADER_BUSINESSID='<广发基金 businessId>'
|
||||
export ELEVATOR_HEADER_AUTHORIZATION='Bearer ...'
|
||||
python3 scripts/verify_gf_visitor_default_floor.py \\
|
||||
--base-url http://127.0.0.1:18081 \\
|
||||
--person-id '<被访人员工 personId>' \\
|
||||
--visitor-id '<访客 personId>' \\
|
||||
--beg-visitor-time 1714406400000 \\
|
||||
--end-visitor-time 1714492800000
|
||||
|
||||
可选:仅回读已有访客楼层(跳过开通)
|
||||
python3 scripts/verify_gf_visitor_default_floor.py ... --skip-add
|
||||
|
||||
可选:校验库内策略行(需 pip install pymysql)
|
||||
export MYSQL_HOST=... MYSQL_PORT=3307 MYSQL_USER=root MYSQL_PASSWORD=... MYSQL_DATABASE=cw-elevator-application
|
||||
python3 scripts/verify_gf_visitor_default_floor.py ... --check-policy-row
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_ROOT))
|
||||
|
||||
from parity.client import default_headers # noqa: E402
|
||||
|
||||
|
||||
def _post_json(session: requests.Session, base: str, path: str, body: dict) -> tuple[int, dict | list | None, str]:
|
||||
url = base.rstrip("/") + path
|
||||
r = session.post(
|
||||
url,
|
||||
headers={**default_headers(), **dict(session.headers)},
|
||||
data=json.dumps(body, ensure_ascii=False),
|
||||
timeout=120,
|
||||
)
|
||||
text = r.text or ""
|
||||
try:
|
||||
parsed = json.loads(text) if text.strip() else None
|
||||
except json.JSONDecodeError:
|
||||
parsed = None
|
||||
return r.status_code, parsed, text
|
||||
|
||||
|
||||
def _business_code(payload: dict | list | None) -> str | None:
|
||||
if isinstance(payload, dict) and "code" in payload:
|
||||
c = payload.get("code")
|
||||
return str(c) if c is not None else None
|
||||
return None
|
||||
|
||||
|
||||
def check_mysql_policy(business_id: str) -> tuple[bool, str]:
|
||||
try:
|
||||
import pymysql # type: ignore
|
||||
except ImportError:
|
||||
return False, "未安装 pymysql,跳过库表校验(pip install pymysql)"
|
||||
|
||||
host = os.environ.get("MYSQL_HOST", "127.0.0.1").strip()
|
||||
port = int(os.environ.get("MYSQL_PORT", "3306"))
|
||||
user = os.environ.get("MYSQL_USER", "root").strip()
|
||||
password = os.environ.get("MYSQL_PASSWORD", "").strip()
|
||||
database = os.environ.get("MYSQL_DATABASE", "cw-elevator-application").strip()
|
||||
if not password and not os.environ.get("MYSQL_ALLOW_EMPTY_PASSWORD"):
|
||||
return False, "未设置 MYSQL_PASSWORD,跳过(或设置 MYSQL_ALLOW_EMPTY_PASSWORD=1)"
|
||||
|
||||
conn = pymysql.connect(
|
||||
host=host, port=port, user=user, password=password, database=database, charset="utf8mb4"
|
||||
)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, business_id, policy_type, allow_zone_ids, enabled, policy_version
|
||||
FROM tenant_visitor_floor_policy
|
||||
WHERE business_id = %s AND enabled = 1
|
||||
AND policy_type = 'INTERSECT_ALLOWLIST'
|
||||
AND (building_id IS NULL OR building_id = '')
|
||||
ORDER BY updated_at DESC, policy_version DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(business_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return False, f"未查到启用中的租户默认策略 business_id={business_id!r}"
|
||||
cols = [d[0] for d in cur.description]
|
||||
detail = dict(zip(cols, row))
|
||||
return True, json.dumps(detail, ensure_ascii=False)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def parse_floor_list(data_obj: dict | list | None) -> list[dict]:
|
||||
if data_obj is None:
|
||||
return []
|
||||
if isinstance(data_obj, list):
|
||||
return [x for x in data_obj if isinstance(x, dict)]
|
||||
return []
|
||||
|
||||
|
||||
def assert_floors(js2: dict | list | None, args: argparse.Namespace) -> int:
|
||||
try:
|
||||
cre = re.compile(args.floor_name_regex)
|
||||
except re.error as e:
|
||||
print(f"floor-name-regex 无效: {e}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
data = parse_floor_list((js2 or {}).get("data") if isinstance(js2, dict) else None)
|
||||
print(f"[floors] count={len(data)}")
|
||||
for row in data:
|
||||
print(f" zoneId={row.get('zoneId')!r} zoneName={row.get('zoneName')!r}")
|
||||
|
||||
if len(data) != args.expected_zone_count:
|
||||
print(
|
||||
f"断言失败:楼层条数期望 {args.expected_zone_count},实际 {len(data)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
for row in data:
|
||||
name = str(row.get("zoneName") or "")
|
||||
if not cre.search(name):
|
||||
print(
|
||||
f"断言失败:zoneName={name!r} 不匹配正则 {args.floor_name_regex!r}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
print("OK:访客生效楼层与预期一致(默认策略求交后仅接待层)。")
|
||||
return 0
|
||||
|
||||
|
||||
def run_http_flow(session: requests.Session, args: argparse.Namespace, biz: str) -> int:
|
||||
if not args.skip_add:
|
||||
add_body = {
|
||||
"visitorId": args.visitor_id,
|
||||
"personId": args.person_id,
|
||||
"begVisitorTime": args.beg_visitor_time,
|
||||
"endVisitorTime": args.end_visitor_time,
|
||||
"floorIds": [],
|
||||
}
|
||||
st, js, raw = _post_json(session, args.base_url, "/elevator/person/add/visitor", add_body)
|
||||
code = _business_code(js if isinstance(js, dict) else None)
|
||||
print(f"[add/visitor] http={st} code={code!r}")
|
||||
if st != 200:
|
||||
print(raw[:2000], file=sys.stderr)
|
||||
return 1
|
||||
if code is not None and str(code) not in ("0", "200"):
|
||||
print(raw[:2000], file=sys.stderr)
|
||||
return 1
|
||||
|
||||
img_body = {"personId": args.visitor_id, "businessId": biz}
|
||||
st2, js2, raw2 = _post_json(session, args.base_url, "/elevator/passRule/image", img_body)
|
||||
code2 = _business_code(js2 if isinstance(js2, dict) else None)
|
||||
print(f"[passRule/image] http={st2} code={code2!r}")
|
||||
|
||||
if st2 != 200:
|
||||
print(raw2[:2000], file=sys.stderr)
|
||||
return 1
|
||||
if code2 is not None and str(code2) not in ("0", "200"):
|
||||
print(raw2[:2000], file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return assert_floors(js2 if isinstance(js2, dict) else None, args)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser(description="广发基金租户访客默认楼层(典型 28 层)HTTP 验证")
|
||||
p.add_argument("--base-url", default=os.environ.get("ELEVATOR_VERIFY_BASE", "http://127.0.0.1:18081"))
|
||||
p.add_argument("--person-id", required=True, help="被访人 personId(组织侧有 floorList);--skip-add 时仍可填占位")
|
||||
p.add_argument("--visitor-id", required=True, help="访客 personId")
|
||||
p.add_argument(
|
||||
"--beg-visitor-time",
|
||||
type=int,
|
||||
default=int(os.environ.get("ELEVATOR_BEG_VISITOR_TIME", "0")),
|
||||
help="访客有效期开始 epoch 毫秒",
|
||||
)
|
||||
p.add_argument(
|
||||
"--end-visitor-time",
|
||||
type=int,
|
||||
default=int(os.environ.get("ELEVATOR_END_VISITOR_TIME", "0")),
|
||||
help="访客有效期结束 epoch 毫秒",
|
||||
)
|
||||
p.add_argument("--skip-add", action="store_true", help="跳过开通,仅 passRule/image 回读(访客须已开通)")
|
||||
p.add_argument("--expected-zone-count", type=int, default=1, help="期望访客生效楼层条数")
|
||||
p.add_argument(
|
||||
"--floor-name-regex",
|
||||
default=os.environ.get("ELEVATOR_EXPECT_FLOOR_REGEX", r".*28.*"),
|
||||
help="对每条 zoneName 匹配的正则(默认含 28)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--check-policy-row",
|
||||
action="store_true",
|
||||
help="用 MySQL 校验 tenant_visitor_floor_policy 存在启用行(需 pymysql)",
|
||||
)
|
||||
args = p.parse_args()
|
||||
|
||||
if not os.environ.get("ELEVATOR_HEADER_BUSINESSID", "").strip():
|
||||
print("错误:请设置环境变量 ELEVATOR_HEADER_BUSINESSID(广发基金租户 companyId)", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if not args.skip_add:
|
||||
if args.beg_visitor_time <= 0 or args.end_visitor_time <= 0:
|
||||
print(
|
||||
"错误:开通访客时请提供 --beg-visitor-time / --end-visitor-time(毫秒)或对应环境变量",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
biz = os.environ.get("ELEVATOR_HEADER_BUSINESSID", "").strip()
|
||||
if args.check_policy_row:
|
||||
ok, msg = check_mysql_policy(biz)
|
||||
print(f"[policy-db] ok={ok} {msg}")
|
||||
if not ok:
|
||||
return 1
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update(default_headers())
|
||||
|
||||
try:
|
||||
return run_http_flow(session, args, biz)
|
||||
except requests.RequestException as e:
|
||||
print(f"HTTP 请求失败(服务是否已启动、--base-url 是否正确?): {e}", file=sys.stderr)
|
||||
return 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""组织服务桩 — 模拟 ninca-common-component-organization 的 /component/person/detail 端点"""
|
||||
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 被访人数据映射:personId → { floorList, organizationIds }
|
||||
HOST_DATA = {
|
||||
"1060601019894960128": { # 陈国辉 — 1403艾斯 + 星中心物业
|
||||
"name": "陈国辉",
|
||||
"floorList": ["605560541473144832", "605560545117995008"], # 6F, 28F
|
||||
"organizationIds": [
|
||||
"72fb65ec5de94201b909a98b8bae1892", # 1403艾斯
|
||||
"f216235e54ca42bfa0379e69b3754aff", # 星中心物业
|
||||
],
|
||||
},
|
||||
"1090779433129840640": { # 王姣 — 1405一博环保 + 一博
|
||||
"name": "王姣",
|
||||
"floorList": ["605560542752407552", "605560543834537984"], # 15F, 20F
|
||||
"organizationIds": [
|
||||
"2095de3d541f44eba686c78fda68336f", # 1405一博环保
|
||||
"5c129c5eae114309933042d7f2006aa2", # 一博
|
||||
],
|
||||
},
|
||||
"1072908835884208128": { # 秦夏 — 广发基金 + 正式员工
|
||||
"name": "秦夏",
|
||||
"floorList": [
|
||||
"605560545117995008", # 28F
|
||||
"605560545449345024", # 30F
|
||||
"605560545596145664", # 31F
|
||||
"605560545738752000", # 32F
|
||||
"605560545893941248", # 33F
|
||||
],
|
||||
"organizationIds": [
|
||||
"488b8ad049bb43408a6fbcc50bcb89ac", # 广发基金
|
||||
"b549a73065374ecf871841544f329a98", # 正式员工
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.route("/component/person/detail", methods=["POST"])
|
||||
def person_detail():
|
||||
data = request.get_json(force=True, silent=True) or {}
|
||||
person_id = data.get("id", "")
|
||||
host = HOST_DATA.get(person_id)
|
||||
|
||||
if not host:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"code": "76260531",
|
||||
"message": f"person not found: {person_id}",
|
||||
"data": None,
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"code": "0",
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"id": person_id,
|
||||
"businessId": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"name": host["name"],
|
||||
"floorList": host["floorList"],
|
||||
"organizationIds": host["organizationIds"],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@app.route("/health", methods=["GET"])
|
||||
def health():
|
||||
return jsonify({"status": "UP"})
|
||||
|
||||
|
||||
@app.route("/sysetting/zone/page", methods=["POST"])
|
||||
def zone_page():
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"code": "0",
|
||||
"data": {
|
||||
"totalRows": 1,
|
||||
"currentPage": 1,
|
||||
"pageSize": 10,
|
||||
"datas": [{
|
||||
"id": "605560545117995008",
|
||||
"zoneId": "605560545117995008",
|
||||
"parentId": "605560539791228928",
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=18082, debug=False)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# 复制为 .env.quick_verify 并填入真实值(勿提交密钥)
|
||||
|
||||
# 组织服务与电梯服务地址
|
||||
ORG_BASE_URL=http://10.0.22.102:17016
|
||||
ELEVATOR_BASE_URL=http://10.0.22.207:16112
|
||||
|
||||
# 业务参数
|
||||
BUSINESS_ID=2524639890ba4f2cba9ba1a4eeaa4015
|
||||
MENG_PERSON_ID=
|
||||
VISITOR_PERSON_ID=
|
||||
WINDOW_HOURS=24
|
||||
|
||||
# 自动查询蒙海文 personId(可选)
|
||||
AUTO_QUERY_MENG_PERSON_ID=0
|
||||
HOST_NAME=蒙海文
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
DB_NAME=
|
||||
|
||||
# 鉴权头(auth 模式需要)
|
||||
ELEVATOR_HEADER_AUTHORIZATION=
|
||||
ELEVATOR_HEADER_LOGINID=
|
||||
ELEVATOR_HEADER_PLATFORMUSERID=
|
||||
ELEVATOR_HEADER_APPLICATIONID=
|
||||
|
||||
# noauth-probe 模式可选项
|
||||
# 1 表示 noauth-probe 时仍保留 businessid 头
|
||||
PROBE_WITH_BUSINESSID=1
|
||||
@@ -0,0 +1,25 @@
|
||||
# 复制为 .env.visitor_verify 并填入真实值(勿提交密钥)
|
||||
# MySQL:export_catalog 使用(双库:组织库 + 电梯库)
|
||||
MYSQL_HOST=192.168.3.12
|
||||
MYSQL_PORT=3307
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASSWORD=123456
|
||||
MYSQL_DB_ORG=component-organization
|
||||
MYSQL_DB_ELEVATOR=cw-elevator-application
|
||||
|
||||
# 已启动的 V2 电梯应用 HTTP 根地址(与对拍一致)
|
||||
ELEVATOR_VERIFY_BASE=http://127.0.0.1:18081
|
||||
|
||||
# 与 elevator_api_parity 相同请求头(须与 test_matrix 中目标租户 business_id 一致)
|
||||
ELEVATOR_HEADER_BUSINESSID=
|
||||
ELEVATOR_HEADER_AUTHORIZATION=
|
||||
# ELEVATOR_HEADER_LOGINID=
|
||||
# ELEVATOR_HEADER_PLATFORMUSERID=
|
||||
# ELEVATOR_HEADER_APPLICATIONID=
|
||||
|
||||
# 可选:固定访客 visitorId;否则用快照内 visitor_for_api 轮询配对
|
||||
# VISITOR_TEST_PERSON_ID=
|
||||
|
||||
# 联机前健康检查:VISITOR_SKIP_HEALTH_PROBE=1 跳过探测;VISITOR_REQUIRE_HEALTH=1 探测失败则退出
|
||||
# VISITOR_SKIP_HEALTH_PROBE=0
|
||||
# VISITOR_REQUIRE_HEALTH=0
|
||||
@@ -0,0 +1,106 @@
|
||||
# 访客楼层验证工具链
|
||||
|
||||
完整测试方案与计划见:`docs/testing/visitor-registration-floor-validation.md`
|
||||
|
||||
**被访人(员工)与访客 ID、`personId`/`visitorId` 含义及楼层求交逻辑**:`docs/testing/visitor-registration-business-flow.md`
|
||||
|
||||
**租户定制默认楼层与未配置租户隔离(业务边界 + 流程图)**:`docs/testing/tenant-visitor-default-floor-isolation.md`
|
||||
|
||||
## 一键验收(V2 程序)
|
||||
|
||||
需能访问 **MySQL**(组织库 + 电梯库)。联机时脚本会先检测 V2 是否可达;不可达则尝试自动启动 **Maven V2 电梯应用**(`cw-elevator-application-starter`)。
|
||||
|
||||
在同一目录创建 **`.env.visitor_verify`**(仅此文件名),填入 **`MYSQL_*`、`ELEVATOR_VERIFY_BASE`**,以及联机所需的 **`ELEVATOR_HEADER_AUTHORIZATION`**(等网关要求);**`ELEVATOR_HEADER_BUSINESSID` 可不写**——未设置时程序会从 `config/test_matrix.json` 的 **`tenant_primary_business_id`** 自动注入 HTTP 头 `businessid`。测**第二租户**时请加 **`--tenant secondary`**,或 **`--suite tenant_secondary_placeholder`**,或在 `.env` 中手写 `ELEVATOR_HEADER_BUSINESSID`。也可不设文件而在 shell 中 `export`。
|
||||
|
||||
**不同租户是否不同**:是。每个租户有独立的 **`business_id`**(组织/策略/人员数据隔离)。本矩阵中广发套件 `guangfa_fund_10` 与星河湾套件 `xinghewan_star_center` 当前配置为**同一** `business_id`(与 `tenant_primary_business_id` 一致);对照套件 **`tenant_secondary_placeholder`** 使用 **`tenant_secondary_business_id`**,与主租户不同。
|
||||
|
||||
一键脚本会优先加载 **`VISITOR_VERIFY_ENV_FILE`**(若设置),否则加载 **`./.env.visitor_verify`**。
|
||||
|
||||
| CLI / 环境 | 作用 |
|
||||
|------------|------|
|
||||
| (未设 `ELEVATOR_HEADER_BUSINESSID`) | 自动使用矩阵 **`tenant_primary_business_id`** |
|
||||
| **`--tenant primary`** / **`secondary`** | 显式使用矩阵顶层主/次租户 ID |
|
||||
| **`--suite <suites[].id>`** 或 **`VISITOR_SUITE_ID`** | 按套件取该行的 **`business_id`**(适合只验某一业务块) |
|
||||
|
||||
```bash
|
||||
cd maven-cw-elevator-application/tools/visitor_floor_verification
|
||||
chmod +x run_v2_visitor_default_floor_verify.sh
|
||||
./run_v2_visitor_default_floor_verify.sh
|
||||
```
|
||||
|
||||
`.env.visitor_verify` 请保存为 **LF** 换行;若在 Windows 记事本编辑产生 CRLF,脚本已改为加载时自动剥除 `\r`,亦可在本目录执行 `sed -i 's/\r$//' .env.visitor_verify` 修复。
|
||||
|
||||
脚本顺序:`**export_catalog.py`** → `**run_visitor_floor_suite.py --phase all**`(静态校验 + 联机注册与 `passRule/image` 回读),报告写入 `report/visitor-floor-suite-*.md`。
|
||||
|
||||
若出现 **`Can't connect to MySQL server on '127.0.0.1'`**:说明未连上库(本机无 MySQL 或 `.env` 未加载)。请设置可达的 `MYSQL_HOST`/`MYSQL_PORT`,或已有快照时用 **`./run_v2_visitor_default_floor_verify.sh --skip-export`** 仅联机验证。
|
||||
|
||||
**部门员工不足 10 人**:静态报告仅**提示**;联机阶段对**该部门已有全部员工**逐人测试(仍受 `--max-employees-per-department` 上限约束)。**无员工**的部门会记为失败并 SKIP 联机。
|
||||
|
||||
|
||||
| 脚本选项 | 含义 |
|
||||
| --------------- | -------------------------------------- |
|
||||
| `--skip-export` | 不跑导出,沿用现有 `data/catalog_snapshot.json` |
|
||||
| `--report-only` | 仅导出 + 静态 report,无联机(不需起 V2) |
|
||||
| `--smoke` | 联机极简:`--register-only` 且每部门 1 人 |
|
||||
| `--no-auto-start` | 关闭自动启动 V2(仅做探测,不拉起) |
|
||||
|
||||
|
||||
环境变量:`**VISITOR_SKIP_HEALTH_PROBE**`、`**VISITOR_REQUIRE_HEALTH**`、`**VISITOR_SKIP_PIP_INSTALL**` 见 `run_v2_visitor_default_floor_verify.sh` 内注释。联机阶段若出现 **`Connection refused`**:默认会先尝试自动启动 V2。自动启动命令可用 `VISITOR_V2_START_CMD` 覆盖(默认 `mvn -pl cw-elevator-application-starter -am spring-boot:run -Dspring-boot.run.profiles=access-control`),等待上限 `VISITOR_V2_START_MAX_WAIT_SEC`(默认 180s),日志 `VISITOR_V2_START_LOG`。完全关闭自动拉起可用 `--no-auto-start` 或 `VISITOR_AUTO_START_V2=0`。`**VISITOR_SKIP_LIVE_PREFLIGHT=1**` 可跳过 `run_visitor_floor_suite` 的 TCP 预检(仍会在首条业务请求失败;默认开启预检以免报告刷屏)。
|
||||
|
||||
联机参数:`**--max-employees-per-department**`(默认 10;冒烟可设为 **1** 减少 API 次数)。
|
||||
|
||||
**验收口径(与业务一致)**
|
||||
|
||||
- `POST /elevator/person/add/visitor`:完成访客派梯登记;请求里 `floorIds: []` 时由服务端取被访人 `floorList` 并与 `tenant_visitor_floor_policy` 求交后落库。
|
||||
- **HTTP 响应为 `Boolean`,不含允许楼层列表**;要对「注册后实际可访问楼层」做断言,须在注册成功后用 `**POST /elevator/passRule/image`**(`personId` = 访客 `visitorId`)回读 zone 列表。这是与 `tools/elevator_api_parity/scripts/verify_gf_visitor_default_floor.py` 同一套验收路径。
|
||||
- 若只做连通性/业务码冒烟(不测楼层):`**--register-only`**(不回读 passRule)。
|
||||
|
||||
## 目录
|
||||
|
||||
|
||||
| 路径 | 说明 |
|
||||
| ---------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `config/test_matrix.json` | 套件与组织节点(广发10 + 星河湾/星中心代表 + 第二租户占位) |
|
||||
| `data/catalog_snapshot.json` | `scripts/export_catalog.py` 生成,含每部门样例被访人 ID |
|
||||
| `scripts/export_catalog.py` | 从 MySQL 拉取策略、人员锚点,合并矩阵 |
|
||||
| `run_v2_visitor_default_floor_verify.sh` | **一键**:导出 catalog + `--phase all`(推荐) |
|
||||
| `scripts/run_visitor_floor_suite.py` | `report` / `live` / `**all`**(静态 + 联机合并报告);`--register-only` 仅注册 |
|
||||
| `report/` | 运行后 Markdown 报告(默认生成于此) |
|
||||
|
||||
|
||||
## 依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
联机阶段复用对拍请求头:`ELEVATOR_HEADER_BUSINESSID`、`ELEVATOR_HEADER_AUTHORIZATION` 等(见 `elevator_api_parity`)。
|
||||
|
||||
## 典型命令
|
||||
|
||||
```bash
|
||||
export MYSQL_HOST=192.168.3.12 MYSQL_PORT=3307 MYSQL_USER=root MYSQL_PASSWORD='***'
|
||||
export ELEVATOR_HEADER_BUSINESSID=2524639890ba4f2cba9ba1a4eeaa4015
|
||||
export ELEVATOR_HEADER_AUTHORIZATION='Bearer ...'
|
||||
export VISITOR_TEST_PERSON_ID='<测试访客 personId>'
|
||||
|
||||
# 推荐:一键(等同 export + --phase all;businessId 默认取矩阵主租户)
|
||||
./run_v2_visitor_default_floor_verify.sh
|
||||
|
||||
# 第二租户联机(businessId 取自 tenant_secondary_business_id;透传给 Python)
|
||||
./run_v2_visitor_default_floor_verify.sh --tenant secondary
|
||||
|
||||
python scripts/export_catalog.py
|
||||
python scripts/run_visitor_floor_suite.py --phase report --snapshot data/catalog_snapshot.json
|
||||
# 静态 + 联机合并为一份报告
|
||||
python scripts/run_visitor_floor_suite.py --phase all --base-url http://127.0.0.1:18081 \
|
||||
--visitor-person-id "$VISITOR_TEST_PERSON_ID"
|
||||
python scripts/run_visitor_floor_suite.py --phase live --base-url http://127.0.0.1:18081 \
|
||||
--visitor-person-id "$VISITOR_TEST_PERSON_ID"
|
||||
|
||||
# 仅访客注册(不测楼层明细)
|
||||
python scripts/run_visitor_floor_suite.py --phase live --base-url http://127.0.0.1:18081 \
|
||||
--register-only --max-employees-per-department 1
|
||||
```
|
||||
|
||||
第二租户联机需切换 `ELEVATOR_HEADER_BUSINESSID`(若现场无人员则套件自动 SKIP)。
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"version": 3,
|
||||
"notes": "cases 为「部门/公司」节点;export_catalog 为每个 org 拉取最多 employees_per_department 名在职员工,并为每名员工绑定轮询访客。单条 case 的 visitor_person_id 非 null 时该部门内所有员工共用该访客覆盖。",
|
||||
"terminology": {
|
||||
"host_employee": "被访人(员工),API personId,一人一条派梯开通请求",
|
||||
"visitor_person": "访客档案 visitorId",
|
||||
"department_case": "suite.cases[] 的一项,对应 cw_is_organization 的一个组织节点",
|
||||
"employees_per_department": "每个部门用例下导出的样例员工人数(默认 10)"
|
||||
},
|
||||
"export_settings": {
|
||||
"employees_per_department": 10,
|
||||
"visitor_pool_limit": 120,
|
||||
"sort_employees_by": "NAME_ASC"
|
||||
},
|
||||
"visitor_resolution": {
|
||||
"strategy": "organization_label",
|
||||
"label_id": "ed2dbab6d6234a7287106b80857c819e",
|
||||
"label_name": "访客",
|
||||
"pool_limit": 120,
|
||||
"assignment": "round_robin_across_all_employee_slots",
|
||||
"comment": "跨套件内所有「员工槽位」全局递增轮询访客池,降低同一访客连续绑定冲突概率。"
|
||||
},
|
||||
"tenant_primary_business_id": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"tenant_secondary_business_id": "9f19a307b3ea4854bf2d7dafe69649c9",
|
||||
"suites": [
|
||||
{
|
||||
"id": "guangfa_fund_10",
|
||||
"name": "广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)",
|
||||
"business_id": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"cases": [
|
||||
{"org_id": "488b8ad049bb43408a6fbcc50bcb89ac", "org_name": "[28-38F]广发基金管理有限公司", "visitor_person_id": null},
|
||||
{"org_id": "3a9b89a79bbb4c76a7da33b5a0697b79", "org_name": "32F-广发外包", "visitor_person_id": null},
|
||||
{"org_id": "e2cf7c91006542d59341e719ae655ac4", "org_name": "信息技术部", "visitor_person_id": null},
|
||||
{"org_id": "3238b26f4e6c48dd80ae27c159002d80", "org_name": "公司领导", "visitor_person_id": null},
|
||||
{"org_id": "265cc4eaa7434174807f1b7510b2f2c9", "org_name": "外包员工", "visitor_person_id": null},
|
||||
{"org_id": "8e9f88d051cf439287b7b3cfb75ebaac", "org_name": "外包员工", "visitor_person_id": null},
|
||||
{"org_id": "f276c04157ae46a0a237ad8c91e1da7e", "org_name": "外包员工", "visitor_person_id": null},
|
||||
{"org_id": "b549a73065374ecf871841544f329a98", "org_name": "正式员工", "visitor_person_id": null},
|
||||
{"org_id": "1a95bfa1bb6542c8b6c78a22f9f5441a", "org_name": "综合管理部", "visitor_person_id": null},
|
||||
{"org_id": "f83fda04e8f241b39f3f44ce6cf05a8e", "org_name": "绿植", "visitor_person_id": null}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "xinghewan_star_center",
|
||||
"name": "星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)",
|
||||
"business_id": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"cases": [
|
||||
{"org_id": "d656e3ab3f61440bb7b9bc23b76834b9", "org_name": "星河湾中心(根)", "visitor_person_id": null},
|
||||
{"org_id": "21474e012cd14e26bc62771873b22562", "org_name": "星河湾集团总部", "visitor_person_id": null},
|
||||
{"org_id": "348328d755624b3491cd307a3109f36a", "org_name": "星中心物管公司", "visitor_person_id": null}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tenant_secondary_placeholder",
|
||||
"name": "第二租户(对照,无策略;现场常无人员与访客池)",
|
||||
"business_id": "9f19a307b3ea4854bf2d7dafe69649c9",
|
||||
"cases": [
|
||||
{"org_id": "4be6d4f03a0b437690dc17dd9566c4b0", "org_name": "默认根节点", "visitor_person_id": null}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+1287
File diff suppressed because it is too large
Load Diff
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"test": "org_id policy fix verification",
|
||||
"timestamp": "2026-05-01T09:46:09.465192",
|
||||
"elevator_url": "http://127.0.0.1:18081",
|
||||
"mode": "noauth-probe",
|
||||
"business_id": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"summary": {
|
||||
"total": 7,
|
||||
"passed": 0,
|
||||
"failed": 7
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"id": "T1",
|
||||
"name": "有策略→allow替换floorList",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T2",
|
||||
"name": "无策略→floorList",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T3",
|
||||
"name": "allow含无效zone→拒绝",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T4",
|
||||
"name": "多组织命中第一个策略",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T5",
|
||||
"name": "enabled=0等同无策略",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T6",
|
||||
"name": "UC-02策略优先",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T7",
|
||||
"name": "广发基金迁移验证",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"test": "org_id policy fix verification",
|
||||
"timestamp": "2026-05-01T10:09:50.670898",
|
||||
"elevator_url": "http://127.0.0.1:18081",
|
||||
"mode": "noauth-probe",
|
||||
"business_id": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"summary": {
|
||||
"total": 7,
|
||||
"passed": 0,
|
||||
"failed": 7
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"id": "T1",
|
||||
"name": "有策略→allow替换floorList",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T2",
|
||||
"name": "无策略→floorList",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T3",
|
||||
"name": "allow含无效zone→拒绝",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T4",
|
||||
"name": "多组织命中第一个策略",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T5",
|
||||
"name": "enabled=0等同无策略",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T6",
|
||||
"name": "UC-02策略优先",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T7",
|
||||
"name": "广发基金迁移验证",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"test": "org_id policy fix verification",
|
||||
"timestamp": "2026-05-01T10:56:07.632004",
|
||||
"elevator_url": "http://127.0.0.1:18081",
|
||||
"mode": "noauth-probe",
|
||||
"business_id": "2524639890ba4f2cba9ba1a4eeaa4015",
|
||||
"summary": {
|
||||
"total": 7,
|
||||
"passed": 0,
|
||||
"failed": 7
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"id": "T1",
|
||||
"name": "有策略→allow替换floorList",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T2",
|
||||
"name": "无策略→floorList",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T3",
|
||||
"name": "allow含无效zone→拒绝",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "策略配置了被访人无权访问的楼层,请联系管理员",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T4",
|
||||
"name": "多组织命中第一个策略",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T5",
|
||||
"name": "enabled=0等同无策略",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T6",
|
||||
"name": "UC-02策略优先",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
{
|
||||
"id": "T7",
|
||||
"name": "广发基金迁移验证",
|
||||
"add_visitor": {
|
||||
"http_status": 200,
|
||||
"success": false,
|
||||
"code": "76260521",
|
||||
"message": "",
|
||||
"error": null
|
||||
},
|
||||
"passed": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
[INFO] Scanning for projects...
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Reactor Build Order:
|
||||
[INFO]
|
||||
[INFO] cw-elevator-application (Maven reactor) [pom]
|
||||
[INFO] cw-elevator-application-common [jar]
|
||||
[INFO] cw-elevator-application-data [jar]
|
||||
[INFO] cw-elevator-application-service [jar]
|
||||
[INFO] cw-elevator-application-web [jar]
|
||||
[INFO] cw-elevator-application-starter [jar]
|
||||
[INFO]
|
||||
[INFO] -------< cn.cloudwalk.elevator:cw-elevator-application-reactor >--------
|
||||
[INFO] Building cw-elevator-application (Maven reactor) 2.0-SNAPSHOT [1/6]
|
||||
[INFO] from pom.xml
|
||||
[INFO] --------------------------------[ pom ]---------------------------------
|
||||
[INFO]
|
||||
[INFO] >>> spring-boot:1.5.17.RELEASE:run (default-cli) > test-compile @ cw-elevator-application-reactor >>>
|
||||
[INFO]
|
||||
[INFO] --- enforcer:3.4.1:enforce (enforce-jdk8) @ cw-elevator-application-reactor ---
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Reactor Summary for cw-elevator-application (Maven reactor) 2.0-SNAPSHOT:
|
||||
[INFO]
|
||||
[INFO] cw-elevator-application (Maven reactor) ............ FAILURE [ 0.278 s]
|
||||
[INFO] cw-elevator-application-common ..................... SKIPPED
|
||||
[INFO] cw-elevator-application-data ....................... SKIPPED
|
||||
[INFO] cw-elevator-application-service .................... SKIPPED
|
||||
[INFO] cw-elevator-application-web ........................ SKIPPED
|
||||
[INFO] cw-elevator-application-starter .................... SKIPPED
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD FAILURE
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 0.857 s
|
||||
[INFO] Finished at: 2026-04-29T13:50:38+08:00
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-enforcer-plugin:3.4.1:enforce (enforce-jdk8) on project cw-elevator-application-reactor:
|
||||
[ERROR] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion failed with message:
|
||||
[ERROR] 与原始运行包一致须使用 JDK 8 启动 Maven;见 docs/build/ORIGINAL_BUILD_JDK.txt
|
||||
[ERROR] -> [Help 1]
|
||||
[ERROR]
|
||||
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
|
||||
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
|
||||
[ERROR]
|
||||
[ERROR] For more information about the errors and possible solutions, please read the following articles:
|
||||
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T04:49:08.634446+00:00
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金主体与子部门(共10个组织节点)
|
||||
- 用例数: 10;已解析被访人: 6
|
||||
- **缺少样例被访人 ORG** (4): 32F-广发外包(3a9b89a79bbb4c76a7da33b5a0697b79), 外包员工(265cc4eaa7434174807f1b7510b2f2c9), 外包员工(f276c04157ae46a0a237ad8c91e1da7e), 绿植(f83fda04e8f241b39f3f44ce6cf05a8e)
|
||||
|
||||
## xinghewan_star_center — 星河湾/星中心代表(非「广发」部门名)
|
||||
- 用例数: 3;已解析被访人: 1
|
||||
- **缺少样例被访人 ORG** (2): 星河湾中心(根)(d656e3ab3f61440bb7b9bc23b76834b9), 星中心物管公司(348328d755624b3491cd307a3109f36a)
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(策略对照,现场常无人员)
|
||||
- 用例数: 1;已解析被访人: 0
|
||||
- **缺少样例被访人 ORG** (1): 默认根节点(4be6d4f03a0b437690dc17dd9566c4b0)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T04:49:40.712703+00:00
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金主体与子部门(共10个组织节点)
|
||||
- 用例数: 10;已解析被访人: 10
|
||||
|
||||
## xinghewan_star_center — 星河湾/星中心代表(非「广发」部门名)
|
||||
- 用例数: 3;已解析被访人: 1
|
||||
- **缺少样例被访人 ORG** (2): 星河湾中心(根)(d656e3ab3f61440bb7b9bc23b76834b9), 星中心物管公司(348328d755624b3491cd307a3109f36a)
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(策略对照,现场常无人员)
|
||||
- 用例数: 1;已解析被访人: 0
|
||||
- **缺少样例被访人 ORG** (1): 默认根节点(4be6d4f03a0b437690dc17dd9566c4b0)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T04:55:17.266686+00:00
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金主体与子部门(共10个组织节点)
|
||||
- 用例数: 10;**被访人(员工)** 已解析: 10;**访客** 已解析: 10;visitor_pool_size=40
|
||||
|
||||
## xinghewan_star_center — 星河湾/星中心代表(非「广发」部门名)
|
||||
- 用例数: 3;**被访人(员工)** 已解析: 1;**访客** 已解析: 3;visitor_pool_size=40
|
||||
- **缺少被访人** (2): 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(策略对照,现场常无人员)
|
||||
- 用例数: 1;**被访人(员工)** 已解析: 0;**访客** 已解析: 0;visitor_pool_size=0
|
||||
- **缺少被访人** (1): 默认根节点
|
||||
- **缺少访客池/访客ID** (1): 默认根节点
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:03:16.922191+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **未满 10 人/部门或无员工**: 默认根节点
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# 访客楼层联机验收报告
|
||||
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
- Header businessId: `2524639890ba4f2cba9ba1a4eeaa4015`
|
||||
- 访客:使用 **host_employees[].visitor_for_api**(导出时全局轮询)
|
||||
- 每部门最多执行员工数: **1**(`--max-employees-per-department`)
|
||||
- 访客有效期(ms): 1777439006386 ~ 1777525406386 (窗口 1 天)
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3facb0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3fb6d0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3fa7d0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3fb0a0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3fb760>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3fad10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af3fbaf0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af4640a0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af464400>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af464760>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
### 星河湾中心(根) | org=d656e3ab3f61440bb7b9bc23b76834b9
|
||||
- SKIP:无员工(host_employees 空)
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xbf2af464ac0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
### 星中心物管公司 | org=348328d755624b3491cd307a3109f36a
|
||||
- SKIP:无员工(host_employees 空)
|
||||
|
||||
## tenant_secondary_placeholder — SKIP(business_id `9f19a307b3ea4854bf2d7dafe69649c9` ≠ 当前 Header)
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:03:16.922191+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **未满 10 人/部门或无员工**: 默认根节点
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# V2 访客注册默认楼层 — 完整验证报告(catalog 静态 + 联机)
|
||||
|
||||
- 快照: `data/catalog_snapshot.json`
|
||||
- BASE_URL: `http://127.0.0.1:9`
|
||||
|
||||
## 第一部分:catalog_snapshot 静态校验
|
||||
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:03:16.922191+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **未满 10 人/部门或无员工**: 默认根节点
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:联机验收(Maven V2 电梯应用)
|
||||
|
||||
错误: 请设置 ELEVATOR_HEADER_BUSINESSID
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# V2 访客注册默认楼层 — 完整验证报告(catalog 静态 + 联机)
|
||||
|
||||
- 快照: `/media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/反编译/maven-cw-elevator-application/tools/visitor_floor_verification/data/catalog_snapshot.json`
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
|
||||
## 第一部分:catalog_snapshot 静态校验
|
||||
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:29:03.865268+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **未满 10 人/部门或无员工**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **未满 10 人/部门或无员工**: 默认根节点
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:联机验收(Maven V2 电梯应用)
|
||||
|
||||
错误: 请设置 ELEVATOR_HEADER_BUSINESSID
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:32:49.290017+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **提示**(人数少于目标 10,联机时对**现有全部员工**跑用例,直至 `--max-employees-per-department` 上限): 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **无员工可测**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **无员工可测**: 默认根节点
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:32:49.290017+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **提示**(人数少于目标 10,联机时对**现有全部员工**跑用例,直至 `--max-employees-per-department` 上限): 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **无员工可测**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **无员工可测**: 默认根节点
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
# V2 访客注册默认楼层 — 完整验证报告(catalog 静态 + 联机)
|
||||
|
||||
- 快照: `/media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/反编译/maven-cw-elevator-application/tools/visitor_floor_verification/data/catalog_snapshot.json`
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
|
||||
## 第一部分:catalog_snapshot 静态校验
|
||||
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:40:04.272833+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **提示**(人数少于目标 10,联机时对**现有全部员工**跑用例,直至 `--max-employees-per-department` 上限): 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **无员工可测**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **无员工可测**: 默认根节点
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:联机验收(Maven V2 电梯应用)
|
||||
|
||||
# 访客楼层联机验收报告
|
||||
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
- Header businessId: `2524639890ba4f2cba9ba1a4eeaa4015`
|
||||
- **模式**: 注册 + `passRule/image` 回读(add/visitor 响应无楼层字段,回读用于校验策略落地)
|
||||
- 访客:使用 **host_employees[].visitor_for_api**(导出时全局轮询)
|
||||
- 每部门最多执行员工数: **10**(`--max-employees-per-department`)
|
||||
- 访客有效期(ms): 1777441204598 ~ 1777527604598 (窗口 1 天)
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ebf0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718f040>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fc70>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718f070>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ec20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718e0e0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fb50>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718c8b0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004130>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004490>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070047f0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ca60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718f940>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718e9b0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718de10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718eef0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fc40>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004340>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004070>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004b50>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004ee0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005240>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718f5b0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718f0d0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ec50>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718d420>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fd90>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718e950>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004d60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005270>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004f10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004b80>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070041c0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070043a0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070058d0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fa60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718f6a0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ff40>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718dea0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ef50>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fd30>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004520>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004070>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004b50>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004ee0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005240>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070054e0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005c30>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fe20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ef20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ed10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718cb80>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718feb0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fa60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070052a0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070052d0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004f70>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004be0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004250>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070055a0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005fc0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fa60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718feb0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718cb80>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ed10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ef20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fe20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005600>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004310>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004af0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004e80>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070051e0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005570>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007006320>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fe20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ef20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ed10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718cb80>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718feb0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fa60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005150>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070050f0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004bb0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004970>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004220>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004430>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007006680>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fa60>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718feb0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
### 星河湾中心(根) | org=d656e3ab3f61440bb7b9bc23b76834b9
|
||||
- SKIP:无员工(host_employees 空)
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[1]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718cb80>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[2]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ed10>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[3]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718ef20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[4]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x9900718fe20>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[5]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004070>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[6]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070049d0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[7]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007004cd0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[8]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x990070050c0>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[9]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005480>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[10]
|
||||
- **HTTP 异常**: `HTTPConnectionPool(host='127.0.0.1', port=18081): Max retries exceeded with url: /elevator/person/add/visitor (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x99007005120>: Failed to establish a new connection: [Errno 111] Connection refused'))`
|
||||
|
||||
### 星中心物管公司 | org=348328d755624b3491cd307a3109f36a
|
||||
- SKIP:无员工(host_employees 空)
|
||||
|
||||
## tenant_secondary_placeholder — SKIP(business_id `9f19a307b3ea4854bf2d7dafe69649c9` ≠ 当前 Header)
|
||||
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
# V2 访客注册默认楼层 — 完整验证报告(catalog 静态 + 联机)
|
||||
|
||||
- 快照: `/media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/反编译/maven-cw-elevator-application/tools/visitor_floor_verification/data/catalog_snapshot.json`
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
|
||||
## 第一部分:catalog_snapshot 静态校验
|
||||
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T05:55:23.111958+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **提示**(人数少于目标 10,联机时对**现有全部员工**跑用例,直至 `--max-employees-per-department` 上限): 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **无员工可测**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **无员工可测**: 默认根节点
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:联机验收(Maven V2 电梯应用)
|
||||
|
||||
# 访客楼层联机验收报告
|
||||
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
- Header businessId: `2524639890ba4f2cba9ba1a4eeaa4015`
|
||||
- **模式**: 注册 + `passRule/image` 回读(add/visitor 响应无楼层字段,回读用于校验策略落地)
|
||||
- 访客:使用 **host_employees[].visitor_for_api**(导出时全局轮询)
|
||||
- 每部门最多执行员工数: **10**(`--max-employees-per-department`)
|
||||
- 访客有效期(ms): 1777442123506 ~ 1777528523506 (窗口 1 天)
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099747442982129664` (杨麒澄)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[2]
|
||||
- **被访人** personId=`1068847941529210880` (丁世楠);**访客** visitorId=`1099741738995650560` (梁慧燕)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[3]
|
||||
- **被访人** personId=`1068848810849333248` (丁伟伦);**访客** visitorId=`1099741701406420992` (家长)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[4]
|
||||
- **被访人** personId=`1068847791264075776` (丁冬);**访客** visitorId=`1099741073113616384` (张科)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[5]
|
||||
- **被访人** personId=`1068847652277710848` (丁朝飞);**访客** visitorId=`1099739312806170624` (漆得意)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[6]
|
||||
- **被访人** personId=`1088406469587603456` (丁朝飞);**访客** visitorId=`1099738829331611648` (陈剑文)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[7]
|
||||
- **被访人** personId=`1068849132883800064` (丁泽);**访客** visitorId=`1099732079842168832` (王桢)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[8]
|
||||
- **被访人** personId=`1072909344981860352` (丘涛);**访客** visitorId=`1099730000078422016` (罗)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[9]
|
||||
- **被访人** personId=`1072908679215792128` (乔昕);**访客** visitorId=`1099726498095976448` (李景成)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[10]
|
||||
- **被访人** personId=`1098605389736841216` (买小虎);**访客** visitorId=`1099723374785040384` (潘江林)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099722869440008192` (罗紫琳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[2]
|
||||
- **被访人** personId=`1068847941529210880` (丁世楠);**访客** visitorId=`1099720468811988992` (贺)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[3]
|
||||
- **被访人** personId=`1068848810849333248` (丁伟伦);**访客** visitorId=`1099718117216731136` (贺总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[4]
|
||||
- **被访人** personId=`1068847791264075776` (丁冬);**访客** visitorId=`1099716725865431040` (胡宜涛)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[5]
|
||||
- **被访人** personId=`1068847652277710848` (丁朝飞);**访客** visitorId=`1099715882801917952` (胡宜涛)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[6]
|
||||
- **被访人** personId=`1088406469587603456` (丁朝飞);**访客** visitorId=`1099715567968948224` (贺兰梅)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[7]
|
||||
- **被访人** personId=`1068849132883800064` (丁泽);**访客** visitorId=`1099715263199727616` (张雷)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[8]
|
||||
- **被访人** personId=`1072909344981860352` (丘涛);**访客** visitorId=`1099710867300700160` (邓华全)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[9]
|
||||
- **被访人** personId=`1072908679215792128` (乔昕);**访客** visitorId=`1099709124079357952` (李冠贤)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[10]
|
||||
- **被访人** personId=`1098605389736841216` (买小虎);**访客** visitorId=`1099708757079248896` (周新良)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[1]
|
||||
- **被访人** personId=`883344816140783616` (余建新);**访客** visitorId=`1099708676152856576` (苏锟)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[2]
|
||||
- **被访人** personId=`883309597287321600` (刘泽培);**访客** visitorId=`1099708495178502144` (陈海川)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[3]
|
||||
- **被访人** personId=`1056144294592712704` (周沅宁);**访客** visitorId=`1099705142594342912` (庞德权)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[4]
|
||||
- **被访人** personId=`1056169001723752448` (李斌);**访客** visitorId=`1099704456968245248` (李晋杰)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[5]
|
||||
- **被访人** personId=`1080784292173844480` (林在华);**访客** visitorId=`1099702148918251520` (林展辉)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[6]
|
||||
- **被访人** personId=`1048191389378088960` (罗若凡);**访客** visitorId=`1099700458019639296` (彭翠莎)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[1]
|
||||
- **被访人** personId=`1053709690434818048` (刘格菘);**访客** visitorId=`1099697776815001600` (余狄坤)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[2]
|
||||
- **被访人** personId=`1073242528087642112` (向书娟);**访客** visitorId=`1099696249975083008` (柏湘莹)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[3]
|
||||
- **被访人** personId=`1053710981773258752` (孔伟英);**访客** visitorId=`1099694807172255744` (戴敬飞)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[4]
|
||||
- **被访人** personId=`1086327006411886592` (孙树明);**访客** visitorId=`1099694544146100224` (殷同学)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[5]
|
||||
- **被访人** personId=`1053721543890243584` (张敬晗);**访客** visitorId=`1099694298860359680` (倪晨宇)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[6]
|
||||
- **被访人** personId=`1053712831138361344` (张永梅);**访客** visitorId=`1099691021880020992` (李尚)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[7]
|
||||
- **被访人** personId=`1053721811994669056` (张芊);**访客** visitorId=`1099688191721574400` (刘述寒)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[8]
|
||||
- **被访人** personId=`1053706140334002176` (朱平);**访客** visitorId=`1099688000118853632` (祝王)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[9]
|
||||
- **被访人** personId=`1053711790963265536` (杨冬);**访客** visitorId=`1099682884284321792` (罗耀洛)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 公司领导 | org=3238b26f4e6c48dd80ae27c159002d80 — 员工[10]
|
||||
- **被访人** personId=`1053713520564498432` (杨柳);**访客** visitorId=`1099682648585408512` (米澄宇)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099679447214272512` (许津海)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[2]
|
||||
- **被访人** personId=`1068847941529210880` (丁世楠);**访客** visitorId=`1099677947251617792` (王先)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[3]
|
||||
- **被访人** personId=`1068848810849333248` (丁伟伦);**访客** visitorId=`1099674985134837760` (洪秀)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[4]
|
||||
- **被访人** personId=`1068847791264075776` (丁冬);**访客** visitorId=`1099674707492884480` (余意)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[5]
|
||||
- **被访人** personId=`1068847652277710848` (丁朝飞);**访客** visitorId=`1099661649391390720` (陈淑芳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[6]
|
||||
- **被访人** personId=`1088406469587603456` (丁朝飞);**访客** visitorId=`1099660229739646976` (张若妤)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[7]
|
||||
- **被访人** personId=`1068849132883800064` (丁泽);**访客** visitorId=`1099657320789970944` (伍婉婷)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[8]
|
||||
- **被访人** personId=`1072909344981860352` (丘涛);**访客** visitorId=`1099651188163874816` (廖雷志)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[9]
|
||||
- **被访人** personId=`1072908679215792128` (乔昕);**访客** visitorId=`1099648343363129344` (邢露尹)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=265cc4eaa7434174807f1b7510b2f2c9 — 员工[10]
|
||||
- **被访人** personId=`1098605389736841216` (买小虎);**访客** visitorId=`1099642597628022784` (袁丽杰)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[1]
|
||||
- **被访人** personId=`1079803295989600256` (刘宗样);**访客** visitorId=`1099642518692696064` (张珀瑜)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[2]
|
||||
- **被访人** personId=`1079385743295614976` (周伟国);**访客** visitorId=`1099641622336393216` (陈勇荣)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=8e9f88d051cf439287b7b3cfb75ebaac — 员工[3]
|
||||
- **被访人** personId=`1086575917009473536` (王一豪);**访客** visitorId=`1099639258208538624` (龙法蓉)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099639088993554432` (朱理轩)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[2]
|
||||
- **被访人** personId=`1068847941529210880` (丁世楠);**访客** visitorId=`1099636391379283968` (璐)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[3]
|
||||
- **被访人** personId=`1068848810849333248` (丁伟伦);**访客** visitorId=`1099636247477055488` (张总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[4]
|
||||
- **被访人** personId=`1068847791264075776` (丁冬);**访客** visitorId=`1099635545073741824` (张总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[5]
|
||||
- **被访人** personId=`1068847652277710848` (丁朝飞);**访客** visitorId=`1099635360935260160` (陈嘉恩)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[6]
|
||||
- **被访人** personId=`1088406469587603456` (丁朝飞);**访客** visitorId=`1099634924838211584` (郑颖彤)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[7]
|
||||
- **被访人** personId=`1068849132883800064` (丁泽);**访客** visitorId=`1099634519335604224` (刘倬宇)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[8]
|
||||
- **被访人** personId=`1072909344981860352` (丘涛);**访客** visitorId=`1099634395689013248` (项强)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[9]
|
||||
- **被访人** personId=`1072908679215792128` (乔昕);**访客** visitorId=`1099634311731613696` (王柯涵)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 外包员工 | org=f276c04157ae46a0a237ad8c91e1da7e — 员工[10]
|
||||
- **被访人** personId=`1098605389736841216` (买小虎);**访客** visitorId=`1099632973849317376` (陈剑云)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099631111267946496` (邓云靓)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[2]
|
||||
- **被访人** personId=`1068847941529210880` (丁世楠);**访客** visitorId=`1099625794236846080` (许依琳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[3]
|
||||
- **被访人** personId=`1068848810849333248` (丁伟伦);**访客** visitorId=`1099624546471874560` (林琳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[4]
|
||||
- **被访人** personId=`1068847791264075776` (丁冬);**访客** visitorId=`1099623021007847424` (黄总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[5]
|
||||
- **被访人** personId=`1088406469587603456` (丁朝飞);**访客** visitorId=`1099622218381639680` (政法)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[6]
|
||||
- **被访人** personId=`1068847652277710848` (丁朝飞);**访客** visitorId=`1099621556260253696` (何总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[7]
|
||||
- **被访人** personId=`1068849132883800064` (丁泽);**访客** visitorId=`1099621393543745536` (吴振华)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[8]
|
||||
- **被访人** personId=`1072909344981860352` (丘涛);**访客** visitorId=`1099620259184472064` (李总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[9]
|
||||
- **被访人** personId=`1072908679215792128` (乔昕);**访客** visitorId=`1099619894967767040` (王雪儿)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 正式员工 | org=b549a73065374ecf871841544f329a98 — 员工[10]
|
||||
- **被访人** personId=`1098605389736841216` (买小虎);**访客** visitorId=`1099619222679678976` (梁雪枫)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[1]
|
||||
- **被访人** personId=`1053719590191828992` (刘梓健);**访客** visitorId=`1099618834334633984` (李亦婷)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[2]
|
||||
- **被访人** personId=`1053719202151600128` (吴晓芳);**访客** visitorId=`1099618448406966272` (严烨辰)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[3]
|
||||
- **被访人** personId=`883307982669344768` (吴泽辉);**访客** visitorId=`1099618426923761664` (项总)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[4]
|
||||
- **被访人** personId=`1053971160586326016` (周洁);**访客** visitorId=`1099618304558968832` (严烨辰)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[5]
|
||||
- **被访人** personId=`1057760057170661376` (孙土华);**访客** visitorId=`1099617621098647552` (周金鑫)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[6]
|
||||
- **被访人** personId=`1057999061870809088` (宋鑫亮);**访客** visitorId=`1099612467318231040` (孙林佳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[7]
|
||||
- **被访人** personId=`1053349438057156608` (林媛);**访客** visitorId=`1099437398242344960` (陈志鹏)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[8]
|
||||
- **被访人** personId=`1057702933807828992` (樊群英);**访客** visitorId=`1099437386103861248` (陈志鹏)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[9]
|
||||
- **被访人** personId=`883294187892576256` (熊传杰);**访客** visitorId=`1099038425484607488` (殷梓婷)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 综合管理部 | org=1a95bfa1bb6542c8b6c78a22f9f5441a — 员工[10]
|
||||
- **被访人** personId=`1053719939640299520` (秦煜翔);**访客** visitorId=`1099037654995136512` (刘晨宇)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099034355080347648` (池)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[2]
|
||||
- **被访人** personId=`1068847941529210880` (丁世楠);**访客** visitorId=`1099029780063547392` (陈沺妮)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[3]
|
||||
- **被访人** personId=`1068848810849333248` (丁伟伦);**访客** visitorId=`1099013427889946624` (陈剑文)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[4]
|
||||
- **被访人** personId=`1068847791264075776` (丁冬);**访客** visitorId=`1099012152007667712` (彭诗雨)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[5]
|
||||
- **被访人** personId=`1068847652277710848` (丁朝飞);**访客** visitorId=`1099009991769141248` (刘潮洋)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[6]
|
||||
- **被访人** personId=`1088406469587603456` (丁朝飞);**访客** visitorId=`1098999371204358144` (朱雪涵)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[7]
|
||||
- **被访人** personId=`1068849132883800064` (丁泽);**访客** visitorId=`1098999298420621312` (卢颖瑜)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[8]
|
||||
- **被访人** personId=`1072909344981860352` (丘涛);**访客** visitorId=`1098999030270210048` (胡宜涛)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[9]
|
||||
- **被访人** personId=`1072908679215792128` (乔昕);**访客** visitorId=`1098998885168410624` (段斯健)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 绿植 | org=f83fda04e8f241b39f3f44ce6cf05a8e — 员工[10]
|
||||
- **被访人** personId=`1098605389736841216` (买小虎);**访客** visitorId=`1098729940020441088` (吉阳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
### 星河湾中心(根) | org=d656e3ab3f61440bb7b9bc23b76834b9
|
||||
- SKIP:无员工(host_employees 空)
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[1]
|
||||
- **被访人** personId=`653604973099094016` (丁玲玲);**访客** visitorId=`1098729823297302528` (古舞)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[2]
|
||||
- **被访人** personId=`1099708972838424576` (丁珍);**访客** visitorId=`1098729651397967872` (Jerry)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[3]
|
||||
- **被访人** personId=`653602652961116160` (万珍);**访客** visitorId=`1098729473639170048` (雅博)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[4]
|
||||
- **被访人** personId=`653608024367173632` (严青洲);**访客** visitorId=`1098729312258961408` (雅清)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[5]
|
||||
- **被访人** personId=`661237935575683072` (任伟);**访客** visitorId=`1098729122768842752` (择炬)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[6]
|
||||
- **被访人** personId=`703625240202227712` (伍汉锋);**访客** visitorId=`1098728963355783168` (梓)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[7]
|
||||
- **被访人** personId=`655015666482442240` (何劲涛);**访客** visitorId=`1098728828483891200` (王安妮)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[8]
|
||||
- **被访人** personId=`654048186582720512` (何嘉浩);**访客** visitorId=`1098728630298853376` (王璐)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[9]
|
||||
- **被访人** personId=`749970868603555840` (何夙慧);**访客** visitorId=`1098728473180057600` (任萌)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 星河湾集团总部 | org=21474e012cd14e26bc62771873b22562 — 员工[10]
|
||||
- **被访人** personId=`654738413823479808` (何恺);**访客** visitorId=`1098728319039533056` (敏华)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
### 星中心物管公司 | org=348328d755624b3491cd307a3109f36a
|
||||
- SKIP:无员工(host_employees 空)
|
||||
|
||||
## tenant_secondary_placeholder — SKIP(business_id `9f19a307b3ea4854bf2d7dafe69649c9` ≠ 当前 Header)
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# V2 访客注册默认楼层 — 完整验证报告(catalog 静态 + 联机)
|
||||
|
||||
- 快照: `/media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/反编译/maven-cw-elevator-application/tools/visitor_floor_verification/data/catalog_snapshot.json`
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
|
||||
## 第一部分:catalog_snapshot 静态校验
|
||||
|
||||
# catalog_snapshot 校验报告
|
||||
|
||||
- 导出时间: 2026-04-29T06:02:42.515202+00:00;snapshot version=3
|
||||
- 每部门目标员工数: **10**(见 export_settings)
|
||||
|
||||
- 启用策略行数: **1**
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- 部门用例数: 10;**员工槽位合计**: 89;visitor_pool_size=200
|
||||
- **提示**(人数少于目标 10,联机时对**现有全部员工**跑用例,直至 `--max-employees-per-department` 上限): 信息技术部(6/10), 外包员工(3/10)
|
||||
|
||||
## xinghewan_star_center — 星河湾 / 星中心 — 代表部门(每部门最多 10 名员工)
|
||||
- 部门用例数: 3;**员工槽位合计**: 10;visitor_pool_size=200
|
||||
- **无员工可测**: 星河湾中心(根), 星中心物管公司
|
||||
|
||||
## tenant_secondary_placeholder — 第二租户(对照,无策略;现场常无人员与访客池)
|
||||
- 部门用例数: 1;**员工槽位合计**: 0;visitor_pool_size=0
|
||||
- **无员工可测**: 默认根节点
|
||||
|
||||
---
|
||||
|
||||
## 第二部分:联机验收(Maven V2 电梯应用)
|
||||
|
||||
# 访客楼层联机验收报告
|
||||
|
||||
- BASE_URL: `http://127.0.0.1:18081`
|
||||
- Header businessId: `2524639890ba4f2cba9ba1a4eeaa4015`
|
||||
- **模式**: `register-only` — 仅调用 `/elevator/person/add/visitor`,**不**回读楼层明细
|
||||
- 访客:使用 **host_employees[].visitor_for_api**(导出时全局轮询)
|
||||
- 每部门最多执行员工数: **1**(`--max-employees-per-department`)
|
||||
- 访客有效期(ms): 1777442562924 ~ 1777528962924 (窗口 1 天)
|
||||
|
||||
## guangfa_fund_10 — 广发基金 — 主体公司及下属部门(10 个组织节点 × 每部门最多 10 名员工)
|
||||
- allow_zone_ids: `['605560545117995008']`
|
||||
|
||||
#### [28-38F]广发基金管理有限公司 | org=488b8ad049bb43408a6fbcc50bcb89ac — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099747442982129664` (杨麒澄)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 32F-广发外包 | org=3a9b89a79bbb4c76a7da33b5a0697b79 — 员工[1]
|
||||
- **被访人** personId=`1068848973891452928` (丁一凡);**访客** visitorId=`1099722869440008192` (罗紫琳)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
|
||||
#### 信息技术部 | org=e2cf7c91006542d59341e719ae655ac4 — 员工[1]
|
||||
- **被访人** personId=`883344816140783616` (余建新);**访客** visitorId=`1099708676152856576` (苏锟)
|
||||
- add/visitor: http=200 code=76260521
|
||||
- 响应摘要: `{"code":"76260521","success":false,"message":"","data":null}` …
|
||||
- **快速失败**: 已连续 3 次命中 `76260521`(空 message/null data)。
|
||||
- **疑似原因**: Cloudwalk 调用上下文缺失(常见于 `authorization/loginid/platformuserid/applicationid` 头缺失或无效、token 过期)。
|
||||
- **建议**: 用与前端同源的完整请求头重试;先用 `--smoke`(每部门 1 人)确认首条能成功后再全量。可设 `VISITOR_FAIL_FAST_ON_76260521=0` 关闭快速失败。
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
requests>=2.28.0
|
||||
PyMySQL>=1.1.0
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bash
|
||||
# 一键:基于 Maven V2 电梯应用(cw-elevator-application)做访客注册默认楼层验证。
|
||||
# 步骤:export_catalog(MySQL)→ --phase all(静态校验 + add/visitor + passRule/image)。
|
||||
#
|
||||
# 用法:
|
||||
# cd tools/visitor_floor_verification
|
||||
# ./run_v2_visitor_default_floor_verify.sh
|
||||
#
|
||||
# 可选:在本目录创建 .env.visitor_verify(见 README),由脚本自动 source。
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
TOOL_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$TOOL_ROOT"
|
||||
APP_ROOT="$(cd "$TOOL_ROOT/../.." && pwd)"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
一键:从 MySQL 导出 catalog,再对 V2 电梯服务做访客默认楼层验证(静态 + 联机)。
|
||||
|
||||
用法: ./run_v2_visitor_default_floor_verify.sh [选项] [ -- 透传 run_visitor_floor_suite 参数 ]
|
||||
|
||||
选项:
|
||||
--skip-export 跳过 export_catalog
|
||||
--report-only 仅导出 + 静态 report
|
||||
--smoke 联机仅注册、每部门 1 人(--register-only --max-employees-per-department 1)
|
||||
--no-auto-start 不自动拉起 V2(默认会在联机前探测并尝试启动)
|
||||
-h, --help 本说明
|
||||
|
||||
环境变量: MYSQL_* ELEVATOR_VERIFY_BASE、ELEVATOR_HEADER_*;VISITOR_SUITE_ID 等同 --suite
|
||||
可选: ./.env.visitor_verify 或 VISITOR_VERIFY_ENV_FILE
|
||||
EOF
|
||||
}
|
||||
|
||||
ENV_FILE="${VISITOR_VERIFY_ENV_FILE:-$TOOL_ROOT/.env.visitor_verify}"
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
# 兼容 Windows CRLF:直接 source 会因 \r 导致「未找到命令」
|
||||
source <(sed 's/\r$//' "$ENV_FILE")
|
||||
set +a
|
||||
echo "[visitor_floor_verify] 已加载: $ENV_FILE(MYSQL_HOST=${MYSQL_HOST:-未设置→默认127.0.0.1})"
|
||||
else
|
||||
echo "[visitor_floor_verify] 未找到 $ENV_FILE;使用进程环境变量或脚本默认值(MySQL 默认 127.0.0.1:3306)" >&2
|
||||
fi
|
||||
|
||||
SKIP_EXPORT=0
|
||||
REPORT_ONLY=0
|
||||
SMOKE=0
|
||||
NO_AUTO_START=0
|
||||
PASS_THROUGH=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--skip-export) SKIP_EXPORT=1 ;;
|
||||
--report-only) REPORT_ONLY=1 ;;
|
||||
--smoke) SMOKE=1 ;;
|
||||
--no-auto-start) NO_AUTO_START=1 ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) PASS_THROUGH+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "${VISITOR_SKIP_PIP_INSTALL:-0}" != "1" ]]; then
|
||||
export PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
python3 -m pip install -q -r "$TOOL_ROOT/requirements.txt" >/dev/null \
|
||||
|| {
|
||||
echo "[visitor_floor_verify] pip install 失败,请手动: pip install -r requirements.txt" >&2
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_EXPORT" -eq 0 ]]; then
|
||||
python3 "$TOOL_ROOT/scripts/export_catalog.py"
|
||||
else
|
||||
echo "[visitor_floor_verify] 跳过 export_catalog(--skip-export)"
|
||||
fi
|
||||
|
||||
BASE_URL="${ELEVATOR_VERIFY_BASE:-http://127.0.0.1:18081}"
|
||||
|
||||
SMOKE_ARGS=()
|
||||
if [[ "$SMOKE" -eq 1 ]]; then
|
||||
SMOKE_ARGS+=(--register-only --max-employees-per-department 1)
|
||||
fi
|
||||
|
||||
if [[ "$REPORT_ONLY" -eq 1 ]]; then
|
||||
exec python3 "$TOOL_ROOT/scripts/run_visitor_floor_suite.py" \
|
||||
--phase report \
|
||||
--snapshot "$TOOL_ROOT/data/catalog_snapshot.json" \
|
||||
"${SMOKE_ARGS[@]}" \
|
||||
"${PASS_THROUGH[@]}"
|
||||
fi
|
||||
|
||||
# businessId 由 Python 根据 test_matrix.json / --suite / --tenant 自动填充(见 README)
|
||||
|
||||
_probe_health() {
|
||||
local base="${1%/}"
|
||||
local p status
|
||||
for p in /actuator/health /health; do
|
||||
status="$(curl -s -o /tmp/vfv-health.$$.txt -w '%{http_code}' "${base}${p}" --max-time 5 || true)"
|
||||
if [[ "$status" == "200" ]] && grep -qiE 'up|"status"[[:space:]]*:[[:space:]]*"ok"' /tmp/vfv-health.$$.txt 2>/dev/null; then
|
||||
rm -f /tmp/vfv-health.$$.txt
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
rm -f /tmp/vfv-health.$$.txt
|
||||
return 1
|
||||
}
|
||||
|
||||
_start_v2_if_needed() {
|
||||
local base="$1"
|
||||
local auto_start_env="${VISITOR_AUTO_START_V2:-1}"
|
||||
if [[ "$NO_AUTO_START" -eq 1 || "$auto_start_env" == "0" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if _probe_health "$base"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local start_cmd="${VISITOR_V2_START_CMD:-mvn -pl cw-elevator-application-starter -am spring-boot:run -Dspring-boot.run.profiles=access-control}"
|
||||
local log_file="${VISITOR_V2_START_LOG:-$TOOL_ROOT/report/v2-autostart.log}"
|
||||
mkdir -p "$(dirname "$log_file")"
|
||||
|
||||
echo "[visitor_floor_verify] 检测到 V2 未就绪,尝试自动启动..."
|
||||
echo "[visitor_floor_verify] APP_ROOT=$APP_ROOT"
|
||||
echo "[visitor_floor_verify] START_CMD=$start_cmd"
|
||||
echo "[visitor_floor_verify] LOG=$log_file"
|
||||
|
||||
# 避免同一命令重复拉起导致端口/资源冲突
|
||||
local start_pid=""
|
||||
if pgrep -f "cw-elevator-application-starter.*spring-boot:run|cn.cloudwalk.elevator.ElevatorApplication" >/dev/null 2>&1; then
|
||||
echo "[visitor_floor_verify] 检测到疑似 V2 进程已在运行,先等待其就绪..."
|
||||
else
|
||||
nohup bash -lc "cd \"$APP_ROOT\" && $start_cmd" >"$log_file" 2>&1 &
|
||||
start_pid="$!"
|
||||
echo "[visitor_floor_verify] 已发起启动进程 PID=$start_pid"
|
||||
fi
|
||||
|
||||
local waited=0
|
||||
local max_wait="${VISITOR_V2_START_MAX_WAIT_SEC:-180}"
|
||||
while (( waited < max_wait )); do
|
||||
if _probe_health "$base"; then
|
||||
echo "[visitor_floor_verify] V2 自动启动完成并健康:$base"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "$start_pid" ]] && ! kill -0 "$start_pid" 2>/dev/null; then
|
||||
echo "[visitor_floor_verify] 错误: 自动启动进程已退出,V2 仍未就绪。" >&2
|
||||
echo "[visitor_floor_verify] 最近日志($log_file):" >&2
|
||||
sed -n '1,200p' "$log_file" >&2 || true
|
||||
if rg -q "RequireJavaVersion|须使用 JDK 8|enforce-jdk8" "$log_file" 2>/dev/null; then
|
||||
echo "[visitor_floor_verify] 检测到 JDK 版本不匹配:此项目要求 JDK 8。可在 .env.visitor_verify 设置 VISITOR_V2_START_CMD(含 JDK8 环境)后重试。" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
sleep 3
|
||||
waited=$((waited + 3))
|
||||
done
|
||||
|
||||
echo "[visitor_floor_verify] 错误: 自动启动后仍未就绪(等待 ${max_wait}s)。" >&2
|
||||
echo "[visitor_floor_verify] 请检查日志: $log_file" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${VISITOR_SKIP_HEALTH_PROBE:-0}" != "1" ]]; then
|
||||
_start_v2_if_needed "$BASE_URL" || exit 4
|
||||
if _probe_health "$BASE_URL"; then
|
||||
echo "[visitor_floor_verify] V2 健康检查通过: $BASE_URL"
|
||||
else
|
||||
echo "[visitor_floor_verify] 警告: 未探测到典型 actuator/health 200(仍可尝试联机)。跳过探测: VISITOR_SKIP_HEALTH_PROBE=1" >&2
|
||||
if [[ "${VISITOR_REQUIRE_HEALTH:-0}" == "1" ]]; then
|
||||
echo "[visitor_floor_verify] VISITOR_REQUIRE_HEALTH=1 已设置,退出。" >&2
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec python3 "$TOOL_ROOT/scripts/run_visitor_floor_suite.py" \
|
||||
--phase all \
|
||||
--base-url "$BASE_URL" \
|
||||
--snapshot "$TOOL_ROOT/data/catalog_snapshot.json" \
|
||||
"${SMOKE_ARGS[@]}" \
|
||||
"${PASS_THROUGH[@]}"
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+310
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
合并 config/test_matrix.json 与 MySQL,生成 data/catalog_snapshot.json。
|
||||
|
||||
员工:每个部门(case.org_id)最多导出 employees_per_department 人(direct org;不足则父组织补足)。
|
||||
访客:「访客」标签池;对每个员工槽位全局轮询分配 visitor_for_api。
|
||||
|
||||
环境变量:MYSQL_HOST MYSQL_PORT MYSQL_USER MYSQL_PASSWORD
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
_ROOT = Path(__file__).resolve().parents[1]
|
||||
_CFG = _ROOT / "config" / "test_matrix.json"
|
||||
_OUT = _ROOT / "data" / "catalog_snapshot.json"
|
||||
|
||||
|
||||
def _conn_params():
|
||||
return dict(
|
||||
host=os.environ.get("MYSQL_HOST", "127.0.0.1").strip(),
|
||||
port=int(os.environ.get("MYSQL_PORT", "3306")),
|
||||
user=os.environ.get("MYSQL_USER", "root").strip(),
|
||||
password=os.environ.get("MYSQL_PASSWORD", "").strip(),
|
||||
charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
def _print_mysql_unavailable(kw: dict, err: Exception) -> None:
|
||||
print(
|
||||
"MySQL 无法连接,export_catalog 中止。\n"
|
||||
f" 错误: {err}\n"
|
||||
f" 当前: MYSQL_HOST={kw.get('host')!r} MYSQL_PORT={kw.get('port')!r} "
|
||||
f"MYSQL_USER={kw.get('user')!r}\n"
|
||||
" 处理: 1) 确认本机/远程库已启动且网络可达 2) 在 .env.visitor_verify 中设置正确的 "
|
||||
"MYSQL_HOST、MYSQL_PORT、MYSQL_PASSWORD\n"
|
||||
" 或: 使用已有 data/catalog_snapshot.json 跳过导出 —\n"
|
||||
" ./run_v2_visitor_default_floor_verify.sh --skip-export",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def _fetch_person_name(cur, person_id: str) -> str | None:
|
||||
cur.execute(
|
||||
"SELECT NAME FROM cw_is_person WHERE ID = %s LIMIT 1",
|
||||
(person_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return str(row[0]) if row and row[0] is not None else None
|
||||
|
||||
|
||||
def _fetch_visitor_pool(cur, business_id: str, label_id: str, limit: int) -> list[dict[str, str]]:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT p.ID, p.NAME
|
||||
FROM cw_is_person p
|
||||
INNER JOIN cw_is_person_label_ref lr ON lr.PERSON_ID = p.ID
|
||||
WHERE lr.LABEL_ID = %s
|
||||
AND p.BUSINESS_ID = %s
|
||||
AND (p.IS_DEL = 0 OR p.IS_DEL IS NULL)
|
||||
ORDER BY p.LAST_UPDATE_TIME DESC
|
||||
LIMIT %s
|
||||
""",
|
||||
(label_id, business_id, limit),
|
||||
)
|
||||
out: list[dict[str, str]] = []
|
||||
for row in cur.fetchall():
|
||||
out.append({"person_id": str(row[0]), "person_name": str(row[1] or "")})
|
||||
return out
|
||||
|
||||
|
||||
def _pick_hosts(cur, org_id: str, limit: int) -> tuple[list[dict[str, str]], str]:
|
||||
"""返回 [{person_id, person_name}, ...] 与 resolution 标记。"""
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT p.ID, p.NAME
|
||||
FROM cw_is_person p
|
||||
INNER JOIN cw_is_person_organization_ref r ON r.PERSON_ID = p.ID
|
||||
WHERE r.ORG_ID = %s AND (p.IS_DEL = 0 OR p.IS_DEL IS NULL)
|
||||
ORDER BY p.NAME ASC
|
||||
LIMIT %s
|
||||
""",
|
||||
(org_id, limit),
|
||||
)
|
||||
rows = [{"person_id": str(a), "person_name": str(b or "")} for a, b in cur.fetchall()]
|
||||
if rows:
|
||||
return rows, "direct_org"
|
||||
cur.execute(
|
||||
"SELECT PARENT_ID FROM cw_is_organization WHERE ID = %s LIMIT 1",
|
||||
(org_id,),
|
||||
)
|
||||
prow = cur.fetchone()
|
||||
if not prow or not prow[0]:
|
||||
return [], "none"
|
||||
pid = prow[0]
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT p.ID, p.NAME
|
||||
FROM cw_is_person p
|
||||
INNER JOIN cw_is_person_organization_ref r ON r.PERSON_ID = p.ID
|
||||
WHERE r.ORG_ID = %s AND (p.IS_DEL = 0 OR p.IS_DEL IS NULL)
|
||||
ORDER BY p.NAME ASC
|
||||
LIMIT %s
|
||||
""",
|
||||
(pid, limit),
|
||||
)
|
||||
rows2 = [{"person_id": str(a), "person_name": str(b or "")} for a, b in cur.fetchall()]
|
||||
if rows2:
|
||||
return rows2, "parent_org_fallback"
|
||||
return [], "none"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
import pymysql
|
||||
from pymysql.err import OperationalError
|
||||
except ImportError:
|
||||
print("需要: pip install pymysql", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if not _CFG.is_file():
|
||||
print(f"缺少 {_CFG}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
matrix: dict[str, Any] = json.loads(_CFG.read_text(encoding="utf-8"))
|
||||
org_db = os.environ.get("MYSQL_DB_ORG", "component-organization")
|
||||
el_db = os.environ.get("MYSQL_DB_ELEVATOR", "cw-elevator-application")
|
||||
|
||||
es = matrix.get("export_settings") or {}
|
||||
emp_limit = int(es.get("employees_per_department") or 10)
|
||||
pool_cap = int(es.get("visitor_pool_limit") or 120)
|
||||
|
||||
vr = matrix.get("visitor_resolution") or {}
|
||||
label_id = str(vr.get("label_id") or "ed2dbab6d6234a7287106b80857c819e")
|
||||
pool_limit = max(int(vr.get("pool_limit") or 40), pool_cap, emp_limit * 20)
|
||||
|
||||
kw = _conn_params()
|
||||
snapshot: dict[str, Any] = {
|
||||
"version": 3,
|
||||
"exported_at": datetime.now(timezone.utc).isoformat(),
|
||||
"matrix_version": matrix.get("version"),
|
||||
"export_settings": {**es, "employees_per_department_resolved": emp_limit},
|
||||
"terminology": matrix.get("terminology"),
|
||||
"api_field_mapping": {
|
||||
"personId": "被访人(员工)人员主键,用于拉取 floorList",
|
||||
"visitorId": "访客人员主键,用于写 image_rule_ref.personId 与图库绑定",
|
||||
},
|
||||
"visitor_resolution": {**vr, "label_id_resolved": label_id, "pool_limit_resolved": pool_limit},
|
||||
"sources": {
|
||||
"organization_db": org_db,
|
||||
"elevator_db": el_db,
|
||||
"zone_names_from": "image_rule_ref.zone_id / zone_name",
|
||||
},
|
||||
"tenant_visitor_floor_policy": [],
|
||||
"zone_name_by_id": {},
|
||||
"suites": [],
|
||||
}
|
||||
|
||||
global_slot = 0
|
||||
|
||||
try:
|
||||
corg = pymysql.connect(database=org_db, **kw)
|
||||
except OperationalError as e:
|
||||
_print_mysql_unavailable(kw, e)
|
||||
return 2
|
||||
with corg:
|
||||
with corg.cursor() as cur:
|
||||
business_pools: dict[str, list[dict[str, str]]] = {}
|
||||
|
||||
for suite in matrix["suites"]:
|
||||
bid = str(suite["business_id"])
|
||||
if bid not in business_pools:
|
||||
business_pools[bid] = _fetch_visitor_pool(cur, bid, label_id, pool_limit)
|
||||
|
||||
for suite in matrix["suites"]:
|
||||
bid = str(suite["business_id"])
|
||||
pool = business_pools.get(bid) or []
|
||||
out_cases = []
|
||||
|
||||
for case in suite["cases"]:
|
||||
oid = case["org_id"]
|
||||
hosts_raw, how = _pick_hosts(cur, oid, emp_limit)
|
||||
|
||||
vis_override = case.get("visitor_person_id")
|
||||
|
||||
host_employees: list[dict[str, Any]] = []
|
||||
for hem in hosts_raw:
|
||||
he_copy = dict(hem)
|
||||
if vis_override:
|
||||
vid = str(vis_override)
|
||||
vname = _fetch_person_name(cur, vid) or ""
|
||||
vf = {
|
||||
"person_id": vid,
|
||||
"person_name": vname,
|
||||
"assignment": "matrix_override_dept_wide",
|
||||
}
|
||||
elif pool:
|
||||
p = pool[global_slot % len(pool)]
|
||||
global_slot += 1
|
||||
vf = {
|
||||
"person_id": p["person_id"],
|
||||
"person_name": p["person_name"],
|
||||
"assignment": "round_robin_global_slot",
|
||||
}
|
||||
else:
|
||||
vf = {
|
||||
"person_id": None,
|
||||
"person_name": None,
|
||||
"assignment": "none",
|
||||
"reason": "no_visitor_label_pool_for_business",
|
||||
}
|
||||
he_copy["visitor_for_api"] = vf
|
||||
host_employees.append(he_copy)
|
||||
|
||||
first = host_employees[0] if host_employees else {}
|
||||
first_vis = first.get("visitor_for_api") if first else {}
|
||||
first_host_id = first.get("person_id") if first else None
|
||||
|
||||
row_obj = {
|
||||
**case,
|
||||
"host_employees": host_employees,
|
||||
"employee_count": len(host_employees),
|
||||
"employee_target": emp_limit,
|
||||
"host_resolution": how,
|
||||
"host_employee": {
|
||||
"person_id": first.get("person_id"),
|
||||
"person_name": first.get("person_name"),
|
||||
"org_id": oid,
|
||||
"org_name": case.get("org_name"),
|
||||
"resolution": how,
|
||||
},
|
||||
"visitor_for_api": first_vis,
|
||||
"sample_host_person_id": first_host_id,
|
||||
"host_resolved": bool(first_host_id),
|
||||
}
|
||||
out_cases.append(row_obj)
|
||||
|
||||
snapshot["suites"].append(
|
||||
{
|
||||
"id": suite["id"],
|
||||
"name": suite["name"],
|
||||
"business_id": bid,
|
||||
"visitor_pool_size": len(pool),
|
||||
"cases": out_cases,
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
cel_pol = pymysql.connect(database=el_db, **kw)
|
||||
except OperationalError as e:
|
||||
_print_mysql_unavailable(kw, e)
|
||||
return 2
|
||||
with cel_pol:
|
||||
with cel_pol.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT business_id, policy_type, allow_zone_ids, enabled, building_id, remark
|
||||
FROM tenant_visitor_floor_policy
|
||||
WHERE enabled = 1
|
||||
"""
|
||||
)
|
||||
cols = [d[0] for d in cur.description]
|
||||
for row in cur.fetchall():
|
||||
snapshot["tenant_visitor_floor_policy"].append(dict(zip(cols, row)))
|
||||
|
||||
allow_ids: set[str] = set()
|
||||
for pol in snapshot["tenant_visitor_floor_policy"]:
|
||||
raw = pol.get("allow_zone_ids") or ""
|
||||
if not raw.strip():
|
||||
continue
|
||||
try:
|
||||
arr = json.loads(raw)
|
||||
if isinstance(arr, list):
|
||||
allow_ids.update(str(x) for x in arr if x is not None)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
if allow_ids:
|
||||
try:
|
||||
cel_zone = pymysql.connect(database=el_db, **kw)
|
||||
except OperationalError as e:
|
||||
_print_mysql_unavailable(kw, e)
|
||||
return 2
|
||||
with cel_zone:
|
||||
with cel_zone.cursor() as cur:
|
||||
ph = ",".join(["%s"] * len(allow_ids))
|
||||
cur.execute(
|
||||
f"""
|
||||
SELECT DISTINCT zone_id, zone_name FROM image_rule_ref
|
||||
WHERE zone_id IN ({ph}) LIMIT 50
|
||||
""",
|
||||
tuple(allow_ids),
|
||||
)
|
||||
for zid, zname in cur.fetchall():
|
||||
snapshot["zone_name_by_id"][str(zid)] = zname
|
||||
|
||||
_OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
_OUT.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"已写入 {_OUT}(每部门最多 {emp_limit} 名员工,全局访客槽位 {global_slot})")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
访客楼层策略生产快测脚本。
|
||||
|
||||
模式:
|
||||
- auth:标准鉴权调用(正式验收)
|
||||
- noauth-probe:无鉴权探测(安全审计,不作为业务通过依据)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PARITY_ROOT = ROOT.parent / "elevator_api_parity"
|
||||
if str(PARITY_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PARITY_ROOT))
|
||||
from parity.client import default_headers # noqa: E402
|
||||
|
||||
REPORT_DIR = ROOT / "report"
|
||||
OK_CODES = {"0", "200"}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="访客楼层策略生产快测脚本")
|
||||
parser.add_argument("--mode", choices=["auth", "noauth-probe"], default="auth")
|
||||
parser.add_argument("--org-base-url", required=True)
|
||||
parser.add_argument("--elevator-base-url", required=True)
|
||||
parser.add_argument(
|
||||
"--meng-person-id",
|
||||
default="964454497399468032",
|
||||
help="被访人 personId(仅接口调用,不访问数据库,默认蒙海文)",
|
||||
)
|
||||
parser.add_argument("--visitor-person-id", required=True)
|
||||
parser.add_argument("--business-id", default="2524639890ba4f2cba9ba1a4eeaa4015")
|
||||
parser.add_argument("--window-hours", type=int, default=24)
|
||||
parser.add_argument("--timeout-seconds", type=int, default=30)
|
||||
parser.add_argument("--strict-name-check", action="store_true")
|
||||
parser.add_argument("--host-name", default="蒙海文", help="被访人姓名(仅用于报告展示)")
|
||||
parser.add_argument("--print-visitor-sql-only", action="store_true", help="仅打印黄平手工查询 SQL 后退出")
|
||||
parser.add_argument(
|
||||
"--probe-with-businessid",
|
||||
action="store_true",
|
||||
help="仅 noauth-probe 模式下生效,是否保留 businessid 头",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def resolve_meng_person_id(args: argparse.Namespace) -> str:
|
||||
return args.meng_person_id
|
||||
|
||||
|
||||
def visitor_manual_sql(args: argparse.Namespace) -> str:
|
||||
return (
|
||||
"SELECT person_id, name, mobile, business_id, deleted, create_time, update_time\n"
|
||||
"FROM `component-organization`.`cw_is_person`\n"
|
||||
f"WHERE business_id = '{args.business_id}'\n"
|
||||
" AND name = '黄平'\n"
|
||||
" AND mobile = '13926442944'\n"
|
||||
"ORDER BY update_time DESC;"
|
||||
)
|
||||
|
||||
|
||||
def build_headers(args: argparse.Namespace) -> Dict[str, str]:
|
||||
if args.mode == "auth":
|
||||
os.environ["ELEVATOR_HEADER_BUSINESSID"] = args.business_id
|
||||
headers = default_headers()
|
||||
headers["Content-Type"] = "application/json"
|
||||
if "businessid" not in headers:
|
||||
headers["businessid"] = args.business_id
|
||||
return headers
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if args.probe_with_businessid:
|
||||
headers["businessid"] = args.business_id
|
||||
return headers
|
||||
|
||||
|
||||
def business_code(resp_json: Dict[str, Any] | None) -> str:
|
||||
if not isinstance(resp_json, dict):
|
||||
return ""
|
||||
value = resp_json.get("code")
|
||||
return "" if value is None else str(value)
|
||||
|
||||
|
||||
def is_business_ok(resp_json: Dict[str, Any] | None) -> bool:
|
||||
if not isinstance(resp_json, dict):
|
||||
return False
|
||||
code = business_code(resp_json)
|
||||
if code in OK_CODES:
|
||||
return True
|
||||
return resp_json.get("success") is True
|
||||
|
||||
|
||||
def call_step(
|
||||
session: requests.Session,
|
||||
url: str,
|
||||
headers: Dict[str, str],
|
||||
payload: Dict[str, Any],
|
||||
timeout_seconds: int,
|
||||
) -> Dict[str, Any]:
|
||||
response = session.post(url, json=payload, headers=headers, timeout=timeout_seconds)
|
||||
text = response.text or ""
|
||||
try:
|
||||
body = response.json()
|
||||
except Exception:
|
||||
body = None
|
||||
return {
|
||||
"url": url,
|
||||
"http_status": response.status_code,
|
||||
"request": payload,
|
||||
"response_text_head": text[:1200],
|
||||
"response_json": body,
|
||||
"business_code": business_code(body),
|
||||
"business_ok": is_business_ok(body),
|
||||
}
|
||||
|
||||
|
||||
def ts_range_ms(hours: int) -> tuple[int, int]:
|
||||
now = datetime.now(timezone.utc)
|
||||
return int(now.timestamp() * 1000), int((now + timedelta(hours=hours)).timestamp() * 1000)
|
||||
|
||||
|
||||
def classify(args: argparse.Namespace, steps: Dict[str, Dict[str, Any]]) -> tuple[str, str]:
|
||||
detail = steps["person_detail"]
|
||||
add = steps["add_visitor"]
|
||||
image = steps["passrule_image"]
|
||||
|
||||
if args.mode == "noauth-probe":
|
||||
statuses = [detail["http_status"], add["http_status"], image["http_status"]]
|
||||
if any(s in (401, 403) for s in statuses):
|
||||
return "expected_block", "无鉴权请求被拦截(401/403),符合安全预期"
|
||||
if add["http_status"] < 300 and add["business_ok"]:
|
||||
return "high_risk", "noauth-probe 下 add/visitor 业务成功,存在安全放开风险"
|
||||
return "needs_review", "noauth-probe 返回非拦截但未成功,需要人工复核网关策略"
|
||||
|
||||
if detail["http_status"] != 200 or add["http_status"] != 200 or image["http_status"] != 200:
|
||||
return "failed", "auth 模式存在 HTTP 异常"
|
||||
add_code = add["business_code"]
|
||||
if add["business_ok"] or add_code == "76260532":
|
||||
return "passed", "auth 模式调用链路正常(含交集为空预期失败码)"
|
||||
return "failed", "auth 模式业务码异常"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
if args.print_visitor_sql_only:
|
||||
print(visitor_manual_sql(args))
|
||||
return 0
|
||||
effective_meng_person_id = resolve_meng_person_id(args)
|
||||
headers = build_headers(args)
|
||||
session = requests.Session()
|
||||
started_at = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
begin_ms, end_ms = ts_range_ms(args.window_hours)
|
||||
detail_payload = {"id": effective_meng_person_id, "businessId": args.business_id}
|
||||
add_payload = {
|
||||
"visitorId": args.visitor_person_id,
|
||||
"personId": effective_meng_person_id,
|
||||
"begVisitorTime": begin_ms,
|
||||
"endVisitorTime": end_ms,
|
||||
"floorIds": [],
|
||||
}
|
||||
image_payload = {
|
||||
"personId": args.visitor_person_id,
|
||||
"businessId": args.business_id,
|
||||
"imageStoreIds": [],
|
||||
"includeOrganizations": [],
|
||||
"includeLabels": [],
|
||||
}
|
||||
|
||||
steps = {
|
||||
"person_detail": call_step(
|
||||
session,
|
||||
args.org_base_url.rstrip("/") + "/component/person/detail",
|
||||
headers,
|
||||
detail_payload,
|
||||
args.timeout_seconds,
|
||||
),
|
||||
"add_visitor": call_step(
|
||||
session,
|
||||
args.elevator_base_url.rstrip("/") + "/elevator/person/add/visitor",
|
||||
headers,
|
||||
add_payload,
|
||||
args.timeout_seconds,
|
||||
),
|
||||
"passrule_image": call_step(
|
||||
session,
|
||||
args.elevator_base_url.rstrip("/") + "/elevator/passRule/image",
|
||||
headers,
|
||||
image_payload,
|
||||
args.timeout_seconds,
|
||||
),
|
||||
}
|
||||
|
||||
detail_data = (steps["person_detail"]["response_json"] or {}).get("data") or {}
|
||||
detail_name = detail_data.get("name")
|
||||
detail_floor_list = detail_data.get("floorList")
|
||||
strict_name_ok = (detail_name == "蒙海文") if args.strict_name_check else True
|
||||
|
||||
grade, summary = classify(args, steps)
|
||||
report = {
|
||||
"started_at": started_at,
|
||||
"mode": args.mode,
|
||||
"grade": grade,
|
||||
"summary": summary,
|
||||
"args": {
|
||||
"org_base_url": args.org_base_url,
|
||||
"elevator_base_url": args.elevator_base_url,
|
||||
"business_id": args.business_id,
|
||||
"meng_person_id": effective_meng_person_id,
|
||||
"visitor_person_id": args.visitor_person_id,
|
||||
"window_hours": args.window_hours,
|
||||
"probe_with_businessid": args.probe_with_businessid,
|
||||
"host_name": args.host_name,
|
||||
},
|
||||
"headers_used": {k: ("***" if k.lower() == "authorization" else v) for k, v in headers.items()},
|
||||
"derived": {
|
||||
"detail_name": detail_name,
|
||||
"detail_floor_count": len(detail_floor_list) if isinstance(detail_floor_list, list) else 0,
|
||||
"strict_name_ok": strict_name_ok,
|
||||
"visitor_manual_sql": visitor_manual_sql(args),
|
||||
},
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
REPORT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
report_file = REPORT_DIR / f"quick-verify-{stamp}.json"
|
||||
report_file.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
print("=== 访客楼层策略快测结果 ===")
|
||||
print(f"mode: {args.mode}")
|
||||
print(f"grade: {grade}")
|
||||
print(f"summary: {summary}")
|
||||
print(f"report: {report_file}")
|
||||
print(f"detail_name: {detail_name}")
|
||||
print(f"detail_floor_count: {report['derived']['detail_floor_count']}")
|
||||
print(f"add_visitor_status/code: {steps['add_visitor']['http_status']}/{steps['add_visitor']['business_code']}")
|
||||
print(f"passrule_image_status/code: {steps['passrule_image']['http_status']}/{steps['passrule_image']['business_code']}")
|
||||
|
||||
if args.mode == "auth":
|
||||
return 0 if grade == "passed" and strict_name_ok else 1
|
||||
return 0 if grade in {"expected_block", "needs_review"} else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+598
@@ -0,0 +1,598 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
访客楼层验证套件执行器。
|
||||
|
||||
--phase report 仅校验 catalog_snapshot.json(套件完整性、策略行、主机缺口)
|
||||
--phase all 先 report 再 live,合并为一份 Markdown(一键 V2 验收推荐)
|
||||
--phase live 访客注册验收(默认):
|
||||
1) POST /elevator/person/add/visitor(floorIds 为空,由服务端按被访人 floorList
|
||||
与 tenant_visitor_floor_policy 求交后落库)
|
||||
2) POST /elevator/passRule/image(personId=visitorId)回读该访客已生效的 zone 列表
|
||||
|
||||
说明:add/visitor 的 HTTP 契约返回 CloudwalkResult<Boolean>,响应体不含楼层明细;要对「允许访问楼层」
|
||||
做断言,必须通过注册写入后的规则回读(与本仓库 elevator_api_parity/scripts/verify_gf_visitor_default_floor.py
|
||||
一致)。若仅需连通性/业务码冒烟,可加 --register-only 跳过回读。
|
||||
|
||||
联机需 HTTP 头 businessId 与当前验收租户一致;可从环境变量、--suite / --tenant 或矩阵 tenant_primary_business_id 自动填充(见 resolve_header_business_id)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
_ROOT = Path(__file__).resolve().parents[1]
|
||||
_PARITY = _ROOT.parent / "elevator_api_parity"
|
||||
if str(_PARITY) not in sys.path:
|
||||
sys.path.insert(0, str(_PARITY))
|
||||
from parity.client import default_headers # noqa: E402
|
||||
|
||||
REPORT_DIR = _ROOT / "report"
|
||||
SNAP_DEFAULT = _ROOT / "data" / "catalog_snapshot.json"
|
||||
MATRIX_DEFAULT = _ROOT / "config" / "test_matrix.json"
|
||||
CODE_OK = {"0", "200"}
|
||||
|
||||
|
||||
def resolve_header_business_id(suite_id: str, tenant_key: str) -> None:
|
||||
"""写入 os.environ['ELEVATOR_HEADER_BUSINESSID']。
|
||||
|
||||
优先级:suite_id(含 CLI --suite 或 env VISITOR_SUITE_ID)> tenant_key(--tenant)>
|
||||
已有 ELEVATOR_HEADER_BUSINESSID > test_matrix tenant_primary_business_id。
|
||||
"""
|
||||
if suite_id and tenant_key:
|
||||
print("错误: 不要同时使用 --suite(或 VISITOR_SUITE_ID)与 --tenant", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
if not MATRIX_DEFAULT.is_file():
|
||||
if suite_id or tenant_key:
|
||||
print(f"缺少矩阵文件: {MATRIX_DEFAULT}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
return
|
||||
|
||||
matrix = json.loads(MATRIX_DEFAULT.read_text(encoding="utf-8"))
|
||||
|
||||
if suite_id:
|
||||
for s in matrix.get("suites") or []:
|
||||
if s.get("id") == suite_id:
|
||||
bid = str(s.get("business_id") or "").strip()
|
||||
if not bid:
|
||||
print(f"套件 {suite_id!r} 未配置 business_id", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
os.environ["ELEVATOR_HEADER_BUSINESSID"] = bid
|
||||
print(
|
||||
f"[visitor_floor_suite] ELEVATOR_HEADER_BUSINESSID={bid}(套件 id={suite_id})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
print(f"test_matrix.json 中无 suites[].id == {suite_id!r}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
if tenant_key:
|
||||
if tenant_key == "primary":
|
||||
bid = str(matrix.get("tenant_primary_business_id") or "").strip()
|
||||
label = "tenant_primary_business_id"
|
||||
elif tenant_key == "secondary":
|
||||
bid = str(matrix.get("tenant_secondary_business_id") or "").strip()
|
||||
label = "tenant_secondary_business_id"
|
||||
else:
|
||||
print(f"--tenant 仅支持 primary / secondary,收到 {tenant_key!r}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
if not bid:
|
||||
print(f"test_matrix.json 缺少 {label}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
os.environ["ELEVATOR_HEADER_BUSINESSID"] = bid
|
||||
print(f"[visitor_floor_suite] ELEVATOR_HEADER_BUSINESSID={bid}(--tenant {tenant_key})", file=sys.stderr)
|
||||
return
|
||||
|
||||
if os.environ.get("ELEVATOR_HEADER_BUSINESSID", "").strip():
|
||||
return
|
||||
|
||||
bid = str(matrix.get("tenant_primary_business_id") or "").strip()
|
||||
if bid:
|
||||
os.environ["ELEVATOR_HEADER_BUSINESSID"] = bid
|
||||
print(
|
||||
f"[visitor_floor_suite] 未设置 ELEVATOR_HEADER_BUSINESSID,已自动使用 test_matrix.json "
|
||||
f"tenant_primary_business_id={bid}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def _ts_ms_range(window_days: int) -> tuple[int, int]:
|
||||
now = datetime.now(timezone.utc)
|
||||
start = int(now.timestamp() * 1000)
|
||||
end = int((now + timedelta(days=window_days)).timestamp() * 1000)
|
||||
return start, end
|
||||
|
||||
|
||||
def _preflight_base_url(session: requests.Session, base_url: str) -> tuple[bool, str]:
|
||||
"""联机前探测 BASE_URL 是否可达(避免 Connection refused 时刷屏数百条相同错误)。"""
|
||||
root = base_url.rstrip("/") + "/"
|
||||
try:
|
||||
session.get(root, timeout=5, allow_redirects=True)
|
||||
return True, ""
|
||||
except requests.exceptions.ConnectionError:
|
||||
return (
|
||||
False,
|
||||
"无法建立到该地址的 TCP 连接(常见错误:**Connection refused**)。\n"
|
||||
"1) 在运行联机用例的机器上**启动** V2 电梯应用(如 `cw-elevator-application-starter`),并确认日志中的 **监听端口**。\n"
|
||||
"2) 将 **ELEVATOR_VERIFY_BASE** 或 **`--base-url`** 改为实际 `http://主机:端口`(默认 18081 仅作示例)。\n"
|
||||
"3) 设置 **VISITOR_SKIP_LIVE_PREFLIGHT=1** 可跳过本预检(不建议;仅无服务时的调试)。",
|
||||
)
|
||||
except requests.exceptions.SSLError as e:
|
||||
return False, f"TLS/HTTPS 错误: {e}"
|
||||
except requests.exceptions.Timeout:
|
||||
return False, "连接 BASE_URL 超时(对端无响应或网络不通)。"
|
||||
except requests.RequestException as e:
|
||||
return False, f"预检异常: {e}"
|
||||
|
||||
|
||||
def _post_json(
|
||||
session: requests.Session, base: str, path: str, body: dict
|
||||
) -> tuple[int, dict | None, str]:
|
||||
url = base.rstrip("/") + path
|
||||
h = {**default_headers(), **dict(session.headers)}
|
||||
r = session.post(
|
||||
url,
|
||||
headers=h,
|
||||
data=json.dumps(body, ensure_ascii=False),
|
||||
timeout=120,
|
||||
)
|
||||
txt = r.text or ""
|
||||
try:
|
||||
j = json.loads(txt) if txt.strip() else None
|
||||
except json.JSONDecodeError:
|
||||
j = None
|
||||
return r.status_code, j if isinstance(j, dict) else None, txt
|
||||
|
||||
|
||||
def _business_code(js: dict | None) -> str | None:
|
||||
if js is None or "code" not in js:
|
||||
return None
|
||||
return str(js.get("code"))
|
||||
|
||||
|
||||
def allow_zone_ids_from_snapshot(snapshot: dict, business_id: str) -> list[str]:
|
||||
for pol in snapshot.get("tenant_visitor_floor_policy") or []:
|
||||
if str(pol.get("business_id")) != str(business_id):
|
||||
continue
|
||||
raw = pol.get("allow_zone_ids") or ""
|
||||
try:
|
||||
arr = json.loads(raw)
|
||||
if isinstance(arr, list):
|
||||
return [str(x) for x in arr if x is not None]
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
def phase_report(snapshot_path: Path) -> tuple[list[str], int]:
|
||||
lines: list[str] = []
|
||||
snap = json.loads(snapshot_path.read_text(encoding="utf-8"))
|
||||
lines.append("# catalog_snapshot 校验报告")
|
||||
lines.append("")
|
||||
lines.append(f"- 导出时间: {snap.get('exported_at')};snapshot version={snap.get('version')}")
|
||||
es = snap.get("export_settings") or {}
|
||||
target = int(es.get("employees_per_department_resolved") or es.get("employees_per_department") or 10)
|
||||
lines.append(f"- 每部门目标员工数: **{target}**(见 export_settings)")
|
||||
lines.append("")
|
||||
exit_code = 0
|
||||
|
||||
pol = snap.get("tenant_visitor_floor_policy") or []
|
||||
lines.append(f"- 启用策略行数: **{len(pol)}**")
|
||||
if not pol:
|
||||
lines.append("- **警告**: 未配置租户策略,求交逻辑将退化为「仅 floorList」。")
|
||||
exit_code = 1
|
||||
|
||||
for suite in snap.get("suites") or []:
|
||||
sid = suite.get("id")
|
||||
lines.append("")
|
||||
lines.append(f"## {sid} — {suite.get('name')}")
|
||||
cases = suite.get("cases") or []
|
||||
|
||||
tot_emp = 0
|
||||
zero_employee_orgs: list[str] = []
|
||||
partial_employee_orgs: list[str] = []
|
||||
no_vis_orgs: list[str] = []
|
||||
|
||||
for c in cases:
|
||||
emps = c.get("host_employees") or []
|
||||
n = len(emps)
|
||||
tot_emp += n
|
||||
if n == 0:
|
||||
zero_employee_orgs.append(c.get("org_name") or "(未命名部门)")
|
||||
elif n < target:
|
||||
partial_employee_orgs.append(f"{c.get('org_name')}({n}/{target})")
|
||||
|
||||
ok_vis = True
|
||||
for hem in emps:
|
||||
vf = hem.get("visitor_for_api") or {}
|
||||
if not vf.get("person_id"):
|
||||
ok_vis = False
|
||||
break
|
||||
if emps and not ok_vis:
|
||||
no_vis_orgs.append(c.get("org_name") or "")
|
||||
|
||||
lines.append(
|
||||
f"- 部门用例数: {len(cases)};**员工槽位合计**: {tot_emp};visitor_pool_size={suite.get('visitor_pool_size', 'n/a')}"
|
||||
)
|
||||
if partial_employee_orgs:
|
||||
lines.append(
|
||||
f"- **提示**(人数少于目标 {target},联机时对**现有全部员工**跑用例,直至 `--max-employees-per-department` 上限): "
|
||||
+ ", ".join(partial_employee_orgs[:12])
|
||||
)
|
||||
if len(partial_employee_orgs) > 12:
|
||||
lines.append(" …")
|
||||
if zero_employee_orgs:
|
||||
lines.append(f"- **无员工可测**: " + ", ".join(zero_employee_orgs[:12]))
|
||||
if len(zero_employee_orgs) > 12:
|
||||
lines.append(" …")
|
||||
exit_code = 1
|
||||
if no_vis_orgs:
|
||||
lines.append(f"- **存在员工但访客未解析**: " + ", ".join(no_vis_orgs[:8]))
|
||||
exit_code = 1
|
||||
|
||||
return lines, exit_code
|
||||
|
||||
|
||||
def phase_live(
|
||||
snapshot_path: Path,
|
||||
base_url: str,
|
||||
visitor_person_id: str,
|
||||
window_days: int,
|
||||
zone_name_regex: str,
|
||||
skip_secondary_mismatch: bool,
|
||||
max_employees_per_department: int,
|
||||
register_only: bool,
|
||||
) -> tuple[list[str], int]:
|
||||
lines: list[str] = []
|
||||
snap = json.loads(snapshot_path.read_text(encoding="utf-8"))
|
||||
header_biz = os.environ.get("ELEVATOR_HEADER_BUSINESSID", "").strip()
|
||||
if not header_biz:
|
||||
return [
|
||||
"错误: 未设置 **ELEVATOR_HEADER_BUSINESSID**(HTTP 头 `businessid`,与目标租户一致)。",
|
||||
"请在运行环境中 export,或在 `tools/visitor_floor_verification/.env.visitor_verify` 中填写该变量后重新执行一键脚本。",
|
||||
], 2
|
||||
use_global_visitor = bool(visitor_person_id.strip())
|
||||
|
||||
beg, end = _ts_ms_range(window_days)
|
||||
session = requests.Session()
|
||||
session.headers.update(default_headers())
|
||||
|
||||
lines.append("# 访客楼层联机验收报告")
|
||||
lines.append("")
|
||||
lines.append(f"- BASE_URL: `{base_url}`")
|
||||
lines.append(f"- Header businessId: `{header_biz}`")
|
||||
if register_only:
|
||||
lines.append("- **模式**: `register-only` — 仅调用 `/elevator/person/add/visitor`,**不**回读楼层明细")
|
||||
else:
|
||||
lines.append(
|
||||
"- **模式**: 注册 + `passRule/image` 回读(add/visitor 响应无楼层字段,回读用于校验策略落地)"
|
||||
)
|
||||
if use_global_visitor:
|
||||
lines.append(f"- **全局固定访客** visitorId: `{visitor_person_id}`(覆盖每名员工上的 visitor_for_api)")
|
||||
else:
|
||||
lines.append("- 访客:使用 **host_employees[].visitor_for_api**(导出时全局轮询)")
|
||||
lines.append(f"- 每部门最多执行员工数: **{max_employees_per_department}**(`--max-employees-per-department`)")
|
||||
lines.append(f"- 访客有效期(ms): {beg} ~ {end} (窗口 {window_days} 天)")
|
||||
lines.append("")
|
||||
|
||||
try:
|
||||
cre = re.compile(zone_name_regex)
|
||||
except re.error as e:
|
||||
return [f"zone-name-regex 无效: {e}"], 2
|
||||
|
||||
fail_fast_76260521 = os.environ.get("VISITOR_FAIL_FAST_ON_76260521", "1").strip().lower() not in (
|
||||
"0",
|
||||
"false",
|
||||
"no",
|
||||
)
|
||||
streak_76260521 = 0
|
||||
|
||||
skip_pf = os.environ.get("VISITOR_SKIP_LIVE_PREFLIGHT", "").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
)
|
||||
if not skip_pf:
|
||||
ok_pf, err_pf = _preflight_base_url(session, base_url)
|
||||
if not ok_pf:
|
||||
lines.append("### 联机前置检查失败")
|
||||
lines.append("")
|
||||
for block in err_pf.split("\n"):
|
||||
t = block.strip()
|
||||
if t:
|
||||
lines.append(f"- {t}")
|
||||
lines.append("")
|
||||
return lines, 1
|
||||
|
||||
exit_code = 0
|
||||
for suite in snap.get("suites") or []:
|
||||
sbiz = str(suite.get("business_id") or "")
|
||||
if sbiz != header_biz:
|
||||
if skip_secondary_mismatch or suite.get("id") == "tenant_secondary_placeholder":
|
||||
lines.append(f"## {suite.get('id')} — SKIP(business_id `{sbiz}` ≠ 当前 Header)")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
allow_ids = allow_zone_ids_from_snapshot(snap, sbiz)
|
||||
lines.append(f"## {suite.get('id')} — {suite.get('name')}")
|
||||
lines.append(f"- allow_zone_ids: `{allow_ids}`")
|
||||
lines.append("")
|
||||
|
||||
for case in suite.get("cases") or []:
|
||||
dept_label = f"{case.get('org_name')} | org={case.get('org_id')}"
|
||||
employees = case.get("host_employees") or []
|
||||
if not employees:
|
||||
he1 = case.get("host_employee") or {}
|
||||
if he1.get("person_id"):
|
||||
employees = [
|
||||
{
|
||||
"person_id": he1.get("person_id"),
|
||||
"person_name": he1.get("person_name"),
|
||||
"visitor_for_api": case.get("visitor_for_api") or {},
|
||||
}
|
||||
]
|
||||
|
||||
if not employees:
|
||||
lines.append(f"### {dept_label}")
|
||||
lines.append("- SKIP:无员工(host_employees 空)")
|
||||
lines.append("")
|
||||
exit_code = 1
|
||||
continue
|
||||
|
||||
for ei, hem in enumerate(employees[:max_employees_per_department]):
|
||||
host = hem.get("person_id")
|
||||
hname = hem.get("person_name") or ""
|
||||
vf = hem.get("visitor_for_api") or {}
|
||||
vis = (visitor_person_id.strip() if use_global_visitor else None) or vf.get("person_id")
|
||||
vname = vf.get("person_name") or ""
|
||||
|
||||
sub = f"{dept_label} — 员工[{ei + 1}]"
|
||||
if not host:
|
||||
lines.append(f"#### {sub}")
|
||||
lines.append("- SKIP:无 person_id")
|
||||
lines.append("")
|
||||
exit_code = 1
|
||||
continue
|
||||
if not vis:
|
||||
lines.append(f"#### {sub}")
|
||||
lines.append("- SKIP:无访客(访客池或 `--visitor-person-id`)")
|
||||
lines.append("")
|
||||
exit_code = 1
|
||||
continue
|
||||
|
||||
add_body = {
|
||||
"visitorId": vis,
|
||||
"personId": host,
|
||||
"begVisitorTime": beg,
|
||||
"endVisitorTime": end,
|
||||
"floorIds": [],
|
||||
}
|
||||
try:
|
||||
st, js, raw = _post_json(session, base_url, "/elevator/person/add/visitor", add_body)
|
||||
except requests.RequestException as e:
|
||||
lines.append(f"#### {sub}")
|
||||
lines.append(f"- **HTTP 异常**: `{e}`")
|
||||
lines.append("")
|
||||
exit_code = 1
|
||||
continue
|
||||
|
||||
code = _business_code(js)
|
||||
lines.append(f"#### {sub}")
|
||||
lines.append(
|
||||
f"- **被访人** personId=`{host}` ({hname});**访客** visitorId=`{vis}` ({vname})"
|
||||
)
|
||||
lines.append(f"- add/visitor: http={st} code={code}")
|
||||
|
||||
if (
|
||||
fail_fast_76260521
|
||||
and st == 200
|
||||
and code == "76260521"
|
||||
and isinstance(raw, str)
|
||||
and "\"data\":null" in raw
|
||||
):
|
||||
streak_76260521 += 1
|
||||
else:
|
||||
streak_76260521 = 0
|
||||
|
||||
if st != 200 or (code is not None and code not in CODE_OK):
|
||||
lines.append(f"- 响应摘要: `{raw[:400]}` …")
|
||||
if code == "76260532":
|
||||
lines.append("- **判定**: 交集为空 — ACCEPTABLE_FAIL。")
|
||||
else:
|
||||
exit_code = 1
|
||||
if streak_76260521 >= 3:
|
||||
lines.append(
|
||||
"- **快速失败**: 已连续 3 次命中 `76260521`(空 message/null data)。"
|
||||
)
|
||||
lines.append(
|
||||
"- **疑似原因**: Cloudwalk 调用上下文缺失(常见于 `authorization/loginid/platformuserid/applicationid` 头缺失或无效、token 过期)。"
|
||||
)
|
||||
lines.append(
|
||||
"- **建议**: 用与前端同源的完整请求头重试;先用 `--smoke`(每部门 1 人)确认首条能成功后再全量。可设 `VISITOR_FAIL_FAST_ON_76260521=0` 关闭快速失败。"
|
||||
)
|
||||
lines.append("")
|
||||
return lines, 1
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
st2, js2, raw2 = _post_json(
|
||||
session,
|
||||
base_url,
|
||||
"/elevator/passRule/image",
|
||||
{"personId": vis, "businessId": header_biz},
|
||||
)
|
||||
code2 = _business_code(js2)
|
||||
lines.append(f"- passRule/image: http={st2} code={code2}")
|
||||
data = js2.get("data") if isinstance(js2, dict) else None
|
||||
rows = [x for x in (data or []) if isinstance(x, dict)]
|
||||
if st2 != 200 or (code2 is not None and code2 not in CODE_OK):
|
||||
lines.append(f"- **失败** body: `{raw2[:500]}`")
|
||||
exit_code = 1
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
bad_names = []
|
||||
bad_ids = []
|
||||
for z in rows:
|
||||
zid = str(z.get("zoneId") or "")
|
||||
zn = str(z.get("zoneName") or "")
|
||||
if allow_ids and zid and zid not in allow_ids:
|
||||
bad_ids.append(zid)
|
||||
if zn and not cre.search(zn):
|
||||
bad_names.append(zn)
|
||||
|
||||
lines.append(f"- 回读楼层数: **{len(rows)}**")
|
||||
for z in rows:
|
||||
lines.append(f" - zoneId=`{z.get('zoneId')}` zoneName=`{z.get('zoneName')}`")
|
||||
|
||||
if allow_ids and bad_ids:
|
||||
lines.append(f"- **FAIL**: zoneId 不在 allowlist: {bad_ids}")
|
||||
exit_code = 1
|
||||
elif bad_names:
|
||||
lines.append(f"- **FAIL**: zoneName 未匹配 `{zone_name_regex}`: {bad_names}")
|
||||
exit_code = 1
|
||||
else:
|
||||
lines.append("- **PASS**")
|
||||
lines.append("")
|
||||
|
||||
return lines, exit_code
|
||||
|
||||
|
||||
def phase_all(
|
||||
snapshot_path: Path,
|
||||
base_url: str,
|
||||
visitor_person_id: str,
|
||||
window_days: int,
|
||||
zone_name_regex: str,
|
||||
skip_secondary_mismatch: bool,
|
||||
max_employees_per_department: int,
|
||||
register_only: bool,
|
||||
) -> tuple[list[str], int]:
|
||||
lines_r, code_r = phase_report(snapshot_path)
|
||||
lines_l, code_l = phase_live(
|
||||
snapshot_path,
|
||||
base_url,
|
||||
visitor_person_id,
|
||||
window_days,
|
||||
zone_name_regex,
|
||||
skip_secondary_mismatch,
|
||||
max_employees_per_department,
|
||||
register_only,
|
||||
)
|
||||
lines: list[str] = [
|
||||
"# V2 访客注册默认楼层 — 完整验证报告(catalog 静态 + 联机)",
|
||||
"",
|
||||
f"- 快照: `{snapshot_path}`",
|
||||
f"- BASE_URL: `{base_url}`",
|
||||
"",
|
||||
"## 第一部分:catalog_snapshot 静态校验",
|
||||
"",
|
||||
*lines_r,
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 第二部分:联机验收(Maven V2 电梯应用)",
|
||||
"",
|
||||
*lines_l,
|
||||
]
|
||||
return lines, max(code_r, code_l)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--phase", choices=("report", "live", "all"), default="report")
|
||||
ap.add_argument("--snapshot", type=Path, default=SNAP_DEFAULT)
|
||||
ap.add_argument("--base-url", default=os.environ.get("ELEVATOR_VERIFY_BASE", "http://127.0.0.1:18081"))
|
||||
ap.add_argument(
|
||||
"--visitor-person-id",
|
||||
default=os.environ.get("VISITOR_TEST_PERSON_ID", "").strip(),
|
||||
help="若设置则所有用例固定使用该 visitorId;否则用快照中 visitor_for_api 逐条配对",
|
||||
)
|
||||
ap.add_argument("--window-days", type=int, default=int(os.environ.get("VISITOR_WINDOW_DAYS", "1")))
|
||||
ap.add_argument(
|
||||
"--zone-name-regex",
|
||||
default=os.environ.get("VISITOR_ZONE_NAME_REGEX", r".*28.*"),
|
||||
help="生效楼层 zoneName 验收(默认匹配含 28 的展示名)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--skip-suite-on-business-mismatch",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Header businessId 与套件不一致时跳过(默认开启)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--max-employees-per-department",
|
||||
type=int,
|
||||
default=int(os.environ.get("VISITOR_MAX_EMPLOYEES_PER_DEPT", "10")),
|
||||
help="每部门最多跑多少名员工(联机;默认 10,冒烟可设 1)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--register-only",
|
||||
action="store_true",
|
||||
help="仅 POST /elevator/person/add/visitor;不回读 passRule/image(无法校验楼层明细)",
|
||||
)
|
||||
mx = ap.add_mutually_exclusive_group()
|
||||
mx.add_argument(
|
||||
"--suite",
|
||||
default="",
|
||||
metavar="SUITE_ID",
|
||||
help="使用 test_matrix.json 中该 suites[].id 的 business_id 作为 businessid 头;也可设环境变量 VISITOR_SUITE_ID",
|
||||
)
|
||||
mx.add_argument(
|
||||
"--tenant",
|
||||
choices=("primary", "secondary"),
|
||||
default=None,
|
||||
help="使用矩阵顶层 tenant_primary_business_id / tenant_secondary_business_id",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
suite_arg = (args.suite or "").strip() or os.environ.get("VISITOR_SUITE_ID", "").strip()
|
||||
tenant_arg = (args.tenant or "").strip().lower() if args.tenant else ""
|
||||
resolve_header_business_id(suite_arg, tenant_arg)
|
||||
|
||||
if not args.snapshot.is_file():
|
||||
print(f"缺少快照: {args.snapshot} — 请先运行 export_catalog.py", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if args.phase == "report":
|
||||
lines, code = phase_report(args.snapshot)
|
||||
elif args.phase == "all":
|
||||
lines, code = phase_all(
|
||||
args.snapshot,
|
||||
args.base_url,
|
||||
str(args.visitor_person_id or ""),
|
||||
args.window_days,
|
||||
args.zone_name_regex,
|
||||
args.skip_suite_on_business_mismatch,
|
||||
max(1, args.max_employees_per_department),
|
||||
bool(args.register_only),
|
||||
)
|
||||
else:
|
||||
lines, code = phase_live(
|
||||
args.snapshot,
|
||||
args.base_url,
|
||||
str(args.visitor_person_id or ""),
|
||||
args.window_days,
|
||||
args.zone_name_regex,
|
||||
args.skip_suite_on_business_mismatch,
|
||||
max(1, args.max_employees_per_department),
|
||||
bool(args.register_only),
|
||||
)
|
||||
|
||||
REPORT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
rep = REPORT_DIR / f"visitor-floor-suite-{datetime.now().strftime('%Y%m%d-%H%M%S')}.md"
|
||||
rep.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print("\n".join(lines))
|
||||
print(f"\n报告已写入: {rep}")
|
||||
return code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
方案B:生产接口三步模拟验证脚本。
|
||||
|
||||
流程:
|
||||
1) /component/person/detail 验证被访员工(蒙海文)关联信息与 floorList
|
||||
2) /elevator/person/add/visitor 以 floorIds=[] 开通访客
|
||||
3) /elevator/passRule/image 回读访客最终楼层权限
|
||||
|
||||
输出:
|
||||
- 控制台摘要
|
||||
- report/gf-visitor-sim-<timestamp>.json(完整执行记录)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PARITY_ROOT = ROOT.parent / "elevator_api_parity"
|
||||
if str(PARITY_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PARITY_ROOT))
|
||||
from parity.client import default_headers # noqa: E402
|
||||
|
||||
REPORT_DIR = ROOT / "report"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="广发访客楼层接口三步模拟验证(方案B)")
|
||||
parser.add_argument("--org-base-url", required=True, help="组织服务基地址,例如 http://10.0.22.102:17016")
|
||||
parser.add_argument("--elevator-base-url", required=True, help="电梯服务基地址,例如 http://10.0.22.207:16112")
|
||||
parser.add_argument("--meng-person-id", required=True, help="蒙海文的 personId")
|
||||
parser.add_argument("--visitor-person-id", required=True, help="测试访客 personId")
|
||||
parser.add_argument("--business-id", default="2524639890ba4f2cba9ba1a4eeaa4015", help="业务租户 businessId")
|
||||
parser.add_argument("--window-hours", type=int, default=24, help="访客有效期小时数,默认24")
|
||||
parser.add_argument("--timeout-seconds", type=int, default=30, help="接口超时秒数,默认30")
|
||||
parser.add_argument(
|
||||
"--strict-name-check",
|
||||
action="store_true",
|
||||
help="开启后要求人员详情返回 name 精确为“蒙海文”,否则判定失败",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def ensure_business_header(business_id: str) -> None:
|
||||
os.environ["ELEVATOR_HEADER_BUSINESSID"] = business_id
|
||||
|
||||
|
||||
def call_json(
|
||||
session: requests.Session,
|
||||
method: str,
|
||||
url: str,
|
||||
headers: Dict[str, str],
|
||||
payload: Dict[str, Any],
|
||||
timeout_seconds: int,
|
||||
) -> Dict[str, Any]:
|
||||
response = session.request(method=method, url=url, headers=headers, json=payload, timeout=timeout_seconds)
|
||||
text = response.text or ""
|
||||
record = {
|
||||
"url": url,
|
||||
"http_status": response.status_code,
|
||||
"request": payload,
|
||||
"response_text_head": text[:1200],
|
||||
}
|
||||
try:
|
||||
record["response_json"] = response.json()
|
||||
except Exception:
|
||||
record["response_json"] = None
|
||||
return record
|
||||
|
||||
|
||||
def business_code(resp_json: Dict[str, Any] | None) -> str:
|
||||
if not isinstance(resp_json, dict):
|
||||
return ""
|
||||
v = resp_json.get("code")
|
||||
return "" if v is None else str(v)
|
||||
|
||||
|
||||
def bool_success(resp_json: Dict[str, Any] | None) -> bool:
|
||||
if not isinstance(resp_json, dict):
|
||||
return False
|
||||
code = business_code(resp_json)
|
||||
if code in {"0", "200"}:
|
||||
return True
|
||||
success = resp_json.get("success")
|
||||
return success is True
|
||||
|
||||
|
||||
def ts_range_ms(window_hours: int) -> tuple[int, int]:
|
||||
now = datetime.now(timezone.utc)
|
||||
start = int(now.timestamp() * 1000)
|
||||
end = int((now + timedelta(hours=window_hours)).timestamp() * 1000)
|
||||
return start, end
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
ensure_business_header(args.business_id)
|
||||
|
||||
headers = default_headers()
|
||||
if "businessid" not in headers:
|
||||
headers["businessid"] = args.business_id
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
session = requests.Session()
|
||||
started_at = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
detail_payload = {"id": args.meng_person_id, "businessId": args.business_id}
|
||||
add_start_ms, add_end_ms = ts_range_ms(args.window_hours)
|
||||
add_payload = {
|
||||
"visitorId": args.visitor_person_id,
|
||||
"personId": args.meng_person_id,
|
||||
"begVisitorTime": add_start_ms,
|
||||
"endVisitorTime": add_end_ms,
|
||||
"floorIds": [],
|
||||
}
|
||||
image_payload = {
|
||||
"personId": args.visitor_person_id,
|
||||
"businessId": args.business_id,
|
||||
"imageStoreIds": [],
|
||||
"includeOrganizations": [],
|
||||
"includeLabels": [],
|
||||
}
|
||||
|
||||
detail_url = args.org_base_url.rstrip("/") + "/component/person/detail"
|
||||
add_url = args.elevator_base_url.rstrip("/") + "/elevator/person/add/visitor"
|
||||
image_url = args.elevator_base_url.rstrip("/") + "/elevator/passRule/image"
|
||||
|
||||
detail_result = call_json(session, "POST", detail_url, headers, detail_payload, args.timeout_seconds)
|
||||
add_result = call_json(session, "POST", add_url, headers, add_payload, args.timeout_seconds)
|
||||
image_result = call_json(session, "POST", image_url, headers, image_payload, args.timeout_seconds)
|
||||
|
||||
detail_json = detail_result.get("response_json")
|
||||
add_json = add_result.get("response_json")
|
||||
image_json = image_result.get("response_json")
|
||||
|
||||
detail_data = detail_json.get("data") if isinstance(detail_json, dict) else {}
|
||||
detail_name = (detail_data or {}).get("name")
|
||||
detail_floor_list = (detail_data or {}).get("floorList")
|
||||
|
||||
checks = {
|
||||
"detail_http_ok": detail_result["http_status"] == 200,
|
||||
"detail_business_ok": bool_success(detail_json),
|
||||
"detail_has_floor_list": isinstance(detail_floor_list, list) and len(detail_floor_list) > 0,
|
||||
"detail_name_ok": (detail_name == "蒙海文") if args.strict_name_check else True,
|
||||
"add_http_ok": add_result["http_status"] == 200,
|
||||
"add_business_ok_or_expected_empty_intersection": bool_success(add_json)
|
||||
or business_code(add_json) == "76260532",
|
||||
"image_http_ok": image_result["http_status"] == 200,
|
||||
"image_business_ok": bool_success(image_json),
|
||||
"image_has_data": isinstance((image_json or {}).get("data"), list),
|
||||
}
|
||||
all_pass = all(checks.values())
|
||||
|
||||
report = {
|
||||
"started_at": started_at,
|
||||
"args": {
|
||||
"org_base_url": args.org_base_url,
|
||||
"elevator_base_url": args.elevator_base_url,
|
||||
"meng_person_id": args.meng_person_id,
|
||||
"visitor_person_id": args.visitor_person_id,
|
||||
"business_id": args.business_id,
|
||||
"window_hours": args.window_hours,
|
||||
"strict_name_check": args.strict_name_check,
|
||||
},
|
||||
"headers_used": {k: ("***" if k.lower() == "authorization" else v) for k, v in headers.items()},
|
||||
"steps": {
|
||||
"person_detail": detail_result,
|
||||
"add_visitor": add_result,
|
||||
"passrule_image": image_result,
|
||||
},
|
||||
"derived": {
|
||||
"person_name": detail_name,
|
||||
"person_floor_list": detail_floor_list,
|
||||
"add_visitor_code": business_code(add_json),
|
||||
"passrule_image_code": business_code(image_json),
|
||||
"passrule_image_data_size": len((image_json or {}).get("data") or []),
|
||||
},
|
||||
"checks": checks,
|
||||
"all_pass": all_pass,
|
||||
}
|
||||
|
||||
REPORT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
report_file = REPORT_DIR / f"gf-visitor-sim-{stamp}.json"
|
||||
report_file.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
print("=== 方案B模拟验证结果 ===")
|
||||
print(f"report: {report_file}")
|
||||
print(f"person_name: {detail_name}")
|
||||
print(f"person_floor_list_size: {len(detail_floor_list or []) if isinstance(detail_floor_list, list) else 0}")
|
||||
print(f"add_visitor_code: {business_code(add_json)}")
|
||||
print(f"passrule_image_code: {business_code(image_json)}")
|
||||
print(f"passrule_image_data_size: {len((image_json or {}).get('data') or [])}")
|
||||
print(f"all_pass: {all_pass}")
|
||||
|
||||
if not all_pass:
|
||||
print("某些检查未通过,请查看报告文件中的 checks 与 steps 详情。")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
#!/usr/bin/env python3
|
||||
"""org_id 策略修复 — 无鉴权验证脚本"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pymysql
|
||||
import requests
|
||||
|
||||
# ===== 配置常量 =====
|
||||
DB_CONFIG = {
|
||||
"host": "192.168.3.12",
|
||||
"port": 3307,
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"db_org": "component-organization",
|
||||
"db_elevator": "cw-elevator-application",
|
||||
}
|
||||
|
||||
BUSINESS_ID = "2524639890ba4f2cba9ba1a4eeaa4015"
|
||||
|
||||
ORG_1403 = "72fb65ec5de94201b909a98b8bae1892"
|
||||
ORG_1405 = "2095de3d541f44eba686c78fda68336f"
|
||||
ORG_GUANGFA = "488b8ad049bb43408a6fbcc50bcb89ac"
|
||||
|
||||
HOST_CHEN = "1060601019894960128"
|
||||
HOST_WANG = "1090779433129840640"
|
||||
HOST_QIN = "1072908835884208128"
|
||||
|
||||
VISITOR_IDS = [
|
||||
"9199000100000000001", "9199000100000000002", "9199000100000000003",
|
||||
"9199000100000000004", "9199000100000000005", "9199000100000000006",
|
||||
"9199000100000000007",
|
||||
]
|
||||
|
||||
ZONE_28F = "605560545117995008"
|
||||
ZONE_99F = "605560540000000000"
|
||||
|
||||
OK_CODES = {"0", "200"}
|
||||
|
||||
TEST_CASES = [
|
||||
{"id":"T1","name":"有策略→allow替换floorList","host_id":HOST_CHEN,"visitor_id":VISITOR_IDS[0],"policy_id":"policy_t1_1403","expected_pass":True,"expected_floors":[ZONE_28F]},
|
||||
{"id":"T2","name":"无策略→floorList","host_id":HOST_WANG,"visitor_id":VISITOR_IDS[1],"policy_id":None,"expected_pass":True,"expected_floors":None},
|
||||
{"id":"T3","name":"allow含无效zone→拒绝","host_id":HOST_CHEN,"visitor_id":VISITOR_IDS[2],"policy_id":"policy_t3_invalid","expected_pass":False,"expected_code":"76260533"},
|
||||
{"id":"T4","name":"多组织命中第一个策略","host_id":HOST_CHEN,"visitor_id":VISITOR_IDS[3],"policy_id":"policy_t1_1403","expected_pass":True,"expected_floors":[ZONE_28F]},
|
||||
{"id":"T5","name":"enabled=0等同无策略","host_id":HOST_CHEN,"visitor_id":VISITOR_IDS[4],"policy_id":"policy_t5_disabled","expected_pass":True,"expected_floors":None},
|
||||
{"id":"T6","name":"UC-02策略优先","host_id":HOST_CHEN,"visitor_id":VISITOR_IDS[5],"policy_id":"policy_t1_1403","expected_pass":True,"expected_floors":[ZONE_28F],"floor_ids_override":["605560541473144832"]},
|
||||
{"id":"T7","name":"广发基金迁移验证","host_id":HOST_QIN,"visitor_id":VISITOR_IDS[6],"policy_id":"gf_vstr_policy_guangfa_fund_001x","expected_pass":True,"expected_floors":[ZONE_28F]},
|
||||
]
|
||||
|
||||
|
||||
def parse_args():
|
||||
p = argparse.ArgumentParser(description="org_id 策略修复验证")
|
||||
p.add_argument("--elevator-base-url", default="http://127.0.0.1:18081")
|
||||
p.add_argument("--skip-db", action="store_true")
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def health_check(base_url):
|
||||
try:
|
||||
r = requests.get(f"{base_url}/health", timeout=10)
|
||||
ok = r.status_code == 200
|
||||
print(f"[HEALTH] {base_url} -> {r.status_code} {'OK' if ok else 'FAIL'}")
|
||||
return ok
|
||||
except Exception as e:
|
||||
print(f"[HEALTH] {base_url} -> ERROR: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_db_conn():
|
||||
return pymysql.connect(
|
||||
host=DB_CONFIG["host"], port=DB_CONFIG["port"],
|
||||
user=DB_CONFIG["user"], password=DB_CONFIG["password"],
|
||||
database=DB_CONFIG["db_elevator"],
|
||||
charset="utf8mb4", autocommit=True,
|
||||
)
|
||||
|
||||
|
||||
def execute_sql(sql, params=None):
|
||||
conn = get_db_conn()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, params)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def prepare_test_data():
|
||||
policies = [
|
||||
("policy_t1_1403", ORG_1403, f'["{ZONE_28F}"]', 1),
|
||||
("policy_t3_invalid", ORG_1403, f'["{ZONE_28F}","{ZONE_99F}"]', 1),
|
||||
("policy_t5_disabled", ORG_1403, f'["{ZONE_28F}"]', 0),
|
||||
]
|
||||
for pid, oid, zones_json, enabled in policies:
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id=%s", (pid,))
|
||||
execute_sql(
|
||||
"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 (%s, %s, NULL, 'INTERSECT_ALLOWLIST', %s, NULL, %s, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000)",
|
||||
(pid, oid, zones_json, enabled),
|
||||
)
|
||||
print(f" INSERT policy {pid} org={oid} enabled={enabled}")
|
||||
execute_sql("UPDATE tenant_visitor_floor_policy SET org_id=%s WHERE id='gf_vstr_policy_guangfa_fund_001x'", (ORG_GUANGFA,))
|
||||
print(f" UPDATE 广发基金 org_id={ORG_GUANGFA}")
|
||||
|
||||
|
||||
def cleanup_test_data():
|
||||
for pid in ["policy_t1_1403", "policy_t3_invalid", "policy_t5_disabled"]:
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id=%s", (pid,))
|
||||
print(f" DELETE {pid}")
|
||||
execute_sql("UPDATE tenant_visitor_floor_policy SET org_id=NULL WHERE id='gf_vstr_policy_guangfa_fund_001x'")
|
||||
print(" UPDATE 广发基金 org_id=NULL (回滚)")
|
||||
|
||||
|
||||
def build_noauth_headers():
|
||||
return {"Content-Type": "application/json", "businessid": BUSINESS_ID}
|
||||
|
||||
|
||||
def now_ms():
|
||||
return int(time.time() * 1000)
|
||||
|
||||
|
||||
def tomorrow_ms():
|
||||
return int((time.time() + 86400) * 1000)
|
||||
|
||||
|
||||
def call_add_visitor(base_url, person_id, visitor_id, floor_ids=None):
|
||||
body = {
|
||||
"personId": person_id, "visitorId": visitor_id,
|
||||
"floorIds": floor_ids if floor_ids is not None else [],
|
||||
"begVisitorTime": now_ms(), "endVisitorTime": tomorrow_ms(),
|
||||
}
|
||||
try:
|
||||
r = requests.post(f"{base_url}/elevator/person/add/visitor", json=body, headers=build_noauth_headers(), timeout=30)
|
||||
return {"http_status": r.status_code, "body": r.json() if r.headers.get("content-type","").startswith("application/json") else r.text}
|
||||
except Exception as e:
|
||||
return {"http_status": 0, "error": str(e)}
|
||||
|
||||
|
||||
def call_passrule_image(base_url, visitor_id):
|
||||
body = {"personId": visitor_id}
|
||||
try:
|
||||
r = requests.post(f"{base_url}/elevator/passRule/image", json=body, headers=build_noauth_headers(), timeout=30)
|
||||
return {"http_status": r.status_code, "body": r.json() if r.headers.get("content-type","").startswith("application/json") else r.text}
|
||||
except Exception as e:
|
||||
return {"http_status": 0, "error": str(e)}
|
||||
|
||||
|
||||
def extract_zone_ids(passrule_response):
|
||||
try:
|
||||
datas = passrule_response["body"]["data"]["datas"]
|
||||
return [d["zoneId"] for d in datas if "zoneId" in d]
|
||||
except (KeyError, TypeError):
|
||||
return []
|
||||
|
||||
|
||||
def run_case(base_url, case):
|
||||
cid = case["id"]
|
||||
print(f"\n[{cid}] {case['name']}")
|
||||
floor_ids = case.get("floor_ids_override")
|
||||
pid = case.get("policy_id")
|
||||
if pid and cid == "T3":
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id='policy_t1_1403'")
|
||||
print(" [DB] 临时删除 policy_t1_1403")
|
||||
result = {"id": cid, "name": case["name"]}
|
||||
r = call_add_visitor(base_url, case["host_id"], case["visitor_id"], floor_ids)
|
||||
body = r.get("body") if isinstance(r.get("body"), dict) else {}
|
||||
result["add_visitor"] = {
|
||||
"http_status": r.get("http_status"),
|
||||
"success": body.get("success"),
|
||||
"code": body.get("code"),
|
||||
"message": body.get("message"),
|
||||
"error": r.get("error"),
|
||||
}
|
||||
av = result["add_visitor"]
|
||||
business_ok = av["http_status"] == 200 and str(av.get("code", "")) in OK_CODES
|
||||
if case["expected_pass"]:
|
||||
if business_ok:
|
||||
pr = call_passrule_image(base_url, case["visitor_id"])
|
||||
actual_zones = extract_zone_ids(pr)
|
||||
result["passrule_image"] = {"zones": actual_zones}
|
||||
expected = case.get("expected_floors")
|
||||
if expected is not None:
|
||||
match = set(actual_zones) == set(expected)
|
||||
result["floor_match"] = match
|
||||
result["passed"] = match
|
||||
print(f" add/visitor OK, floors actual={actual_zones} expected={expected} match={match}")
|
||||
else:
|
||||
result["passed"] = True
|
||||
print(f" add/visitor OK, floors={actual_zones}")
|
||||
else:
|
||||
result["passed"] = False
|
||||
print(f" expected success but got code={av.get('code')} msg={av.get('message')}")
|
||||
else:
|
||||
expected_code = case.get("expected_code")
|
||||
actual_code = str(av.get("code", ""))
|
||||
result["passed"] = (not business_ok) and (actual_code == expected_code)
|
||||
print(f" expected fail code={expected_code} actual={actual_code} passed={result['passed']}")
|
||||
if cid == "T3":
|
||||
execute_sql(
|
||||
"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', %s, NULL, 'INTERSECT_ALLOWLIST', %s, NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000)",
|
||||
(ORG_1403, f'["{ZONE_28F}"]'),
|
||||
)
|
||||
print(" [DB] 恢复 policy_t1_1403")
|
||||
return result
|
||||
|
||||
|
||||
def generate_report(results, base_url):
|
||||
passed = sum(1 for r in results if r.get("passed"))
|
||||
return {
|
||||
"test": "org_id policy fix verification",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"elevator_url": base_url,
|
||||
"mode": "noauth-probe",
|
||||
"business_id": BUSINESS_ID,
|
||||
"summary": {"total": len(results), "passed": passed, "failed": len(results) - passed},
|
||||
"results": results,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
base = args.elevator_base_url.rstrip("/")
|
||||
|
||||
if not health_check(base):
|
||||
print("FATAL: elevator not reachable")
|
||||
sys.exit(1)
|
||||
|
||||
if not args.skip_db:
|
||||
print("\n=== Phase 1: prepare ===")
|
||||
prepare_test_data()
|
||||
|
||||
print(f"\n=== Phase 2: run {len(TEST_CASES)} cases ===")
|
||||
results = [run_case(base, c) for c in TEST_CASES]
|
||||
|
||||
if not args.skip_db:
|
||||
print("\n=== Phase 3: cleanup ===")
|
||||
cleanup_test_data()
|
||||
|
||||
report = generate_report(results, base)
|
||||
report_path = f"report/org-policy-fix-verify-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json"
|
||||
os.makedirs("report", exist_ok=True)
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n=== Report: {report_path} ===")
|
||||
print(f"Passed: {report['summary']['passed']}/{report['summary']['total']}")
|
||||
for r in results:
|
||||
print(f" {'OK' if r.get('passed') else 'FAIL'} [{r['id']}] {r['name']}")
|
||||
sys.exit(0 if report["summary"]["failed"] == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user