17 - common-pdf 工具包

common-pdf

概述

java导出数据的方式有很多种,这边大概做一个总结 :
Itext,wkhtmltopdf,Spire.Doc,Flying Sauser,PD4ML,x-easypdf,目前使用量最多的就是Itext(Itext5 和 Itext7),Itext7也有一个高级的模块html2pdf能快速实现功能,后端的代码量最少。

wkhtmltopdf,Spire.Doc,Flying Sauser,PD4ML 可以参考:https://blog.csdn.net/weixin_43981813/article/details/128135730
Itext 系列(包含html2pdf):血推 https://blog.csdn.net/u012397189/category_9270695.html
x-easypdf是新的pdf框架 https://x-easypdf.cn/,2023-7-2发的第一个版本,类似于 easyExcel,hutool这样的框架,目前还不是很成熟,未来应该会很不错,后续可以关注。
在这里插入图片描述
x-easypdf 目前还不成熟,有一些问题还是影响功能的,比如目前不能创建table,只能填充等等,具体的其他问题可以关注 issue
在这里插入图片描述
目前生产慎用,也可以自己优化源码,但是后续值得期待

当前案例方案

本案列主要用的是Itext实现
Itext实现的又有一下几种实现方式:

方式一: 利用Itext提供的接口,以纯代码画格式(可调字体,大小,颜色,表单,对复杂没有规则的数据都可以)输出pdf文档,
方式二: 提前准备一个pdf模版,首先需要相关的编辑工具,把模版内容编辑好,需要填充的占位符占参,然后代码中读取模版填充参数后把文档输出。
方式三: 利用HTML将需要导出的模版定义好,在代码逻辑中利用模版引擎将真实的数据进行填充,然后利用html2pdf提供的api将填充后的HTML以PDF的形式输出。

当前案列也是使用的方式三,因为足够灵活,足够省事,样式的调试也很方便

方式三使用到的相关资源

字体库:http://xiazaiziti.com/210356.html
模版引擎thymeleaf: https://www.thymeleaf.org/documentation.html

先看效果

在这里插入图片描述
在这里插入图片描述

代码实现

实现步骤分析
  1. 写一个HTML文件模版 如 heroList.html
  2. 文件IO读取这个文件模版-heroList.html
  3. 数据库查询出数据,利用模板引擎(freemark,thymeleaf …)TemplateEngine将数据库查询出的数据填充到上一步读取的heroList.html中去。
  4. 将填充完毕的html以pdf的形式输出
引入Maven依赖

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>html2pdf</artifactId>
            <version>4.0.3</version>
        </dependency>
heroList.html

在resource > templates > 下新建一个个模版HTML文件
在这里插入图片描述

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>英雄导出列表</title>
    <style>
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            border: 1px solid black;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
    </style>
</script>
</head>

<body>
<h1 style="text-align: center;">英雄导出列表</h1>
<table>
    <thead>
    <tr>
        <th>英雄名称</th>
        <th>英雄类型</th>
        <th>英雄技能</th>
        <th>英雄图片</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="hero : ${heroes}">
        <td th:text="${hero.heroName}"></td>
        <td th:text="${hero.heroType}"></td>
        <td th:text="${hero.heroSkill}"></td>
        <td><img th:src="@{${hero.heroImage}}" alt="英雄图片" width="50" height="50"></td>
    </tr>
    </tbody>
</table>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

PdfUtils 中的工具方法
将pdf 响应到浏览器
/**
     * @description 导出文件为 pdf,并有一定的样式(页脚,水印),并将文件输出到浏览器
     * @param response 响应对象
     * @param templateFile HTML文件的路径: /templates/heroList.html
     * @param fileName  要输出的pdf文件名: 最后的文件名=fileName+当前时间.pdf,如果文件名为空则=当前时间.pdf
     * @param context thymeleaf上下文,本质就是一个Map,用于存放填充的动态的数据
     * @return void
     **/
    public static void downloadPdfToViewWithStyle(HttpServletResponse response, String templateFile, String fileName, Context context) throws Exception {
        String templatePath = checkTemplate(templateFile);
        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider();
        String typeface = Objects.requireNonNull(PdfUtils.class.getResource("/templates/SimHei.ttf")).getPath();
        dfp.addFont(typeface);
        converterProperties.setFontProvider(dfp);
        //根据templatePath=/templates/heroList.html将本地的html读取到内存并转为字符串
        String htmlStr = toHtmlString(new File(templatePath));
        //模板参数替换:利用thymeleaf模版引擎将数据库查询出来的数据(放在了context中)和 读取过来的html字符串模版进行数据填充,replaceHtmlStr就是一个填充完数据的html字符串
        String replaceHtmlStr = new TemplateEngine().process(htmlStr, context);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        setCommonResp(response,fileName);
        PdfWriter pdfWriter = new PdfWriter(bos);
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        //设置为A4大小
        pdfDocument.setDefaultPageSize(PageSize.A4);
        //添加页码
        pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE,new PageEventHandler());
        //添加水印
        pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new WaterMarkEventHandler("FANG-SHENG"));
        //利用html2pdf中的工具将填充完毕的html以pdf的形式输到ByteArrayOutputStream中
        HtmlConverter.convertToPdf(replaceHtmlStr, pdfDocument, converterProperties);
        ServletOutputStream outputStream = response.getOutputStream();
        //将文件写到页面
        outputStream.write(bos.toByteArray());



    }
将pdf写入到文件中
 /**
     * @description 导出文件为pdf,并将pdf文件写到文件中:默认会写到target>classes>下
     * @param templateFile HTML文件的路径: /templates/heroList.html
     * @param fileName  要输出的pdf文件名: 最后的文件名=fileName+当前时间.pdf,如果文件名为空则=当前时间.pdf
     * @param context thymeleaf上下文,本质就是一个Map,用于存放填充的动态的数据
     **/
    public static void downloadPdfToFile(String templateFile, String fileName, Context context) {
        String templatePath = checkTemplate(templateFile);
        String typeface = Objects.requireNonNull(PdfUtils.class.getResource("/templates/SimHei.ttf")).getPath();
        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider();
        dfp.addFont(typeface);
        converterProperties.setFontProvider(dfp);
        //将本地的模板转换成字符串
        String htmlStr = toHtmlString(new File(templatePath));
        //模板参数替换
        String replaceHtmlStr = new TemplateEngine().process(htmlStr, context);
        String distPath = Objects.requireNonNull(PdfUtils.class.getResource("/")).getPath();
        File destDir = new File(distPath);
        if (!destDir.exists()) {
            destDir.mkdirs();
        }
        try (OutputStream out = new FileOutputStream(new File(distPath + fileName))) {
            HtmlConverter.convertToPdf(replaceHtmlStr, out, converterProperties);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("pdf转换异常3{}" + e);
        }
    }

完整的工具类
package com.fangsheng.technology.pdf;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.fangsheng.technology.pdf.config.PageEventHandler;
import com.fangsheng.technology.pdf.config.WaterMarkEventHandler;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Objects;

/**
 * @author lht
 * @date 2023/12/11 18:19:17
 * pdf 导出工具类,封装了一系列的导出静态方法
 */
@Slf4j
public class PdfUtils {

    /**
     * @description 导出文件为 pdf,并将文件输出到浏览器
     * @param response 响应对象
     * @param templateFile HTML文件的路径: /templates/heroList.html
     * @param fileName  要输出的pdf文件名: 最后的文件名=fileName+当前时间.pdf,如果文件名为空则=当前时间.pdf
     * @param context thymeleaf上下文,本质就是一个Map,用于存放填充的动态的数据
     * @return void
     **/
    public static void downloadPdfToView(HttpServletResponse response, String templateFile, String fileName, Context context) throws Exception {
        String templatePath = checkTemplate(templateFile);
        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider();
        /** 添加中文字体库 方式 a   -window环境有自带字体 linux环境需要安装中文字体,可参考 https://www.zhihu.com/question/423159370/answer/2706867741
         *              方式 b 使用项目导入的字体库 (已验证linux环境不安装中文字体,能正常显示中文)
         *              下载字体库(SimHei.ttf) http://xiazaiziti.com/210356.html
         */
        String typeface = Objects.requireNonNull(PdfUtils.class.getResource("/templates/SimHei.ttf")).getPath();
        dfp.addFont(typeface);
        converterProperties.setFontProvider(dfp);
        //默认每页是显示 10 条
        converterProperties.setLimitOfLayouts(30);
        //将本地的模板转换成字符串
        String htmlStr = toHtmlString(new File(templatePath));
        //模板参数替换
        String replaceHtmlStr = new TemplateEngine().process(htmlStr, context);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        setCommonResp(response,fileName);
        HtmlConverter.convertToPdf(replaceHtmlStr, bos, converterProperties);
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(bos.toByteArray());
    }

    /**
     * @description 导出文件为 pdf,并有一定的样式(页脚,水印),并将文件输出到浏览器
     * @param response 响应对象
     * @param templateFile HTML文件的路径: /templates/heroList.html
     * @param fileName  要输出的pdf文件名: 最后的文件名=fileName+当前时间.pdf,如果文件名为空则=当前时间.pdf
     * @param context thymeleaf上下文,本质就是一个Map,用于存放填充的动态的数据
     * @return void
     **/
    public static void downloadPdfToViewWithStyle(HttpServletResponse response, String templateFile, String fileName, Context context) throws Exception {
        String templatePath = checkTemplate(templateFile);
        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider();
        String typeface = Objects.requireNonNull(PdfUtils.class.getResource("/templates/SimHei.ttf")).getPath();
        dfp.addFont(typeface);
        converterProperties.setFontProvider(dfp);
        //将本地的模板转换成字符串
        String htmlStr = toHtmlString(new File(templatePath));
        //模板参数替换
        String replaceHtmlStr = new TemplateEngine().process(htmlStr, context);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        setCommonResp(response,fileName);
        PdfWriter pdfWriter = new PdfWriter(bos);
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        //设置为A4大小
        pdfDocument.setDefaultPageSize(PageSize.A4);
        //添加页码
        pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE,new PageEventHandler());
        //添加水印
        pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new WaterMarkEventHandler("FANG-SHENG"));
        HtmlConverter.convertToPdf(replaceHtmlStr, pdfDocument, converterProperties);
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(bos.toByteArray());



    }


    /**
     * @description 导出文件为pdf,并将pdf文件写到文件中:默认会写到target>classes>下
     * @param templateFile HTML文件的路径: /templates/heroList.html
     * @param fileName  要输出的pdf文件名: 最后的文件名=fileName+当前时间.pdf,如果文件名为空则=当前时间.pdf
     * @param context thymeleaf上下文,本质就是一个Map,用于存放填充的动态的数据
     **/
    public static void downloadPdfToFile(String templateFile, String fileName, Context context) {
        String templatePath = checkTemplate(templateFile);
        String typeface = Objects.requireNonNull(PdfUtils.class.getResource("/templates/SimHei.ttf")).getPath();
        ConverterProperties converterProperties = new ConverterProperties();
        FontProvider dfp = new DefaultFontProvider();
        dfp.addFont(typeface);
        converterProperties.setFontProvider(dfp);
        //将本地的模板转换成字符串
        String htmlStr = toHtmlString(new File(templatePath));
        //模板参数替换
        String replaceHtmlStr = new TemplateEngine().process(htmlStr, context);
        String distPath = Objects.requireNonNull(PdfUtils.class.getResource("/")).getPath();
        File destDir = new File(distPath);
        if (!destDir.exists()) {
            destDir.mkdirs();
        }
        try (OutputStream out = new FileOutputStream(new File(distPath + fileName))) {
            HtmlConverter.convertToPdf(replaceHtmlStr, out, converterProperties);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("pdf转换异常3{}" + e);
        }
    }




    /**
     * 校验用户需要使用的模板文件是否存在
     * @param templateHtml html模板文件
     */
    public static String checkTemplate(String templateHtml){
        URL resource = PdfUtils.class.getResource(templateHtml);
        if(resource == null){
            throw new RuntimeException("Html模板文件未找到!");
        }
        return resource.getPath();
    }

    /**
     * 设置response的属性
     * @param response 响应对象
     * @param fileName 文件名
     */
    public static void setCommonResp(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        // 设置中文文件名
        String fileNewName = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
        if(StringUtils.isNotBlank(fileName)){
            fileName = new String(fileName.getBytes("utf-8"),"iso8859-1");
            String encode = URLEncoder.encode(fileName, "iso8859-1");
            fileNewName = encode + fileNewName;
        }
        response.setContentType("application/x-download");
        response.addHeader("Content-Disposition", "attachment; filename=" + fileNewName + ".pdf");
        response.setCharacterEncoding("UTF-8");
    }

    /**
     *  读取本地html文件里的html代码
     * @param file  File file=new File("文件的绝对路径")
     * @return
     */
    public static String toHtmlString(File file) {
        // 获取HTML文件流
        StringBuffer htmlSb = new StringBuffer();
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    new FileInputStream(file), "utf-8"));
            while (br.ready()) {
                htmlSb.append(br.readLine());
            }
            br.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // HTML文件字符串
        String htmlStr = htmlSb.toString();
        // 返回经过清洁的html文本
        return htmlStr;
    }
    
    
}

添加页眉页脚的Handler
package com.fangsheng.technology.pdf.config;

import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;


/**
 * @author lht
 * @date 2023/12/12 10:42:25
 */
public class PageEventHandler implements IEventHandler {

    /**
     * 每写一页就会触发当前方法一次
     **/
    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();

        Rectangle pageSize = page.getPageSize();

        PdfCanvas pdfCanvas = new PdfCanvas(
                page.newContentStreamBefore(), page.getResources(), pdf);

        float  x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float  y = pageSize.getBottom() + 15;

        Rectangle rect = new Rectangle(x-20, y, 100, 35);
        Canvas canvas = new Canvas(pdfCanvas, rect, true);

        Paragraph paragraph = new Paragraph("page " + pdf.getPageNumber(page))
                .setFontSize(15).setFontColor(new DeviceRgb(0, 0, 0));
        canvas.add(paragraph);

        canvas.close();
    }
}


添加水印的Handler
package com.fangsheng.technology.pdf.config;

import com.itextpdf.kernel.colors.WebColors;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.VerticalAlignment;

import java.io.IOException;

/**
 * @author lht
 * @date 2023/12/12 14:02:03
 */
public class WaterMarkEventHandler implements IEventHandler {

    /**
     * 水印内容
     */
    private String waterMarkContent;

    /**
     * 一页中有几列水印
     */
    private int waterMarkX;

    /**
     * 一页中每列有多少水印
     */
    private int waterMarkY;

    public WaterMarkEventHandler(String waterMarkContent) {
        this(waterMarkContent, 5, 5);
    }

    public WaterMarkEventHandler(String waterMarkContent, int waterMarkX, int waterMarkY) {
        this.waterMarkContent = waterMarkContent;
        this.waterMarkX = waterMarkX;
        this.waterMarkY = waterMarkY;
    }

    @Override
    public void handleEvent(Event event) {

        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();

        PdfFont pdfFont = null;
        try {
            pdfFont = PdfFontFactory.createFont();
        } catch (IOException e) {
            e.printStackTrace();
        }

        PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), document);

        Paragraph waterMark = new Paragraph(waterMarkContent).setOpacity(0.6f);
        Canvas canvas = new Canvas(pdfCanvas, pageSize)
                .setFontColor(WebColors.getRGBColor("lightgray"))
                .setFontSize(16)
                .setFont(pdfFont);

        for (int i = 0; i < waterMarkX; i++) {
            for (int j = 0; j < waterMarkY; j++) {
                canvas.showTextAligned(waterMark, (150 + i * 300), (160 + j * 150), document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.BOTTOM, 120);
            }
        }
        canvas.close();
    }

}


测试方法

循环了50条数据,模拟从数据库查询出来的,然后将这些数据放到thymeleaf的Context容器中,TemplateEngine会取出数据填充到HTML的模版文件中去。

package com.fangsheng.technology.app.hero.executor;

import com.fangsheng.technology.pdf.PdfUtils;
import com.fangsheng.technology.pdf.template.HeroPdfDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.Context;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* @author lht
* @date 2023/12/11 18:00:07
*/
@Slf4j
@Component
public class HeroDownloadPdfExe {


   public void execute(HttpServletResponse response) {

       //模板内容填充参数
       Context context = new Context();
       List<HeroPdfDTO> heroes = new ArrayList<>();

       for (int i = 1; i <= 50; i++) {
           heroes.add(HeroPdfDTO.builder()
                   .heroName("皮城女警·凯特琳 "+ i)
                   .heroType("射手")
                   .heroSkill("R - 让子弹飞")
                   .heroImage("https://bkimg.cdn.bcebos.com/pic/3801213fb80e7bec54e7e5bebc7fae389b504fc23d68?x-bce-process=image/resize,m_lfit,w_536,limit_1/quality,Q_70").build()
           );
       }

       context.setVariable("heroes", heroes);
       try {
           PdfUtils.downloadPdfToViewWithStyle(response,"/templates/heroList.html",  "英雄", context);
       } catch (IOException e) {
           e.printStackTrace();
           log.error("导出数据到pdf失败了...................");
       } catch (Exception e) {
           e.printStackTrace();
           log.error("导出数据到pdf失败了...................");
       }



   }
}

小结

至此简单的基本功能就实现完了,调试还是很方便的,如果是纯代码的操作就要看文件的输出效果了,代码量也是很少了,如果之前做过单应用系统,特别是基于jsp,thymeleaf。 基本不需要去代码中创建元素对象:Paragraph,Table,Cell,Image,Canvas,Rectangle ...,但是如果需要高级或者特殊的功能还是需要的。

扎实基本功 https://blog.csdn.net/u012397189/category_9270695.html,从第一篇看到最后一篇,创建和读取pdf,页眉页脚水印,只读文档,文档的组合,拼接,根据模板填充,分裂,缩放,聚集,拼接和锁定表单 … 功能都有

pdf 文件的解析

场景:公司的报销业务,员工的差旅,吃,住,等场景的报销,那么这些发票最终会有财务的人统一收集,然后整理,月底发放。如果公司的员工多,报销项也多财务也需要忙乎半天(这需要大量的时间和资源,需要考虑输入错误或安全问题的风险)。能否写一个程序自动整理然后将结果输出 ?

发票汇总的小工具

简单的实现(具体要根据公司的具体情况了)

  1. 默认所有的发票都是 pdf 文件

  2. 约定每个员工的报销发票pdf文件名必须是以员工名字+者工号开头(这样具有唯一性,即使同个员工的多个发票下载后系统会自动给文件名加(x)),然后以邮件主题为“报销”发给财务的如花,

  3. 月底如花将在邮箱上根据邮件主题和时间过滤所有的发票然后将其下载到一个文件夹下(E:\invoice\temp\202312),(其实也可以代码去读取公司邮件服务器的文件,或者员工将pdf放到约定好的共享文件夹中 …)

  4. 然后运行小工具,代码读取E:\invoice\temp\202312下所有的pdf文件,然后遍历提取每个pdf上的所需数据,整理成 excel 统计输出。

  5. 输出的excel包含的列如下:
    在这里插入图片描述

部分代码实现

package com.fangsheng.technology.pdf.test;

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author lht
 * @date 2023/12/14 19:15:36
 */
public class Test {
    public static void main(String[] args) throws MalformedURLException {
        //URL url = new URL("file:/E:\\fangsheng\\temp\\fapiao.pdf");
        //存放pdf发票的文件夹
        String folderPath = "E:\invoice\temp\202312";
        try {
            List<File> pdfFiles = Files.walk(Paths.get(folderPath), Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS)
                    .filter(Files::isRegularFile)
                    .filter(path -> path.toString().toLowerCase().endsWith(".pdf"))
                    .map(Path::toFile)
                    .collect(Collectors.toList());
            for (File pdfFile : pdfFiles) {
                System.out.println(pdfFile.getName());
                readPdf(pdfFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 将需要解析字段的正则集合,利用正则表达式读取文本中我们需要的字段
     **/
    static final List<String> REGULAR = List.of(
            "发票代码: (\\d+)"
            ,"发票号码: (\\d+)"
            ,"开票日期: (\\d{4}年\\d{2}月\\d{2}日)"
            ,"校 验 码: ((\\d+|\\s)+)"
            ,"名    称:(\n.+)"
            ,"价税合计(大写) 肆拾圆陆角肆分 (小写)\n" +
                    "¥(\\d+\\.\\d+)"
            );

    private static void readPdf(File file) {
        // 读取PDF中的文本信息
        StringBuilder text = new StringBuilder();
        try {
            PdfReader reader = new PdfReader(file);
            PdfDocument document = new PdfDocument(reader);
            int pageNum = document.getNumberOfPages();
            for (int i = 1; i <= pageNum; i++) {
                String textFromPage = PdfTextExtractor.getTextFromPage(document.getPage(i));
                text.append(textFromPage);
            }
            String res = text.toString();
            System.out.println(res);
            System.out.println("\n\n\n");
            System.out.println("结果如下:");
            for (String re : REGULAR) {
                Pattern compile = Pattern.compile(re);
                Matcher matcher = compile.matcher(res);
                while (matcher.find()){
                    System.out.println(matcher.group());
                }
            }
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


}

解析测试

我的打车发票
在这里插入图片描述
解析结果输出
在这里插入图片描述

弊端:
还是是有很大的工作量(需要手动的一个个将文件下载到同一个文件夹)
如果某个人的发票不符合规范,也只有等到月底才能知晓,时间长了细节估计本人也忘记了

思路:就将公司常用的类似的此类工作专门集中到这个程序(日报,报销 …)里面,部署成公司内部的系统,然后员工直接将报销单上传在自己的程序里面,那样上传的时候就解析pdf,如有问题立马能反馈给员工,
这样源文件放置的位置,统一的命名我们自己就能控制了,有问题能及时反馈出去,财务的人能实时查看结果

… 越扯越大了…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值