Files
XingHuo/scripts/demo/subscribe_alerts.py
T

104 lines
3.8 KiB
Python
Raw 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.
#!/usr/bin/env python3
"""risk:pub:alert 预警推送订阅演示(B9a · PRD FR-4 通知 / A-3 验收第三条)。
演示 SOP:
终端 1 python scripts/demo/subscribe_alerts.py
终端 2 uvicorn app.main:app --reload → Swagger 发演示交易
(A-3:CUST-3001 申购 50 万 PROD-510300,RISK-001/002 命中)
终端 1 应实时打印预警推送行;无推送时每 30s 打印心跳提示仍在监听。
payload 口径(PRD §4 FR-4 · 02-redis-keys.md §2.4):
{alert_id, alert_type, customer_id_mask, risk_score, trace_id, notify_role}
--duration N:监听 N 秒后自动退出(默认 0 = 不限,Ctrl+C 退出)。
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.config.settings import settings # noqa: E402
CHANNEL = "risk:pub:alert"
def format_alert(payload: dict[str, Any]) -> str:
"""推送 payload → 单行可读文本(演示展示用,字段缺失容错)。"""
if "raw" in payload:
return f"[raw] {payload['raw']}"
roles = ",".join(payload.get("notify_role") or [])
return (
f"[{payload.get('alert_type', '?'):>12}] {payload.get('alert_id', '?')}"
f" score={payload.get('risk_score', '?')}"
f" customer={payload.get('customer_id_mask', '?')}"
f" trace={payload.get('trace_id', '?')}"
f" notify=[{roles}]"
)
def run(duration: float) -> None:
try:
import redis
except ImportError:
print("缺少 redis 包:请执行 python -m pip install -r requirements.txt", file=sys.stderr)
raise SystemExit(1)
client = redis.Redis.from_url(settings.redis_url, decode_responses=True)
try:
client.ping()
except Exception as exc:
print(f"Redis 不可达({settings.redis_url}):{exc}", file=sys.stderr)
print("请先启动本机 Redis 服务(见 FLOW §0 本机状态)。", file=sys.stderr)
raise SystemExit(1)
pubsub = client.pubsub(ignore_subscribe_messages=True)
pubsub.subscribe(CHANNEL)
print(f"已订阅 {CHANNEL} @ {settings.redis_url}(Ctrl+C 退出)")
print("等待预警推送…(另开终端发演示交易,SOP 见脚本头注释)")
deadline = time.monotonic() + duration if duration > 0 else None
received = 0
last_heartbeat = time.monotonic()
try:
while True:
msg = pubsub.get_message(timeout=1.0)
if msg and msg.get("type") == "message":
received += 1
try:
payload = json.loads(msg["data"])
if not isinstance(payload, dict):
payload = {"raw": str(msg["data"])}
except (TypeError, ValueError):
payload = {"raw": str(msg["data"])}
print(f"{time.strftime('%H:%M:%S')} #{received} {format_alert(payload)}")
elif deadline is not None and time.monotonic() >= deadline:
print(f"已监听 {duration:g}s,共收到 {received} 条推送,退出。")
return
elif deadline is None and time.monotonic() - last_heartbeat >= 30:
last_heartbeat = time.monotonic()
print(f"…监听中(已收到 {received} 条)")
except KeyboardInterrupt:
print(f"\n退出(共收到 {received} 条推送)。")
finally:
pubsub.close()
client.close()
def main() -> None:
parser = argparse.ArgumentParser(description="订阅 risk:pub:alert(风控预警推送演示)")
parser.add_argument("--duration", type=float, default=0.0, help="监听秒数;0=不限(Ctrl+C 退出)")
args = parser.parse_args()
run(args.duration)
if __name__ == "__main__":
main()