下载 Markdown

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 连接。当前版本不自动切换已经完成的初始化类型;初始化选择及本机默认值在重启后保留。

仓库端初始化

  1. 在打印电脑安装对应型号的系统驱动,并确认本机可以打印 PDF。
  2. 启动 RootFlow Print Agent,打开 http://127.0.0.1:18188/。
  3. 进入“配置”,选择“仓库端初始化”,填写管理员提供的 IoT 地址、仓库代码、工位、打印机编号、打印机和一次性配对码,完成初始化。
  4. 在“打印监测”页设置纸张、份数、缩放、旋转、单双面,点击“保存为默认”。不需要上传文件,也不会提交打印任务。
  5. 管理员向业务后端提供该仓库专用的业务 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 上传或签名接口。

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;键在每个队列内唯一。

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. 接入验收

随附示例只定义客户端函数,导入不会自动打印。文档及示例不包含生产 Key、设备 Token 或可直接使用的 S3 签名。