Files
iot-device-management-service/test_work_order.py
16337 44c5df7302 功能:area_id 存储 + 工单对接代码完善 + 心跳端点
- AlarmEvent 模型添加 area_id 字段
- create_from_edge_report 提取 ext_data.area_id 存储
- 心跳端点 POST /api/ai/device/heartbeat
- work_order_client: create_order 支持完整参数(description/priority/triggerSource/cameraId/imageUrl)
- notify_dispatch: 工单标题中文化、alarmType 中文映射、永久 COS URL、triggerSource 来源判断
- oss_storage: 新增 get_permanent_url 方法
- 工单创建测试脚本

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:05:05 +08:00

276 lines
8.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
安保工单开放接口测试脚本
基于最新接入文档v1.0.0 / 2026-03-10测试内容
1. SHA256 签名生成验证
2. 创建工单 API含 tenant-id 请求头)
3. 自动完单 API
签名公式SHA256(Query参数排序后 + Body原始JSON + Header参数 + appSecret)
- Query 参数: 按 key 字母升序排序,无参数时为空串
- Header 参数: 固定顺序 appId=&nonce=&timestamp=
使用方法:
python test_work_order.py
"""
import asyncio
import hashlib
import json
import time
import uuid
import httpx
# ===== 配置(使用实际凭证) =====
BASE_URL = "http://192.168.0.104:48080"
APP_ID = "alarm-system"
APP_SECRET = "tQ3v5q1z2ZLu7hrU1yseaHwg1wJUcmF1"
TENANT_ID = "1"
def build_sign(query_str: str, body_json: str, nonce: str, timestamp: str) -> str:
"""
构建签名
签名字符串 = Query参数(排序后) + Body原始JSON + Header参数 + appSecret
Header参数 = appId={appId}&nonce={nonce}&timestamp={timestamp}
"""
header_str = f"appId={APP_ID}&nonce={nonce}&timestamp={timestamp}"
sign_string = f"{query_str}{body_json}{header_str}{APP_SECRET}"
return hashlib.sha256(sign_string.encode("utf-8")).hexdigest(), sign_string
def build_headers(body_json: str, query_str: str = "") -> dict:
"""构造完整请求头(含 tenant-id + 签名)"""
nonce = uuid.uuid4().hex[:16]
timestamp = str(int(time.time() * 1000))
sign, sign_raw = build_sign(query_str, body_json, nonce, timestamp)
headers = {
"Content-Type": "application/json",
"tenant-id": TENANT_ID,
"appId": APP_ID,
"timestamp": timestamp,
"nonce": nonce,
"sign": sign,
}
return headers, sign_raw
async def test_signature():
"""测试 0: 签名生成验证(用固定参数便于手动对比)"""
print("=" * 60)
print("测试 0: 签名生成验证")
print("=" * 60)
body = {"title": "A栋3层入侵告警", "areaId": 1309, "alarmId": "ALM001", "alarmType": "intrusion"}
body_json = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
nonce = "a1b2c3d4e5f6g7h8"
timestamp = "1704357025000"
sign, sign_raw = build_sign("", body_json, nonce, timestamp)
print(f" Body JSON: {body_json}")
print(f" Query 参数: (空)")
print(f" nonce: {nonce}")
print(f" timestamp: {timestamp}")
print(f" 签名原文: {sign_raw}")
print(f" 签名结果: {sign}")
print()
# 带 Query 参数的签名示例(文档示例)
query_str = "k1=v1&k2=v2"
sign2, sign_raw2 = build_sign(query_str, body_json, nonce, timestamp)
print(f" [带 Query] 签名原文: {sign_raw2}")
print(f" [带 Query] 签名结果: {sign2}")
print()
async def test_create_order():
"""测试 1: 创建安保工单"""
print("=" * 60)
print("测试 1: 创建安保工单")
print("=" * 60)
body = {
"title": "【一般】人员离岗告警 - A座大堂吧台",
"areaId": 1317,
"alarmId": f"TEST_ALARM_{int(time.time())}",
"alarmType": "leave_post",
"priority": 2,
}
body_json = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
headers, sign_raw = build_headers(body_json)
url = f"{BASE_URL}/open-api/ops/security/order/create"
print(f" URL: {url}")
print(f" Body: {body_json}")
print(f" Headers:")
for k, v in headers.items():
print(f" {k}: {v}")
print(f" 签名原文: {sign_raw}")
print()
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(url, content=body_json, headers=headers)
print(f" HTTP Status: {resp.status_code}")
print(f" Response Body: {resp.text[:1000]}")
if resp.status_code == 200:
try:
data = resp.json()
if data.get("code") == 0:
order_id = str(data.get("data", ""))
print(f"\n [成功] 工单已创建, orderId = {order_id}")
return order_id
else:
print(f"\n [失败] API 返回错误: code={data.get('code')}, msg={data.get('msg')}")
except Exception as e:
print(f"\n [失败] 解析响应失败: {e}")
else:
print(f"\n [失败] HTTP {resp.status_code}")
return None
async def test_create_order_full():
"""测试 1b: 创建工单(含全部可选参数)"""
print()
print("=" * 60)
print("测试 1b: 创建工单(全部参数)")
print("=" * 60)
body = {
"title": "【严重】入侵告警 - C座消控室",
"description": "摄像头检测到异常人员入侵",
"priority": 0,
"areaId": 1318,
"location": "C座1层消控室门口",
"alarmId": f"TEST_ALARM_{int(time.time())}",
"alarmType": "intrusion",
"cameraId": "cam_172_16_8_37_fa4c",
"imageUrl": "https://example.com/alarm/snapshot.jpg",
}
body_json = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
headers, sign_raw = build_headers(body_json)
url = f"{BASE_URL}/open-api/ops/security/order/create"
print(f" URL: {url}")
print(f" Body: {body_json}")
print()
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(url, content=body_json, headers=headers)
print(f" HTTP Status: {resp.status_code}")
print(f" Response Body: {resp.text[:1000]}")
if resp.status_code == 200:
try:
data = resp.json()
if data.get("code") == 0:
order_id = str(data.get("data", ""))
print(f"\n [成功] 工单已创建, orderId = {order_id}")
return order_id
else:
print(f"\n [失败] code={data.get('code')}, msg={data.get('msg')}")
except Exception as e:
print(f"\n [失败] {e}")
return None
async def test_auto_complete(order_id: str):
"""测试 2: 自动完单"""
print()
print("=" * 60)
print(f"测试 2: 自动完单 (orderId={order_id})")
print("=" * 60)
body = {
"orderId": int(order_id),
"remark": "告警自动解除 - 人员回岗",
}
body_json = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
headers, sign_raw = build_headers(body_json)
url = f"{BASE_URL}/open-api/ops/security/order/auto-complete"
print(f" URL: {url}")
print(f" Body: {body_json}")
print(f" 签名原文: {sign_raw}")
print()
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(url, content=body_json, headers=headers)
print(f" HTTP Status: {resp.status_code}")
print(f" Response Body: {resp.text[:1000]}")
if resp.status_code == 200:
try:
data = resp.json()
if data.get("code") == 0:
print(f"\n [成功] 工单已自动完单")
else:
print(f"\n [失败] code={data.get('code')}, msg={data.get('msg')}")
except Exception as e:
print(f"\n [失败] {e}")
else:
print(f"\n [失败] HTTP {resp.status_code}")
async def test_curl_equivalent():
"""测试 3: 生成等效 curl 命令(便于手动调试)"""
print()
print("=" * 60)
print("测试 3: 生成等效 curl 命令")
print("=" * 60)
body = {"title": "测试告警", "areaId": 1309, "alarmId": "TEST001"}
body_json = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
headers, _ = build_headers(body_json)
url = f"{BASE_URL}/open-api/ops/security/order/create"
curl_parts = [f'curl -X POST "{url}"']
for k, v in headers.items():
curl_parts.append(f' -H "{k}: {v}"')
curl_parts.append(f" -d '{body_json}'")
print()
print(" \\\n".join(curl_parts))
print()
async def main():
print(f"工单系统地址: {BASE_URL}")
print(f"appId: {APP_ID}")
print(f"tenant-id: {TENANT_ID}")
print()
# 0. 签名验证
await test_signature()
# 1. 创建工单(最简参数)
order_id = await test_create_order()
# 1b. 创建工单(全部参数)
order_id_full = await test_create_order_full()
# 2. 自动完单(仅在创建成功时测试)
target_order = order_id or order_id_full
if target_order:
input("\n按回车继续测试自动完单...")
await test_auto_complete(target_order)
else:
print("\n[跳过] 创建工单均失败,无法测试自动完单")
# 3. 生成 curl 命令
await test_curl_equivalent()
print()
print("=" * 60)
print("测试完成")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(main())