基于 WebSocket 实现浏览器调用打印机

5个月前发布 阿紫Amy
0 0 0

在 Web 应用中,直接通过浏览器调用打印机存在诸多限制(如跨浏览器兼容性、权限控制、高级功能支持等)。而 WebSocket 技术通过建立客户端与服务端的实时通信通道,能够突破这些限制,实现灵活、可控的打印功能。本文将详细介绍如何通过 WebSocket 架构实现浏览器调用打印机的完整方案。

一、方案整体架构

基于 WebSocket 的打印方案需要前端、后端服务、打印服务三者协同工作,核心架构如下:

浏览器(前端) <–WebSocket–> 后端服务 <–本地接口–> 打印机

前端:负责收集打印数据、发起打印请求、展示打印状态WebSocket 服务:建立长连接,实现前后端实时通信后端服务:处理打印任务队列、权限验证、调用系统打印接口打印服务:与操作系统交互,直接控制打印机硬件

该架构的优势在于:

突破浏览器沙箱限制,实现对打印机的直接控制支持跨浏览器(Chrome、Firefox、Edge 等均支持 WebSocket)可实现高级打印功能(指定打印机、设置参数、批量打印等)便于集中管理打印任务和权限控制

二、技术栈选择

1. 前端技术

原生 JavaScript(或框架如 Vue/React)处理 WebSocket 通信HTML/CSS 构建打印预览界面JSON 格式封装打印数据

2. 后端技术

WebSocket 服务:Node.js(ws 库)、Java(Netty)、Python(WebSocket-server)等打印接口:根据操作系统选择相应库
Windows:Win32 API、PyWin32Linux:CUPS(Common Unix Printing System)macOS:CUPS 或 AppleScript

3. 通信协议

采用 JSON 格式封装消息,结构示例:

{

  “type”: “print”,       // 消息类型:print/state/cancel

  “taskId”: “12345”,     // 任务ID

  “data”: {              // 打印数据

    “content”: “<h1>打印内容</h1>”,  // HTML内容或文本

    “printerName”: “HP-LaserJet”,   // 目标打印机

    “copies”: 1,          // 份数

    “orientation”: “portrait”       // 方向:portrait/landscape

  },

  “timestamp”: 1629260000000

}

三、实现步骤(以 Node.js 为例)

1. 搭建 WebSocket 服务端

使用 Node.js 的ws库创建 WebSocket 服务,处理客户端连接和打印请求:

const WebSocket = require('ws');

const { handlePrintTask } = require('./printService'); // 打印处理模块

// 创建WebSocket服务器

const wss = new WebSocket.Server({ port: 8080 });

// 监听连接

wss.on('connection', (ws) => {

  console.log('客户端已连接');

  // 接收客户端消息

  ws.on('message', (message) => {

    const data = JSON.parse(message.toString());

   

    // 处理打印请求

    if (data.type === 'print') {

      const taskId = `task-${Date.now()}`;

     

      // 发送任务受理回执

      ws.send(JSON.stringify({

        type: 'taskAccepted',

        taskId,

        status: 'pending'

      }));

     

      // 处理打印任务(异步)

      handlePrintTask({

        …data.data,

        taskId

      }).then(result => {

        // 发送打印结果

        ws.send(JSON.stringify({

          type: 'printResult',

          taskId,

          status: result.success ? 'completed' : 'failed',

          message: result.message

        }));

      });

    }

  });

  // 监听断开连接

  ws.on('close', () => {

    console.log('客户端已断开');

  });

});

console.log('WebSocket打印服务已启动,端口:8080');

2. 实现打印服务模块

根据操作系统实现具体打印逻辑,以下是 Windows 系统的示例(使用pdf-to-printer库打印 PDF 文件):

const fs = require('fs');

const path = require('path');

const { print } = require('pdf-to-printer');

const { JSDOM } = require('jsdom');

const PDFDocument = require('pdfkit');

// 处理打印任务

async function handlePrintTask(task) {

  try {

    // 1. 将HTML内容转换为PDF(便于精确控制打印格式)

    const pdfPath = await htmlToPdf(task.content);

   

    // 2. 调用打印机

    const printerOptions = {

      printer: task.printerName,

      pages: '1', // 打印页码范围

      copies: task.copies.toString()

    };

   

    await print(pdfPath, printerOptions);

   

    // 3. 清理临时文件

    fs.unlinkSync(pdfPath);

   

    return {

      success: true,

      message: `打印成功,任务ID:${task.taskId}`

    };

  } catch (error) {

    return {

      success: false,

      message: `打印失败:${error.message}`

    };

  }

}

// HTML转PDF工具函数

function htmlToPdf(htmlContent) {

  return new Promise((resolve, reject) => {

    const tempPath = path.join(os.tmpdir(), `print-${Date.now()}.pdf`);

    const doc = new PDFDocument();

    const stream = fs.createWriteStream(tempPath);

   

    doc.pipe(stream);

   

    // 解析HTML并添加到PDF(简化示例)

    const dom = new JSDOM(htmlContent);

    const textContent = dom.window.document.body.textContent;

    doc.text(textContent, 50, 50);

   

    doc.end();

   

    stream.on('finish', () => resolve(tempPath));

    stream.on('error', reject);

  });

}

module.exports = { handlePrintTask };

3. 前端实现(浏览器端)

前端需要实现 WebSocket 连接、打印数据处理、状态展示等功能:

<!DOCTYPE html>

<html>

<head>

  <title>WebSocket打印示例</title>

  <style>

    .print-preview { border: 1px solid #ccc; padding: 20px; margin: 20px 0; }

    .status { color: #666; margin: 10px 0; }

  </style>

</head>

<body>

  <h1>打印控制台</h1>

  <div class=”print-preview”>

    <h2>测试打印内容</h2>

    <p>这是通过WebSocket发送的打印测试页</p>

    <p>打印时间:<span></span></p>

  </div>

  <select>

    <!– 打印机列表将通过WebSocket获取 –>

  </select>

  <button onclick=”sendPrintRequest()”>打印</button>

  <div class=”status”></div>

  <script>

    // 连接WebSocket服务

    const ws = new WebSocket('ws://localhost:8080');

    let currentTaskId = null;

    // 初始化

    ws.onopen = () => {

      setStatus('已连接到打印服务');

      // 请求打印机列表

      ws.send(JSON.stringify({ type: 'getPrinters' }));

    };

    // 接收消息

    ws.onmessage = (event) => {

      const data = JSON.parse(event.data);

      handleServerMessage(data);

    };

    // 处理服务端消息

    function handleServerMessage(data) {

      switch (data.type) {

        case 'printersList':

          // 渲染打印机列表

          const select = document.getElementById('printerSelect');

          data.printers.forEach(printer => {

            const option = document.createElement('option');

            option.value = printer.name;

            option.textContent = printer.name;

            select.appendChild(option);

          });

          break;

         

        case 'taskAccepted':

          setStatus(`任务已受理,ID:${data.taskId}`);

          currentTaskId = data.taskId;

          break;

         

        case 'printResult':

          setStatus(`任务${data.taskId}:${data.status} – ${data.message}`);

          break;

      }

    }

    // 发送打印请求

    function sendPrintRequest() {

      const printContent = document.getElementById('preview').innerHTML;

      const printerName = document.getElementById('printerSelect').value;

     

      document.getElementById('printTime').textContent = new Date().toLocaleString();

     

      ws.send(JSON.stringify({

        type: 'print',

        data: {

          content: printContent,

          printerName: printerName,

          copies: 1,

          orientation: 'portrait'

        }

      }));

    }

    // 状态显示

    function setStatus(text) {

      document.getElementById('status').textContent = text;

    }

    // 错误处理

    ws.onerror = (error) => {

      setStatus(`连接错误:${error.message}`);

    };

    // 关闭处理

    ws.onclose = () => {

      setStatus('打印服务已断开连接');

    };

  </script>

</body>

</html>

4. 打印机列表获取(服务端扩展)

服务端需要提供获取可用打印机列表的功能(以 Windows 为例):

// 在printService.js中添加

const { exec } = require('child_process');

// 获取Windows系统打印机列表

function getPrinters() {

  return new Promise((resolve, reject) => {

    // 通过wmic命令获取打印机信息

    exec('wmic printer get name', (error, stdout) => {

      if (error) {

        reject(error);

        return;

      }

     

      // 解析输出结果

      const printers = stdout

        .split('
')

        .map(line => line.trim())

        .filter(line => line && line !== 'Name') // 过滤标题和空行

        .map(name => ({ name }));

     

      resolve(printers);

    });

  });

}

// 在WebSocket服务中添加处理

ws.on('message', (message) => {

  const data = JSON.parse(message.toString());

  if (data.type === 'getPrinters') {

    getPrinters().then(printers => {

      ws.send(JSON.stringify({

        type: 'printersList',

        printers

      }));

    });

  }

  // … 其他消息处理

});

四、高级功能扩展

1. 打印任务队列管理

服务端实现任务队列,支持任务优先级、取消、重试前端实时展示队列状态和进度

2. 权限控制

在 WebSocket 连接时验证用户身份(如 JWT 令牌)限制用户可使用的打印机和打印份数

3. 批量打印

前端支持选择多个文件 / 数据服务端实现并发控制,避免打印机过载

4. 打印模板管理

前端提供模板编辑功能服务端存储模板,支持动态填充数据

五、部署与安全考量

1. 部署方案

WebSocket 服务与 Web 应用可部署在同一服务器生产环境需使用 wss://(WebSocket Secure)加密通信考虑使用 Nginx 反向代理 WebSocket 连接

2. 安全措施

实现 WebSocket 连接认证(如 Token 验证)限制打印内容大小,防止恶意请求对打印数据进行 sanitize 处理,防止 XSS 攻击定期清理临时打印文件

六、与传统方案对比

方案

优势

劣势

适用场景

原生 window.print ()

简单直接,无需额外服务

无法指定打印机,依赖浏览器预览

简单文档打印

OCX/ActiveX

可直接调用系统打印机

仅支持 IE,安全风险高

老旧 Windows 系统

WebSocket + 后端服务

跨浏览器、功能完整、安全可控

需要后端开发,部署复杂

企业级应用、复杂打印需求

七、总结

通过 WebSocket 实现浏览器调用打印机的方案,本质是利用 WebSocket 的实时通信能力,将前端的打印请求传递给后端服务,再由后端服务与操作系统交互完成打印操作。该方案突破了浏览器的限制,同时保持了 Web 应用的跨平台优势,特别适合需要精确控制打印过程的企业级应用。

在实际开发中,需根据具体需求选择合适的技术栈,重点关注打印格式一致性、任务管理和安全性,以实现稳定、高效的打印功能!

© 版权声明

相关文章

没有相关内容!

暂无评论

none
暂无评论...