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:
+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())
|
||||
Reference in New Issue
Block a user