# PrintService **Repository Path**: bean257/print-service ## Basic Information - **Project Name**: PrintService - **Description**: Java云打印服务 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-07-10 - **Last Updated**: 2024-07-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 云打印 ## 前端页面: ![image-20221008225323364](image/image-20221008225323364.png) ![image-20221008225636416](image/image-20221008225636416.png) ## 当前流程 ![image-20221008231125209](image/image-20221008231125209.png) ## 部署 ZeroTier NetworkID:52b337794f5b7d64 ![image-20221008232625227](image/image-20221008232625227.png) 问题: 1. 工作室服务器1与公网服务器无法通过ZeroTier组件虚拟局域网进行通信。 2. 无安全认证,任何人都可以进行打印。 3. 因为一些水印的字体无法直接打印,某些PDF文件需要先转doc再转pdf,转换耗时太多(61页的pdf转换完毕需要两分钟) 4. 小程序个人账户受到很多限制,目前只能局域网使用。 解决: 1. 使用ngrok进行内网穿透,将工作室服务器1的服务暴露到互联网,使用nginx将用户的请求从公网服务器转发到工作室服务器1上(不直接调用的原因是后续小程序调用接口需要一个备案的域名,无法使用ngrok生成的域名)。 2. 增加JWT验证以及Referer验证,尽量防止除本站之外的接口调用,防止恶意抓包重放,每个Token只能使用一次,最好是可以收费,网站调用接口需要收费(打印收费)。 3. 将所有文档转换成图片打印,有了文档的图片后,还可以增加预览和非常直观的选择打印页的界面。 4. 希望能有企业账户,这样既可以防止恶意调用接口,也可以减少成本,还能做出更多功能。 ## 问题3详情 为了提高效率以及提高用户体验,我们决定对后端代码进行重构。 1. ppt转pdf未进行测试,但经过查资料,发现ppt转pdf吃内存,若是部署在Linux环境下,字体会出问题,部署在docker容器中更是如此。(据说4Mppt转pdf会吃1G内存,7Mppt转pdf直接OOM) 2. word文档和excell表格转pdf目前未发现问题。 3. pdf会有较多不能识别的字体(如某些资料上特殊的水印),直接打印会出现乱码,或是一片空白。目前解决方案是先将pdf转word,再将word转回pdf,但这样的效率极慢,一个60页的pdf需要两分钟。 4. …… 这些问题最终都可以通过一个办法解决——转图片。 经测试,原本需要转换2分钟的pdf,若是转换为图片,只需要20s左右。 并且可以把图合成长图用于预览,还能提供非常直观的选择指定页面打印功能(能够直接看见页面内容选择)。 思路: ![image-20221009162031301](image/image-20221009162031301.png) 理论存在,实践开始 ### 1. 创建容纳用户上传文件的文件夹并定期清理 在程序接收第一个文件时,创建所有需要的文件夹。 ```java // 创建文件夹 if (!dest.getParentFile().exists()) { Path path = Paths.get(Constants.RESULT_PATH); try { Path pathCreate = Files.createDirectories(path); } catch (IOException e) { throw new RuntimeException(e); } } ``` 每日凌晨一点清理当日处理的文件 技术栈:spring task 和 cron表达式 https://cloud.tencent.com/developer/article/1582434 ```java @Scheduled(cron = "0 0 1 * * ?") public void clearDir() { Path path = Paths.get(Constants.FILE_PATH); try { Files.walkFileTree(path, new SimpleFileVisitor() { // 先去遍历删除文件 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); System.out.printf("文件被删除 : %s%n", file); return FileVisitResult.CONTINUE; } // 再去遍历删除目录 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); System.out.printf("文件夹被删除: %s%n", dir); return FileVisitResult.CONTINUE; } } ); } catch (IOException e) { throw new RuntimeException(e); } } ``` 多线程执行定时任务 在刚刚的方法上加上@Async注解,并配置线程池 ```java @Configuration @EnableAsync public class AsyncConfig { @Value("${asyncThreadPool.corePoolSize}") private int corePoolSize; @Value("${asyncThreadPool.maxPoolSize}") private int maxPoolSize; @Value("${asyncThreadPool.queueCapacity}") private int queueCapacity; @Bean public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(corePoolSize); // 最大线程数 executor.setMaxPoolSize(maxPoolSize); // 队列数量 executor.setQueueCapacity(queueCapacity); // 初始化 executor.initialize(); return executor; } } ``` 坑: 在一个接口实现类里,非该接口的定时任务加上@Async注解会导致异步任务无法找到该方法导致异常。 解决办法: 多实现一个 ```java public interface ClearDir { void clearDir(); } ``` ```java public class Initiate implements ApplicationRunner, ClearDir { …… } ``` ### 2. 根据用户上传的不同文件类型选择执行对应的转换方法 ```java public interface ToImg { String toImg (String filePath); String wordToImg(String filePath, String resultPath); String picToImg(String filePath, String resultPath); String excelToImg(String filePath, String resultPath); String pdfToImg(String filePath, String resultPath); } ``` ### 3. 打印图片 ```java @Override synchronized public Boolean print(@RequestBody DocumentConfiguration documentConfiguration) { boolean flag = false; String path = documentConfiguration.getFileName(); // 要遍历的路径 PrintService printService = PrintServiceLookup.lookupDefaultPrintService(); // 获取本地打印机 try { File file = new File(path); // 获取其file对象 File[] fs = file.listFiles(); // 遍历path下的文件和目录,放在File数组中 for (int i = 0; i < documentConfiguration.getCount(); i++) { // 用户指定打印份数 for(File f:fs){ // 遍历File[]数组 if(!f.isDirectory() && documentConfiguration.getPageIndex().contains(Integer.valueOf(f.getName().substring(0, f.getName().lastIndexOf("."))))) { // 若非目录(即文件)且是用户指定的页面之一,则打印 DocPrintJob job = printService.createPrintJob(); // 文件类型 DocFlavor flavor = DocFlavor.INPUT_STREAM.JPEG; PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet(); pras.add(MediaSizeName.ISO_A4); DocAttributeSet das = new HashDocAttributeSet(); InputStream input = new FileInputStream(f); Doc doc = new SimpleDoc(input, flavor, das); job.print(doc, pras); flag = true; } } } } catch (PrintException | FileNotFoundException e) { flag = false; e.printStackTrace(); } } ```