接口地址

从 ZeroPrint 客户端复制

请求打印

POST /api/print
Content-Type: application/json

请求参数

字段类型必填说明
targetstring目标打印机 ID,可从客户端复制
datastring打印任务数据。明文 JSON(仅调试)或 RSA+AES-256-GCM 加密后 Base64 编码(生产推荐)

请求示例

明文模式(仅调试)

{
  "target": "<打印机 ID>",
  "data": "{\"url\":\"https://example.com/document.pdf\",\"copies\":1,\"id\":\"order_123\",\"callbackUrl\":\"https://your-server.com/callback\"}"
}

加密模式(生产推荐)

{
  "target": "<打印机 ID>",
  "data": "SGVsbG8gV29ybGQgZm9yIGVuY3J5cHRlZC9pbnRlcmFjdGlvbiBvbmx5..."
}

data 字段参数

pageSizeduplexcolororientationresolution 需从客户端复制打印机支持的参数值,不提供则使用打印机默认设置。

字段类型必填说明
urlstring待打印文件的 URL,支持公网和内网地址
copiesnumber打印份数,默认 1
idstring任务 ID,用于回调关联
callbackUrlstring打印结果回调地址
pageSizestring纸张尺寸(从客户端复制)
duplexstring双面模式(从客户端复制)
colorstring颜色模式(从客户端复制)
orientationstring页面方向(从客户端复制)
resolutionstring打印分辨率(从客户端复制)

响应

成功:HTTP 200 OK,无响应体。

失败:HTTP 400 Bad Request

{
  "code": "PRINTER_OFFLINE",
  "message": "target printer is offline"
}
codemessage
PRINTER_OFFLINEtarget printer is offline
INVALID_TARGETtarget is required and must be a 64-character lowercase hex string
INVALID_DATAdata is required and must be a non-empty string
INVALID_REQUESTInvalid JSON format

回调通知

打印任务完成后,客户端会向 callbackUrl 发送 POST 请求通知结果。需要同时提供 idcallbackUrl 才会触发回调。

POST {callbackUrl}
Content-Type: application/json

回调参数

字段类型说明
idstring任务 ID(与请求中的 id 一致)
successboolean是否打印成功
errorstring错误描述(仅失败时)

成功回调

{ "id": "order_123", "success": true }

失败回调

{ "id": "order_123", "success": false, "error": "PDF printing failed: ..." }

回调超时 5 秒。HTTP 状态码 2xx 视为成功。不论打印成功或失败都会执行回调。


加密方案

生产环境推荐 RSA-2048 + AES-256-GCM 混合加密。私钥只保存在客户端本地,云端无法解密。

加密流程

  1. 复制公钥(来自客户端)
  2. 生成 AES-256 密钥和 12 字节 Nonce
  3. AES-256-GCM 加密 JSON(含认证标签)
  4. RSA-OAEP(SHA256) 加密 AES 密钥(256 字节)
  5. 拼接:加密密钥 + Nonce + 密文 + 标签
  6. Base64 编码后作为 data 值

数据结构

[RSA加密的AES密钥 256字节][GCM Nonce 12字节][密文 + 认证标签]

示例代码

const crypto = require("crypto");

// RSA+AES-256-GCM 混合加密:用公钥加密明文,返回 Base64 密文
function encryptData(publicKey, plaintext) {
  const wrapped = publicKey.match(/.{1,64}/g).join("\n");
  const pemKey = `-----BEGIN PUBLIC KEY-----\n${wrapped}\n-----END PUBLIC KEY-----`;

  const aesKey = crypto.randomBytes(32);
  const nonce = crypto.randomBytes(12);

  const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, nonce);
  const ciphertext = Buffer.concat([
    cipher.update(plaintext, "utf8"),
    cipher.final(),
  ]);
  const tag = cipher.getAuthTag();

  const encryptedAesKey = crypto.publicEncrypt(
    {
      key: pemKey,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: "sha256",
    },
    aesKey,
  );

  return Buffer.concat([encryptedAesKey, nonce, ciphertext, tag]).toString("base64");
}

// 发送打印请求(publicKey 为空时使用明文模式)
async function sendPrintRequest(target, printData, publicKey) {
  const plaintext = JSON.stringify(printData);
  const data = publicKey ? encryptData(publicKey, plaintext) : plaintext;

  const response = await fetch("https://<接口地址>/api/print", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ target, data }),
  });

  if (!response.ok) throw await response.json();
  return { status: "success" };
}

const target = "<打印机 ID>";
const publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAO...";
const printData = {
  url: "https://example.com/document.pdf",
  copies: 1,
  id: "order_123",
  callbackUrl: "https://your-server.com/callback",
};

sendPrintRequest(target, printData, publicKey)
  .then(() => console.log("加密模式 - 请求成功"))
  .catch((error) => console.error("请求失败:", error));

sendPrintRequest(target, printData)
  .then(() => console.log("明文模式 - 请求成功"))
  .catch((error) => console.error("请求失败:", error));
// RSA+AES-256-GCM 混合加密(浏览器版)
async function encryptData(publicKey, plaintext) {
  const publicKeyBytes = Uint8Array.from(atob(publicKey), c => c.charCodeAt(0));
  const publicKeyObj = await crypto.subtle.importKey(
    "spki", publicKeyBytes,
    { name: "RSA-OAEP", hash: "SHA-256" }, true, ["encrypt"]
  );

  const aesKey = await crypto.subtle.generateKey(
    { name: "AES-GCM", length: 256 }, true, ["encrypt"]
  );
  const nonce = crypto.getRandomValues(new Uint8Array(12));
  const data = new TextEncoder().encode(plaintext);

  const ciphertext = new Uint8Array(
    await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, aesKey, data)
  );

  const aesKeyBytes = new Uint8Array(await crypto.subtle.exportKey("raw", aesKey));
  const encryptedAesKey = new Uint8Array(
    await crypto.subtle.encrypt(
      { name: "RSA-OAEP", hash: "SHA-256" }, publicKeyObj, aesKeyBytes
    )
  );

  const combined = new Uint8Array(encryptedAesKey.length + nonce.length + ciphertext.length);
  combined.set(encryptedAesKey, 0);
  combined.set(nonce, encryptedAesKey.length);
  combined.set(ciphertext, encryptedAesKey.length + nonce.length);
  return btoa(String.fromCharCode(...combined));
}

// 发送打印请求(浏览器版,publicKey 为空时使用明文模式)
async function sendPrintRequest(target, printData, publicKey) {
  const plaintext = JSON.stringify(printData);
  const data = publicKey ? await encryptData(publicKey, plaintext) : plaintext;

  const response = await fetch("https://<接口地址>/api/print", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ target, data }),
  });

  if (!response.ok) throw await response.json();
  return { status: "success" };
}

const target = "<打印机 ID>";
const publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAO...";
const printData = {
  url: "https://example.com/document.pdf",
  copies: 1,
  id: "order_123",
  callbackUrl: "https://your-server.com/callback",
};

sendPrintRequest(target, printData, publicKey)
  .then(() => console.log("加密模式 - 请求成功"))
  .catch((error) => console.error("请求失败:", error));

sendPrintRequest(target, printData)
  .then(() => console.log("明文模式 - 请求成功"))
  .catch((error) => console.error("请求失败:", error));

其他语言可参照此逻辑自行实现,或使用 AI 辅助翻译。