设备故障与库存信号上传:门店经营通知的数据入口
功能定位:门店经营通知系统的一线"感官"——售卖机故障/恢复信号与食材库存信号的统一接入层。设备、网关、店员的每一次上报都会被识别、去重、落流水,并驱动故障事件与低库存事件,最终触发分钟级告警。
一、为什么需要信号上传
门店经营里最怕"发现滞后":
- 售卖机故障:机器坏了没人知道,顾客到店才发现无法出餐;
- 食材短缺:缺货直到下单失败或临时下架才暴露,补采不及时造成拒单。
这两类信息过去依赖线下口头沟通和人工巡检。本版功能把它们变成结构化的信号:设备/网关按接口上报,店员在后台手工兜底,两条路径汇入同一套识别与留痕机制,事件一经识别即触发通知配置里的短信/邮件告警。
信号上传只负责"把事实送进来、识别清楚、留下证据",控制类能力(设备远程控制、维修工单)明确不在范围内。
二、三个接入端点
接口位于 /api/operation/ 前缀下,均为 POST,需携带管理员/机器账号的 JWT(IsShopAdmin 按店授权):
| 端点 | 用途 |
|---|---|
/api/operation/device-signals/fault/ |
售卖机故障信号上报 |
/api/operation/device-signals/recovery/ |
售卖机恢复信号上报 |
/api/operation/stock-signals/ |
食材库存信号批量上报 |
完整入参/出参见 Swagger UI:/api/schema/swagger-ui/(生产环境 https://freerain.cloud/api/schema/swagger-ui/),标签为「门店经营通知-设备接入」。
2.1 故障上报
POST /api/operation/device-signals/fault/
{
"device_no": "SM-2026-0001",
"fault_type": "heater_overheat",
"signal_time": "2026-10-30T09:15:00+08:00",
"remark": "加热模块过温保护",
"payload": {"temp": 96, "code": "E05"},
"idempotency_key": "evt-20261030-0915-0001"
}
2.2 恢复上报
请求体与故障上报一致,fault_type 可为空——为空表示恢复该设备的全部活跃故障:
POST /api/operation/device-signals/recovery/
{
"device_no": "SM-2026-0001",
"fault_type": "heater_overheat",
"idempotency_key": "evt-20261030-0940-0001"
}
2.3 库存批量上报
一次提交一个门店的多条库存条目,items 至少 1 条:
POST /api/operation/stock-signals/
{
"shop": 3,
"signal_source": "api",
"signal_time": "2026-10-30T21:00:00+08:00",
"items": [
{"shop_product": 101, "stock_num": 12, "idempotency_key": "it-0001"},
{"shop_product": 102, "stock_num": 0, "remark": "米粉告急", "idempotency_key": "it-0002"}
]
}
signal_source 由调用方声明:manual(人工录入)、stocktake(扫码盘点)、inbound(入库更新)、api(外部系统/设备对接)。未声明按 manual 记,对接方请显式声明 api。
三、识别动作:每条信号都有明确去向
系统不会"收到什么算什么",而是给出统一的 action 回执,与信号流水的 action 字段同源,方便上报方与排查者对齐口径。
设备信号:
| action | 含义 |
|---|---|
fault_opened |
新故障:生成故障事件,设备置为故障态 |
fault_duplicate |
同设备同故障类型未恢复,去重不重复告警 |
recovered |
对应故障事件已关闭,设备恢复正常 |
no_active_fault |
设备无对应活跃故障,仅留痕 |
device_not_found |
设备不在台账:拒绝并留痕,不自动建档 |
device_inactive |
设备已停用:信号忽略 |
idempotent_replay |
命中幂等键的重放(仅回执,不新增流水) |
库存信号(逐条独立处理):
| action | 含义 |
|---|---|
low_stock_opened |
首次跌破预警阈值,生成低库存事件 |
low_stock_daily_remind |
事件持续中跨自然日,当日首次提醒 |
low_stock_duplicate |
当日已提醒,去重不重复 |
recovered |
补货后高于阈值,事件恢复 |
stock_updated |
库存充足,仅更新账面库存 |
ignored_inactive |
商品已下架,不预警 |
product_not_found / shop_mismatch |
条目级拒绝,不影响同批次其他条目 |
回执示例(故障上报):
{
"signal_uuid": "6f1c1d2e-8a4b-4c6d-9f10-2b3c4d5e6f70",
"action": "fault_opened",
"message": "已生成故障事件并置设备为故障态",
"event_id": 12,
"device_no": "SM-2026-0001",
"device_status": "fault"
}
回执里的 signal_uuid 是信号流水的全局唯一回执 ID,event_id 关联故障事件/低库存事件,两者共同构成"一条信号 → 一个事件"的可追溯链路。
四、三个关键设计
4.1 幂等:重复上报不重复处理
设备网络抖动、网关重试是常态。上报方传入 idempotency_key(自身事件 ID),系统命中后直接返回 idempotent_replay,不新增流水、不重复生成事件、不重复告警。库存条目支持条目级幂等键,批量上报里单条重放不影响其余条目。
4.2 留痕:所有信号落流水
无论识别结果是通过还是拒绝,每一次上报都会写入信号流水(DeviceSignal / StockSignal),包含原始 payload、来源、幂等键、识别动作与处理消息。未知设备不会被自动建档,而是拒绝并留痕——设备必须先进入台账,避免脏数据自动蔓延。
4.3 门店隔离:按店授权
接口使用 IsShopAdmin 权限:设备/门店超出调用者的授权范围直接 403 且不留流水(未知设备放行给服务层拒绝留痕)。超级管理员不受范围限制,普通管理员与机器账号只能操作本店数据。
五、人工兜底与管理端
自动化上报覆盖不到的场景(网关离线、临时发现),门店人员可以在 Admin 页面手工登记故障、人工确认恢复——人工动作同样落 signal_source=manual 的信号流水,与自动信号走同一套事件识别逻辑,保证口径一致。
管理端(/api/admin/operation/)提供只读查询与台账维护:
- 设备台账:设备编号、所属门店、启用状态;
status、last_signal_at为只读字段,仅由信号驱动; - 信号流水 / 库存信号:按门店范围检索全部上报记录;
- 故障事件 / 低库存事件:活跃与已恢复事件、持续时长、阈值快照与库存快照;
- 通知配置与通知记录:接收人配置、投递状态与重试留痕。
六、端到端链路
设备/网关 ──┐
店员手工登记 ─┼─▶ 信号接入(鉴权 / 幂等 / 门店隔离)
外部库存系统 ─┘ │
▼
信号流水(全部留痕)
│
▼
识别动作:生成事件 / 去重 / 拒绝留痕
│
▼
故障事件 · 低库存事件 ─▶ 通知投递(短信 + 邮件抄送)
一条故障信号从上报到接收人收到短信是分钟级的;一条库存信号跌破阈值即触发补货提醒,并在每日缺货汇总中再次汇总。事后,流水与事件可以按 signal_uuid / event_id 完整回溯。
七、小结
本版落地的是门店经营通知的数据入口:
- 三个接入端点覆盖设备故障、设备恢复、库存批量上报,统一 JWT + 按店授权;
- 幂等键 + 全量流水保证重复不炸、拒绝留痕,事件链路可追溯;
- 识别动作与回执同源,上报方能明确知道每条信号被如何处理;
- 人工兜底与自动上报共用同一套识别逻辑,管理端可查台账、流水、事件与通知记录。
后续计划(P1)包括通知发送统计报表、日报按周/按店铺汇总;设备离线检测信号接入也在台账的 offline 状态上预留了位置。