接口地址

从 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

服务器错误:HTTP 500 Internal Server Error

{
  "code": "INVALID_DATA",
  "message": "data is required and must be a non-empty string"
}
codemessage
INVALID_TARGETtarget is required and must be a 64-character lowercase hex string (a-f, 0-9)
INVALID_DATAdata is required and must be a non-empty string
INTERNAL_ERRORinternal server error

回调通知

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

POST {callbackUrl}
Content-Type: application/json

回调参数

字段类型说明
idstring任务 ID(与请求中的 id 一致)
successboolean是否打印成功

成功回调

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

失败回调

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

回调超时 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 辅助翻译。