依赖com.itextpdf
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13</version>
</dependency>
第一步:将指定的html内容转化成pdf文档
/**
* 将指定的html内容转化成pdf文档之后,写入到指定的输出流.
*/
public static void write(String htmlContent, OutputStream os) {
if (htmlContent == null || htmlContent.length() == 0) {
return;
}
if (os == null) {
return;
}
htmlContent = getIntactHtml(htmlContent);
doWrite(htmlContent, os);
}
/**
* html完整内容的前缀标识
*/
private static final String INTACT_FLAG = "<html>";
/**
* html模板,当待转换的html只是片断时,需将其插入到模板的body内.
*/
private static final String TEMPLATE_HTML = "<html><head><style>h2,h3,h4{text-align: center}</style></head><body style=\"font-family:微软雅黑;\">${content}</body></html>";
/**
* 根据提供的html内容,获取完整的html内容.
*
* @param htmlContent html内容
* @return 完整的html内容
*/
private static String getIntactHtml(String htmlContent) {
boolean intact = htmlContent.trim().toLowerCase().startsWith(INTACT_FLAG);
if (!intact) {
htmlContent = TEMPLATE_HTML.replaceFirst("\\$\\{content}", htmlContent);
}
return htmlContent;
}
// pdf密码
private static final byte[] PASSWORD_BYTES = "password".getBytes();
/**
* 实施写操作.
*
* @param htmlContent html内容
* @param os outputStream
*/
private static void doWrite(String htmlContent, OutputStream os) {
InputStream is = new ByteArrayInputStream(htmlContent.getBytes(Charset.forName("UTF-8")));
Document document = new Document();
PdfWriter writer = null;
try {
writer = PdfWriter.getInstance(document, os);
// 不可打印
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_PRINTING, STANDARD_ENCRYPTION_40);
// 不可更改
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_MODIFY_CONTENTS, STANDARD_ENCRYPTION_40);
// 不可复制
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_COPY, STANDARD_ENCRYPTION_40);
// 不可注释
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_MODIFY_ANNOTATIONS, STANDARD_ENCRYPTION_40);
// 不可填写表单域
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_FILL_IN, STANDARD_ENCRYPTION_40);
// 不可页面提取
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_SCREENREADERS, STANDARD_ENCRYPTION_40);
// 不可文档组合
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_ASSEMBLY, STANDARD_ENCRYPTION_40);
// 不可签名
writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_DEGRADED_PRINTING, STANDARD_ENCRYPTION_40);
// 标题
document.addTitle("title");
// 作者
document.addAuthor("author");
// 关键字
document.addKeywords("keywords");
// 主题
document.addSubject("subject");
document.open();
// html设置<body style="font-family:微软雅黑;">
// 或加载字体
/*XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider() {
@Override
public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) {
//你的字体文件的位置
//这里把所有字体都设置为宋体了,可以根据fontname的值设置字体
String yaHeiFontName = "msyh.ttc";
//如果是ttc需要这一行,ttf不需要
yaHeiFontName += ",1";
Font font = null;
try {
font = new Font(BaseFont.createFont(yaHeiFontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED));
font.setStyle(style);
font.setColor(color);
if (size > 0) {
font.setSize(size);
}
} catch (DocumentException | IOException e) {
}
return font;
}
};*/
XMLWorkerHelper.getInstance().parseXHtml(writer, document, is, Charset.forName(CommonConstants.DEFAULT_ENCODING));
} catch (DocumentException | IOException e) {
throw new BizException(10061);
} finally {
IOUtils.closeQuietly(is);
if (null != writer) {
writer.flush();
}
document.close();
}
}
第二步:签名
private static final String KEYSTORE = "keystore.p12";
// keystore密码
private static final char[] PASSWORD = "xxxyyy".toCharArray();
/**
* @param src 需要签章的pdf文件路径
* @param dest 签完章的pdf文件路径
* chain 证书链
* pk 签名私钥
* digestAlgorithm 摘要算法名称,例如SHA-1
* provider 密钥算法提供者,可以为null
* cryptoStandard 数字签名格式,itext有2种
* reason 签名的原因,显示在pdf签名属性中,随便填
* location 签名的地点,显示在pdf签名属性中,随便填
* @throws GeneralSecurityException
* @throws IOException
* @throws DocumentException
*/
public static void sign(String src, String dest) throws GeneralSecurityException, IOException, DocumentException {
// Creating the reader and the stamper,开始pdf reader
PdfReader reader = new PdfReader(src, PASSWORD_BYTES);
// 目标文件输出流
FileOutputStream os = new FileOutputStream(dest);
// 创建签章工具PdfStamper ,最后一个boolean参数
// false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
// true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
// 获取数字签章属性对象,设定数字签章的属性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
String reason = "reason";
String location = "location";
appearance.setReason(reason);
appearance.setLocation(location);
// 设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样
// 签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
// 四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
//appearance.setVisibleSignature(new Rectangle(450, 747, 550, 847), 1, "sig1");
// 读取图章图片,这个image是itext包的image
//Image image = Image.getInstance(new ClassPathResource(SIGNATURE).getFile().getAbsolutePath());
//appearance.setSignatureGraphic(image);
appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
// 设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
// 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
// 摘要算法
ExternalDigest digest = new BouncyCastleDigest();
// 签名算法
String provider = null;
String digestAlgorithm = DigestAlgorithms.SHA1;
KeyStore ks = KeyStore.getInstance("PKCS12");
InputStream inputStream = new ClassPathResource(KEYSTORE).getInputStream();
ks.load(inputStream, PASSWORD);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, provider);
// 调用itext签名方法完成pdf签章
MakeSignature.CryptoStandard cryptoStandard = MakeSignature.CryptoStandard.CMS;
Certificate[] chain = ks.getCertificateChain(alias);
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, cryptoStandard);
// Closes the document. No more content can be written after the document is closed.
stamper.close();
inputStream.close();
}
集成示例
@RequestMapping(path = "pdf-get", method = RequestMethod.GET)
public void getPdfContract(HttpServletResponse response) throws IOException {
String fileName = "contract.pdf";
response.setCharacterEncoding("UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
response.flushBuffer();
File file = new File(fileName);
// 需要填充的数据
Object data = null;
ItextpdfUtils.generatePDFFromHtmlContent(template.getContent(), data, file);
FileInputStream fileInputStream = new FileInputStream(file);
byte b[] = new byte[1024];
int j;
ServletOutputStream os = response.getOutputStream();
while ((j = fileInputStream.read(b)) != -1) {
os.write(b, 0, j);
os.flush();
}
fileInputStream.close();
os.close();
FileUtils.deleteQuietly(file);
}
/**
* 把html形式的合同模板转换为PDF
*
* @param content 合同模板内容-未设值
* @param object 数据(对象/Map)
*/
public static void generatePDFFromHtmlContent(String content, Object object, File file) {
File copyFile = new File("copy-" + file.getName());
FileOutputStream os = null;
try {
if (!copyFile.exists()) {
boolean result = copyFile.createNewFile();
if (result) {
LOG.info("The named copy file does not exist and was successfully created");
} else {
LOG.info("The named copy file already exists");
}
}
// 如需要,可以使用freemarker对html文本进行数据填充,FreemarkerUtils可自行编写
// content = FreemarkerUtils.parseString(content, object);
os = new FileOutputStream(copyFile);
// HTML转PDF
ItextpdfUtils.write(content, os);
// 数字签名
ItextpdfUtils.sign(copyFile.getAbsolutePath(), file.getAbsolutePath());
} catch (IOException | GeneralSecurityException | DocumentException e) {
LOG.error(e.getMessage());
} finally {
IOUtils.closeQuietly(os);
FileUtils.deleteQuietly(copyFile);
}
}