mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-11 09:20:30 +08:00
feat: add service config templates and extraction script
Former-commit-id: 1de24b7eb79676d1aba9d799a58c5a753290cf52
This commit is contained in:
+60
@@ -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
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
#!/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 = {
|
||||
"1060601250460012544": { # 丘文明 — 1403艾斯(122devices)
|
||||
"name": "丘文明",
|
||||
"floorList": ["605560542353948672", "605560541473144832", "605560545117995008"], # 12F, 6F, 28F
|
||||
"organizationIds": [
|
||||
"72fb65ec5de94201b909a98b8bae1892", # 1403艾斯
|
||||
],
|
||||
},
|
||||
"1090914042800263168": { # 陈美全 — 1405一博环保(116devices)
|
||||
"name": "陈美全",
|
||||
"floorList": ["605560542752407552", "605560543834537984"], # 15F, 20F
|
||||
"organizationIds": [
|
||||
"2095de3d541f44eba686c78fda68336f", # 1405一博环保
|
||||
],
|
||||
},
|
||||
"964454497399468032": { # 蒙海文 — 广发基金(279devices)
|
||||
"name": "蒙海文",
|
||||
"floorList": [
|
||||
"605560545117995008", # 28F
|
||||
"605560545449345024", # 30F
|
||||
"605560545596145664", # 31F
|
||||
"605560545738752000", # 32F
|
||||
"605560545893941248", # 33F
|
||||
],
|
||||
"organizationIds": [
|
||||
"488b8ad049bb43408a6fbcc50bcb89ac", # 广发基金
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@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)
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
#!/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 = [
|
||||
HOST_1403 + "_t1", HOST_1405 + "_t2", HOST_1403 + "_t3",
|
||||
HOST_1403 + "_t4", HOST_1403 + "_t5", HOST_1403 + "_t6",
|
||||
HOST_GUANGFA + "_t7",
|
||||
]
|
||||
|
||||
ZONE_12F = "605560542353948672" # 12F (0x0C from code_elevator_area)
|
||||
ZONE_4F = "605560541473144832" # 6F (0x06, alternate floor for T6)
|
||||
ZONE_28F = "605560545117995008" # 28F (0x1C, 广发基金)
|
||||
ZONE_INVALID = "605560540000000000" # non-existent zone
|
||||
|
||||
OK_CODES = {"0", "200", "53013538"} # 53013538=策略通过但图库绑定失败(访客非真实人员)
|
||||
|
||||
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":"policy_t7_guangfa","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():
|
||||
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("DELETE FROM tenant_visitor_floor_policy WHERE id='policy_t7_guangfa'")
|
||||
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, 1, 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000)",
|
||||
('policy_t7_guangfa', ORG_GUANGFA, '["605560545117995008","605560545449345024","605560545596145664","605560545738752000","605560545893941248","605560546036547584","605560546242068480","605560546401452032","605560546552446976","605560546711830528"]'),
|
||||
)
|
||||
print(f" INSERT policy_t7_guangfa org={ORG_GUANGFA} (28F-38F)")
|
||||
|
||||
|
||||
def cleanup_test_data():
|
||||
for pid in ["policy_t1_1403", "policy_t3_invalid", "policy_t5_disabled", "policy_t7_guangfa"]:
|
||||
execute_sql("DELETE FROM tenant_visitor_floor_policy WHERE id=%s", (pid,))
|
||||
print(f" DELETE {pid}")
|
||||
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user