RootFlow 打印使用与嵌入接入说明
更新:2026-10-28。适用版本:本地打印程序 0.3.6、IoT 服务 0.1.3。
本文说明如何在业务网页、ERP、WMS、桌面程序或后端服务中接入打印。当前输出格式为 PDF,纸张、份数和旋转均由打印参数控制。
1. 接入方式
| 场景 | 方式 | 请求发往 |
|---|---|---|
| 从任何地点向仓库发起打印 | 业务后端调用公网云打印 API | https://jiaxing.rootflow.cn/iot |
| 网页中的“打印面单”按钮 | 网页调用自己的业务后端,再调用云打印 API | 业务系统自己的接口 |
| 仓库电脑上的程序或受信网页直接打印 | 调用本机 API / JavaScript SDK | http://127.0.0.1:18188 |
公网云打印流程:
生成 PDF → 上传 S3 → 业务后端提交文件地址和 SHA-256
→ 仓库专属队列 → 本机程序通过长连接获知任务
→ 领取任务并下载已校验的文件 → 系统打印机 → 回传提交结果
普通业务系统只需查询打印机、提交任务、查询任务。长连接、领取、文件下载和回执由本机程序处理,无需前端自行维护 WebSocket,也无需从公网访问仓库电脑。
网页跨地点集成优先采用“网页 → 业务后端 → IoT”。云端 API 当前不开放浏览器 CORS;仓库业务 Key 保存在后端,不放进公开网页代码。软件提供的是独立本机服务和 HTTP/SDK 接口,不需要安装浏览器扩展。
2. 初始化:自贴单用户与仓库端
自贴单用户初始化
在配置页选择“自贴单用户初始化”,选中本机已安装的打印机,点击“初始化本机打印”。随后通过本机测试页、HTTP API 或 JavaScript SDK 打印 PDF。在“打印监测”页设置尺寸等参数并“保存为默认”,无需每次重设。
自贴单模式仅支持本机打印,不要求云地址、仓库、工位和配对码,不注册云端设备或队列,不建立云端 HTTP/WebSocket 连接。当前版本不自动切换已经完成的初始化类型;初始化选择及本机默认值在重启后保留。
仓库端初始化
- 在打印电脑安装对应型号的系统驱动,并确认本机可以打印 PDF。
- 启动 RootFlow Print Agent,打开
http://127.0.0.1:18188/。 - 进入“配置”,选择“仓库端初始化”,填写管理员提供的 IoT 地址、仓库代码、工位、打印机编号、打印机和一次性配对码,完成初始化。
- 在“打印监测”页设置纸张、份数、缩放、旋转、单双面,点击“保存为默认”。不需要上传文件,也不会提交打印任务。
- 管理员向业务后端提供该仓库专用的业务 API Key;业务系统查询打印机后保存对应
queueId。
Windows 还需安装或配置 SumatraPDF:将 SumatraPDF.exe 放在代理程序旁,或在控制台配置其路径。Mac、Linux 使用系统 CUPS。本次已验证的实体打印设备为 Mac 上的快麦 KM-202MD,其他系统及型号应按本文末尾的接入验收逐项检查。
当前嘉兴已保存的配置为 70×50 mm、适应页面、不额外旋转、单面、1 份。这是当前设备配置,新接入的设备应按实际纸张设置。
仓库端默认参数会保存在本机并同步到云端,刷新页面、重启程序后保留;后续请求省略参数即可使用。修改默认值只影响后续新任务,既有任务保留入队时的参数。软件仍需在打印电脑后台运行;关闭控制台网页不影响后台领取,退出程序会停止领取。
| 凭据 | 使用方 | 用途 |
|---|---|---|
| 仓库业务 API Key | 业务后端 | 查询本仓打印机、提交和查询云任务 |
| 本机 API Key | 本机集成程序 / 经授权网页 | 调用该电脑的本机打印 API |
| 设备 Token | 本机打印程序内部 | 长连接、领取、下载及回执;由程序管理 |
| 配对码 | 初始化操作人员 | 首次注册使用,不用于日常提交任务 |
3. 公网接口快速开始
在业务服务器配置环境变量;下面的变量值由管理员提供,示例不包含真实密钥:
export IOT_BASE_URL='https://jiaxing.rootflow.cn/iot'
export IOT_WAREHOUSE_API_KEY='替换为嘉兴仓业务Key'
3.1 查询嘉兴有哪些打印机
curl "$IOT_BASE_URL/v1/printers?warehouse=jiaxing" \
-H "Authorization: Bearer $IOT_WAREHOUSE_API_KEY"
返回示意,实际值和状态以实时接口为准:
{
"warehouse": "jiaxing",
"total": 1,
"printers": [{
"id": "设备ID",
"queueId": "队列ID",
"warehouse": "jiaxing",
"warehouseName": "嘉兴仓",
"station": "01",
"printerNumber": "01",
"printer": "_KM_202MD",
"enabled": true,
"connected": true,
"usbStatus": "connected",
"readyToPrint": true,
"lastSeen": 1793166000,
"profile": {
"paper": "Custom.70x50mm",
"scaling": "fit",
"rotation": 0,
"copies": 1,
"duplex": "one-sided",
"color": "default"
}
}]
}
用仓库、工位、打印机编号选择目标,保存 queueId。不要把数组第一项永久当作指定工位。打印机名称是系统队列名,云端发送任务应使用 queueId,不要把名称当作 URL 中的队列 ID。
connected 是客户端最近 25 秒内有心跳;usbStatus 是最近上报的 USB 状态,断连后可能保留旧值;readyToPrint 表示启用、心跳在线且 USB 已连接,不保证有纸、无卡纸或实体已出纸。lastSeen 是 Unix 秒。
3.2 准备 S3 文件地址
将原始 PDF 上传到自己的 S3/兼容对象存储,记录原始文件 SHA-256。私有文件生成一个临时可用的预签名 GET 地址。上传和签名由业务系统的存储模块完成,IoT API 本身不提供 S3 上传或签名接口。
- 嘉兴当前允许文件域名为
s3.rootflow.cn;其他存储域名需由运维加入 IoT 的IOT_DOCUMENT_HOSTS。 - 地址必须使用 HTTPS、默认端口或 443,不接受重定向。提交签名地址,不要只提交私有文件的无签名地址或
s3://URI。 - SHA-256 是 PDF 原始字节的 64 位小写十六进制摘要,不是下载 URL 的摘要,也不是 S3 ETag。
- 签名至少覆盖入队时的下载时间,例如 10 分钟。服务在入队前下载、校验并持久保存文件快照,设备离线等待不依赖原签名继续有效。
- 文件上限 25MB、1000 页,不接受加密或无效 PDF。
Mac 可使用 shasum -a 256 label.pdf,Linux 可使用 sha256sum label.pdf;程序也可直接对生成文件的原始字节计算摘要。
3.3 提交打印请求
把查询到的队列 ID 配入业务后端:
export IOT_QUEUE_ID='替换为查询到的queueId'
准备 print-request.json,只包含以下两个字段,值需替换为真实内容:
{
"documentUrl": "https://s3.rootflow.cn/你的桶/label.pdf?完整预签名参数",
"sha256": "替换为原PDF的64位小写SHA256"
}
curl "$IOT_BASE_URL/v1/queues/$IOT_QUEUE_ID/jobs" \
-H "Authorization: Bearer $IOT_WAREHOUSE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-20261028-001-print-1' \
--data-binary @print-request.json
没有指定纸张参数,因此使用该设备已保存的默认值。首次成功创建返回 HTTP 202;相同文件、参数和幂等键再次提交返回 HTTP 200,并返回同一任务:
{
"job": {
"id": "云任务ID",
"queueId": "队列ID",
"status": "queued",
"options": {
"paper": "Custom.70x50mm",
"scaling": "fit",
"rotation": 0,
"copies": 1,
"duplex": "one-sided",
"color": "default"
},
"externalJobId": null,
"message": ""
},
"deduplicated": false
}
响应还包含文件 sha256、创建时间和更新时间。业务系统应保存云任务 ID、queueId 和本次幂等键,用于后续查询和审计。
也支持直接上传 PDF,认证、幂等键和返回规则相同:
curl "$IOT_BASE_URL/v1/queues/$IOT_QUEUE_ID/jobs" \
-H "Authorization: Bearer $IOT_WAREHOUSE_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'Idempotency-Key: order-20261028-002-print-1' \
--data-binary @label.pdf
3.4 查询任务执行结果
curl "$IOT_BASE_URL/v1/queues/$IOT_QUEUE_ID/jobs" \
-H "Authorization: Bearer $IOT_WAREHOUSE_API_KEY"
返回 {"jobs":[...]},按时间倒序,最多最近 50 条。按提交返回的 job.id 匹配,不要把第一条任务当作本次结果。云端目前没有独立的业务端 GET /jobs/{id} 接口,也不提供业务端任务状态 WebSocket;近期任务可每 2–5 秒查询一次,由业务后端设置超时和停止条件。业务系统应自行持久保存历史任务映射。
4. 在项目中嵌入调用
Node.js 业务后端
将随附 examples/cloud-print.mjs 放入后端项目,Node.js 18+ 可直接使用内置 fetch,无需安装额外库。导入文件不会自动发起打印。
import { createCloudPrinter } from './examples/cloud-print.mjs';
const printer = createCloudPrinter(); // 从后端环境变量读取仓库 Key
const devices = await printer.listPrinters('jiaxing');
const target = devices.printers.find(p =>
p.station === '01' && p.printerNumber === '01' && p.enabled);
if (!target) throw new Error('该工位没有已注册打印机');
// 从业务数据库、存储模块取得以下值;相同打印操作重试时保持 requestId 不变。
const result = await printer.submitPdfUrl({
queueId: target.queueId,
documentUrl: signedPdfGetUrl,
sha256: originalPdfSha256,
idempotencyKey: printRequestId,
});
const recent = await printer.listJobs(target.queueId);
const current = recent.jobs.find(job => job.id === result.job.id);
signedPdfGetUrl、originalPdfSha256 和 printRequestId 是业务项目传入的变量,不是本软件自动生成的全局值。URL 签名可以更新,原文件和幂等键保持一致;相同内容及生效参数仍会返回同一任务。
Python 业务后端
将 examples/cloud_print.py 放入后端项目,只使用 Python 标准库:
from cloud_print import CloudPrinter
printer = CloudPrinter() # 读取后端环境变量
devices = printer.list_printers('jiaxing')['printers']
target = next(p for p in devices
if p['station'] == '01' and p['printerNumber'] == '01' and p['enabled'])
result = printer.submit_pdf_url(
target['queueId'], signed_pdf_get_url, original_pdf_sha256, print_request_id)
recent = printer.list_jobs(target['queueId'])['jobs']
current = next((job for job in recent if job['id'] == result['job']['id']), None)
任意业务网页的打印按钮
网页按钮调用自己的业务后端;后端完成用户授权、面单查找、S3 签名、工位路由和上面的云打印调用。
// 放入 Vue、React 或普通网页的按钮处理函数。
// printRequestId 由业务系统为本次“打印操作”创建并保存,网络重试时复用。
async function printLabel(labelId, printRequestId) {
const response = await fetch('/api/printing/labels', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ labelId, printRequestId }),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || '提交失败');
return result;
}
/api/printing/labels 是业务系统需要实现的示例接口,不是 IoT 已有路径。后端返回云任务编号,页面显示“已入队”或“已提交系统”;出纸确认单独显示。不要把仓库业务 Key 和 S3 签名生成密钥交给网页。
5. 单次覆盖默认参数
打印参数放在任务 URL 的 query 中,不放进 JSON 文件引用 body:
POST /v1/queues/{queueId}/jobs?copies=2&paper=Custom.70x50mm&rotation=0&scaling=fit
| 参数 | 可选值 | 省略时 |
|---|---|---|
| copies | 1–100 | 使用设备已保存的份数 |
| paper | Custom.70x50mm 或驱动支持的纸型名 |
使用设备已保存的纸型 |
| scaling | fit / actual |
使用已保存值 |
| rotation | 0 / 90 / 180 / 270,额外顺时针旋转 | 使用已保存值 |
| duplex | one-sided / two-sided-long-edge / two-sided-short-edge |
使用已保存值 |
| color | default / monochrome / color |
使用已保存值 |
已旋转好的 PDF 通常使用 rotation=0,避免再次旋转。自定义纸型、双面及彩色是否可用取决于型号和驱动。云端队列已绑定打印机,不接受 printer 参数切换目标;切换目标应选择另一个 queueId。
Node.js 示例的单次覆盖方式:
await printer.submitPdfUrl({
queueId, documentUrl, sha256, idempotencyKey,
options: { copies: 2, paper: 'Custom.70x50mm' },
});
6. 防重复打印规则
每一个明确的打印操作使用一个 Idempotency-Key,例如 order-123-label-1-attempt-1。允许字符为字母、数字、点、下划线、冒号、短横线,长度 1–128;键在每个队列内唯一。
- 请求超时、页面重试、网络断线后重发同一操作:沿用原 Key,不自动生成新 Key。
- 同一 Key、相同文件和生效参数:返回原任务,不重复创建。
- 同一 Key 改文件或参数:HTTP 409。设备默认参数在两次请求之间被修改,也可能引起该冲突;先查询原任务,或按原生效参数重试。
- 用户明确补打、新增一份:创建新的业务打印操作和新 Key。
- 不要看到 failed 或 unknown 就自动换 Key 重打。unknown 可能已经部分打印,应先核对纸张及系统队列。
7. 云任务状态与出纸确认
| 状态 | 含义 | 业务处理 |
|---|---|---|
| queued | 云端已持久保存,等待领取 | 允许设备离线等待 |
| claimed | 设备已领取,尚未进入本机提交边界 | 继续查询 |
| submitting | 即将或正在提交到本机系统 | 不自动创建补打 |
| submitted | 本机系统接口已接受任务 | 显示“已提交系统”,核对出纸 |
| failed | 明确失败或人工确认未出纸 | 查看 message,处理原因 |
| unknown | 本机提交结果不确定 | 暂停该队列后续领取,人工核对 |
externalJobId 在 Mac 上通常为 _KM_202MD-53 这样的 CUPS 编号;Windows 可能为 null。云端使用 camelCase;本机任务接口使用 external_job_id 等 snake_case,两个响应不要混用。
submitted 不等于实体已出纸。 当前云服务不自动将系统已提交任务更新为“实体完成”,也不自动检测所有型号的卡纸、缺纸或条码质量。系统队列为空也不能单独证明出纸。现场实物确认应记录为独立业务结果。
云端 unknown 任务核对后可调用 POST /v1/queues/{queueId}/jobs/{jobId}/resolve,JSON {"outcome":"printed"} 或 {"outcome":"not_printed"}。这只记录核对结果、解除不确定状态,不执行补打,也不是通用实体完成回调。普通业务端目前不提供云任务取消接口。
8. 本机直接嵌入
适用于程序与打印机在同一台电脑,或已明确授权的业务网页。
curl 'http://127.0.0.1:18188/v1/jobs' \
-H "Authorization: Bearer $LOCAL_PRINT_API_KEY" \
-H 'Content-Type: application/pdf' \
-H 'Idempotency-Key: local-order-123-print-1' \
--data-binary @label.pdf
已保存默认打印机和参数时,不需要传任何 query 参数。需要指定打印机时使用 printer,中文或空格名称必须 URL 编码。本机 /v1/jobs 接收原始 PDF,不接收 S3 JSON;S3 文件地址提交使用公网云接口。
将随附 examples/local-client.js 复制到前端项目,再使用 JavaScript SDK:
import { PrintAgent } from './local-client.js';
const agent = new PrintAgent({
baseURL: 'http://127.0.0.1:18188',
apiKey: localMachineApiKey,
});
const result = await agent.printPDF(pdfBlob, {
idempotencyKey: printRequestId,
});
const current = await agent.job(result.job.id);
本机 Key 从该电脑控制台“配置”页取得,按设备授权,不与云端仓库 Key 混用。网页来源需要加入本机“允许的网站来源”,例如 https://wms.example.com;还需通过实际浏览器的本地网络权限、HTTPS 和 CSP 检查。跨地点网页优先采用云方式,不能假定所有浏览器均可直接访问本机 HTTP。
本机常用接口:
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /health |
本机程序状态 |
| GET | /v1/printers |
系统打印队列列表 |
| GET | /v1/monitor |
本机打印监测 |
| POST | /v1/jobs |
上传 PDF |
| GET | /v1/jobs/{id} |
查询某个本机任务 |
| GET | /v1/jobs |
最近 50 条本机任务 |
| POST | /v1/jobs/{id}/cancel |
仅取消本机 queued 任务 |
9. 错误处理与现场排查
| 云端 HTTP 状态 | 常见原因 | 处理 |
|---|---|---|
| 400 | JSON/参数/PDF/摘要无效、S3 下载失败或地址不允许 | 根据 error 或 detail 修正;下载签名可能已过期 |
| 401 | 仓库业务 Key 无效、使用错类型的凭据 | 检查后端凭据 |
| 403 | 查询了其他仓库 | 使用所属仓库 |
| 404 | queueId 不存在、已停用或无权访问 | 重新查询打印机列表 |
| 409 | 幂等冲突、队列满、状态不允许 | 查询原任务,不盲目更换 Key |
| 413 | 文件或请求过大 | 缩小 PDF,检查请求大小 |
| 415 | Content-Type 不匹配 | URL 用 application/json,文件用 application/pdf |
打印机离线时,云端仍可接受任务并排队;这是云打印的正常行为。若必须只允许在线发起,由业务系统在提交前检查状态,但状态检查不保证后续打印一定成功。每个设备队列最多 200 条未完成/待核对任务。
排查顺序:确认云任务已创建 → 检查客户端长连接 → 检查 USB 与系统队列 → 检查纸张、电源、指示灯 → 查询本机任务及驱动日志。不要为排障删除 cloud.json、设备 Token、SQLite 数据或回执日志。
同一 USB 打印机可能出现多个系统队列名,硬件识别应结合序列号。绑定云端的队列应保持稳定;不要用修改厂家 USB URI 的方式解决名称显示问题。
10. 接入验收
- 能按仓库、工位和编号查到正确 queueId。
- 单击一次打印,只创建一个任务;模拟网络超时后用同一 Key 重试,仍是同一任务。
- 文件地址和摘要校验通过,默认参数无需每次传入,单次覆盖不改变默认值。
- 关闭控制台网页仍可领取;断网恢复后未提交任务可继续处理。
- submitted 与实体出纸确认分开,unknown 有人工核对流程。
- 用实际驱动和纸张核对尺寸、完整内容、二维码可读性;Windows 和其他型号应分别验收。
随附示例只定义客户端函数,导入不会自动打印。文档及示例不包含生产 Key、设备 Token 或可直接使用的 S3 签名。