# app/services/track_webhook_service.py """ Track 系统 Webhook 通知服务(发送侧) 真实扫码入库成功后,通过后台线程 + httpx 异步 POST 通知 Track 系统。 容错策略: - 未配置 TRACK_WEBHOOK_URL 时静默跳过,不通知。 - 全包裹 try-except,任何异常(连接拒绝/超时/网络错误)仅记录 logger.error, 绝不抛出异常阻断入库主业务流程。 """ import logging import threading import httpx from flask import current_app logger = logging.getLogger(__name__) # 请求超时(秒):后台线程,宁可超时失败也不能拖住业务 _WEBHOOK_TIMEOUT = 5.0 def _post_to_track(payload, url, api_key): """后台线程中执行的真正 POST 请求(含详细日志)""" headers = { 'Content-Type': 'application/json', # ★ 鉴权字段名必须为 X-API-Key,与 Track 接收端严格对齐 'X-API-Key': api_key, } try: resp = httpx.post(url, json=payload, headers=headers, timeout=_WEBHOOK_TIMEOUT) logger.info( "[TrackWebhook] event=%s source_table=%s status=%s", payload.get('event'), payload.get('source_table'), resp.status_code, ) except Exception as e: logger.error("[TrackWebhook] 发送失败 url=%s, err=%s", url, e) def get_current_operator(): """从 JWT 中安全获取当前操作人姓名(失败返回空字符串,不抛异常)""" try: from flask_jwt_extended import get_jwt claims = get_jwt() if not claims: return '' # 优先显示名(如"张三"),回退到账号ID return claims.get('display_name') or claims.get('username') or '' except Exception: return '' def notify_track(payload, url=None): """ 异步通知 Track 系统业务事件(入库/出库等)。 调用方在 db.session.commit() 成功后调用,本函数绝不抛出异常: - 未配置 URL / Key 为空 -> 静默跳过 - 网络异常 / 超时 -> 仅记录日志 :param url: 指定 Track webhook 地址;不传则用默认 TRACK_WEBHOOK_URL(入库) """ try: url = (url or current_app.config.get('TRACK_WEBHOOK_URL') or '').strip() api_key = (current_app.config.get('TRACK_WEBHOOK_KEY') or '').strip() if not url: logger.info("[TrackWebhook] 未配置 webhook URL,跳过通知") return thread = threading.Thread( target=_post_to_track, args=(payload, url, api_key), daemon=True, # 守护线程:不阻塞进程退出 name='track_webhook_notify', ) thread.start() except Exception as e: logger.error("[TrackWebhook] 通知调用失败(不影响业务): %s", e)