用户看到的是“扫码、上传、付款、打印”四个动作,但后台实际上要完成身份、设备、文件、价格、资金和物理输出六类状态的一致性控制。
状态说明
本章描述生产目标流程。当前单机原型已经具备扫码入口、上传、基础预检、订单、任务领取和队列提交,但真实支付、设备证书、完整
lease_token栅栏、提交前本地回执数据库和多打印机调度仍属于待实现目标。
一、标准用户流程
1. 扫描设备二维码
每台物理打印机贴一个独立的小程序码。二维码场景值建议只保存一个可轮换的短别名,例如 p_x7k9m2,不直接暴露数据库自增 ID、门店网络地址或任何密钥。
小程序启动后把场景值提交给中心 API,服务器解析出:
- 租户和门店
- 目标打印机
- 纸张、颜色、单双面等能力
- 在线、繁忙、维护或停用状态
- 当前价格版本和营业状态
如果二维码被撤销、设备停用或不在线,小程序应在上传和支付之前明确提示。
2. 微信登录
小程序调用微信登录获得一次性 code,服务端再与微信接口交换用户标识并创建业务会话。AppSecret 只允许保存在服务端,不能出现在小程序代码、网页 JavaScript、PDF 或日志中。
业务会话应为短期令牌,包含最少权限。接口同时检查订单所有权,避免用户通过修改 URL 查询他人的文件或订单。
3. 创建上传会话
小程序先向 API 声明文件名、大小、类型和目标打印机。服务器检查用户限额和设备能力后,返回短期对象存储上传凭证。文件最好从手机直接上传到私有对象存储,避免大文件全部穿过 API 进程。
上传凭证必须满足:
- 只允许指定对象键和指定 HTTP 动作
- 短时间过期
- 限制最大字节数和内容类型
- 不能列出 Bucket
- 不能下载其他用户文件
4. 上传完成与文件预检
小程序通知服务端上传完成。服务端读取真实对象信息,不信任客户端声明,依次检查:
- 实际字节长度
- 扩展名、MIME 和文件魔数
- 文件 SHA-256
- PDF 是否损坏或加密
- PDF 真实页数
- 图片尺寸与总像素
- 恶意文件和解析资源限制
预检通过后生成不可变的文件清单。清单至少记录文件 ID、哈希、大小、格式、页数和过期时间。
5. 用户选择打印参数
小程序根据目标设备能力展示:
- 纸张规格
- 黑白或彩色
- 单面或双面
- 打印份数
- 页面范围
- 方向、缩放或适应纸张
不支持的选项不应只是隐藏,还要在服务端再次拒绝。例如 L3250 当前不允许 A3 和自动双面。
6. 服务端生成报价
客户端把文件 ID 和打印参数提交给报价接口。服务端根据真实页数、设备、纸张、颜色、单双面、份数、门店和活动规则计算最终金额。
报价应包含:
quote_id- 价格版本
- 明细和总金额
- 币种
- 过期时间
- 文件哈希和参数摘要
客户端只展示结果,不允许提交“我应付 0.01 元”作为最终金额。
7. 创建订单并支付
小程序使用唯一 client_request_id 创建订单。服务端以 (user_id, client_request_id) 做唯一约束,重复点击返回原订单。
真实支付时,服务端依据有效报价创建微信支付商户单,再把临时支付参数返回小程序。订单只有在服务端完成支付回调验签或主动查单确认后,才进入 paid。
8. 创建打印任务
支付成功事务中创建唯一打印任务,并写入可靠的 outbox 事件。数据库中 order_id 对打印任务建立唯一约束,保证一个已支付订单不会因为重复回调生成多个打印任务。
9. 门店代理领取任务
代理每隔数秒长轮询,或保持受控 WebSocket,向中心平台报告自己管理的打印机。调度器只返回:
- 属于该代理门店的任务
- 目标打印机在线且能力匹配的任务
- 当前没有有效租约的任务
- 没有超过设备并发上限的任务
领取后服务器返回 attempt、lease_token 和租约到期时间。
10. 下载、复验与提交打印
代理用短期 URL 下载文件,再次核对字节数和 SHA-256。随后根据平台 printer_id 找到固定 Windows 队列名,使用锁定版本的打印工具和驱动参数提交。
每台打印机只能有一个物理提交锁。多台不同打印机可以同时打印,但同一队列不能被多个线程同时提交。
11. 回报状态
代理先在本地持久化回执,再幂等上报:
- 下载完成
- 校验完成
- 已提交 Windows 队列
- Windows Spooler 作业号
- 完成、失败或需人工确认
用户页面轮询或订阅订单状态,显示预计等待、当前步骤和售后入口。
二、支付和打印之间的关键顺序
正确顺序是:
文件预检通过
→ 服务端报价
→ 用户支付
→ 服务端确认支付成功
→ 创建唯一打印任务
→ 代理领取并打印
不能先打印后确认支付,也不能仅凭小程序的“支付成功”回调生成任务。
三、异常恢复
上传中断
重新获取上传凭证或续传。文件未通过预检前不生成可支付订单。
用户已扣款但页面没有成功
前端查询订单,服务端以微信支付回调和主动查单为准。不得要求用户重复支付。
打印机在支付前离线
设备标记不可用,阻止支付,并提示选择附近其他设备或稍后重试。
支付后设备离线
按业务规则提供等待、授权改派或退款。改派必须重新核对目标设备能力和价格差异。
代理在下载时崩溃
租约过期后可以安全重新领取,因为尚未执行物理打印命令。
代理在打印提交后崩溃
如果无法确定是否已经提交,进入 attention_required。后台核对现场、Spooler 记录和本地回执后,人工决定完成、授权重打或退款。
四、用户页面应展示什么
- 当前打印机名称、门店和可用状态
- 文件名的必要部分、页数和大小
- 每项打印参数及价格明细
- 支付状态和商户订单号的脱敏版本
- 排队位置或预计等待
- “正在打印”“已完成”“需要人工确认”等清晰状态
- 退款或客服入口
- 文件自动删除时间和隐私说明
体验的重点不是加入更多按钮,而是让用户在每个异常状态都知道:钱有没有扣、文件在哪里、会不会重复打印、下一步由谁处理。