PDF 是我们日常办公中常常接触的一种文件格式,本篇文章主要讲解如何使用 Apache 的开源插件 pdfbox 对 pdf 文档进行操作。同时也复习之前所学的内容,进行一个完整的电子发票处理流程:pdf转图片、图片识别、文件上传。
首先在 pom.xml 中添加依赖:
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.14</version>
</dependency>
当然本次功能主要实现的是 pdf 文件的读取功能,如果要写或生成 pdf 文件还需要引入其余的依赖包:
<!--字体-->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.14</version>
</dependency>
<!--pdf-->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
本次先写出控制层代码,然后再分步骤简介实现代码:
@PostMapping(value = "/vatInvoice")
@ApiOperation(value = "普通增值税发票识别(图片/pdf)")
public String vatInvoice(@ApiParam(value = "发票 图片/pdf", required = true) MultipartFile file) {
if (file.getSize() > 2 * 1024 * 1024) return "大小限制:2M";
// 获取文件名
String fileName = Objects.requireNonNull(file.getOriginalFilename()).toLowerCase();
// 获取文件后缀
String suffix = fileName.substring(fileName.lastIndexOf('.'));
// 文件上传到 OSS
String name = "Temp/" + UUID.randomUUID().toString().replaceAll("-", "") + suffix;
// 转换后的图片 base64, 文件上传链接
String base64Str, fileUrl;
if (suffix.equalsIgnoreCase(".pdf")) {
base64Str = pdfUtil.pdf2Image(file);
} else if (suffix.equalsIgnoreCase(".png")
|| suffix.equalsIgnoreCase(".jpg")
|| suffix.equalsIgnoreCase(".jpeg")
|| suffix.equalsIgnoreCase(".bmp")) {
base64Str = imageUtils.image2Base64(file);
} else {
return "支持的格式:PDF、PNG、JPG、JPEG、BMP。";
}
if (Strings.isNullOrEmpty(base64Str)) return "格式转换有误";
String data = baiduAIUtil.vatInvoice(base64Str);
if (Strings.isNullOrEmpty(data)) return "识别失败";
try {
String ret = ossClientUtil.uploadFile2OSS(file.getInputStream(), name);
if (Strings.isNullOrEmpty(ret)) return "上传失败";
} catch (IOException e) {
e.printStackTrace();
return "上传出错";
}
fileUrl = ossClientUtil.getUrl(name);
if (!fileUrl.startsWith("http")) return "获取失败";
return "源文件上传:\n" + fileUrl.substring(0, fileUrl.indexOf('?')) + "\n识别结果:\n" + data;
}
运行截图如下:
本次测试用例:发票.pdf 和 发票.png。
控制器前几行代码自不必多说,限制上传大小、获取文件名称、初始化变量。
接下来是根据文件后缀判断文件格式,如果是 pdf 格式就进入 pdfUtil.pdf2Image(file) 处理,将 MultipartFile 文件格式转化成图片,再编码成 Base64 格式;如果传入文件是图片类型,就调用 imageUtils.image2Base64(file) 将文件直接转成 Base64 格式。
pdf 转Base64 代码如下:
/**
* PDF文件转PNG图片,第N页
*
* @param file pdf文件 MultipartFile
* @param pageIndex 第几页
* @param dpi dpi越大转换后越清晰,相对转换速度越慢 默认300
* @return Base64 String
*/
public String pdf2Image(MultipartFile file, int pageIndex, int dpi) {
if (dpi <= 0 || dpi > 300) dpi = 300;
PDDocument pdDocument;
String base64Str = null;
try {
pdDocument = PDDocument.load(file.getInputStream());
PDFRenderer renderer = new PDFRenderer(pdDocument);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedImage image = renderer.renderImageWithDPI(pageIndex, dpi);
ImageIO.write(image, "png", baos);
base64Str = Base64Util.encode(baos.toByteArray());
log.info("PDF文档转PNG图片成功!大小: " + base64Str.length() / 1024 + "KB");
} catch (IOException e) {
e.printStackTrace();
return "";
}
return base64Str;
}
/**
* @param file pdf文件 MultipartFile
* 默认第一页; dpi: 300
* @return base64 String
*/
public String pdf2Image(MultipartFile file) {
return this.pdf2Image(file, 0, 300);
}
图片转 Base64 代码如下:
/**
* MultipartFile图片转base64
*
* @param file MultipartFile类型的图片
* @return base64字符串
*/
public String image2Base64(MultipartFile file) {
InputStream is;
ByteArrayOutputStream outStream;
try {
is = file.getInputStream();
outStream = new ByteArrayOutputStream();
// 创建一个Buffer字符串
byte[] buffer = new byte[1024];
// 每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
// 使用一个输入流从buffer里把数据读取出来
while ((len = is.read(buffer)) != -1) {
// 用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
outStream.write(buffer, 0, len);
}
// 对字节数组Base64编码
return Base64Util.encode(outStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
接下来是调用 API 接口,发票图片识别获取发票信息:baiduAIUtil.vatInvoice(base64Str)
/**
* 增值税发票识别
*
* @param img base64图片 (无uri)
* @return 识别结果
*/
public String vatInvoice(String img) {
try {
Map<String, String> param = new TreeMap<>();
param.put("image", URLEncoder.encode(img.replace("\r\n", ""), "UTF-8"));
param.put("accuracy", "normal");
String result = httpsPost(param, vat_invoice);
return JSONObject.parseObject(result).get("words_result").toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
图片识别大致就是调用接口,然后 POST 发送信息,然后接收数据并返回。这部分前几篇文章详细的讲解过,所以此处不再详细讲解。可参考我以前的文章:SpringBoot API 系列(八) 发送请求
再往下就是上传源文件和获取源文件的链接:
/**
* 上传到OSS服务器 如果同名文件会覆盖服务器上的
*
* @param instream 文件流
* @param fileName 文件名称 包括后缀名
* @return 出错返回"" ,唯一MD5数字签名
*/
public String uploadFile2OSS(InputStream instream, String fileName) {
String ret = "";
try {
//创建上传Object的Metadata
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(instream.available());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma", "no-cache");
objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));
objectMetadata.setContentDisposition("inline;filename=" + fileName);
//上传文件
PutObjectResult putResult = ossClient.putObject(ossBucketName, fileName, instream, objectMetadata);
ret = putResult.getETag();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
if (instream != null) {
instream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return ret;
}
/**
* 获得url链接
*
* @param key
* @return url
*/
public String getUrl(String key) {
// 设置URL过期时间为10年 3600l* 1000*24*365*10
Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10);
// 生成URL
URL url = ossClient.generatePresignedUrl(ossBucketName, key, expiration);
if (url != null) {
return url.toString();
}
return null;
}
测试结果:
上传文件为 PDF 类型:
上传文件为图片类型: