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,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)
@@ -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()