场景
最近跟进的 温氏拍照检测上报管理系统 项目中拥有一个需求是 自动将做完实验后的实验板拍照检测后将结果输出为PDF文件并且加上水印上传 防止实验作假。因此系统需要一个功能来实现 将实验结果动态生成为PDF文件并且输出 。
解决方案
1.制作PDF模板
(1)在Word内制作模板
因为PDF常用的软件不支持编辑,所以先用Word工具,如WPS或者Office新建一个空白Word文档,里面制作出自己想要的样式。
(2)将Word转换为PDF保存起来
(2)利用 Adobe Acrobat DC 编辑保存好的PDF文件
Adobe Acrobat DC 是一个专业的PDF文件处理软件,不是免费的,但可以免费试用7天。
点击准备表单按钮。
详细配置所需要的数据源(注意,配置的数据源字段必须与Java中的实体类对象的字段名保持一致),若要显示图像,在要显示图像的区域,点击鼠标右键,选择文本域,设定好图像的显示位置,并指定数据源字段。
配置完成后保存作为模板使用。
2.代码实现
(1)创建一个 SpringBoot 项目,导入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
</dependency>
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
(2)编写Swagger配置文件
在项目中创建 resource 目录,创建 bootstrap.yml 文件,输入swagger配置信息,具体配置信息根据个人项目而定,模板如下。
swagger:
title: "PDF生成系统"
description: "PDF生成"
base-package: cn.loungexi
enabled: true
version: 1.0.0
在 SpringBoot 启动类上面添加 Swagger 启动注解。
@EnableSwagger2Doc
(2)编写pojo层代码
import lombok.Data;
@Data
public class Result {
private String experimenter;
private String experimentalResult;
private String experimentalResultImage;
}
(3)编写service层代码
import cn.loungexi.pojo.Result;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
public interface OutputPdfService {
void generatorPdf(Result result, HttpServletResponse response) throws UnsupportedEncodingException;
}
import cn.loungexi.pojo.Result;
import cn.loungexi.service.OutputPdfService;
import com.itextpdf.text.Document;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class OutputPdfServiceImpl implements OutputPdfService {
@Override
public void generatorPdf(Result result, HttpServletResponse response) throws UnsupportedEncodingException {
// 模板名称
String templateName = "检测结果打印模板.pdf";
String path = "";
// 获取操作系统名称,根据系统名称确定模板存放的路径
String systemName = System.getProperty("os.name");
if (systemName.toUpperCase().startsWith("WIN")) {
path = "D:/桌面/JAVA/JavaOutputPDF/";//改成自己的模板路径
} else {
path = "/usr/local/JAVA/JavaOutputPDF/";//改成自己的模板路径
}
// 生成导出PDF的文件名称
String fileName = result.getExperimenter() + "实验结果.pdf";
fileName = URLEncoder.encode(fileName, "UTF-8");
// 设置响应头
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
OutputStream out = null;
ByteArrayOutputStream bos = null;
PdfStamper stamper = null;
PdfReader reader = null;
try {
// 输出到浏览器端
out = response.getOutputStream();
// 读取PDF模板表单
reader = new PdfReader(path + templateName);
// 字节数组流,用来缓存文件流
bos = new ByteArrayOutputStream();
// 根据模板表单生成一个新的PDF
stamper = new PdfStamper(reader, bos);
// 获取新生成的PDF表单
AcroFields form = stamper.getAcroFields();
// 给表单生成中文字体,这里采用系统字体,不设置的话,中文显示会有问题
BaseFont font = BaseFont.createFont("C:/WINDOWS/Fonts/SIMSUN.TTC,1", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
form.addSubstitutionFont(font);
// 装配数据
Map<String, Object> data = new HashMap<>(15);
data.put("experimenter", result.getExperimenter());
data.put("experimentalResult", result.getExperimentalResult());
data.put("experimentalResultImage", result.getExperimentalResultImage());
// 遍历data,给pdf表单赋值
for (String key : data.keySet()) {
// 图片要单独处理
if ("experimentalResultImage".equals(key)) {
int pageNo = form.getFieldPositions(key).get(0).page;
Rectangle signRect = form.getFieldPositions(key).get(0).position;
float x = signRect.getLeft();
float y = signRect.getBottom();
String resultImage = data.get(key).toString();
//根据路径或Url读取图片
Image image = Image.getInstance(resultImage);
//获取图片页面
PdfContentByte under = stamper.getOverContent(pageNo);
//图片大小自适应
image.scaleToFit(signRect.getWidth(), signRect.getHeight());
//添加图片
image.setAbsolutePosition(x, y);
under.addImage(image);
}
// 设置普通文本数据
else {
form.setField(key, data.get(key).toString());
}
}
// 表明该PDF不可修改
stamper.setFormFlattening(true);
// 关闭资源
stamper.close();
// 将ByteArray字节数组中的流输出到out中(即输出到浏览器)
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
log.info("*****************************PDF导出成功*********************************");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
if (reader != null) {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(4)编写controller层代码
import cn.loungexi.pojo.Result;
import cn.loungexi.service.OutputPdfService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
@RestController
@Api(value = "PDF相关操作接口",tags = "PDF相关操作接口")
@RequestMapping("/pdf")
public class PdfController {
@Autowired
private OutputPdfService outputPdfService;
@ApiOperation(value = "导出PDF")
@PostMapping("/outresult")
public void generatorPdf(@RequestBody Result result, HttpServletResponse response){
try {
outputPdfService.generatorPdf(result, response);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
3.测试接口
利用 Swagger 请求接口。
SpingBoot 启动后默认端口为8080,利用 http://localhost:8080/swagger-ui.html 进入Swagger 面板。若在配置文件中改动了默认启动端口,则在 url 中改为自己的端口即可。
进入 PDF相关操作接口 。
点击 Try it out 后将要上传的 Json 数据补充完整,注意图片一行需要填写正确的图片路径,否则会报 400 错误,尽量将 "\" 换成 "/" 填写路径,填写完毕后点击 Execute 。
点击 Download file 下载生成的 PDF 文件,浏览器可能响应比较慢,耐心等待。
打开下载的 PDF 文件。
导出正常,显示正常,至此需求完成。
2023.07.06更新:完成项目需求pdf导出的最终版本如下。