跳转到正文

SSE、导入导出与外围能力

本页覆盖三类容易被混在一起的能力:实时事件流、批量导入导出、以及图片/外部信息这类外围服务。它们的共同点是都不直接承载核心业务状态,但会影响前端刷新、数据落地和安全边界。

SSE 职责

GET /api/events 是统一事件流入口。它不负责首屏数据加载,首屏仍然由普通 REST 接口完成;SSE 只处理“列表已加载后,数据又发生变化”的增量同步与 stale 提示。

当前允许的房间包括:

  • inventory
  • common_shelf
  • reagent_orders
  • consumable_orders
  • dashboard

客户端使用 rooms=inventory,common_shelf 这样的逗号分隔参数订阅,服务端会先校验房间白名单。

SSE 运行机制

app/services/sse_manager.py 维护了“本地队列 + Redis bridge”的混合实现:

  • 单个浏览器连接对应一个 SSEClient,内部保存队列、订阅房间和 last_seq_by_room
  • broadcast() 先为房间分配递增序号,再推送给本地订阅者。
  • Redis 可用时会同步 publish,用于多进程或多实例同步。
  • stream() 持续产出事件,并按固定间隔发送 heartbeat,避免代理层断开长连接。

前端不会无条件接受所有推送:useListSSE 只对不影响搜索和排序语义的更新做安全 patch,其余情况会直接标记 stale。

导入链路

Excel 导入

库存导入由 /api/inventory/import 提供。这个接口的重点不是“上传文件”,而是后端要完成完整的导入判定:

  • 文件大小、扩展名和内容的早期校验
  • 表头与字段映射校验
  • 规格解析、CAS 标准化、拼音字段生成
  • 批量创建库存
  • 失败时回滚批次

因此前端只负责上传和展示结果,不应复制导入规则。

浏览器扩展导入

购物车同步也是导入能力,但它分成“采集”和“写入系统”两段:

  1. 扩展在外部平台采集购物车与商品详情。
  2. popup 把最近一次导入批次写入 chrome.storage.local
  3. 导入页桥接脚本把批次复制到页面 localStorage.cart_import_batch_latest
  4. 前端再调用 /api/cart-sync/api/cart-sync/import

这样做的边界是明确的:扩展负责采集,后端负责最终订单写入。

导出链路

导出不是简单把 REST 列表 JSON 转存,而是正式的数据输出能力:

  • 库存、常用货架、试剂订单、耗材订单都支持导出。
  • 统一由专门的导出服务生成面向用户的文件结构。
  • 前端通过 blob 下载,文件名由页面和后端共同约定。

新增导出页面时,建议继续沿用“后端生成文件,前端只负责触发下载”的模式。

图片与静态资源

项目坚持图片不上数据库:

  • 头像、公告图片、订单相关图片都写入文件系统。
  • 数据库只存 URL 或文件名。
  • CachedStaticFiles 为静态资源统一写入超长缓存头和安全头。
  • Nginx 同时转发 /static//api/static/,便于不同入口访问。

如果新增图片类字段,应优先复用 image_service 的压缩、命名和路径策略,而不是把二进制直接写进模型。

化学信息服务

app/services/chemical_info.py 同时承担服务和路由职责:

  • 路由是 GET /api/chemical-info/{cas_number}
  • 请求会先做 CAS 校验和标准化。
  • 英文名优先来自 PubChem,中文名来自 chemblink;必要时可结合翻译能力。
  • 内部有独立缓存和外部请求安全限制,避免 SSRF 风险。

这部分能力适合复用于入库页、订单页和脚本工具,但仍应通过后端统一对外暴露,不建议让前端直接请求第三方站点。

补充:房间、序号与降级路径

  • 房间白名单:inventory / common_shelf / reagent_orders / consumable_orders / cart_sync,在 /api/events 中校验。
  • 序号生成优先使用 Redis INCR,失败时回退本地计数 fallback_seq
  • sse_manager.broadcast 会调用 redis_pubsub.publish,Redis 不可用时只保留本地推送。
  • 慢客户端队列满时会被断开,避免阻塞服务器。
  • event_generator 会定期发送 : heartbeat,前端 useSSE 会把 stale 作为重连后的刷新信号。

补充:导入与导出

  • Excel 导入接口限制 2MB,只接受 csv/xlsx/xls,逐行错误会汇总返回。
  • 购物车导入对应 /api/cart-sync/sync/api/cart-sync/import 两步,导入后会广播相关房间。
  • 导出接口只负责产出文件,不负责把结果写回缓存。

边界与风险

  • 新增 SSE 房间时,要同时更新服务端白名单和前端订阅列表。
  • Redis 不可用时,消息只在当前实例可见,多副本部署会丢失跨实例广播。
  • 导入文件超限或格式错误时要正确返回 400/413,前端也需要匹配提示。
  • 新增外部数据抓取能力时,要继续沿用 chemical_info 的出站访问限制和缓存策略。

验证建议

  • 手动断开 Redis,确认当前实例仍能广播,但跨实例不可达。
  • 压测 SSE,确认慢客户端会被自动断开。
  • 上传超 2MB 或不支持的扩展名,确认会立即拒绝。
  • 成功导入后,确认 SSE 推送能让前端列表刷新。

二次开发建议

  • 需要实时同步的新列表,优先复用现有 SSE 房间和 useListSSE 模式。
  • 新增导入接口时,要同时考虑上传大小限制、缓存失效和 SSE 广播。
  • 新增导出接口时,保持“后端生成正式文件格式”的边界。
  • 新增外部数据抓取能力时,继续通过后端统一暴露,不要把第三方调用直接暴露给浏览器。

参考代码

开源项目 · Apache-2.0 license