多台打印机不是把小程序复制多份,也不是给每台打印机开一个公网地址。正确做法是让所有用户和代理连接同一个中心平台,由中心平台根据二维码、设备能力、门店和任务状态完成路由。
一、目标拓扑
用户微信小程序
│
│ HTTPS:登录、上传、报价、支付、订单状态
▼
固定入口 print-api.munvor.com
│
├── 无状态 API 集群
├── PostgreSQL:订单与任务唯一事实源
├── 私有对象存储:源文件与规范化文件
├── 可靠队列:调度信号、重试与死信
├── 微信支付:下单、回调、查单、退款、对账
└── 管理后台:门店、设备、订单、告警和审计
▲
│ 所有门店代理只主动出站 HTTPS
│
门店 A 代理 ── 打印机 A1 / A2 / A3
门店 B 代理 ── 打印机 B1 / B2
门店 C 代理 ── 打印机 C1 / C2 / C3 / C4
二、各组件的责任边界
微信小程序
负责用户交互,不负责最终价格和业务真相。它可以缓存页面,但不能持有服务端密钥,也不能直接访问打印机。
中心 API
负责鉴权、二维码解析、服务器定价、订单状态机、支付编排和代理接口。API 应无状态化,多实例之间通过数据库和可靠队列协作。
PostgreSQL
订单、支付、打印任务和审计的唯一事实源。所有关键唯一约束和状态推进在数据库事务中完成。
对象存储
保存源文件、图片转 PDF 或 Office 转 PDF 的派生文件。对象默认为私有,只通过短期签名上传和下载,并按生命周期删除。
可靠队列
用于削峰、唤醒消费者和重试安全步骤。队列通常提供至少一次投递,因此消费者必须幂等。队列消息不是“允许重新打印”的凭证。
门店边缘代理
代理注册后只管理被明确绑定的打印机。它周期性心跳、领取任务、下载校验、持久化回执、调用本地队列和上报诊断。
管理后台
运营人员管理门店、设备、二维码、价格、订单、退款、告警、版本和审计。高风险动作需要二次确认和完整操作记录。
三、多打印机数据路由
每台打印机至少有:
- 平台
printer_id - 可轮换二维码别名
alias - 所属
tenant_id和store_id - 对应门店代理
agent_id - 固定 Windows
queue_name - 能力 JSON 和能力版本
- 在线状态、维护状态和最后心跳
用户扫码得到的是 alias,服务端解析为 printer_id。创建报价、订单和打印任务时都固定这个 printer_id。代理只能领取自己绑定设备的任务,不能通过修改请求打印其他门店文件。
四、并发模型
每台打印机串行
实体打印机的驱动、纸张和队列状态难以支持同一时刻多个控制线程。因此同一 printer_id 的物理提交并发固定为 1。
不同打印机并行
同一个代理可以同时处理多台不同打印机。例如 A1 正在打印时,A2 仍可领取并执行自己的任务。实现上使用 Map<printerId, Mutex> 或独立 worker,而不是全局 busy。
代理不是唯一锁
真正的所有权由中心数据库租约和尝试号保证。本地锁只防止同一进程误并发;即使两台电脑配置错绑到同一打印机,中心租约也必须阻止重复领取。
五、任务调度策略
第一期坚持“二维码指定设备”,不做复杂自动调度。这样用户知道去哪台机器取纸,故障定位也最简单。
后续可以增加受控改派:
- 原设备在支付后长时间离线。
- 同门店存在能力一致的备用设备。
- 用户明确同意改派。
- 价格不增加,或重新获得用户确认。
- 原任务租约已失效且确认没有物理提交。
不能在任务已经可能提交后自动改派,否则两台打印机都可能出纸。
六、连接方式
门店代理推荐:
- 通过 HTTPS 长轮询领取任务,逻辑简单、穿透大多数企业网络。
- 或使用鉴权 WebSocket 接收唤醒,再通过 HTTPS 领取实际任务。
- 心跳间隔建议从 30 秒开始,根据规模和费用调整。
- 连接失败采用指数退避和随机抖动,避免中心恢复时所有代理同时重连。
不推荐:
- 把打印机 9100 端口暴露公网。
- 开放 Windows 文件共享或远程桌面给中心服务。
- 每台打印机建立随机临时隧道。
- 让小程序直接连接门店局域网 IP。
七、规模阶段
| 阶段 | 规模 | 重点 |
|---|---|---|
| 试点 | 3—5 台 | 固定域名、真实支付、中心存储、基础后台 |
| 小规模 | 10—50 台 | 可靠队列、告警、设备注册、代理灰度升级 |
| 规模化 | 50—300 台 | 分区调度、财务对账、备件、SLA 和容量自动扩展 |
| 大规模 | 300 台以上 | 多区域、灾备、专门运维中心、供应链和合规体系 |
扩容前先观察打印成功率、人工确认率、退款率、单机利用率和故障恢复时间。API 吞吐通常不是最早的瓶颈,打印机速度、纸仓、耗材和现场处理才是。