Qypt5 Excel 转 PDF 连接RabbitMQ自动打印

CyberSicko
hava a nice day.

介绍

自动打印程序,Server端产生单子,Excel格式,将文件地址根据组发送到rabbitmq队列,程序订阅rabbitmq,抓取文件地址下载并调用打印机打印。
这里的订阅分组,每个组一个routing-key,程序可以自由订阅N个组。

Python实现,GUI框架为Pyqt5。

连接打印机

程序关闭会最小化到托盘,同时托盘拥有选择默认打印机功能。


class TrayIcon(QSystemTrayIcon):
    def __init__(self, parent=None, ):
        super(TrayIcon, self).__init__(parent)
        self.quit_action = QAction("退出", self, triggered=self.quit)
        self.default_printer = QAction('当前打印机:' + DEFAULT_PRINTER, self)
        self.refresh_action = QAction('刷新')
        self.refresh_action.triggered.connect(parent.init_data)
        self.printer_menus = QMenu()
        self.menu = QMenu()
        self.setToolTip("监控打印程序")

        self.show_menu()
        self.other()

    def show_menu(self):
        " 设计托盘的菜单,这里我实现了一个二级菜单 "
        print(DEFAULT_PRINTER)
        self.menu.addAction(self.default_printer)
        self.menu.addAction(self.refresh_action)
        self.menu.addSeparator()
        self.menu.addMenu(self.printer_menus, )

        self.menu.addAction(self.quit_action)
        self.printer_menus.setTitle("打印机列表")

        self.get_all_printer_actions()

        self.setContextMenu(self.menu)

    def get_all_printer_actions(self):
        for item in PRINTERS:
            action = QAction(item, self)
            action.triggered.connect(lambda: self.set_use_printer(self.sender()))
            if item == DEFAULT_PRINTER:
                action.setCheckable(True)
                action.setChecked(True)
            self.printer_menus.addAction(action)
            # self.printer_menus.addActions(printers)

    def set_use_printer(self, action):
        for item in self.printer_menus.actions():
            item.setCheckable(False)
            item.setChecked(False)
        action.setCheckable(True)
        action.setChecked(True)
        win32print.SetDefaultPrinterW(action.text())
        self.default_printer.setText('当前打印机:' + action.text())
        self.showMessage("信息", "当前打印机为:" + action.text(), self.icon)

    def other(self):
        self.activated.connect(self.iconClied)
        # 把鼠标点击图标的信号和槽连接
        # self.messageClicked.connect(self.mClied)
        # 把鼠标点击弹出消息的信号和槽连接
        self.setIcon(QIcon(":/images/logo.ico"))
        self.icon = self.MessageIcon()
        # 设置图标

    def iconClied(self, reason):
        "鼠标点击icon传递的信号会带有一个整形的值,1是表示单击右键,2是双击,3是单击左键,4是用鼠标中键点击"
        if reason == 2 or reason == 3:
            pw = self.parent()
            if pw.isVisible():
                pw.hide()
            else:
                pw.show()
        print(reason)


    def quit(self):
        "保险起见,为了完整的退出"
        self.setVisible(False)
        self.parent().exit()
        qApp.quit()
        sys.exit()

程序运行会获取所有本地打印机:

  if file.tryLock():  # 防止多开
        for it in win32print.EnumPrinters(2):
            PRINTERS.append(it[2])
        app.setQuitOnLastWindowClosed(False)
        w = window()

RabbitMQ 管理

RabbitMQ 订阅,取消订阅,文件下载,都在这了。

import logging
import os
import shutil
import threading
import time
from urllib import request

import pika
import win32api
import win32print
import xlwings as xw

logging.basicConfig(level=logging.DEBUG,  # 控制台打印的日志级别
                    filename='./printer.log',
                    filemode='a',
                    # a是追加模式,默认如果不写的话,就是追加模式
                    format=
                    '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
                    # 日志格式
                    )

import pythoncom

EXCHANGE_NAME = 'exchange-printOrder'
SAVE_PATH = os.path.abspath('.') + '\\download'
LOCK = 0

import PDF_Util


class MQ(threading.Thread):
    def __init__(self, keys, window):
        super().__init__()
        credentials = pika.PlainCredentials('rabbitmq', 'P@ssw0rd')  # mq用户名和密码,没有则需要自己创建
        self.conn = pika.BlockingConnection(
            pika.ConnectionParameters(host='10.86.13.83', virtual_host='/', credentials=credentials))
        self.channel = self.conn.channel()
        self.current_keys = keys
        self.log_area = window.log_area
        self.log_area.append('初始化RabbitMQ 成功')
        self.window = window
        self.window.statusbar.showMessage('已连接')
        self.lock = False
        # threading.Thread(target=self.clear_file).start()
        self.clear_file()

    def clear_file(self):
        try:
            if not os.path.exists(SAVE_PATH):
                os.mkdir(SAVE_PATH)
            else:
                shutil.rmtree(SAVE_PATH)
                os.mkdir(SAVE_PATH)
            logging.info('清空文件')
            print('clear')
        except PermissionError as p:
            print('文件被占用,wait')
            logging.info('文件被占用,wait')
            pass

    def refresh(self, key, flag):
        if flag == 0:
            self.channel.queue_unbind(self.queue_name, exchange=EXCHANGE_NAME, routing_key=key)
            print('解绑:', key)
            logging.info('解绑:' + key)
            self.log_area.append('解绑:' + key + '成功')
        elif flag == 1:
            self.channel.queue_bind(self.queue_name, exchange=EXCHANGE_NAME, routing_key=key)
            logging.info('绑定:' + key)
            print('绑定:', key)
            self.log_area.append('绑定:' + key + '成功')
        else:
            pass
        pass

    def call_back(self, ch, method, properties, body):
        print(ch, method, properties)
        self.lock = True

        logging.info('收到来自: %s 的消息: %s' % (method.routing_key, body))
        logging.info('下载来自: %s 的文件: %s' % (method.routing_key, body))
        self.log_area.append('下载来自: %s 的文件: %s' % (method.routing_key, body))
        if not os.path.exists(SAVE_PATH):
            os.makedirs(SAVE_PATH)
        self.download(str(body)[2:][0:-1])

    def get_file_name(self, url: str):
        return url[url.rindex('/') + 1:]

    def download(self, URL):
        try:
            req = request.Request(URL)
            res = request.urlopen(url=req, timeout=5)
            get_img = res.read()
            with open(SAVE_PATH + '\\' + self.get_file_name(URL), 'wb') as f:
                f.write(get_img)
            f.close()
            self.window.ti.showMessage('提示', "打印: " + self.get_file_name(URL), self.window.ti.icon)
            self.log_area.append('文件: %s 下载成功,开始打印' % (self.get_file_name(URL)))
            logging.info('文件: %s 下载成功,开始打印' % (self.get_file_name(URL)))
            currentPrinter = win32print.GetDefaultPrinterW()
            # hinstance = win32api.ShellExecute(0, "print", SAVE_PATH + '\\' + self.get_file_name(URL),
            #                                   '/d:"%s"' % currentPrinter,
            #                                   None, 0)
            # logging.info('打印返回值 %s' % hinstance)

            # order = '{0} {1} {2}'.format(GS_PRINT, pdf, currentPrinter)
            # os.system(order)
            pythoncom.CoInitialize()
            pdf_util = PDF_Util.PDFConverter(SAVE_PATH + '\\' + self.get_file_name(URL))
            pdf_util.run_conver()

            self.log_area.append('打印完成')
            logging.info('打印完成')
            self.lock = False
        except Exception as e:
            logging.error(str(e))
            self.log_area.append(str(e))

    def run(self):
        self.channel.exchange_declare(EXCHANGE_NAME, 'topic', True)
        result = self.channel.queue_declare('', exclusive=True)
        self.queue_name = result.method.queue
        logging.info('队列名称:{0}'.format(self.queue_name))
        print('队列名称:{0}'.format(self.queue_name))
        self.log_area.append('当前队列名称:' + self.queue_name)

        # routing_key = 'printOrder[06-15-KIT-B-R-01]'
        for key in self.current_keys:
            self.channel.queue_bind(exchange=EXCHANGE_NAME, queue=self.queue_name, routing_key=key)
        self.channel.basic_consume(queue=self.queue_name, on_message_callback=self.call_back, auto_ack=True)
        currentPrinter = win32print.GetDefaultPrinterW()
        self.log_area.append('当前默认打印机:' + currentPrinter)
        self.channel.start_consuming()


if __name__ == '__main__':
    mq = MQ(['printOrder[06-15-KIT-B-R-01]', 'printOrder[06-15-KIT-B-R-02]', 'printOrder[06-15-KIT-B-R-03]'])
    mq.start()
    mq.refresh(['printOrder[06-15-KIT-B-R-01]', 'printOrder[06-15-KIT-B-R-02]'])

我这里还做了本地持久化,会把订阅的信息存在C盘User下的.monitor文件夹的setting.ini里面,也就是程序关闭后,再次打开会自动订阅上次已订阅的组。

class Config(object):

    def __init__(self):
        self.conf = ConfigParser()
        self.path = os.path.expanduser('~') + '\\' + '.monitor'
        self.name = "setting.ini"
        self.dir = self.path + '\\' + self.name
        if not os.path.isdir(self.path):
            os.makedirs(self.path)
        if not os.path.isfile(self.dir):
            fd = open(self.dir, mode="w", encoding="utf-8")
            fd.close()
            self.init_data()

    # 初始化数据
    def init_data(self):
        self.conf.add_section("TOPIC")
        self.conf.set("TOPIC", "DATA", json.dumps(GROUPS))
        self.conf.write(open(self.dir, 'a'))

    def refresh(self):
        self.conf.remove_section('TOPIC')
        self.conf.add_section('TOPIC')
        self.conf.set("TOPIC", "DATA", json.dumps(GROUPS))
        self.conf.write(open(self.dir, 'w'))

    def get_groups(self):
        self.conf.read(self.dir)
        data = self.conf.get('TOPIC', 'DATA')
        arr = json.loads(data)
        return arr

打印Excel

我本以为打印Excel没什么难的,可是客户机器上的Excel版本都是2016,太老了,在打印Excel的时候是要调用Excel程序的,而Excel打印,然后还会弹出 ‘当前文件已被修改,XXXXXX’,这就很不友好,需要人去点,就做不到静默打印的效果,
这问题无解,我总不能去动Excel,最后憋急了,将Excel转PDF,再用第三方工具实现静默打印。

# -*- coding:utf-8 -*-
import logging
import os
import subprocess

import win32api
import win32print
from win32com.client import Dispatch, constants, gencache, DispatchEx

SAVE_PATH = os.path.abspath('.') + '\\download'
# GS_WIN32 = r'C:\Program Files (x86)\gs\gs9.55.0\bin\gswin32.exe'
GS_PRINT = os.path.abspath('.') + '\\PDFtoPrinter.exe'
logging.basicConfig(level=logging.DEBUG,  # 控制台打印的日志级别
                    filename='./printer.log',
                    filemode='a',
                    # a是追加模式,默认如果不写的话,就是追加模式
                    format=
                    '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
                    # 日志格式
                    )


def print_pdf(export_file: str):
    currentPrinter = win32print.GetDefaultPrinterW()
    order = '{0} {1} {2}'.format(GS_PRINT, export_file, currentPrinter)
    res = subprocess.call(order, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    print('打印返回值{0}'.format(res))
    logging.info('打印返回值{0}'.format(res))

class PDFConverter:
    def __init__(self, pathname, export='.'):
        self._handle_postfix = ['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx']
        self._filename_list = list()
        self._export_folder = SAVE_PATH
        if not os.path.exists(self._export_folder):
            os.mkdir(self._export_folder)
        self._enumerate_filename(pathname)

    def _enumerate_filename(self, pathname):
        '''
        读取所有文件名
        '''
        full_pathname = os.path.abspath(pathname)
        if os.path.isfile(full_pathname):
            if self._is_legal_postfix(full_pathname):
                self._filename_list.append(full_pathname)
            else:
                raise TypeError('文件 {} 后缀名不合法!仅支持如下文件类型:{}。'.format(pathname, '、'.join(self._handle_postfix)))
        elif os.path.isdir(full_pathname):
            for relpath, _, files in os.walk(full_pathname):
                for name in files:
                    filename = os.path.join(full_pathname, relpath, name)
                    if self._is_legal_postfix(filename):
                        self._filename_list.append(os.path.join(filename))
        else:
            raise TypeError('文件/文件夹 {} 不存在或不合法!'.format(pathname))

    def _is_legal_postfix(self, filename):
        return filename.split('.')[-1].lower() in self._handle_postfix and not os.path.basename(filename).startswith(
            '~')

    def run_conver(self):
        '''
        进行批量处理,根据后缀名调用函数执行转换
        '''
        print('需要转换的文件数:', len(self._filename_list))
        for filename in self._filename_list:
            postfix = filename.split('.')[-1].lower()
            funcCall = getattr(self, postfix)
            print('原文件:', filename)
            funcCall(filename)
        print('转换完成!')

    def doc(self, filename):
        '''
        doc 和 docx 文件转换
        '''
        name = os.path.basename(filename).split('.')[0] + '.pdf'
        exportfile = os.path.join(self._export_folder, name)
        print('保存 PDF 文件:', exportfile)
        gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
        w = Dispatch("Word.Application")
        doc = w.Documents.Open(filename)
        doc.ExportAsFixedFormat(exportfile, constants.wdExportFormatPDF,
                                Item=constants.wdExportDocumentWithMarkup,
                                CreateBookmarks=constants.wdExportCreateHeadingBookmarks)

        w.Quit(constants.wdDoNotSaveChanges)

    def docx(self, filename):
        self.doc(filename)

    def xls(self, filename):
        '''
        xls 和 xlsx 文件转换
        '''
        name = os.path.basename(filename).split('.')[0] + '.pdf'
        exportfile = os.path.join(self._export_folder, name)
        xlApp = DispatchEx("Excel.Application")
        xlApp.Visible = False
        xlApp.DisplayAlerts = 0
        books = xlApp.Workbooks.Open(filename, False)
        books.ExportAsFixedFormat(0, exportfile)
        books.Close(False)
        print('保存 PDF 文件:', exportfile)
        print_pdf(exportfile)
        xlApp.Quit()

    def xlsx(self, filename):
        self.xls(filename)

    def ppt(self, filename):
        '''
        ppt 和 pptx 文件转换
        '''
        name = os.path.basename(filename).split('.')[0] + '.pdf'
        exportfile = os.path.join(self._export_folder, name)
        gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
        p = Dispatch("PowerPoint.Application")
        ppt = p.Presentations.Open(filename, False, False, False)
        ppt.ExportAsFixedFormat(exportfile, 2, PrintRange=None)
        print('保存 PDF 文件:', exportfile)
        p.Quit()

    def pptx(self, filename):
        self.ppt(filename)
def print_pdf(export_file: str):
    currentPrinter = win32print.GetDefaultPrinterW()
    order = '{0} {1} {2}'.format(GS_PRINT, export_file, currentPrinter)
    res = subprocess.call(order, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    print('打印返回值{0}'.format(res))
    logging.info('打印返回值{0}'.format(res))

打印PDF需要借助PDFPrinter.exe,下载地址: http://s3.z100.vip:28592/%E7%A8%8B%E5%BA%8F/AutoPrint/PDFtoPrinter.exe

评论

还没有评论

发表评论