From 817183062dd974b9f97834b311249b011ec848e0 Mon Sep 17 00:00:00 2001 From: duxingchen Date: Tue, 22 Sep 2026 13:43:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(webhook):=20MOM=20=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E9=97=A8=E5=BD=92=E5=B1=9E=E5=88=86=E6=B5=81?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E5=8F=AA=E6=94=BE=E8=A1=8C=20IRIS=20?= =?UTF-8?q?=E4=B8=8E=E7=A9=BA=E7=99=BD=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MOM 现在同时对接 IRIS 与 LICA 两个 Track 实例,按载荷里的 company_name 分流。 实现: · MomInboundPayload / MomOutboundPayload 补 company_name 字段 (不补的话会被 Pydantic 静默丢弃,分流无从谈起) · 新增 _is_foreign_company(),在**鉴权之后、匹配产品之前**拦截 · 拦截时返回 200 + matched=False + reason="ignored_company" —— 与「未命中」 保持同一契约,避免 MOM 侧把它当成故障反复重推 ⚠️ 判定刻意做成「只排除已知的外来公司」(黑名单),而非「白名单只认 IRIS」: MOM 在无法确定公司归属时会回落到扁平配置,该配置指向本实例 —— 这类消息的 company_name 会是空 / 缺失。若按白名单把空白也拒掉,它们就彻底丢了:MOM 那边已收到 200、认为投递成功,不会再重推。同理,未见过的新值也一律照常处理。 reason 的取值 "ignored_company" 与 LICA 实例(~/track-lica)保持一致 —— MOM 侧 不解析它,但排查时两边日志要对着看,字段名不一致会白白浪费时间。 实测: · LICA 载荷 → {"ok":true,"matched":false,"reason":"ignored_company"} · IRIS/空串/缺失 → 照常处理 · 无 X-API-Key 仍返回 401(鉴权未被绕过) --- backend/app/api/v1/endpoints/webhooks.py | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/backend/app/api/v1/endpoints/webhooks.py b/backend/app/api/v1/endpoints/webhooks.py index 649e0e8..9bf108b 100644 --- a/backend/app/api/v1/endpoints/webhooks.py +++ b/backend/app/api/v1/endpoints/webhooks.py @@ -36,6 +36,7 @@ class MomInboundPayload(BaseModel): event: str | None = None # 事件名,如 inbound.created / outbound.revoked action: str | None = None # 显式动作指令,如 revoke_outbound source_table: str | None = None # stock_product / stock_semi + company_name: str | None = None # 目标公司(IRIS / LICA),MOM 据此分流到不同 Track 实例 # 「撤回出库」信号词 —— 只在 action / event 里做子串匹配。 @@ -44,6 +45,28 @@ class MomInboundPayload(BaseModel): _OUTBOUND_REVOKE_TOKENS = ("revoke", "rollback", "revert", "cancel") +# ── 公司归属分流 ────────────────────────────────────────────────────────── +# MOM 现在会在载荷里带 company_name,同一套物理库可能同时向多个 Track 实例 +# (IRIS / LICA)回调。本实例服务的是 IRIS,故只放行 IRIS 与空白值。 +# +# ⚠️ 判定刻意做成「只排除已知的外来公司」,而非「白名单只认 IRIS」: +# MOM 在无法确定公司归属时会回落到扁平配置,该配置指向本实例 —— 这类 +# 消息的 company_name 会是空 / 缺失。若此处按白名单把空白也拒掉,它们 +# 就彻底丢了:MOM 那边已收到 200、认为投递成功,不会再重推。 +# 同理,未见过的新值(不是 IRIS 也不是 LICA)也一律照常处理。 +_FOREIGN_COMPANIES = {"LICA"} + + +def _is_foreign_company(company_name: str | None) -> bool: + """载荷是否属于本实例不该处理的其它公司。 + + 返回 True 表示应原样忽略(仍回 200,避免 MOM 反复重推)。 + """ + # 大小写 / 首尾空白都容忍:MOM 侧常量书写方式未必冻结,误判的代价是 + # 一条消息被错误地当成本公司处理(有唯一匹配约束,最坏是 matched=False)。 + return (company_name or "").strip().upper() in _FOREIGN_COMPANIES + + def _is_outbound_revoke(payload: MomInboundPayload) -> bool: """payload 是否携带**显式**的撤回出库信号。 @@ -136,12 +159,20 @@ async def mom_inbound_webhook( - 常规入库:用 serial_number(优先)或 sku 匹配「当前位于 virtual_warehouse」 的产品,命中则标记"已实收"(overall_status=已入库 + 记录 task_logs)。 - 撤回出库:MOM 把误出库的设备物理回滚到仓库 → 本接口强制执行特权回滚。 + - 公司归属:company_name 明确写着其它公司(LICA)时原样忽略;空白 / 缺失 + 一律照常处理(见 _is_foreign_company 的说明)。 - 未命中返回 200(MOM 可能操作了非 Track 生产的物料,直接忽略)。 """ # ── 鉴权 ── if not settings.TRACK_WEBHOOK_KEY or x_api_key != settings.TRACK_WEBHOOK_KEY: raise HTTPException(status_code=401, detail="Unauthorized: invalid X-API-Key") + # ── 公司归属:不是本实例的消息原样忽略(仍回 200,避免 MOM 当作失败而重推) ── + # ⚠️ 键名 reason / 取值 "ignored_company" 与 LICA 实例(~/track-lica)保持一致: + # MOM 侧不解析它,但排查时两边日志对着看,字段名不一致会白白浪费时间。 + if _is_foreign_company(payload.company_name): + return {"ok": True, "matched": False, "reason": "ignored_company"} + explicit_revoke = _is_outbound_revoke(payload) # ── 匹配产品 ── @@ -329,6 +360,7 @@ class MomOutboundPayload(BaseModel): sku: str | None = None # 规格型号 spec_model(serial 缺失时的兜底匹配) operator: str | None = None # 出库操作人(写入 task_logs.operator_id) outbound_time: datetime | None = None # 出库时间 + company_name: str | None = None # 目标公司(IRIS / LICA),MOM 据此分流到不同 Track 实例 @router.post("/mom-outbound") @@ -342,12 +374,20 @@ async def mom_outbound_webhook( - 鉴权:Header X-API-Key 必须等于环境变量 TRACK_WEBHOOK_KEY。 - 用 serial_number(优先)或 sku 匹配"在仓库/已入库"的产品; 命中则标记"已出库"(overall_status=已出库 + status=OUTBOUND + 记录 task_logs)。 + - 公司归属:company_name 明确写着其它公司(LICA)时原样忽略;空白 / 缺失 + 一律照常处理(见 _is_foreign_company 的说明)。 - 未命中返回 200(MOM 出库的可能是非 Track 生产的物料,直接忽略)。 """ # ── 鉴权 ── if not settings.TRACK_WEBHOOK_KEY or x_api_key != settings.TRACK_WEBHOOK_KEY: raise HTTPException(status_code=401, detail="Unauthorized: invalid X-API-Key") + # ── 公司归属:不是本实例的消息原样忽略(仍回 200,避免 MOM 当作失败而重推) ── + # ⚠️ 键名 reason / 取值 "ignored_company" 与 LICA 实例(~/track-lica)保持一致: + # MOM 侧不解析它,但排查时两边日志对着看,字段名不一致会白白浪费时间。 + if _is_foreign_company(payload.company_name): + return {"ok": True, "matched": False, "reason": "ignored_company"} + # ── 按 serial_number(优先)或 sku 匹配"在仓库/已入库"的产品 ── product = None where_cond = or_(