用户看到的是“扫码、上传、付款、打印”四个动作,但后台实际上要完成身份、设备、文件、价格、资金和物理输出六类状态的一致性控制。

状态说明

本章描述生产目标流程。当前单机原型已经具备扫码入口、上传、基础预检、订单、任务领取和队列提交,但真实支付、设备证书、完整 lease_token 栅栏、提交前本地回执数据库和多打印机调度仍属于待实现目标。

一、标准用户流程

1. 扫描设备二维码

每台物理打印机贴一个独立的小程序码。二维码场景值建议只保存一个可轮换的短别名,例如 p_x7k9m2,不直接暴露数据库自增 ID、门店网络地址或任何密钥。

小程序启动后把场景值提交给中心 API,服务器解析出:

  • 租户和门店
  • 目标打印机
  • 纸张、颜色、单双面等能力
  • 在线、繁忙、维护或停用状态
  • 当前价格版本和营业状态

如果二维码被撤销、设备停用或不在线,小程序应在上传和支付之前明确提示。

2. 微信登录

小程序调用微信登录获得一次性 code,服务端再与微信接口交换用户标识并创建业务会话。AppSecret 只允许保存在服务端,不能出现在小程序代码、网页 JavaScript、PDF 或日志中。

业务会话应为短期令牌,包含最少权限。接口同时检查订单所有权,避免用户通过修改 URL 查询他人的文件或订单。

3. 创建上传会话

小程序先向 API 声明文件名、大小、类型和目标打印机。服务器检查用户限额和设备能力后,返回短期对象存储上传凭证。文件最好从手机直接上传到私有对象存储,避免大文件全部穿过 API 进程。

上传凭证必须满足:

  • 只允许指定对象键和指定 HTTP 动作
  • 短时间过期
  • 限制最大字节数和内容类型
  • 不能列出 Bucket
  • 不能下载其他用户文件

4. 上传完成与文件预检

小程序通知服务端上传完成。服务端读取真实对象信息,不信任客户端声明,依次检查:

  1. 实际字节长度
  2. 扩展名、MIME 和文件魔数
  3. 文件 SHA-256
  4. PDF 是否损坏或加密
  5. PDF 真实页数
  6. 图片尺寸与总像素
  7. 恶意文件和解析资源限制

预检通过后生成不可变的文件清单。清单至少记录文件 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,向中心平台报告自己管理的打印机。调度器只返回:

  • 属于该代理门店的任务
  • 目标打印机在线且能力匹配的任务
  • 当前没有有效租约的任务
  • 没有超过设备并发上限的任务

领取后服务器返回 attemptlease_token 和租约到期时间。

10. 下载、复验与提交打印

代理用短期 URL 下载文件,再次核对字节数和 SHA-256。随后根据平台 printer_id 找到固定 Windows 队列名,使用锁定版本的打印工具和驱动参数提交。

每台打印机只能有一个物理提交锁。多台不同打印机可以同时打印,但同一队列不能被多个线程同时提交。

11. 回报状态

代理先在本地持久化回执,再幂等上报:

  • 下载完成
  • 校验完成
  • 已提交 Windows 队列
  • Windows Spooler 作业号
  • 完成、失败或需人工确认

用户页面轮询或订阅订单状态,显示预计等待、当前步骤和售后入口。

二、支付和打印之间的关键顺序

正确顺序是:

文件预检通过
  → 服务端报价
  → 用户支付
  → 服务端确认支付成功
  → 创建唯一打印任务
  → 代理领取并打印

不能先打印后确认支付,也不能仅凭小程序的“支付成功”回调生成任务。

三、异常恢复

上传中断

重新获取上传凭证或续传。文件未通过预检前不生成可支付订单。

用户已扣款但页面没有成功

前端查询订单,服务端以微信支付回调和主动查单为准。不得要求用户重复支付。

打印机在支付前离线

设备标记不可用,阻止支付,并提示选择附近其他设备或稍后重试。

支付后设备离线

按业务规则提供等待、授权改派或退款。改派必须重新核对目标设备能力和价格差异。

代理在下载时崩溃

租约过期后可以安全重新领取,因为尚未执行物理打印命令。

代理在打印提交后崩溃

如果无法确定是否已经提交,进入 attention_required。后台核对现场、Spooler 记录和本地回执后,人工决定完成、授权重打或退款。

四、用户页面应展示什么

  • 当前打印机名称、门店和可用状态
  • 文件名的必要部分、页数和大小
  • 每项打印参数及价格明细
  • 支付状态和商户订单号的脱敏版本
  • 排队位置或预计等待
  • “正在打印”“已完成”“需要人工确认”等清晰状态
  • 退款或客服入口
  • 文件自动删除时间和隐私说明

体验的重点不是加入更多按钮,而是让用户在每个异常状态都知道:钱有没有扣、文件在哪里、会不会重复打印、下一步由谁处理。

上一篇:当前真实基线下一篇:当前单机原型