SSE、导入导出与外围能力
本页覆盖三类容易被混在一起的能力:实时事件流、批量导入导出、以及图片/外部信息这类外围服务。它们的共同点是都不直接承载核心业务状态,但会影响前端刷新、数据落地和安全边界。
SSE 职责
GET /api/events 是统一事件流入口。它不负责首屏数据加载,首屏仍然由普通 REST 接口完成;SSE 只处理“列表已加载后,数据又发生变化”的增量同步与 stale 提示。
当前允许的房间包括:
inventorycommon_shelfreagent_ordersconsumable_ordersdashboard
客户端使用 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 标准化、拼音字段生成
- 批量创建库存
- 失败时回滚批次
因此前端只负责上传和展示结果,不应复制导入规则。
浏览器扩展导入
购物车同步也是导入能力,但它分成“采集”和“写入系统”两段:
- 扩展在外部平台采集购物车与商品详情。
- popup 把最近一次导入批次写入
chrome.storage.local。 - 导入页桥接脚本把批次复制到页面
localStorage.cart_import_batch_latest。 - 前端再调用
/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 广播。
- 新增导出接口时,保持“后端生成正式文件格式”的边界。
- 新增外部数据抓取能力时,继续通过后端统一暴露,不要把第三方调用直接暴露给浏览器。