PDF发票解析并将信息回填到前端(1)后端解析PDF

news2024/9/21 0:42:41

文章目录

  • 参考文章
    • 技术栈
    • 需求
    • 解析发票类型
  • 1. 最终项目结构
    • 1.1 说明
  • 2. 相关代码
    • 2.1 导入相应的maven依赖
    • 2.2 实体类
    • 2.3 工具类
    • 2.4 三层架构
      • controller
      • service
      • mapper

参考文章

参考文章

技术栈

SpringBoot+Vue

需求

本文主要是实现提取发票中的部分内容,并实现自动回填到页面中对应位置。

解析发票类型

在这里插入图片描述

1. 最终项目结构

新建一个Springboot项目

在这里插入图片描述

1.1 说明

  1. 实体 entity 包下
    • 主要有两个实体,分别是发票实体NewInvoice,和发票备注实体Note
  2. 工具 utils 包下
    • PDF类用于解析PDF文件
    • InvoiceRegexEnum 类是发票信息的正则表达式枚举类,提取发票信息所用到的正则表达式都在这个枚举类中

2. 相关代码

2.1 导入相应的maven依赖

<!--        添加依赖开始位置-->
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.21</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>fontbox</artifactId>
            <version>2.0.21</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>jempbox</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>xmpbox</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>preflight</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox-tools</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

<!--        添加依赖结束位置-->

2.2 实体类

电子发票实体类NewInvoice

package com.example.pdf.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * 电子发票实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NewInvoice {
//    private String  fileAbsolutePath;   // 文件绝对路径
    private String number;  // 发票号码
    private String date;    // 开票日期
//    private String sellerName;  // 销售方名称
    private BigDecimal amount;  // 合计金额
    private BigDecimal taxAmount;   // 合计税额
    private BigDecimal totalAmount; // 价税合计金额
    private Note Detail;    //  发票备注信息
    private String note;    //  备注

}

备注实体类Note

package com.example.pdf.entity;

import lombok.Data;

/**
 * 发票备注信息
 */
@Data
public class Note {
    private String buyer; // 购方
    private String buyerAccount; // 购方银行账号
    private String seller; // 销方
    private String sellerAccount; // 销方银行账号
    private String payee; // 收款人
    private String checker; // 复核人
//    @Override
//    public String toString() {
//        return  "购方开户银行:" + buyer + ";    "+
//                "银行账号:" + buyerAccount + ";" +"\n"+
//                "销方开户银行:" + seller + ";    "+
//                "银行账号:" + sellerAccount +";" +"\n\n"+
//                " 收款人:" + payee + ";" +"    "+
//                " 复核人:" + checker +";";
//    }
}

2.3 工具类

正则表达式枚举类

package com.example.pdf.utils;

/**
 * 正则表达式枚举类
 */
public enum InvoiceRegexEnum {

    /**
     * 机器编码、发票代码、发票号码、开票日期和校验码的提取正则
     */
    REGULAR_A("机器编号:(?<machineNumber>\\d{12})|发票代码:(?<code>\\d{12})|发票号码:(?<number>\\d{8})|:(?<date>\\d{4}年\\d{2}月\\d{2}日)|校验码:(?<checksum>\\d{20}|\\S{4,})"),

    /**
     * 新版发票的机器编码、发票代码、发票号码、开票日期和校验码的提取正则
     */
    REGULAR_A_NEW("发票号码:(?<number>\\d{20})|:(?<date>\\d{4}年\\d{2}月\\d{2}日)|(售名称|销名称):(?<name>\\S*)"),

    /**
     * 发票号码备用提取正则
     */
    REGULAR_A_1("(国制|制普通发票)(?<number>\\d{8})"),

    /**
     * 发票号码跨行提取正则
     */
    REGULAR_A_1R("发票号码:(?<number>\\d{7})[\\s\\S]*?(\\d+)"),

    /**
     * 开票日期备用提取正则
     */
    REGULAR_A_2("开票日期:(?<date>\\d{4}\\d{2}月\\d{2}日)"),

    /**
     * 发票代码备用提取正则
     */
    REGULAR_A_3("发票代码(?<code>\\d{12})"),

    /**
     * 发票代码跨行提取正则
     */
    REGULAR_A_3R("发票代码:(?<code>\\d{10})[\\s\\S]*?(\\d+)"),

    /**
     * 金额、税额提取正则,匹配形如 "合计¥?金额¥?税额" 的文本
     */
    REGULAR_B("合计¥?(?<amount>[^ \\f\\n\\r\\t\\v*]*)(?:¥?(?<taxAmount>\\S*)|\\*+)\\s"),

    /**
     * 金额提取正则,用于匹配结果有误的修正
     */
    REGULAR_BR("合计¥(?<amount>\\d+\\.\\d+)"),

    /**
     * 金额、税额备用提取正则
     */
    REGULAR_B_1("合\\u0020*计\\u0020*¥?(?<amount>[^ ]*)\\u0020+¥?(?:(?<taxAmount>\\S*)|\\*+)\\s"),

    /**
     * 价税合计提取正则,匹配“价税合计(大写)XXX(小写)¥YYY”格式的文本
     */
    REGULAR_C("价税合计\\u0028大写\\u0029(?<amountString>\\S*)\\u0028小写\\u0029¥?(?<amount>\\S*)\\s"),

    /**
     * 收款人、复核、开票人、销售方提取正则,匹配格式为“收款人:xxx复核:xxx开票人:xxx销售方”的字符串
     */
    REGULAR_D("收款人:(?<payee>\\S*)复核:(?<reviewer>\\S*)开票人:(?<drawer>\\S*)销售方"),

    /**
     * 发票类型提取正则,匹配"xxx通发票"格式的发票类型
     */
    REGULAR_E("(?<p>\\S*)通发票"),

    /**
     * 发票类型提取正则,匹配"xxx用发票"格式的发票类型
     */
    REGULAR_E_1("(?<p>\\S*)用发票"),

    /**
     * 发票类型提取 - 辅助正则
     */
    REGULAR_E_AUX("(?:国|统|一|发|票|监|制)"),

    /**
     * 购买方信息提取正则
     */
    REGULAR_F("名称:(?<name>\\S*)|纳税人识别号:(?<code>\\S*)|地址、电话:(?<address>\\S*)|开户行及账号:(?<account>\\S*)|电子支付标识:(?<account2>\\S*)"),

    /**
     * 针对深圳发票的销售方名称提取正则
     */
    REGULAR_FR("名称:(?<name>\\S*)"),

    /**
     * 处理除了金额和税额之外的其他文本元素正则
     */
    REGULAR_G("^(-?\\d+)(\\.\\d+)?$"),

    /**
     * 备注信息提取正则
     */

    REGULAR_A_NOTE_BUYER("购方开户银行:(?<buyer>[^;]+);"),
    REGULAR_A_NOTE_BUYERACCOUNT("银行账号:(?<buyerAccount>\\d+)(?=[,;])"),
    REGULAR_A_NOTE_SELLER("销方开户银行:(?<seller>.*?)(?=[,;]|\\Z)"),
    REGULAR_A_NOTE_SELLERACCOUNT("银行账号:(?<sellerAccount>\\d+)(?=[,;]|\\Z)"),
    REGULAR_A_NOTE_PAYEE("收款人:(?<payee>.*?)(?=[,;]|\\Z)"),
    REGULAR_A_NOTE_CHECKER("复核人:(?<checker>.*?)(?=[,;]|\\Z)"),

    /**
     * 检查当前详细项字符串是否符合特定条件正则
     */
    REGULAR_H("\\S+\\d*(%|免税|不征税|出口零税率|普通零税率)\\S*"),
    REGULAR_H_1("^ *\\d*(%|免税|不征税|出口零税率|普通零税率)\\S*"),
    REGULAR_H_2("\\S+\\d+%[\\-\\d]+\\S*"),
    REGULAR_H_3("^ *\\d*(%|免税|不征税|出口零税率|普通零税率)\\S*");


    private final String regex;


    InvoiceRegexEnum(String regex) {
        this.regex = regex;
    }


    public String getRegex() {
        return regex;
    }
}


解析PDF工具类

package com.example.pdf.utils;

import com.example.pdf.entity.NewInvoice;
import com.example.pdf.entity.Note;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.example.pdf.utils.InvoiceRegexEnum.*;

@Slf4j
public class PDF {

    private static final String PDF_EXTENSION = ".pdf";

    /**
     * 调用该方法将前端接受到的文件暂存
     *
     * @param file
     */
    public static NewInvoice parsePdfFile(MultipartFile file) {
        NewInvoice newInvoice = new NewInvoice();
        try {
            // 创建一个临时文件
            Path tempFile = Files.createTempFile("tempPrefix", ".pdf");
            File tempFilePath = tempFile.toFile();

            // 将MultipartFile的内容写入到临时文件
            try (FileOutputStream fos = new FileOutputStream(tempFilePath)) {
                fos.write(file.getBytes());
            }

            // 现在你可以使用临时文件的路径来调用你的解析方法
            newInvoice = extractInvoiceInformation(tempFilePath.getAbsolutePath());


            // 删除临时文件,或者在某些情况下保留它
            tempFilePath.delete();


        } catch (IOException e) {
            // 处理异常
            e.printStackTrace();
        }
        // 返回值
        return newInvoice;
    }
    /**
     * 提取发票信息
     */
    /**
     * 提取发票信息
     *
     * @param filePath 发票地址
     * @return
     */
    public static NewInvoice extractInvoiceInformation(String filePath) {
        // 指定要处理的文件夹路径
        NewInvoice newInvoice1 = newPdfProcessInvoicesInFile(filePath);

        String note = "";
        if (newInvoice1.getDetail().getBuyer() != null) {
            note = "购方开户银行:" + newInvoice1.getDetail().getBuyer() + ";    ";
        } else {
            note = "";
        }
        if (newInvoice1.getDetail().getBuyerAccount() != null) {
            note = note + "银行账号:" + newInvoice1.getDetail().getBuyerAccount() + ";    ";
        } else {
            note = note + "";
        }
        if (newInvoice1.getDetail().getSeller() != null) {
            note = note + "销方开户银行:" + newInvoice1.getDetail().getSeller() + ";    ";
        } else {
            note = note + "";
        }
        if (newInvoice1.getDetail().getSellerAccount() != null) {
            note = note + "银行账号:" + newInvoice1.getDetail().getSellerAccount() + ";    ";
        } else {
            note = note + "";
        }
        if (newInvoice1.getDetail().getPayee() != null) {
            note = note + " 收款人:" + newInvoice1.getDetail().getPayee() + ";" + "    ";
        } else {
            note = note + "";
        }
        if (newInvoice1.getDetail().getChecker() != null) {
            note = note + " 复核人:" + newInvoice1.getDetail().getChecker() + ";";
        } else {
            note = note + "";
        }
        newInvoice1.setNote(note);

        return newInvoice1;
    }

    /**
     * 处理指定的PDF发票文件
     *
     * @param filePath 文件路径
     * @return 包含提取信息的 NewInvoice 列表
     */
    public static NewInvoice newPdfProcessInvoicesInFile(String filePath) {
        File file = new File(filePath);
        NewInvoice returnResult = new NewInvoice();
        if (isPdfFile(file)) {
            NewInvoice result = extractInvoice(file.getAbsolutePath()); // 提取文件内容

            if (result != null) {
                returnResult = createProcessedInvoice(result);// 创建一个新的发票对象

            } else {
                handleExtractionError(file);
            }
        }

        return returnResult;
    }

    /**
     * 创建一个处理后的 NewInvoice 对象
     *
     * @param result 原始的 NewInvoice 对象
     * @return 处理后的 NewInvoice 对象
     */
    private static NewInvoice createProcessedInvoice(NewInvoice result) {
        NewInvoice returnResult = new NewInvoice();
        returnResult.setNumber(result.getNumber());
        returnResult.setDate(result.getDate());
        returnResult.setTotalAmount(result.getTotalAmount());
        returnResult.setAmount(result.getAmount());
        returnResult.setTaxAmount(result.getTaxAmount());
        returnResult.setDetail(result.getDetail());
        return returnResult;
    }

    /**
     * 处理提取失败的情况,输出错误信息
     *
     * @param file 提取失败的文件
     */
    private static void handleExtractionError(File file) {
        log.warn("文件: {}\t提取失败~~~\n", file.getName());
    }

    /**
     * 检查文件是否为PDF文件
     *
     * @param file 要检查的文件
     * @return 如果是PDF文件,返回 true,否则返回 false
     */
    private static boolean isPdfFile(File file) {
        return file.isFile() && file.getName().toLowerCase().endsWith(PDF_EXTENSION);
    }

    /**
     * 从本地文件或URL中提取发票信息。
     *
     * @param filePath 本地文件路径或发票的URL。
     * @return 包含提取信息的 NewInvoice 对象。
     */
    private static NewInvoice extractInvoice(String filePath) {
        File sourceFile = new File(filePath);

        if (!sourceFile.exists()) {
            log.error("指定的源文件不存在");
        }

        NewInvoice result = null;

        try {
            // 调用函数解析PDF ,返回 发票对象【基本信息】
            result = extract(sourceFile);
        } catch (Exception e) {
            e.printStackTrace();
            result = new NewInvoice();
        }
        return result;
    }

    /**
     * 解析PDF 文件,返回发票对象
     *
     * @param file PDF文件
     * @return
     * @throws IOException
     */
    public static NewInvoice extract(File file) throws IOException {
        NewInvoice invoice = new NewInvoice();  // 新建发票对象
        // 接收一个表示 PDF 文件路径的字符串作为参数,并返回一个 PDDocument 对象。
        // 这个对象代表了整个PDF 文档,可以通过这个对象来访问文档的各个部分
        PDDocument doc = PDDocument.load(file);
        // 从 PDDocument 对象 doc 中获取第一页,并将这个页面对象赋值给PDPage类型的变量
        // PDPage 对象代表了文档中的一个页面
        PDPage firstPage = doc.getPage(0);

        // 获取页面裁剪框宽度,并将宽度四舍五入为整数
        // 【页面裁剪宽度定义了页面上用于显示内容的区域】
        int pageWidth = Math.round(firstPage.getCropBox().getWidth());

        // PDFTextStripper 用于从PDF文档中提取文本的工具
        PDFTextStripper textStripper = new PDFTextStripper(); // 创建一个实例
        textStripper.setSortByPosition(true); // 提取文本时按照物理位置进行排序

        // 提取整个文档的所有文本内容,并将这些文本内容作为一个长字符串返回
        String fullText = textStripper.getText(doc);

        // 页面翻转? 不重要
        if (firstPage.getRotation() != 0) {
            pageWidth = Math.round(firstPage.getCropBox().getHeight());
        }

        // 处理文本中可能有错误的符号
        String allText = replace(fullText).replaceAll("(", "(").replaceAll(")", ")").replaceAll("¥", "¥");

        // 提取 新版发票的机器编码、发票代码、发票号码、开票日期和检验码
        {
            Pattern pattern = Pattern.compile(REGULAR_A_NEW.getRegex()); // 新版发票的机器编码、发票代码、发票号码、开票日期和检验码的提取正则
            Pattern patternNumber = Pattern.compile(REGULAR_A_1.getRegex());// 发票号码备用提取正则
            Pattern patternDate = Pattern.compile(REGULAR_A_2.getRegex()); // 开票日期备用提取正则

            // matcer 类对于输入字符串进行解释和匹配操作,这些操作是基于某个Pattern对象定义的规则(正则表达式)进行的。
            // 检查allText 字符串中是否匹配pattern中定义的正则表达式的文本
            Matcher matcher = pattern.matcher(allText);
            while (matcher.find()) {// 在输入字符串allText中查找与模式匹配的第一个子序列
                // 如果 提取到发票号码,则设置发票号码
                if (matcher.group("number") != null) {
                    invoice.setNumber(matcher.group("number"));
                } else if (matcher.group("date") != null) {
                    String rawDate = matcher.group("date"); // 发票日期,解析日期并设置日期
                    try {
                        SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
                        SimpleDateFormat outputDateFormat = new SimpleDateFormat("yyyy-MM-dd");

                        Date parsedDate = inputDateFormat.parse(rawDate);
                        String formattedDate = outputDateFormat.format(parsedDate);
                        invoice.setDate(formattedDate);
                    } catch (ParseException e) {
                        System.out.println("无法解析日期:" + rawDate);
                    }
                }

                // 如果没有提取到的话使用备用在进行提取
                if (matcher.group("number") == null) {
                    Matcher matcher2 = patternNumber.matcher(allText);
                    if (matcher2.find()) {
                        invoice.setNumber(matcher2.group("number"));
                    }
                }
                if (matcher.group("date") == null) {
                    Matcher matcher3 = patternDate.matcher(allText);
                    if (matcher3.find()) {
                        String rawDate = matcher3.group("date");
                        try {
                            SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyyMM月dd日");
                            SimpleDateFormat outputDateFormat = new SimpleDateFormat("yyyy-MM-dd");

                            Date parsedDate = inputDateFormat.parse(rawDate);
                            String formattedDate = outputDateFormat.format(parsedDate);

                            invoice.setDate(formattedDate);
                        } catch (Exception e) {
                            System.out.println("无法解析日期:" + rawDate);
                        }
                    }
                }
            }
        }

        // 提取 金额、税额等
        {
            Pattern pattern = Pattern.compile(REGULAR_B.getRegex()); // 金额、税额提取正则,匹配形如“合计¥?金额¥?税额”的文本

            Matcher matcher = pattern.matcher(allText);

            if (matcher.find()) {
                try {
                    invoice.setAmount(new BigDecimal(matcher.group("amount")));
                } catch (Exception e) {
                    // 不处理
                }
                try {
                    invoice.setTaxAmount(new BigDecimal(matcher.group("taxAmount")));
                } catch (Exception e) {
                    invoice.setTaxAmount(new BigDecimal(0));
                }
            }
        }

        // 如果没有提取到,则再使用备用的正则进行提取
        if (null == invoice.getAmount()) {
            Pattern pattern = Pattern.compile(REGULAR_B_1.getRegex());

            Matcher matcher = pattern.matcher(fullText);

            if (matcher.find()) {
                try {
                    invoice.setAmount(new BigDecimal(matcher.group("amount")));
                } catch (Exception e) {
                    invoice.setAmount(new BigDecimal(0));
                }
                try {
                    invoice.setTaxAmount(new BigDecimal(matcher.group("taxAmount")));
                } catch (Exception e) {
                    invoice.setTaxAmount(new BigDecimal(0));
                }
            }
        }
        invoice.setTotalAmount(invoice.getAmount().add(invoice.getTaxAmount()));

        // 先创建一个发票备注实例
        Note note = new Note();
        // 提取发票备注信息
        {

            // 提取购方开户银行
            Pattern patternBuyer = Pattern.compile(REGULAR_A_NOTE_BUYER.getRegex()); // 提取备注信息
            Pattern patternBuyerAccount = Pattern.compile(REGULAR_A_NOTE_BUYERACCOUNT.getRegex()); // 提取备注信息
            Pattern patternSeller = Pattern.compile(REGULAR_A_NOTE_SELLER.getRegex()); // 提取备注信息
            Pattern patternSellerAccount = Pattern.compile(REGULAR_A_NOTE_SELLERACCOUNT.getRegex()); // 提取备注信息
            Pattern patternPayee = Pattern.compile(REGULAR_A_NOTE_PAYEE.getRegex()); // 提取备注信息
            Pattern patternChecker = Pattern.compile(REGULAR_A_NOTE_CHECKER.getRegex()); // 提取备注信息

            Matcher matcher0 = patternBuyer.matcher(allText);
            if (matcher0.find()) {// 如果查询到的话就设置备注信息
                try {
                    note.setBuyer(new String(matcher0.group("buyer")));
                } catch (Exception e) {
                    // 不处理
                }
            }
            Matcher matcher1 = patternBuyerAccount.matcher(allText);
            if (matcher1.find()) {// 如果查询到的话就设置备注信息
                try {
                    note.setBuyerAccount(new String(matcher1.group("buyerAccount")));
                } catch (Exception e) {
                    // 不处理
                }
            }
            Matcher matcher2 = patternSeller.matcher(allText);
            if (matcher2.find()) {// 如果查询到的话就设置备注信息
                try {
                    note.setSeller(new String(matcher2.group("seller")));
                } catch (Exception e) {
                    // 不处理
                }
            }
            Matcher matcher3 = patternSellerAccount.matcher(allText);
            if (matcher3.find()) {// 如果查询到的话就设置备注信息
                try {
                    note.setSellerAccount(new String(matcher3.group("sellerAccount")));
                } catch (Exception e) {
                    // 不处理
                }
            }
            Matcher matcher4 = patternPayee.matcher(allText);
            if (matcher4.find()) {// 如果查询到的话就设置备注信息
                try {
                    note.setPayee(new String(matcher4.group("payee")));
                } catch (Exception e) {
                    // 不处理
                }
            }
            Matcher matcher5 = patternChecker.matcher(allText);
            if (matcher5.find()) {// 如果查询到的话就设置备注信息
                try {
                    note.setChecker(new String(matcher5.group("checker")));
                } catch (Exception e) {
                    // 不处理
                }
            }

        }
        invoice.setDetail(note);

        return invoice;

    }

    /**
     * 替换字符串中的空格、全角空格、冒号和特殊空白字符为标准字符。
     *
     * @param str 要进行替换的字符串
     * @return 替换后的字符串
     */
    private static String replace(String str) {
        return str.replaceAll(" ", "").replaceAll(" ", "").replaceAll(":", ":").replaceAll(" ", "");
    }

}

2.4 三层架构

controller

package com.example.pdf.controller;

import com.example.pdf.entity.NewInvoice;
import com.example.pdf.service.InvoiceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/invoice")
public class InvoiceController {
    @Autowired
    InvoiceService invoiceService;

    /**
     * @param
     */
//    @PostMapping
//    public void insert() {
//        invoiceService.save();
//    }
    @CrossOrigin(origins = "http://localhost:8081", allowedHeaders = "*", allowCredentials = "true")
    @PostMapping("/upload")
    public ResponseEntity<Object> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            // 调用你的文件解析服务
            NewInvoice parsedData = invoiceService.parsePdfFile(file);

            // 返回解析后的数据
            return ResponseEntity.ok(parsedData);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error parsing file");
        }
    }

}

service

service

package com.example.pdf.service;

import com.example.pdf.entity.NewInvoice;
import org.springframework.web.multipart.MultipartFile;

public interface InvoiceService {
//     void save();

     NewInvoice parsePdfFile(MultipartFile file);
}

serviceImpl

package com.example.pdf.service.impl;

import com.example.pdf.entity.NewInvoice;
import com.example.pdf.mapper.InvoiceMapper;
import com.example.pdf.service.InvoiceService;
import com.example.pdf.utils.PDF;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;


@Service
public class InvoiceServiceImpl implements InvoiceService {

    @Autowired
    InvoiceMapper invoiceMapper;
//
//    @Override
//    public void save(){
//        //获得发票对象
//        InvoiceSubset invoiceSubset = extractInvoiceInformation("D:\\00-sqq\\idea\\test\\dzfp_24952000000116465179_深圳必维华法商品检定有限公司东莞分公司_20240726105216.pdf");
//        //调用mapper将发票对象存入到数据库中
//        invoiceMapper.save(invoiceSubset);
//    }

    /**
     * 调用解析文件的方法,解析上传的文件
     * @param file
     * @return
     */
    @Override
    public NewInvoice parsePdfFile(MultipartFile file) {
        NewInvoice newInvoice = PDF.parsePdfFile(file);
        return newInvoice;
    }
}

mapper

因为没有操作数据库,所以没有使用到mapper层

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1972839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单调栈② | Java | LeetCode 接雨水 最大的矩形

42. 接雨水 暴力法 for循环遍历每一个柱子&#xff0c;内层for循环找到左边和右边比它高的柱子 时间复杂度 n^2 优化&#xff1a;添加一个预处理 定义一个数组&#xff0c;存放该柱子右边比他高的柱子是哪一个 再用一个数组&#xff0c;存放该柱子左边比他高的柱子是哪一个 …

html页面下载及多html文件合成单pdf方法

1&#xff0c;html页面下载 &#xff5e;1&#xff0c;首先在edge中微软扩展商店搜索 “SingleFile”添加扩展 浏览器右上角点击扩展按钮 下拉菜单选择管理扩展 点击 获取 Microsoft Edge 扩展 进入微软商店搜索 “singlefile” 点击获取 点击浏览器右上角扩展按钮&#xff0c;…

IPv4 vs IPv6:了解这两大协议的关键差异

我发现&#xff0c;很多找代理IP的朋友在后台问我的问题都很相似&#xff01;都被配置IP的最后一步&#xff1a;选择IPv4还是IPv6&#xff1f;给难住了。昨晚我一晚没睡&#xff0c;终于整理出对IPv4和IPv6的对比总结&#xff0c;从概念阐述到特点对比&#xff0c;再解答IPv6总…

利用 IP 地址进行社交工程攻击?

社交工程攻击是网络安全的主要威胁之一。现在攻击者不再仅依赖技术漏洞&#xff0c;而是想利用人性的弱点来获取有价值的信息或实现非法目的。 IP 地址在社交工程攻击中的作用 定位和伪装获取目标用户的 IP 地址&#xff0c;大致确定目标用户地理位置&#xff0c;然后伪装成当…

spring原理(自学第六天)

Aware 接口及 InitializingBean 接口 今天将会学到Aware 接口及 InitializingBean 接口 我们可以先了解他们的作用&#xff1a; 1. Aware 接口用于注入一些与容器相关信息, 例如 a. BeanNameAware 注入 bean 的名字 b. BeanFactoryAware 注入…

解锁LLM应用潜能:提示工程的39种方法与应用全解析!

大型语言模型&#xff08;LLMs&#xff09;在许多不同的自然语言处理&#xff08;NLP&#xff09;任务上表现出了显著的性能。提示工程在提升LLMs已有能力方面发挥着关键作用&#xff0c;使其在各种NLP任务上取得了显著的性能提升。提示工程需要编写自然语言指令&#xff0c;即…

QT 应用程序输出中文乱码

一 &#xff0c;选择文本编码 1. 点击编辑再点击Select Encoding选择编码 2 .在弹出的窗口&#xff0c;选择UTF-8再点击按编码保存即可 3. 重新编译&#xff0c;可以发现中文乱码问题解决

Spring源码解析(28)之AOP的核心对象创建过程总结

一、总结图 二、总结 以上是对AOP核心对象创建的一个总结&#xff0c;接下来我们分点介绍我们自从定义了aop.xml之后&#xff0c;spring在启动的时候是怎么创建了这些核心对象的。 启动spring容器之后&#xff0c;其实核心是是refresh方法&#xff0c;refresh的13个核心方法&…

CTFHub——XSS——反射型

1、反射型&#xff1a; 发现为表单式&#xff0c;猜测哪个可能存在注入漏洞&#xff0c;分别做测试注入发现name框存在xss漏洞 输入发现有回显但不是对方cookie&#xff0c;参考wp发现要用xss线上平台 将xss平台测试语句注入&#xff0c;将得到的url编码地址填入url框&#xf…

Lineage凌骥电源模块维修CP2725AC54Z

Lineage Power交换机电源常见维修型号有&#xff1a; CAR2024FPZ0-1A、CP2725AC54TEZ、AXA005A0X-SRZ、APXW005A0X3-SRZ、AXA016A0X3-SRZ、EP3000AC48TE、CP3000AC54TEZ、 CP3000AC54TEZ、QBDW033A0B641Z及EP4848系列直流电源维修等。 凌骥电源维修实践中&#xff0c;有许多开关…

【OpenCV C++20 学习笔记】击中击不中(Hit-or-Miss)

击中击不中 Hit-or-Miss 原理代码实现 原理 形态学操作对图片的处理是基于图片的形状的。形态学操作将一个或多个结构元素(structuring elements)&#xff0c;即卷积核&#xff0c;应用到图片上从而获得计算结果。最基本的两个形态学操作就是腐蚀(erosion)和膨胀(dilation)。这…

7 时间序列单特征:多输入->多输出(LSTM/GRU/TCN)

今天看到关于时间序列预测知识点&#xff0c;竟然要收费&#xff01;本着开源第一的思想&#xff0c;自己也找到相关的代码尝试一下写几个通用的模版。 模型想要 输入&#xff1a;Input (input_size, hidden_size),其中&#xff1a;input_size time_stemp,因为是单个变量因此…

从实现第一个ArkTs应用开始入门

前言 新建了个鸿蒙学习项目&#xff0c;后续持续学习会把代码放到这里来&#xff1a;鸿蒙项目仓库学习实践版 基本概念 从HarmonyOS NEXT Developer Preview1(API 11)版本开始&#xff0c;HarmonyOS SDK以Kit维度提供了六大领域的开放能力&#xff0c; 包括&#xff1a; 应用…

800G FR4解决方案:高速数据传输的理想选择

随着业务规模的扩大&#xff0c;数据中心面临着越来越多的数据处理需求。虚拟化、物联网&#xff08;IoT&#xff09;和云计算等数据密集型应用推动了数据中心流量的不断增长&#xff0c;从而提升了对大容量800G解决方案的市场需求。因此&#xff0c;新建800G数据中心&#xff…

marker - PDF 转 markdown

文章目录 一、关于 marker特点它是如何工作的例子性能商业用途托管API限制 二、安装Optional: OCRMyPDF 三、用法1、配置转换单个文件转换多个文件在多个GPU上转换多个文件 三、故障排除四、有用的设置五、基准测试速度精度吞吐量 六、运行自己的基准测试七、感谢 一、关于 mar…

C++初学(9)

9.1、结构简介 虽然数组能够和存储多个元素&#xff0c;但所有元素必须相同&#xff0c;也就是说&#xff0c;同一个数组不能既存放int类型也存放float类型&#xff0c;而C的结构可以满足要求。结构是一种比数组更灵活的数据格式&#xff0c;因为同一个结构可以存储多种类型的…

防御笔记第九天(持续更新)

注意&#xff1a;攻击可能只是一个点&#xff0c;而防御需要全方面进行。 1.IAE引擎 2.DPI DPI ----深度包检测 --- 针对完整的数据包&#xff0c;进行内容的识别和检测 3.基于特征字的检测技术 4&#xff0c;基于应用网关的检测技术 基于应用网关的检测技术 --- 有些应用控…

数据库方言

数据库方言&#xff0c;也称数据库领域特定语言&#xff08;DSL&#xff09;&#xff0c;是针对特定数据库系统的专有扩展或子集&#xff0c;它允许用户在特定环境内使用更高效、更简洁的查询语句。 关键字&#xff08;Keywords&#xff09; 关键字是数据库方言中预定义的单词&…

Windows 安装Redis7.4版本图文教程

本章教程&#xff0c;主要介绍如何在Windows上安装Redis7.4版本的Redis&#xff0c;并以服务方式实现开机自启动。 1、下载安装包 通过百度网盘分享的文件&#xff1a;Redis-7.4.0-Windows-x64-cygwin-with-Service.zip 链接&#xff1a;https://pan.baidu.com/s/1NFGXrCwumDzl…

【计算机毕业设计】703学生考勤管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…