许哲

明道云静默打印代理(解决打印时需要点模板再预览最后打印的问题)

许哲Lv.2入道明道云员工 发布于 最后回复 分享 明道云
3 回复43 浏览

明道云静默打印代理(md-print-agent)

github 地址

给明道云(HAP)记录加一个「打印」按钮,点一下本地打印机直接出纸——无弹窗、无对话框、无需任何手动操作。
单文件 Python 脚本(零 pip 依赖,仅标准库),打包成单 exe 即可分发给客户机。

重要:本项目需要的本地依赖(自动探测):

  • PDF 静默打印:需要先安装 SumatraPDF(https://www.sumatrapdfreader.org)
  • Word/Excel → PDF:MS Office 或 WPS(COM 调用,双引擎兜底)
  • HTML → PDF:系统自带 Edge/Chrome 无头渲染(仅 --test 自检需要)

工作原理

先说核心矛盾:明道云跑在云端,打印机接在办公室的本地电脑上。云端永远无法主动够到 NAT 后面的打印机,浏览器又被安全机制挡住碰不了本地设备——两边之间没有直路。

解法是在中间放一个两边都够得着的信箱:「待打印队列」表。

image

「待打印队列」是什么?

它就是明道云应用里的一张普通工作表——不是什么系统组件,和你平时建的「订单表」「客户表」没有本质区别,只是被专门用来当打印任务的收发室。云端往里投任务,本地代理从里取任务,一条记录 = 一个打印任务:

字段 作用
附件 要打的文件(工作流生成的 Word/Excel/PDF)
打印状态 任务进度:待打印 → 打印中 → 已打印 / 失败
打印编号 任务流水号,方便查档
名称 任务描述,人看得懂

字段作用附件要打的文件(工作流生成的 Word/Excel/PDF)打印状态任务进度:待打印 → 打印中 → 已打印 / 失败打印编号任务流水号,方便查档名称任务描述,人看得懂类比快递柜:明道云是寄件人,把要打的文件"投柜"(新建一条待打印记录);本地代理是快递员,守在柜前,见到新包裹就取出、打印、把回执写回柜子里。

一次打印的完整旅程

1. 用户在业务表记录上点「打印」按钮(自定义动作)
2. 工作流节点1「获取记录打印文件」:按打印模板把这条记录渲染成 Word/Excel 文件
3. 工作流节点2「新建记录」:往「待打印队列」表写入一条记录
   (附件 = 渲染好的文件,打印状态 = 待打印)
   ─── 以上全部发生在云端,本地尚未参与 ───
4. 本地代理每 3 秒轮询队列表,发现新的「待打印」任务
5. 先把状态改成「打印中」(占位,防止处理超时导致重复出纸)
6. 下载附件 → 本地转 PDF(自动调用 Office/WPS)→ SumatraPDF 静默出纸
7. 回写「已打印」(出错则回写「失败」),任务留在表里即为打印日志

为什么要绕这张表

  • 它是唯一的交集:云端够不到打印机,但写表没问题;本地够不到云端推送,但读表没问题
  • 状态即管控:打印中占位防重复出纸,已打印/失败结果可查,表本身就是完整的打印台账
  • 业务表零侵入:订单、合同、出库单……任意业务表加一个按钮就能打印,业务表本身不加字段、不改结构
  • 一个代理管所有:本地代理只盯这一张队列表,不管任务来自哪张业务表、哪个工作流

脚本运行逻辑

启动序列:

  1. 解析命令行参数(--config 指定配置路径,--test 自检后退出)
  2. 探测 SumatraPDF:--sumatra 显式指定 > exe 同目录 Portable 版 > 系统安装路径
  3. 读取 config.json;不存在则进入首次运行向导(填写连接参数 → 建表或填已有表 → 选打印机 → 校验并保存)
  4. 打印启动信息,进入轮询主循环

轮询主循环:

while True:
    rows = 查询(打印状态 == 待打印)     # V3 rows/list,单条件筛选
    for row in rows:
        处理任务(row)                    # 见下方管线
    sleep(轮询间隔)                      # 默认 3 秒
    # 查询或网络异常不会退出进程:打印诊断信息(url/响应体)后继续下一轮

单任务处理管线:

附件字段为空 → 跳过本轮(附件是异步落库的,下一轮自然捞到)
    ↓
回写「打印中」← 先占位:处理耗时超过轮询间隔时,
    ↓            防止同一条记录被重复捞取、重复出纸
逐个附件:按 downloadUrl 下载 → 按扩展名分流
    .pdf            → 直接进入打印
    .doc/.docx/...  → Office/WPS COM 转 PDF 后打印
    .xls/.xlsx/...  → 同上
    其他类型        → 跳过并记日志
    ↓
打印(全局锁串行,多任务自动排队)
    ↓
全部成功 → 回写「已打印」;任一环节异常 → 回写「失败」

三个关键防御点:

  • 回写一律带 triggerWorkflow: false:状态回写绝不触发明道云工作流,否则"回写 → 工作流 → 又生成打印任务"会死循环
  • 打印全局串行锁:并发任务排队出纸,不抢打印机
  • 失败也回写:任务状态永远可查(回写失败本身出错时再兜底记日志),队列表就是完整打印台账

调用的 API 与外部组件

明道云 V3 开放接口——鉴权方式:每个请求携带 HAP-Appkey / HAP-Sign 请求头(应用级密钥,在明道云「应用 → 设置 → API 密钥」页获取):

接口 方法与路径 用途
查询记录 POST /v3/app/worksheets/{工作表ID}/rows/list 轮询捞「待打印」任务,filter 按状态字段 eq 待打印
更新记录 PATCH /v3/app/worksheets/{工作表ID}/rows/{记录ID} 回写状态(打印中/已打印/失败);实测必须 PATCH,POST/PUT 返回 405
下载附件 GET {附件对象的 downloadUrl} 预签名地址,免鉴权直接下载文件内容
创建工作表 POST /v3/app/worksheets 仅向导「自动建表」使用:一次创建四字段标准队列表

接口方法与路径用途查询记录 POST /v3/app/worksheets/{工作表ID}/rows/list 轮询捞「待打印」任务,filter 按状态字段 eq 待打印更新记录 PATCH /v3/app/worksheets/{工作表ID}/rows/{记录ID} 回写状态(打印中/已打印/失败);实测必须 PATCH,POST/PUT 返回 405 下载附件 GET {附件对象的 downloadUrl} 预签名地址,免鉴权直接下载文件内容创建工作表 POST /v3/app/worksheets 仅向导「自动建表」使用:一次创建四字段标准队列表本地组件(全部自动探测,无需配置):

组件 调用方式 用途
MS Office / WPS PowerShell COM:Word 用 Word.Application / KWPS.Application,Excel 用 Excel.Application / KET.Application(Office 优先,WPS 兜底) Word/Excel 附件转 PDF
SumatraPDF 命令行 -print-to <打印机> -silent -exit-when-done PDF 静默出纸
Edge / Chrome 无头模式 --print-to-pdf(独立 user-data-dir) HTML 转 PDF,仅 --test 自检使用

组件调用方式用途 MS Office / WPSPowerShell COM:Word 用 Word.Application / KWPS.Application,Excel 用 Excel.Application / KET.Application(Office 优先,WPS 兜底)Word/Excel 附件转 PDFSumatraPDF 命令行 -print-to <打印机> -silent -exit-when-done PDF 静默出纸 Edge / Chrome 无头模式 --print-to-pdf(独立 user-data-dir)HTML 转 PDF,仅 --test 自检使用## 明道云侧配置(标准工作流)

业务表任意,任务统一汇入「待打印队列」表:

  1. 自定义动作按钮触发

  2. 节点 1「获取记录打印文件」:打印对象 = 按钮触发的工作表;打印模板/文件名按业务配置
    (Word/Excel/PDF)

  3. 节点 2「新建记录」:选择工作表 = 待打印队列,新增一条记录

    • 名称 = 自定义;打印编号 = 留空(自动编号)
    • 打印状态 = 待打印;附件 = 节点 1 输出的打印文件
    image

「待打印队列」表字段规范:

字段 类型 说明
名称 标题/文本 自定义记录名称
打印编号 自动编号 规则 自然数
打印状态 单选 待打印 / 打印中 / 已打印 / 失败
附件 附件字段 工作流写入的打印文件

字段类型说明名称标题/文本自定义记录名称打印编号自动编号规则 自然数打印状态单选待打印 / 打印中 / 已打印 / 失败附件附件字段工作流写入的打印文件代理首次运行向导可选自动建表(按上表字段创建,别名自动对齐),也可以手工建好后在向导里填表名。

image

客户机部署

md-print-agent.exe      # 打包见下方
SumatraPDF.exe          # Portable 版,与 exe 同目录(或装到默认路径)
config.json             # 首次运行向导自动生成

首次运行 md-print-agent.exe 进入交互式向导:明道云 API 地址/AppKey/Sign →
建表或填已有工作表 → 选打印机 → 轮询间隔,自动校验连接并保存配置,随后直接进入轮询。

本地依赖(自动探测):

  • PDF 静默打印:SumatraPDF(https://www.sumatrapdfreader.org)
  • Word/Excel → PDF:MS Office 或 WPS(COM 调用,双引擎兜底)
  • HTML → PDF:系统自带 Edge/Chrome 无头渲染(仅 --test 自检需要)

用法(源码方式)

python print_agent.py            # 启动代理(轮询待打印队列);首次运行自动进配置向导
python print_agent.py --test     # 自检:打一张测试页到默认打印机
python print_agent.py --config D:\mdprint.json
python print_agent.py --sumatra "D:\tools\SumatraPDF.exe"

打包单 exe:

python -m PyInstaller --onefile --name md-print-agent print_agent.py

调试通路的方法

不要为客户创建工作流——直接在「待打印队列」表用 API 创建一条测试记录即可:
状态 = 待打印 + 附件 = 测试 PDF(base64),代理捞到后走完整管线,从日志和状态回写判断链路是否通。这样验证的是代理侧全链路;工作流只是往队列表写数据的生产者,与链路无关。

踩坑经验

  1. 更新记录必须用 PATCH:PATCH /v3/app/worksheets/{id}/rows/{row_id}。
  2. 单选字段直接传选项文本值:筛选和更新都无需选项 key,服务端自动匹配。
  3. 建表 POST /v3/app/worksheets 无需 appId:AppKey 鉴权自带应用归属。
  4. AutoNumber 编号规则 API 传不了:只能建纯数字递增,print- 前缀规则需在界面设置。
  5. 回写状态必须 triggerWorkflow: false:否则渲染 → 打印 → 回写会触发工作流死循环。
  6. 附件异步:写入后 5-10 秒才可下载;下载用返回的 downloadUrl(预签名地址,免鉴权)。
  7. 附件字段名兼容:返回结构用驼峰 fileName/downloadUrl,不同版本字段名不一致,需做兜底链。
  8. PyInstaller onefile 路径坑:exe 模式下 __file__ 指向解压临时目录,必须 sys.frozen 判断后取 sys.executable 所在目录为基准(config.json / SumatraPDF 放 exe 旁才找得到)。
  9. PowerShell 中文输出乱码:枚举打印机等场景需强制 [Console]::OutputEncoding=UTF8 并按字节捕获显式解码,不依赖系统区域设置。
  10. Edge 无头打印拿不到文件:Edge 已在运行时无头进程会把任务转交常驻实例,必须用独立 --user-data-dir(--test 自检场景)。
  11. 打印机枚举含脱机副本:同名打印机可能有多个条目(如"xxx (副本 1)"),config 的 printer 必须填准确名称,留空走系统默认需确认默认打印机在线。

文件说明

文件 说明
print_agent.py 代理主程序(轮询队列 + 配置向导 + 自动建表)
config.example.json 配置模板(真实 config.json 不入库)

文件说明 print_agent.py 代理主程序(轮询队列 + 配置向导 + 自动建表)config.example.json 配置模板(真实 config.json 不入库)