在有些场景下我们可能需要根据指定的模板来生成 PDF,比如说合同、收据、发票等等。因为 PDF 是不可编辑的,所以用代码直接对 PDF 文件进行修改是很不方便的,这里我是通过 itext
和 Adobe Acrobat
来实现的,以下就是具体实现方法。
Adobe Acrobat
是由 Adobe
公司开发的一款 PDF
(Portable Document Format,便携式文档格式)编辑软件。借助它,你可以以 PDF
格式制作和保存文档 ,以便于浏览和打印,或使用更高级的功能。
说白一点就是 Adobe Acrobat
可以让你的 PDF
文件编程可编辑文件,PDF 文件可编辑的话,使用代码去修改就会方便很多。
adobe 中文官网:https://www.adobe.com/cn/
Adobe Acrobat 中文官网:https://www.adobe.com/cn/acrobat.html
如果你之前没有使用过这个软件,可以在上面我提供的官网里面去下载
PS:不过这个软件是收费的,但是可以注册一个账号申请免费试用,然后按照提示去下载该软件,大概有一周左右的使用期限
下载完,打开该软件大概是这个样子的
软件有了之后就是准备模板,这里我以 劳动合同模板
为例:
以上一个为 PDF 的劳动合同模板,使用 Adobe Acrobat
软件中的 准备表单
工具将该 .pdf
文件导入进来
进入到 工具
栏,选择 准备表单
,点击 打开
选择模板文件
再点击 开始
进来之后就可以对 PDF
文件进行编辑,那些需要填入的值的地方,可以 添加文本域
,之后通过代码设置的值就会直接填入到文本域当中
双击文本域,可编辑文本域的信息
其中最重要的就是 名称
,如果想要在这个位置上赋值的话,就需要绑定该名称,类似于给 Map
赋值需要知道 key
一样,虽然说在添加文本域的时候就会生成一个名称,但是还是建议最好自己取一个见名知意的名称。
模板各文本域名称和备注如下:
{
"companyName":"用人单位名称",
"legalPerson":"法人",
"companyAddress":"用人单位地址",
"companyPhone":"用人单位联系方式",
"term":"合同期限",
"startYear":"起始时间-年",
"startMonth":"起始时间-月",
"startDay":"起始时间-日",
"endYear":"终止时间-年",
"endMonth":"终止时间-月",
"endDay":"终止时间-日",
"probationPeriodStartYear":"试用期起始时间-年",
"probationPeriodStartMonth":"试用期起始时间-月",
"probationPeriodStartDay":"试用期起始时间-日",
"probationPeriodEndYear":"试用期终止时间-年",
"probationPeriodEndMonth":"试用期终止时间-月",
"probationPeriodEndDay":"试用期终止时间-日",
"probationPeriodTerm":"试用期期限",
"post":"岗位",
"salary":"薪资",
"probationPeriodSalary":"试用期薪资",
"salaryGrant":"薪资发放时间"
}
同时还能够设置其它一些属性,比如说字体大小、字体、对齐方式等等,这里我是把字体大小都设置为 10,字体设置为宋体,居中对齐
然后保存即可,再打开该 PDF 文件时,该文件就已成为可编辑文件了,模板到此准备完成
导入 itext
相关的依赖:
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itextpdfartifactId>
<version>5.4.2version>
dependency>
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itext-asianartifactId>
<version>5.2.0version>
dependency>
<dependency>
<groupId>org.apache.pdfboxgroupId>
<artifactId>pdfboxartifactId>
<version>2.0.13version>
dependency>
接口编写:
PdfController.java
import com.mike.server.system.service.PdfService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/test")
@Api(tags = "【PDF-管理】")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@CrossOrigin(origins = "*", methods = {RequestMethod.POST, RequestMethod.GET})
public class PdfController {
private final PdfService pdfService;
@PostMapping(value = "/generate-pdf")
@ApiOperation(value = "生成PDF", produces = "application/octet-stream")
public void generatePdf(@RequestBody Map<String, String> params) {
pdfService.generatePdf(params);
}
}
PdfService.java
import java.util.Map;
public interface PdfService {
void generatePdf(Map<String, String> params);
}
PdfServiceImpl.java
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.mike.common.core.utils.ServletUtils;
import com.mike.server.system.service.PdfService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PdfServiceImpl implements PdfService {
private final HttpServletResponse response;
@Override
@SneakyThrows
public void generatePdf(Map<String, String> params) {
// 读取资源文件夹下的模板
ClassPathResource resource = new ClassPathResource("pdf-template/简单劳动合同模板.pdf");
InputStream inputStream = resource.getInputStream();
/*
* 或者通过 url 从网上下载 pdf 模板文件
*
// 获取文件地址
String urlPath = "模板资源文件链接-url";
// 下载文件
URL url = new URL(urlPath);
URLConnection connection = url.openConnection();
// 设置请求超时时长为 5 秒
connection.setConnectTimeout(5*1000);
// 读取数据
InputStream inputStream = connection.getInputStream();
*/
PdfReader reader = null;
ByteArrayOutputStream bos = null;
try {
reader = new PdfReader(inputStream);
bos = new ByteArrayOutputStream();
PdfStamper pdfStamper = new PdfStamper(reader, bos);
AcroFields acroFields = pdfStamper.getAcroFields();
// 中文字体
BaseFont font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
for (Map.Entry<String, String> param : params.entrySet()) {
// 设置文本域的字体为中文字体
acroFields.setFieldProperty(param.getKey(), "textfont", font,null);
// 将 map 中的值写到 pdf 模板对应的文本域中
acroFields.setField(param.getKey(), param.getValue());
}
// 如果为false那么生成的PDF文件还能编辑,所以一定要设为true
pdfStamper.setFormFlattening(true);
pdfStamper.close();
// 返回文件
ServletUtils.writeAttachment(response, "劳动合同.pdf", bos.toByteArray());
} catch (IOException | DocumentException e) {
e.printStackTrace();
} finally {
try {
assert bos != null;
bos.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ServletUtils.java
工具类部分代码:
import cn.hutool.core.io.IoUtil;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
/**
* 客户端工具类
*
* @author system
*/
public class ServletUtils {
...
/**
* 返回附件
*
* @param response 响应
* @param filename 文件名
* @param content 附件内容
*/
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
// 设置 header 和 contentType
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
// 输出附件
IoUtil.write(response.getOutputStream(), false, content);
}
...
}
请求接口:
{
"companyName": "xxxx科技有限公司",
"legalPerson": "米大傻",
"companyAddress": "广州xxxxxxx",
"companyPhone": "18274563214",
"term": "3",
"startYear": "2023",
"startMonth": "9",
"startDay": "15",
"endYear": "2026",
"endMonth": "9",
"endDay": "15",
"probationPeriodStartYear": "2023",
"probationPeriodStartMonth": "9",
"probationPeriodStartDay": "15",
"probationPeriodEndYear": "2023",
"probationPeriodEndMonth": "11",
"probationPeriodEndDay": "15",
"probationPeriodTerm": "2",
"post": "JAVA工程师",
"salary": "23000",
"probationPeriodSalary": "18000",
"salaryGrant": "15"
}
效果:
------------------项目下载------------------
链接:百度网盘
提取码:ihyo
-------------------------------------------
个人觉得代码实现起来不是很难,关键是要知道如何使用 Adobe Acrobat
工具设置 PDF 模板以及 itext
的一些 API 的使用,以后有时间我会出一篇关于 iText
的博客,主要介绍 iText
在日常开发中的主要应用。
itextpdf-接口文档:https://api.itextpdf.com/iText5/java/5.5.9/
itext 生成 PDF(一):https://blog.csdn.net/lcczpp/article/details/125424395
为何选择iText?java PDF开源库选择与iText发展历史:https://zhuanlan.zhihu.com/p/375700748
使用itext填充静态PDF模板,生成PDF新文件:https://www.cnblogs.com/hunter-space/p/static_pdf.html