feat: add service config templates and extraction script

Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
This commit is contained in:
反编译工作区
2026-05-01 19:38:01 +08:00
parent 3175b7074b
commit 8b15445328
2433 changed files with 8322164 additions and 1604 deletions
@@ -0,0 +1,60 @@
#!/bin/bash
# 组织服务楼层数据诊断
# 用法: bash diag_person_floors.sh
HOST="127.0.0.1"
PORT="18081"
BUSINESS_ID="2524639890ba4f2cba9ba1a4eeaa4015"
PERSONS=(
"1060601019894960128|陈国辉|1403艾斯"
"1090779433129840640|王姣|1405一博"
"1072908835884208128|秦夏|广发基金"
)
echo "=== 电梯健康检查 ==="
curl -s "http://${HOST}:${PORT}/health" 2>/dev/null || echo "FAIL"
for p in "${PERSONS[@]}"; do
IFS='|' read -r PID PNAME PORG <<< "$p"
echo ""
echo "=== $PNAME ($PID) [$PORG] ==="
# 调电梯 addVisitor 接口(会内部调组织服务获取人员详情)
RESP=$(curl -s -X POST "http://${HOST}:${PORT}/elevator/person/add/visitor" \
-H "Content-Type: application/json" \
-H "businessid: ${BUSINESS_ID}" \
-d "{
\"personId\": \"${PID}\",
\"visitorId\": \"diag_$(date +%s)\",
\"floorIds\": [],
\"begVisitorTime\": $(date +%s)000,
\"endVisitorTime\": $(($(date +%s) + 86400))000
}")
CODE=$(echo "$RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('code',''))" 2>/dev/null)
MSG=$(echo "$RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('message',''))" 2>/dev/null)
echo " code=$CODE"
echo " message=$MSG"
echo " raw=$RESP"
done
echo ""
echo "=== 试探:用特定 floorId 绕过策略 ==="
# 看看哪些 zoneId 能被组织服务接受
for zone in "605560541473144832" "605560545117995008" "605560542752407552" "605560545449345024"; do
RESP=$(curl -s -X POST "http://${HOST}:${PORT}/elevator/person/add/visitor" \
-H "Content-Type: application/json" \
-H "businessid: ${BUSINESS_ID}" \
-d "{
\"personId\": \"1060601019894960128\",
\"visitorId\": \"zone_test_${zone}\",
\"floorIds\": [\"${zone}\"],
\"begVisitorTime\": $(date +%s)000,
\"endVisitorTime\": $(($(date +%s) + 86400))000
}")
CODE=$(echo "$RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('code',''))" 2>/dev/null)
MSG=$(echo "$RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('message',''))" 2>/dev/null)
echo " zone=$zone → code=$CODE msg=$MSG"
done
@@ -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)
@@ -0,0 +1,13 @@
-- ============================================================
-- org_id 策略修复 — 测试数据清理(验证完成后执行)
-- 目标库:192.168.3.12:3307 / cw-elevator-application
-- 执行:mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application < test_data_cleanup.sql
-- ============================================================
DELETE FROM tenant_visitor_floor_policy WHERE id IN ('policy_t1_1403', 'policy_t3_invalid', 'policy_t5_disabled');
UPDATE tenant_visitor_floor_policy SET org_id = NULL WHERE id = 'gf_vstr_policy_guangfa_fund_001x';
-- 验证清理结果(应返回空集或 org_id=NULL
SELECT id, org_id, enabled FROM tenant_visitor_floor_policy
WHERE id IN ('policy_t1_1403', 'policy_t3_invalid', 'policy_t5_disabled', 'gf_vstr_policy_guangfa_fund_001x');
@@ -0,0 +1,26 @@
DELETE FROM tenant_visitor_floor_policy WHERE id IN ('policy_t1_1403', 'policy_t3_invalid', 'policy_t5_disabled');
INSERT INTO tenant_visitor_floor_policy
(id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at)
VALUES
('policy_t1_1403', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST',
'["db18ffc1346a44fcba96fad0fd2d7d3a"]', NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
INSERT INTO tenant_visitor_floor_policy
(id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at)
VALUES
('policy_t3_invalid', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST',
'["db18ffc1346a44fcba96fad0fd2d7d3a","605560540000000000"]', NULL, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
INSERT INTO tenant_visitor_floor_policy
(id, org_id, business_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, created_at, updated_at)
VALUES
('policy_t5_disabled', '72fb65ec5de94201b909a98b8bae1892', NULL, 'INTERSECT_ALLOWLIST',
'["db18ffc1346a44fcba96fad0fd2d7d3a"]', NULL, 0, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);
UPDATE tenant_visitor_floor_policy SET org_id = '488b8ad049bb43408a6fbcc50bcb89ac'
WHERE id = 'gf_vstr_policy_guangfa_fund_001x';
SELECT id, org_id, enabled, allow_zone_ids
FROM tenant_visitor_floor_policy
WHERE id IN ('policy_t1_1403', 'policy_t3_invalid', 'policy_t5_disabled', 'gf_vstr_policy_guangfa_fund_001x');
@@ -0,0 +1,70 @@
server.port=18081
logging.path=/tmp/elevator-logs
logging.file=cw-elevator
spring.cloud.consul.host=192.168.3.12
spring.cloud.consul.port=8500
spring.cloud.consul.enabled=true
spring.redis.host=127.0.0.1
spring.redis.port=6380
spring.redis.database=5
spring.redis.timeout=3000
spring.redis.pool.max-active=10
spring.redis.pool.max-idle=1
spring.redis.pool.max-wait=10
spring.redis.pool.min-idle=0
spring.shardingsphere.datasource.names=ds0
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.3.12:3307/cw-elevator-application?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
spring.shardingsphere.datasource.ds0.connection-timeout=60000
spring.shardingsphere.datasource.ds0.maximum-pool-size=20
spring.shardingsphere.datasource.ds0.minimum-idle=5
spring.shardingsphere.datasource.ds0.max-lifetime=1765000
spring.shardingsphere.datasource.ds0.auto-commit=true
spring.shardingsphere.datasource.ds0.pool-name=ds0-pool
spring.shardingsphere.props.sql.show=false
spring.shardingsphere.sharding.default-data-source-name=ds0
cloudwalk.event.bootstrap-servers=127.0.0.1:9092
cloudwalk.event.group-id=cw-elevator-local
feign.device.name=cwos-portal
feign.resource.name=cwos-portal
feign.cwos-portal.name=cwos-portal
feign.davinci-portal.name=cwos-portal
feign.ninca-crk-std.name=ninca-crk-std
feign.component-organization.name=ninca-common-component-organization
feign.ninca-common.name=ninca-common
feign.mqtt.name=cloudwalk-device-thirdparty
feign.hystrix.enable=true
feign.okhttp.enable=true
ribbon.okhttp.enabled=true
ribbon.ReadTimeout=10000
ribbon.ConnectTimeout=10000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
spring.mvc.throw-exception-if-no-handler-found=true
spring.messages.basename=access-control
mybatis.mapper-locations=classpath*:cn/cloudwalk/elevator/**/*.xml
mybatis.config-location=classpath:mapper/mybatis-config.xml
cloudwalk.serial.enable=true
cloudwalk.serial.serial-type=redis
cloudwalk.serial.serial-redis-key=CLOUDWALK-ACS-SERIAL-KEY
management.health.redis.enabled=false
management.health.db.enabled=false
cloudwalk.datafield.enable=true
cloudwalk.datafield.securityKey=d4b2aabc97394a12a27fc3cca6cd9ba1
intelligent.lock.enable=true
intelligent.lock.config.default-wait-time=10000
lockWatchdogTimeout=21000
person.name.space=recordEvent
elevator.application.key=xinghewan
elevator.application.time=600
elevator.application.keyA=5B7DEF88FF04
ninca-crk-std.ip=127.0.0.1:16106
sendRecord.boolean=false
floor.building.id=605560539791228928
spring.redis.password=1qaz!QAZ
ninca-common-component-organization.ribbon.listOfServers=127.0.0.1:18082
ninca-common-component-organization.ribbon.ServerListRefreshInterval=5000
ninca-common.ribbon.listOfServers=127.0.0.1:18082
ninca-common.ribbon.ServerListRefreshInterval=5000
@@ -0,0 +1,269 @@
#!/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 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_1403 = "1060601250460012544" # 丘文明 (1403艾斯, 122 devices)
HOST_1405 = "1090914042800263168" # 陈美全 (1405一博环保, 116 devices)
HOST_GUANGFA = "964454497399468032" # 蒙海文 (广发基金, 279 devices)
VISITOR_IDS = [
"vfy_t1_" + str(int(time.time())), "vfy_t2_" + str(int(time.time()) + 1),
"vfy_t3_" + str(int(time.time()) + 2), "vfy_t4_" + str(int(time.time()) + 3),
"vfy_t5_" + str(int(time.time()) + 4), "vfy_t6_" + str(int(time.time()) + 5),
"vfy_t7_" + str(int(time.time()) + 6),
]
ZONE_12F = "db18ffc1346a44fcba96fad0fd2d7d3a" # 12F (from cw_qz_device_area)
ZONE_4F = "dcfeeced3ad040c485df82cc95417b34" # 4F (alternate)
ZONE_INVALID = "605560540000000000" # non-existent zone
OK_CODES = {"0", "200"}
TEST_CASES = [
{"id":"T1","name":"有策略→allow替换floorList","host_id":HOST_1403,"visitor_id":VISITOR_IDS[0],"policy_id":"policy_t1_1403","expected_pass":True,"expected_floors":[ZONE_12F]},
{"id":"T2","name":"无策略→floorList","host_id":HOST_1405,"visitor_id":VISITOR_IDS[1],"policy_id":None,"expected_pass":True,"expected_floors":None},
{"id":"T3","name":"allow含无效zone→拒绝","host_id":HOST_1403,"visitor_id":VISITOR_IDS[2],"policy_id":"policy_t3_invalid","expected_pass":False,"expected_code":"76260533"},
{"id":"T4","name":"多组织命中第一个策略","host_id":HOST_1403,"visitor_id":VISITOR_IDS[3],"policy_id":"policy_t1_1403","expected_pass":True,"expected_floors":[ZONE_12F]},
{"id":"T5","name":"enabled=0等同无策略","host_id":HOST_1403,"visitor_id":VISITOR_IDS[4],"policy_id":"policy_t5_disabled","expected_pass":True,"expected_floors":None},
{"id":"T6","name":"UC-02策略优先","host_id":HOST_1403,"visitor_id":VISITOR_IDS[5],"policy_id":"policy_t1_1403","expected_pass":True,"expected_floors":[ZONE_12F],"floor_ids_override":[ZONE_4F]},
{"id":"T7","name":"广发基金迁移验证","host_id":HOST_GUANGFA,"visitor_id":VISITOR_IDS[6],"policy_id":"gf_vstr_policy_guangfa_fund_001x","expected_pass":True,"expected_floors":[ZONE_12F]},
]
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():
import pymysql # 延迟导入,--skip-db 模式无需安装
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_12F}"]', 1),
("policy_t3_invalid", ORG_1403, f'["{ZONE_12F}","{ZONE_INVALID}"]', 1),
("policy_t5_disabled", ORG_1403, f'["{ZONE_12F}"]', 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, skip_db=False):
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":
if skip_db:
print(" [SKIP-DB] 请 DBA 手动执行: DELETE FROM tenant_visitor_floor_policy WHERE id='policy_t1_1403'")
else:
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":
if skip_db:
print(" [SKIP-DB] 请 DBA 手动恢复 policy_t1_1403 (见 test_data_prepare.sql)")
else:
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_12F}"]'),
)
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, args.skip_db) 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()