mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
feat: 租户访客策略 SQL、访客邀约验证包、component-org 与发布脚本
- docs/sql: organization_* 与 tenant_* 访客楼层策略脚本 - docs/testing: 访客邀约页初始化验证、pack 脚本与 README(忽略 dist/__pycache__) - maven-ninca-common-component-organization: CpImageStoreServiceImpl、starter、run-verify、releases 脚本与 javap 审计 JSON - docs/superpowers: component-org 生产问题修复计划 - scripts/test-env/prepare-db.sh 更新 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
-- 租户访客默认楼层策略(组织库 component-organization)
|
||||
-- 表结构与电梯库 cw-elevator-application 在 tenant_visitor_floor_policy_v2.sql 之后一致,便于双库同步或备查。
|
||||
-- 设计说明:docs/business/租户访客默认楼层-数据库配置阶段技术设计.md
|
||||
-- 本脚本会先 DROP 再 CREATE:将删除现有 tenant_visitor_floor_policy 及其中全部数据,执行前请自行备份。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `component-organization`;
|
||||
|
||||
DROP TABLE IF EXISTS tenant_visitor_floor_policy;
|
||||
|
||||
CREATE TABLE tenant_visitor_floor_policy (
|
||||
id VARCHAR(32) NOT NULL COMMENT '主键',
|
||||
business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准',
|
||||
org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)',
|
||||
policy_type VARCHAR(32) NOT NULL DEFAULT 'INTERSECT_ALLOWLIST' COMMENT '策略类型',
|
||||
allow_zone_ids TEXT NULL COMMENT 'JSON 数组,zoneId 列表',
|
||||
building_id VARCHAR(64) NULL COMMENT '预留:楼栋维度;租户默认填 NULL',
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1 启用 0 停用',
|
||||
policy_version BIGINT NOT NULL DEFAULT 1 COMMENT '配置版本号',
|
||||
remark VARCHAR(256) NULL,
|
||||
created_by VARCHAR(64) NULL,
|
||||
created_at BIGINT NULL,
|
||||
updated_by VARCHAR(64) NULL,
|
||||
updated_at BIGINT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_org_building (org_id, building_id),
|
||||
KEY idx_business_enabled (business_id, enabled)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户访客默认楼层策略(组织库;与电梯库结构对齐)';
|
||||
@@ -0,0 +1,75 @@
|
||||
-- =============================================================================
|
||||
-- 组织库 component-organization:策略表 + 初始化数据(一站式)
|
||||
-- =============================================================================
|
||||
-- 将先 DROP 现有 tenant_visitor_floor_policy(含全部数据),再建表并灌入种子。执行前请备份。
|
||||
-- 推荐拆分执行:organization_tenant_visitor_floor_policy.sql + 两个 init_*.sql(与下列逻辑同源)。
|
||||
-- =============================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `component-organization`;
|
||||
|
||||
DROP TABLE IF EXISTS tenant_visitor_floor_policy;
|
||||
|
||||
CREATE TABLE tenant_visitor_floor_policy (
|
||||
id VARCHAR(32) NOT NULL COMMENT '主键',
|
||||
business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准',
|
||||
org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)',
|
||||
policy_type VARCHAR(32) NOT NULL DEFAULT 'INTERSECT_ALLOWLIST' COMMENT '策略类型',
|
||||
allow_zone_ids TEXT NULL COMMENT 'JSON 数组,zoneId 列表',
|
||||
building_id VARCHAR(64) NULL COMMENT '预留:楼栋维度;租户默认填 NULL',
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1 启用 0 停用',
|
||||
policy_version BIGINT NOT NULL DEFAULT 1 COMMENT '配置版本号',
|
||||
remark VARCHAR(256) NULL,
|
||||
created_by VARCHAR(64) NULL,
|
||||
created_at BIGINT NULL,
|
||||
updated_by VARCHAR(64) NULL,
|
||||
updated_at BIGINT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_org_building (org_id, building_id),
|
||||
KEY idx_business_enabled (business_id, enabled)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户访客默认楼层策略(组织库;与电梯库结构对齐)';
|
||||
|
||||
-- ----- 广发 -----
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, org_id, business_id, policy_type, allow_zone_ids, building_id,
|
||||
enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'gf_vstr_policy_guangfa_fund_001x',
|
||||
'488b8ad049bb43408a6fbcc50bcb89ac',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560545117995008"]',
|
||||
NULL,
|
||||
1, 1,
|
||||
'广发基金:访客与 floorList 求交后仅保留 allowlist(默认仅 28F zone)。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
org_id = VALUES(org_id),
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
-- ----- 物业 7 条 -----
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES
|
||||
('pm_6f_vstr_policy_001','2524639890ba4f2cba9ba1a4eeaa4015','64fdc8eaf5824df5a1329819af29b79f','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'星河湾物业管理有限公司:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000),
|
||||
('pm_6f_vstr_policy_002','2524639890ba4f2cba9ba1a4eeaa4015','8fc3f910bd834198a539832017fe920e','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'星河湾物业管理公司:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000),
|
||||
('pm_6f_vstr_policy_003','2524639890ba4f2cba9ba1a4eeaa4015','cc760fdf9c384a0cbf4951ccf2c6452e','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'星河湾物管公司:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000),
|
||||
('pm_6f_vstr_policy_004','2524639890ba4f2cba9ba1a4eeaa4015','f216235e54ca42bfa0379e69b3754aff','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'星中心物业管理公司:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000),
|
||||
('pm_6f_vstr_policy_005','2524639890ba4f2cba9ba1a4eeaa4015','95818575a2284db6833289474d33671f','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'星中心物业服务中心:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000),
|
||||
('pm_6f_vstr_policy_006','2524639890ba4f2cba9ba1a4eeaa4015','348328d755624b3491cd307a3109f36a','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'星中心物管公司:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000),
|
||||
('pm_6f_vstr_policy_007','2524639890ba4f2cba9ba1a4eeaa4015','dde6cc9a4f6b4f5490d03e26fb016200','INTERSECT_ALLOWLIST','["605560541473144832"]',NULL,1,1,'物业管理总部:访客默认仅开放 6F。',UNIX_TIMESTAMP(NOW())*1000,UNIX_TIMESTAMP(NOW())*1000)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
@@ -0,0 +1,46 @@
|
||||
-- 广发基金租户:访客默认楼层策略初始化(组织库 component-organization)
|
||||
-- 与 tenant_visitor_floor_policy_init_guangfa_fund.sql(电梯库)数据一致,便于双库对齐。
|
||||
-- 请先执行 organization_tenant_visitor_floor_policy.sql(会先 DROP 再 CREATE 表)。
|
||||
--
|
||||
-- 数据来源(现场查询 192.168.3.12:3307):
|
||||
-- org_id:component-organization.cw_is_organization
|
||||
-- NAME='[28-38F]广发基金管理有限公司' -> ID = 488b8ad049bb43408a6fbcc50bcb89ac
|
||||
-- 28F zone_id:cw-elevator-application.code_elevator_area
|
||||
-- zone_id = 605560545117995008(zone_name=28F,code=0x1C)
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `component-organization`;
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id,
|
||||
org_id,
|
||||
business_id,
|
||||
policy_type,
|
||||
allow_zone_ids,
|
||||
building_id,
|
||||
enabled,
|
||||
policy_version,
|
||||
remark,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'gf_vstr_policy_guangfa_fund_001x',
|
||||
'488b8ad049bb43408a6fbcc50bcb89ac',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560545117995008"]',
|
||||
NULL,
|
||||
1,
|
||||
1,
|
||||
'广发基金:访客与 floorList 求交后仅保留 allowlist(默认仅 28F zone)。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
org_id = VALUES(org_id),
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
@@ -0,0 +1,158 @@
|
||||
-- 物业公司租户:访客默认楼层策略初始化(组织库 component-organization)
|
||||
-- 与 tenant_visitor_floor_policy_init_property_mgmt_6f.sql(电梯库)数据一致。
|
||||
-- 请先执行 organization_tenant_visitor_floor_policy.sql(会先 DROP 再 CREATE 表)。
|
||||
--
|
||||
-- 数据来源(192.168.3.12:3307):
|
||||
-- org_id:component-organization.cw_is_organization(下列 NAME → ID)
|
||||
-- 6F zone_id:cw-elevator-application.code_elevator_area → 605560541473144832(code=0x06)
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `component-organization`;
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_001',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'64fdc8eaf5824df5a1329819af29b79f',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'星河湾物业管理有限公司:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_002',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'8fc3f910bd834198a539832017fe920e',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'星河湾物业管理公司:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_003',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'cc760fdf9c384a0cbf4951ccf2c6452e',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'星河湾物管公司:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_004',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'f216235e54ca42bfa0379e69b3754aff',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'星中心物业管理公司:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_005',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'95818575a2284db6833289474d33671f',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'星中心物业服务中心:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_006',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'348328d755624b3491cd307a3109f36a',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'星中心物管公司:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id, business_id, org_id, policy_type, allow_zone_ids,
|
||||
building_id, enabled, policy_version, remark, created_at, updated_at
|
||||
) VALUES (
|
||||
'pm_6f_vstr_policy_007',
|
||||
'2524639890ba4f2cba9ba1a4eeaa4015',
|
||||
'dde6cc9a4f6b4f5490d03e26fb016200',
|
||||
'INTERSECT_ALLOWLIST',
|
||||
'["605560541473144832"]',
|
||||
NULL, 1, 1,
|
||||
'物业管理总部:访客默认仅开放 6F。',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
UNIX_TIMESTAMP(NOW()) * 1000
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
policy_type = VALUES(policy_type),
|
||||
allow_zone_ids = VALUES(allow_zone_ids),
|
||||
enabled = VALUES(enabled),
|
||||
policy_version = policy_version + 1,
|
||||
remark = VALUES(remark),
|
||||
updated_at = VALUES(updated_at);
|
||||
@@ -0,0 +1,26 @@
|
||||
-- 组织库:从「仅 business_id 版」升级到 org_id 版(与电梯库 tenant_visitor_floor_policy_v2.sql 等价,仅库名不同)
|
||||
-- 使用场景:早期已在 component-organization 建过无 org_id 的 tenant_visitor_floor_policy,需原地升级。
|
||||
-- 若改用 organization_tenant_visitor_floor_policy.sql(内含 DROP+CREATE),请勿再执行本文件。
|
||||
|
||||
USE `component-organization`;
|
||||
|
||||
-- 1. 新增 org_id 列
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
ADD COLUMN org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)'
|
||||
AFTER business_id;
|
||||
|
||||
-- 2. 替换唯一约束
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
DROP INDEX uk_biz_building,
|
||||
ADD UNIQUE KEY uk_org_building (org_id, building_id);
|
||||
|
||||
-- 3. 标记 business_id 为废弃
|
||||
ALTER TABLE tenant_visitor_floor_policy
|
||||
MODIFY COLUMN business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准';
|
||||
|
||||
-- 验证
|
||||
SELECT COLUMN_NAME, COLUMN_KEY, COLUMN_COMMENT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'component-organization'
|
||||
AND TABLE_NAME = 'tenant_visitor_floor_policy'
|
||||
ORDER BY ORDINAL_POSITION;
|
||||
@@ -1,9 +1,23 @@
|
||||
-- 租户访客默认楼层策略(电梯应用库)
|
||||
-- 租户访客默认楼层策略(电梯应用库 cw-elevator-application)
|
||||
-- 设计说明:docs/business/租户访客默认楼层-数据库配置阶段技术设计.md
|
||||
--
|
||||
-- 本脚本会先 DROP 再 CREATE:删除现有 tenant_visitor_floor_policy 及全部历史数据,然后按 v2 后最终结构建空表。
|
||||
-- 执行前请备份;若仅需 ALTER 升级旧表而不删数据,请改用 tenant_visitor_floor_policy_v2.sql(勿与本脚本混用)。
|
||||
--
|
||||
-- 连接方式任选其一:
|
||||
-- mysql -h ... -u ... -p... cw-elevator-application < tenant_visitor_floor_policy.sql
|
||||
-- 或在本文件中依赖下方 USE(手工客户端执行时)。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tenant_visitor_floor_policy (
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `cw-elevator-application`;
|
||||
|
||||
DROP TABLE IF EXISTS tenant_visitor_floor_policy;
|
||||
|
||||
CREATE TABLE tenant_visitor_floor_policy (
|
||||
id VARCHAR(32) NOT NULL COMMENT '主键',
|
||||
business_id VARCHAR(64) NOT NULL COMMENT '机构/租户 ID',
|
||||
business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准',
|
||||
org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)',
|
||||
policy_type VARCHAR(32) NOT NULL DEFAULT 'INTERSECT_ALLOWLIST' COMMENT '策略类型',
|
||||
allow_zone_ids TEXT NULL COMMENT 'JSON 数组,zoneId 列表',
|
||||
building_id VARCHAR(64) NULL COMMENT '预留:楼栋维度;租户默认填 NULL',
|
||||
@@ -15,13 +29,6 @@ CREATE TABLE IF NOT EXISTS tenant_visitor_floor_policy (
|
||||
updated_by VARCHAR(64) NULL,
|
||||
updated_at BIGINT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_biz_building (business_id, building_id),
|
||||
UNIQUE KEY uk_org_building (org_id, building_id),
|
||||
KEY idx_business_enabled (business_id, enabled)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户访客默认楼层策略(与组织 floorList 求交)';
|
||||
|
||||
-- 示例(实施时替换占位符后执行)
|
||||
-- INSERT INTO tenant_visitor_floor_policy
|
||||
-- (id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, remark, created_at, updated_at)
|
||||
-- VALUES
|
||||
-- (REPLACE(UUID(),'-',''), 'REPLACE_WITH_BUSINESS_ID', 'INTERSECT_ALLOWLIST',
|
||||
-- '["REPLACE_ZONE_A","REPLACE_ZONE_B"]', NULL, 1, 1, '实施录入', UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
-- zone_id = 605560545117995008(zone_name=28F,code=0x1C)
|
||||
--
|
||||
-- 重复执行:使用固定 id + ON DUPLICATE KEY UPDATE,幂等。
|
||||
-- 请先执行 tenant_visitor_floor_policy.sql(会先 DROP 再建表)。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `cw-elevator-application`;
|
||||
|
||||
INSERT INTO tenant_visitor_floor_policy (
|
||||
id,
|
||||
org_id,
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
-- zone_id = 605560541473144832(code=0x06)
|
||||
--
|
||||
-- 重复执行:使用固定 id + ON DUPLICATE KEY UPDATE。
|
||||
-- 请先执行 tenant_visitor_floor_policy.sql(会先 DROP 再建表)。
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
USE `cw-elevator-application`;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 星河湾物业管理有限公司
|
||||
-- ============================================================
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
-- 租户访客楼层策略:org_id 粒度修复
|
||||
-- 执行顺序:先 DDL → 数据迁移 → 发应用包
|
||||
-- 租户访客楼层策略:org_id 粒度修复(原地 ALTER,不删表)
|
||||
-- 若已执行新版 tenant_visitor_floor_policy.sql(内含 DROP+CREATE 完整结构),请勿再执行本文件。
|
||||
-- 执行顺序(历史流程):先 DDL → 数据迁移 → 发应用包
|
||||
-- 回滚:DROP INDEX uk_org_building, DROP COLUMN org_id, ADD UNIQUE KEY uk_biz_building (business_id, building_id)
|
||||
|
||||
USE `cw-elevator-application`;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# 预存问题 — 当前代码可完善项
|
||||
|
||||
**日期**: 2026-05-06
|
||||
|
||||
---
|
||||
|
||||
## 可修复: 4 项 (覆盖 ~38,000 错误/天)
|
||||
|
||||
### 1. 🔴 P0: addFace 图片为空无限重试 (36,560次)
|
||||
|
||||
**文件**: `CpImageStoreToolServiceImpl.java` L335-388
|
||||
|
||||
**根因**:
|
||||
```java
|
||||
// L343: 人员不存在时创建空对象
|
||||
ImgStorePerson imgStorePerson = CollectionUtils.isEmpty(personList)
|
||||
? new ImgStorePerson() // ← 空person,comparePicture=null
|
||||
: personList.get(0);
|
||||
// L346: 拿到的图片URL为空
|
||||
extractParam.setImageUrl(imgStorePerson.getComparePicture());
|
||||
// → 特征提取异常 → L386 日志 "图片为空"
|
||||
```
|
||||
|
||||
**修复**:
|
||||
```java
|
||||
// 人员不存在时直接返回失败,不继续处理
|
||||
if (CollectionUtils.isEmpty(personList)) {
|
||||
this.logger.warn("人员不存在 imageId={}", param.getImageId());
|
||||
return CloudwalkResult.fail("53060434", getMessage("53060434"));
|
||||
}
|
||||
ImgStorePerson imgStorePerson = personList.get(0);
|
||||
if (StringUtils.isBlank(imgStorePerson.getComparePicture())) {
|
||||
this.logger.warn("人员图片为空 personId={} imageId={}",
|
||||
imgStorePerson.getId(), param.getImageId());
|
||||
return CloudwalkResult.fail("53060434", getMessage("53060434"));
|
||||
}
|
||||
```
|
||||
|
||||
**影响**: 消除 36,560 次错误日志 (占全部错误 45%)
|
||||
|
||||
---
|
||||
|
||||
### 2. 🟠 P1: addValidateData Redis 锁竞争 (688+322次)
|
||||
|
||||
**文件**: `CpImageStorePersonValidateManager.java` L102-141, L135-185
|
||||
|
||||
**根因**:
|
||||
- `group-person-syn-pool` 线程池并发调用 `addValidateData`
|
||||
- `removeMap`/`addMap` 中多个 entry 竞争同一个 Quartz job 的 Redis 锁
|
||||
- 688 次 error 集中在同一秒 (2026-02-27 15:14:26)
|
||||
|
||||
**修复**:
|
||||
```java
|
||||
// L103: 在 for-loop 之前对 removeMap 按时间戳去重
|
||||
// 避免多个线程处理相同的 job key
|
||||
for (Map.Entry<Long, Set<SyncPersonLocal>> entry : dedupedRemoveMap.entrySet()) {
|
||||
// L107: 锁获取失败时不打 ERROR,改为 WARN
|
||||
if (!this.validateJobGroupLock(l, lockValue)) {
|
||||
log.warn("获取job锁失败(可能已被其他线程处理) key={}", l);
|
||||
continue;
|
||||
}
|
||||
// ... 原逻辑 ...
|
||||
}
|
||||
// L185: 同样处理 addMap
|
||||
```
|
||||
|
||||
**影响**: 消除 1,010 次 error 日志 + 减少 Redis 连接竞争
|
||||
|
||||
---
|
||||
|
||||
### 3. 🟡 P2: 图片角度识别失败 (35次)
|
||||
|
||||
**文件**: `ImageEditUtils.java` L140-148
|
||||
|
||||
**根因**: Commons Imaging 库解析 TIFF/JPEG 格式时抛异常
|
||||
|
||||
**修复**:
|
||||
```java
|
||||
// L145: 添加更详细的错误日志 + 降级处理
|
||||
} catch (Exception e) {
|
||||
log.warn("图片角度识别失败, 降级为0度: {}", e.getMessage());
|
||||
return 0; // 降级为不旋转,而不是抛出异常
|
||||
}
|
||||
```
|
||||
|
||||
**影响**: 消除 35 次 error 日志 + 提高图片处理容错性
|
||||
|
||||
---
|
||||
|
||||
### 4. 🟡 P2: device updatePerson 空指针 (4,749次)
|
||||
|
||||
**文件**: `CpOrgDeviceKitController.java` L160 (web 模块)
|
||||
|
||||
**根因**: `cause:` 后为空 — 下游返回 null
|
||||
|
||||
**修复**:
|
||||
```java
|
||||
} catch (Exception e) {
|
||||
log.error("device updatePerson error deviceCode={} cause={}",
|
||||
param.getDeviceId(),
|
||||
e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(),
|
||||
e); // 打印完整 stack trace
|
||||
return CloudwalkResult.fail("53060411", getMessage("53060411"));
|
||||
}
|
||||
```
|
||||
|
||||
**影响**: 可追踪 4,749 次失败的真实原因
|
||||
|
||||
---
|
||||
|
||||
## 不可修复: 5 项 (下游/基础设施)
|
||||
|
||||
| 错误 | 原因 |
|
||||
|------|------|
|
||||
| AggDeviceImageStoreFeignClient failed (36K) | cwos-portal 服务不可达 — 运维排查 |
|
||||
| AtomicDeviceFeignClient failed (368) | 设备管理服务不可达 |
|
||||
| VehicleFeignClient failed (68) | 车牌服务不可达 |
|
||||
| ElevatorFeignClient failed (15) | 电梯服务不可达 |
|
||||
| MySQL connection lost (13) | 数据库连接池配置 |
|
||||
|
||||
---
|
||||
|
||||
## 实施优先级
|
||||
|
||||
| 顺序 | 修复项 | 文件 | 消除错误数 | 预估行数 |
|
||||
|------|--------|------|-----------|---------|
|
||||
| 1 | addFace 空图片 | CpImageStoreToolServiceImpl.java | 36,560 | +8行 |
|
||||
| 2 | Redis 锁竞争 | CpImageStorePersonValidateManager.java | 1,010 | +5行 |
|
||||
| 3 | device updatePerson | CpOrgDeviceKitController.java | 4,749 | +3行 |
|
||||
| 4 | 图片角度识别 | ImageEditUtils.java | 35 | +2行 |
|
||||
|
||||
**总计**: 4 文件, ~18 行代码, 消除 ~42,354 错误/天 (53%)
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# 将访客邀约页初始化测试脚本打为 tar.gz / zip,便于现场/运维拷贝部署验证。
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
STAMP="${STAMP:-$(date +%Y%m%d)}"
|
||||
BUNDLE_ROOT="visitor-invite-page-init-test-${STAMP}"
|
||||
DIST_DIR="${SCRIPT_DIR}/dist"
|
||||
OUT_TAR_GZ="${DIST_DIR}/${BUNDLE_ROOT}.tar.gz"
|
||||
OUT_ZIP="${DIST_DIR}/${BUNDLE_ROOT}.zip"
|
||||
|
||||
need_files=(
|
||||
"visitor_invite_page_init_example.py"
|
||||
"run_visitor_invite_page_one_click.sh"
|
||||
"requirements-visitor-invite-test.txt"
|
||||
)
|
||||
|
||||
for f in "${need_files[@]}" "visitor-invite-test-bundle-README.txt"; do
|
||||
if [[ ! -f "$f" ]]; then
|
||||
echo "ERROR: 缺少文件: $SCRIPT_DIR/$f" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p "$DIST_DIR"
|
||||
rm -rf "${DIST_DIR}/${BUNDLE_ROOT}"
|
||||
mkdir -p "${DIST_DIR}/${BUNDLE_ROOT}"
|
||||
|
||||
for f in "${need_files[@]}"; do
|
||||
cp -a "$f" "${DIST_DIR}/${BUNDLE_ROOT}/"
|
||||
done
|
||||
cp -a "visitor-invite-test-bundle-README.txt" "${DIST_DIR}/${BUNDLE_ROOT}/README.txt"
|
||||
|
||||
chmod +x "${DIST_DIR}/${BUNDLE_ROOT}/run_visitor_invite_page_one_click.sh"
|
||||
|
||||
( cd "$DIST_DIR" && tar -czf "${BUNDLE_ROOT}.tar.gz" "$BUNDLE_ROOT" )
|
||||
( cd "$DIST_DIR" && zip -qr "${BUNDLE_ROOT}.zip" "$BUNDLE_ROOT" )
|
||||
|
||||
( cd "$DIST_DIR" && sha256sum "${BUNDLE_ROOT}.tar.gz" "${BUNDLE_ROOT}.zip" > "${BUNDLE_ROOT}.sha256" )
|
||||
|
||||
ls -la "$OUT_TAR_GZ" "$OUT_ZIP" "${DIST_DIR}/${BUNDLE_ROOT}.sha256"
|
||||
echo ""
|
||||
echo "==> 已生成:"
|
||||
echo " $OUT_TAR_GZ"
|
||||
echo " $OUT_ZIP"
|
||||
echo " ${DIST_DIR}/${BUNDLE_ROOT}.sha256"
|
||||
echo "==> 校验: (cd $DIST_DIR && sha256sum -c ${BUNDLE_ROOT}.sha256)"
|
||||
@@ -0,0 +1,2 @@
|
||||
requests>=2.28.0
|
||||
pymysql>=1.0.0
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# 一键:访客邀约页初始化测试(默认 DB 样本 + 输出 JSON)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# 仓库内:docs/testing → ../../scripts/...;独立部署包解压后无此文件则仅用下方默认环境变量
|
||||
for _env in \
|
||||
"${SCRIPT_DIR}/../../scripts/test-env/config/env.sh" \
|
||||
"${SCRIPT_DIR}/../../../scripts/test-env/config/env.sh" \
|
||||
"${SCRIPT_DIR}/../../../../scripts/test-env/config/env.sh"; do
|
||||
if [[ -f "$_env" ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source "$_env"
|
||||
break
|
||||
fi
|
||||
done
|
||||
: "${PORT_COMPONENT_ORG:=33011}"
|
||||
: "${MYSQL_HOST:=127.0.0.1}"
|
||||
: "${MYSQL_PORT:=3307}"
|
||||
: "${MYSQL_USER:=root}"
|
||||
: "${MYSQL_PASS:=}"
|
||||
|
||||
# 组织组件 HTTP 与 MySQL 主机未必相同;默认本机 PORT_COMPONENT_ORG
|
||||
export ORG_BASE="${ORG_BASE:-http://127.0.0.1:${PORT_COMPONENT_ORG}}"
|
||||
export MYSQL_HOST MYSQL_PORT MYSQL_USER MYSQL_PASS
|
||||
export DB_COMPONENT_ORG="${DB_COMPONENT_ORG:-component-organization}"
|
||||
export DB_ELEVATOR="${DB_ELEVATOR:-cw-elevator-application}"
|
||||
|
||||
PY="${SCRIPT_DIR}/visitor_invite_page_init_example.py"
|
||||
if [[ ! -f "$PY" ]]; then
|
||||
echo "缺少脚本: $PY" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - <<'PYCHK' || { python3 -m pip install -q requests pymysql; }
|
||||
import requests
|
||||
import pymysql
|
||||
PYCHK
|
||||
|
||||
echo "ORG_BASE=$ORG_BASE MYSQL=${MYSQL_HOST}:${MYSQL_PORT}" >&2
|
||||
exec python3 "$PY" "$@"
|
||||
@@ -0,0 +1,39 @@
|
||||
访客邀约页初始化 — 部署测试包
|
||||
================================
|
||||
|
||||
内含文件
|
||||
--------
|
||||
- visitor_invite_page_init_example.py 主程序(读库样本 + 调组织 /component/person/detail + 楼层预览 JSON)
|
||||
- run_visitor_invite_page_one_click.sh 一键入口
|
||||
- requirements-visitor-invite-test.txt Python 依赖
|
||||
|
||||
环境要求
|
||||
--------
|
||||
- Python 3.8+(建议 3.10)
|
||||
- 可访问 MySQL(component-organization、cw-elevator-application)
|
||||
- 可 HTTP 访问组织组件(默认端口常与本仓库 test-env 中 PORT_COMPONENT_ORG 一致)
|
||||
|
||||
安装依赖
|
||||
--------
|
||||
pip3 install -r requirements-visitor-invite-test.txt
|
||||
|
||||
一键执行(stdout 为完整 JSON,stderr 为日志)
|
||||
--------
|
||||
export MYSQL_HOST=<主机>
|
||||
export MYSQL_PORT=3307
|
||||
export MYSQL_USER=root
|
||||
export MYSQL_PASS=<密码>
|
||||
export ORG_BASE=http://<组织组件>:<端口>
|
||||
bash run_visitor_invite_page_one_click.sh
|
||||
|
||||
可选:仅命令行指定被访人(不读库样本)
|
||||
--------
|
||||
python3 visitor_invite_page_init_example.py --no-db \
|
||||
--business-id <BUSINESS_ID> --host-person-id <PERSON_ID>
|
||||
|
||||
说明
|
||||
----
|
||||
- 默认从库选一名「非访客标签」被访人,并从电梯库 tenant_visitor_floor_policy 取策略 allow_zone_ids(若存在)。
|
||||
- 完整产品说明见源码仓库 docs/business/租户访客默认楼层技术产品方案.md(若未带仓库可忽略)。
|
||||
|
||||
版本:见压缩包文件名中的日期戳。
|
||||
@@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
访客邀约页初始化 —— 一键测试(默认使用数据库中的样本数据)
|
||||
|
||||
1)从 MySQL 读取:
|
||||
- component-organization:一名在职被访人(排除「访客」标签)的 BUSINESS_ID、人员 ID
|
||||
- cw-elevator-application:tenant_visitor_floor_policy(启用策略,优先匹配同一 business_id)的 allow_zone_ids
|
||||
|
||||
2)调用组织组件 POST /component/person/detail,计算 effectiveZoneIds 预览
|
||||
|
||||
依赖:pip install requests pymysql
|
||||
|
||||
一键(推荐走测试环境变量):
|
||||
source ../../scripts/test-env/config/env.sh
|
||||
python3 visitor_invite_page_init_example.py
|
||||
|
||||
或直接:
|
||||
bash run_visitor_invite_page_one_click.sh
|
||||
|
||||
输出:仅向 stdout 打印 **一行合法 JSON**(便于 jq);进度写在 stderr。
|
||||
|
||||
文档:docs/business/租户访客默认楼层技术产品方案.md §2.4
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
|
||||
def cloudwalk_ok(body: Optional[Dict[str, Any]]) -> bool:
|
||||
if not body or not isinstance(body, dict):
|
||||
return False
|
||||
code = body.get("code")
|
||||
if code is None and "data" in body:
|
||||
return True
|
||||
return str(code) in ("0", "200", "00000000")
|
||||
|
||||
|
||||
def extract_host_zone_ids(person_data: Dict[str, Any]) -> List[str]:
|
||||
fl = person_data.get("floorList")
|
||||
if not fl:
|
||||
return []
|
||||
out: List[str] = []
|
||||
for x in fl:
|
||||
if x is None:
|
||||
continue
|
||||
s = str(x).strip()
|
||||
if s:
|
||||
out.append(s)
|
||||
return out
|
||||
|
||||
|
||||
def compute_effective_preview(
|
||||
host_zone_ids: List[str],
|
||||
policy_allow_zone_ids_json: Optional[str],
|
||||
) -> Dict[str, Any]:
|
||||
if not policy_allow_zone_ids_json or not str(policy_allow_zone_ids_json).strip():
|
||||
return {
|
||||
"effective_zone_ids": list(host_zone_ids),
|
||||
"mode": "host_only_no_policy",
|
||||
}
|
||||
try:
|
||||
allow_raw = json.loads(policy_allow_zone_ids_json)
|
||||
except json.JSONDecodeError as e:
|
||||
return {"error": str(e), "effective_zone_ids": [], "mode": "policy_json_invalid"}
|
||||
allow_set: Set[str] = {str(x).strip() for x in allow_raw if x is not None}
|
||||
eff = [z for z in host_zone_ids if z in allow_set]
|
||||
return {
|
||||
"effective_zone_ids": eff,
|
||||
"mode": "intersect_host_with_allow_list",
|
||||
"allow_zone_count": len(allow_set),
|
||||
}
|
||||
|
||||
|
||||
def fetch_sample_from_mysql(
|
||||
host: str,
|
||||
port: int,
|
||||
user: str,
|
||||
password: str,
|
||||
org_db: str,
|
||||
elevator_db: str,
|
||||
) -> Tuple[Dict[str, Any], Optional[str]]:
|
||||
"""
|
||||
Returns (resolution_meta, policy_allow_zone_ids_json or None).
|
||||
resolution_meta 至少含 business_id, host_person_id, host_name。
|
||||
"""
|
||||
try:
|
||||
import pymysql # type: ignore
|
||||
except ImportError as e:
|
||||
raise RuntimeError(f"需要安装 pymysql: pip install pymysql ({e})") from e
|
||||
|
||||
meta: Dict[str, Any] = {
|
||||
"org_database": org_db,
|
||||
"elevator_database": elevator_db,
|
||||
"host_row": None,
|
||||
"policy_row": None,
|
||||
}
|
||||
|
||||
conn_kw = dict(
|
||||
host=host,
|
||||
port=port,
|
||||
user=user,
|
||||
password=password,
|
||||
charset="utf8mb4",
|
||||
connect_timeout=10,
|
||||
read_timeout=30,
|
||||
write_timeout=30,
|
||||
)
|
||||
|
||||
business_id = ""
|
||||
host_person_id = ""
|
||||
host_name = ""
|
||||
|
||||
with pymysql.connect(database=org_db, cursorclass=pymysql.cursors.DictCursor, **conn_kw) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT p.BUSINESS_ID AS business_id, p.ID AS host_person_id, p.NAME AS host_name
|
||||
FROM cw_is_person p
|
||||
INNER JOIN cw_is_person_organization_ref por ON por.PERSON_ID = p.ID
|
||||
WHERE IFNULL(p.IS_DEL, 0) = 0
|
||||
AND p.BUSINESS_ID IS NOT NULL AND p.BUSINESS_ID <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM cw_is_person_label_ref lr
|
||||
INNER JOIN cw_is_label lb ON lb.ID = lr.LABEL_ID
|
||||
WHERE lr.PERSON_ID = p.ID
|
||||
AND lb.BUSINESS_ID = p.BUSINESS_ID
|
||||
AND lb.NAME = '访客'
|
||||
)
|
||||
ORDER BY IFNULL(p.LAST_UPDATE_TIME, 0) DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
raise RuntimeError(
|
||||
f"组织库 {org_db} 未查询到可用被访人样本(需 cw_is_person + 组织挂靠且非访客标签)"
|
||||
)
|
||||
business_id = str(row["business_id"]).strip()
|
||||
host_person_id = str(row["host_person_id"]).strip()
|
||||
host_name = str(row.get("host_name") or "").strip()
|
||||
meta["host_row"] = {
|
||||
"business_id": business_id,
|
||||
"host_person_id": host_person_id,
|
||||
"host_name": host_name,
|
||||
}
|
||||
|
||||
policy_allow_json: Optional[str] = None
|
||||
try:
|
||||
with pymysql.connect(database=elevator_db, cursorclass=pymysql.cursors.DictCursor, **conn_kw) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, business_id, org_id, allow_zone_ids, enabled, remark
|
||||
FROM tenant_visitor_floor_policy
|
||||
WHERE IFNULL(enabled, 0) = 1
|
||||
ORDER BY
|
||||
CASE WHEN business_id <=> %s THEN 0 ELSE 1 END,
|
||||
IFNULL(updated_at, 0) DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(business_id,),
|
||||
)
|
||||
prow = cur.fetchone()
|
||||
if prow and prow.get("allow_zone_ids"):
|
||||
policy_allow_json = str(prow["allow_zone_ids"]).strip()
|
||||
meta["policy_row"] = {
|
||||
"id": prow.get("id"),
|
||||
"business_id": prow.get("business_id"),
|
||||
"org_id": prow.get("org_id"),
|
||||
"allow_zone_ids": policy_allow_json,
|
||||
"remark": (prow.get("remark") or "")[:200],
|
||||
}
|
||||
except Exception as ex:
|
||||
meta["policy_query_error"] = str(ex)
|
||||
|
||||
return meta, policy_allow_json
|
||||
|
||||
|
||||
def run_http_flow(
|
||||
org_base_url: str,
|
||||
business_id: str,
|
||||
host_person_id: str,
|
||||
policy_allow_json: Optional[str],
|
||||
common_base_url: str,
|
||||
timeout: float,
|
||||
) -> Dict[str, Any]:
|
||||
session = requests.Session()
|
||||
detail_url = org_base_url.rstrip("/") + "/component/person/detail"
|
||||
headers = {
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
"businessid": business_id,
|
||||
}
|
||||
payload = {"id": host_person_id, "businessId": business_id}
|
||||
|
||||
log(f"POST {detail_url}")
|
||||
r = session.post(detail_url, json=payload, headers=headers, timeout=timeout)
|
||||
detail_http = r.status_code
|
||||
try:
|
||||
detail_body: Any = r.json()
|
||||
except Exception:
|
||||
detail_body = {"_parse_error": True, "text_head": (r.text or "")[:2000]}
|
||||
|
||||
step_detail = {
|
||||
"request": payload,
|
||||
"http_status": detail_http,
|
||||
"response": detail_body,
|
||||
}
|
||||
|
||||
if isinstance(detail_body, dict) and not cloudwalk_ok(detail_body):
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "person_detail_business_failed",
|
||||
"steps": {"person_detail": step_detail},
|
||||
}
|
||||
|
||||
data = detail_body.get("data") if isinstance(detail_body, dict) else {}
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
|
||||
host_zone_ids = extract_host_zone_ids(data)
|
||||
preview = compute_effective_preview(host_zone_ids, policy_allow_json)
|
||||
|
||||
zone_try: Optional[Dict[str, Any]] = None
|
||||
if common_base_url.strip():
|
||||
url = common_base_url.rstrip("/") + "/sysetting/zone/list"
|
||||
zpayload: Dict[str, Any] = {"zoneIds": preview.get("effective_zone_ids") or []}
|
||||
try:
|
||||
zr = session.post(url, json=zpayload, headers=headers, timeout=timeout)
|
||||
zone_try = {"url": url, "http_status": zr.status_code}
|
||||
try:
|
||||
zone_try["response"] = zr.json()
|
||||
except Exception:
|
||||
zone_try["text_head"] = (zr.text or "")[:1200]
|
||||
except Exception as ex:
|
||||
zone_try = {"url": url, "error": str(ex)}
|
||||
|
||||
page_init = {
|
||||
"businessId": business_id,
|
||||
"hostPersonId": host_person_id,
|
||||
"hostPersonName": data.get("name"),
|
||||
"hostZoneIds": host_zone_ids,
|
||||
"effectiveZoneIds": preview.get("effective_zone_ids"),
|
||||
"policyMode": preview.get("mode"),
|
||||
}
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"started_at": datetime.now(timezone.utc).isoformat(),
|
||||
"steps": {
|
||||
"person_detail": step_detail,
|
||||
"floor_preview": {
|
||||
"policy_allow_zone_ids_json": policy_allow_json,
|
||||
**preview,
|
||||
},
|
||||
"zone_optional": zone_try,
|
||||
},
|
||||
"page_init": page_init,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="访客邀约页初始化一键测试")
|
||||
ap.add_argument(
|
||||
"--org-base-url",
|
||||
default=os.environ.get("ORG_BASE", os.environ.get("COMPONENT_ORG_URL", "http://127.0.0.1:33011")),
|
||||
help="组织组件 HTTP 基址(测试环境常见 PORT_COMPONENT_ORG)",
|
||||
)
|
||||
ap.add_argument("--business-id", default=os.environ.get("BUSINESS_ID", "").strip())
|
||||
ap.add_argument("--host-person-id", default=os.environ.get("HOST_PERSON_ID", "").strip())
|
||||
ap.add_argument("--policy-allow-json", default=os.environ.get("POLICY_ALLOW_ZONE_IDS_JSON", "").strip())
|
||||
ap.add_argument("--common-base-url", default=os.environ.get("COMMON_BASE", "").strip())
|
||||
ap.add_argument("--timeout", type=float, default=float(os.environ.get("HTTP_TIMEOUT", "30")))
|
||||
ap.add_argument(
|
||||
"--mysql-host",
|
||||
default=os.environ.get("MYSQL_HOST", "127.0.0.1"),
|
||||
)
|
||||
ap.add_argument(
|
||||
"--mysql-port",
|
||||
type=int,
|
||||
default=int(os.environ.get("MYSQL_PORT", "3307")),
|
||||
)
|
||||
ap.add_argument("--mysql-user", default=os.environ.get("MYSQL_USER", "root"))
|
||||
ap.add_argument(
|
||||
"--mysql-password",
|
||||
default=os.environ.get("MYSQL_PASS", os.environ.get("MYSQL_PASSWORD", "")),
|
||||
)
|
||||
ap.add_argument(
|
||||
"--org-database",
|
||||
default=os.environ.get("DB_COMPONENT_ORG", "component-organization"),
|
||||
)
|
||||
ap.add_argument(
|
||||
"--elevator-database",
|
||||
default=os.environ.get("DB_ELEVATOR", "cw-elevator-application"),
|
||||
)
|
||||
ap.add_argument(
|
||||
"--no-db",
|
||||
action="store_true",
|
||||
help="不从数据库拉样本(必须提供 --business-id 与 --host-person-id)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
report: Dict[str, Any] = {"tool": "visitor_invite_page_init_example", "ok": False}
|
||||
|
||||
policy_allow = args.policy_allow_json or None
|
||||
meta_db: Optional[Dict[str, Any]] = None
|
||||
|
||||
try:
|
||||
if not args.no_db and (not args.business_id or not args.host_person_id):
|
||||
log("从数据库加载默认样本(BUSINESS_ID / 被访人 / 策略 allow_zone_ids)…")
|
||||
meta_db, policy_allow = fetch_sample_from_mysql(
|
||||
args.mysql_host,
|
||||
args.mysql_port,
|
||||
args.mysql_user,
|
||||
args.mysql_password,
|
||||
args.org_database,
|
||||
args.elevator_database,
|
||||
)
|
||||
args.business_id = meta_db["host_row"]["business_id"]
|
||||
args.host_person_id = meta_db["host_row"]["host_person_id"]
|
||||
report["resolved_from_database"] = meta_db
|
||||
elif not args.business_id or not args.host_person_id:
|
||||
log("错误:未指定 --business-id / --host-person-id,且未使用数据库默认。", file=sys.stderr)
|
||||
report["error"] = "missing_ids_use_db_or_pass_explicit"
|
||||
print(json.dumps(report, ensure_ascii=False))
|
||||
return 2
|
||||
|
||||
if policy_allow:
|
||||
report["policy_allow_zone_ids_source"] = (
|
||||
"database" if meta_db and meta_db.get("policy_row") else "cli_or_env"
|
||||
)
|
||||
else:
|
||||
report["policy_allow_zone_ids_source"] = "none"
|
||||
|
||||
result = run_http_flow(
|
||||
args.org_base_url,
|
||||
args.business_id,
|
||||
args.host_person_id,
|
||||
policy_allow,
|
||||
args.common_base_url,
|
||||
args.timeout,
|
||||
)
|
||||
report.update(result)
|
||||
if not report.get("ok"):
|
||||
print(json.dumps(report, ensure_ascii=False))
|
||||
return 1
|
||||
|
||||
log("测试完成。完整 JSON 见 stdout。")
|
||||
print(json.dumps(report, ensure_ascii=False))
|
||||
return 0
|
||||
except Exception as ex:
|
||||
report["ok"] = False
|
||||
report["error"] = str(ex)
|
||||
print(json.dumps(report, ensure_ascii=False))
|
||||
log(str(ex), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user