Java生成pdf文件或jpg圖片的案例講解
在一些業(yè)務(wù)場(chǎng)景中,需要生成pdf文件或者jpg圖片,有時(shí)候還需要帶上水印。我們可以事先用freemarker定義好html模板,然后把模板轉(zhuǎn)換成pdf或jpg文件。
同時(shí)freemarker模板還支持變量的定義,在使用時(shí)可以填充具體的業(yè)務(wù)數(shù)據(jù)。
1、Maven導(dǎo)包
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <dependencies> <!-- freemarker --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency> <!-- pdf核心包 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.12</version> </dependency> <!-- 適配中文字體 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <!-- html轉(zhuǎn)pdf --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.12</version> </dependency> <!-- pdf轉(zhuǎn)圖片 --> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.5</version> </dependency> </dependencies>
2、接口定義
2.1、請(qǐng)求
@Data
public class GeneratePdfReq {
/**
* 生成pdf文件的絕對(duì)路徑
*/
@NotBlank(message = "生成pdf文件的絕對(duì)路徑不能為空")
@Pattern(regexp = "^.*(\\.pdf|\\.jpg)$", message = "生成的文件必須以.pdf或.jpg結(jié)尾")
private String absolutePath;
/**
* 使用html模板的絕對(duì)路徑
*/
@NotBlank(message = "使用的模板路徑不能為空")
private String templateName;
/**
* 渲染模板的業(yè)務(wù)數(shù)據(jù)
*/
private Object dataModel;
/**
* 水印信息
*/
private WaterMarkInfo waterMarkInfo;
/**
* pdf文件的寬,默認(rèn)A4
*/
private float width = 595;
/**
* pdf文件的高,默認(rèn)A4
*/
private float height = 842;
}
2.2、水印
@Data
public class WaterMarkInfo {
/**
* 如果為null設(shè)置水印時(shí)會(huì)報(bào)錯(cuò)
*/
private String waterMark = "";
/**
* 水印透明度,值越小透明度越高
*/
private float opacity = 0.2F;
/**
* 水印字體,如果亂碼設(shè)置為本地宋體字體:fonts/simsun.ttc,1
*/
private String fontName = "STSong-Light";
/**
* 水印編碼格式,如果亂碼設(shè)置為:BaseFont.IDENTITY_H
*/
private String encoding = "UniGB-UCS2-H";
/**
* 字體大小
*/
private float fontSize = 24;
/**
* 橫坐標(biāo)在頁(yè)面寬度的百分比,左下角為原點(diǎn)
*/
private float x = 50;
/**
* 縱坐標(biāo)在頁(yè)面高度的百分比,左下角為原點(diǎn)
*/
private float y = 40;
/**
* 水印旋轉(zhuǎn)角度
*/
private float rotation = 45;
}
2.3、響應(yīng)
@Data
public class GeneratePdfResp {
/**
* 生成pdf的絕對(duì)路徑
*/
private String absolutePath;
}
3、應(yīng)用代碼
3.1、渲染freemarker模板獲取html網(wǎng)頁(yè)
@Service("freeMarkerService")
@Slf4j
public class FreeMarkerServiceImpl implements FreeMarkerService {
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
/**
* 渲染html后獲取整個(gè)頁(yè)面內(nèi)容
*
* @param templatePath 模板路徑
* @param dataModel 業(yè)務(wù)數(shù)據(jù),一般以map形式傳入
* @return
*/
@Override
public String getHtml(String templatePath, Object dataModel) {
log.info("開始將模板{}渲染為html,業(yè)務(wù)數(shù)據(jù){}", templatePath, JSONUtil.toJsonPrettyStr(dataModel));
Configuration cfg = freeMarkerConfigurer.getConfiguration();
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // freemaker異常時(shí)仍舊拋出,統(tǒng)一異常處理
cfg.setClassicCompatible(true);// 不需要對(duì)null值預(yù)處理,否則需要在模板取值時(shí)判斷是否存在,不然報(bào)錯(cuò)
StringWriter stringWriter = new StringWriter();
try {
// 設(shè)置模板所在目錄,絕對(duì)路徑方式,不打進(jìn)jar包
// cfg.setDirectoryForTemplateLoading(new File(templatePath).getParentFile());
// Template temp = cfg.getTemplate(new File(templatePath).getName());
// 相對(duì)路徑設(shè)置模板所在目錄,模板打進(jìn)jar包,默認(rèn)就是resources目錄下的/templates目錄。
cfg.setClassForTemplateLoading(this.getClass(), "/templates");
Template temp = cfg.getTemplate(templatePath);
temp.process(dataModel, stringWriter);
} catch (Exception e) {
log.error(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL.getDesc(), e);
throw new PdfBizException(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL);
}
return stringWriter.toString();
}
}
3.2、將html網(wǎng)頁(yè)轉(zhuǎn)pdf,并添加水印
@Service("pdfService")
@Slf4j
public class PdfServiceImpl implements PdfService {
public static final String FONT_PATH = "fonts/simsun.ttc,1";
@Autowired
private WaterMarkerService waterMarkerService;
/**
* html頁(yè)面內(nèi)容轉(zhuǎn)pdf,并給每頁(yè)附上水印
*
* @param html html頁(yè)面內(nèi)容
* @param width pdf的寬
* @param height pdf的高
* @param waterMarkInfo 水印信息
* @return
*/
@Override
public byte[] html2Pdf(String html, float width, float height, WaterMarkInfo waterMarkInfo) {
log.info("=================開始將html轉(zhuǎn)換為pdf=================");
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.html2Pdf(html, width, height, out);
byte[] bytes = out.toByteArray();
// 設(shè)置水印
if (waterMarkInfo != null) {
bytes = waterMarkerService.addWaterMarker(bytes, waterMarkInfo);
}
return bytes;
}
/**
* html轉(zhuǎn)pdf
*
* @param html html頁(yè)面內(nèi)容
* @param width pdf的寬
* @param height pdf的高
* @param out 輸出流,pdf文件用此流輸出,需要pdf文檔關(guān)閉后流中才會(huì)有數(shù)據(jù)
*/
@Override
@SneakyThrows
public void html2Pdf(String html, float width, float height, OutputStream out) {
@Cleanup Document document = new Document(new RectangleReadOnly(width, height)); // 默認(rèn)A4縱向
// 這里需要關(guān)閉document才能讓生成的pdf字節(jié)數(shù)據(jù)刷到輸出流中
PdfWriter writer = PdfWriter.getInstance(document, out); // 關(guān)閉可能導(dǎo)致生成的pdf顯示異常(Chrome)
document.open();
// 設(shè)置字體,這里統(tǒng)一用simsun.ttc即宋體
XMLWorkerFontProvider asianFontProvider = new XMLWorkerFontProvider() {
@Override
public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color, boolean cached) {
Font font;
try {
font = new Font(BaseFont.createFont(FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED));
} catch (Exception e) {
log.error(PdfErrorCode.SET_PDF_FONT_FAIL.getDesc(), e);
throw new PdfBizException(PdfErrorCode.SET_PDF_FONT_FAIL);
}
font.setStyle(style);
font.setColor(color);
if (size > 0) {
font.setSize(size);
}
return font;
}
};
// 生成pdf
try {
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"), asianFontProvider);
// 如果系統(tǒng)已經(jīng)裝有simsun.ttc字體,則不需要單獨(dú)設(shè)置字體也不需要itext-asian jar包
// XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"));
} catch (RuntimeWorkerException e) {
log.error(PdfErrorCode.HTML_CONVERT2PDF_FAIL.getDesc(), e);
throw new PdfBizException(PdfErrorCode.HTML_CONVERT2PDF_FAIL);
}
}
}
添加水印實(shí)現(xiàn)類
@Service("waterMarkerService")
@Slf4j
public class WaterMarkerServiceImpl implements WaterMarkerService {
/**
* 給pdf文件每頁(yè)添加水印
*
* @param source pdf文件的字節(jié)數(shù)組形式
* @param waterMarkInfo 水印信息
* @return
*/
@Override
public byte[] addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo) {
log.info("開始設(shè)置水印數(shù)據(jù){}", JSONUtil.toJsonPrettyStr(waterMarkInfo));
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.addWaterMarker(source, waterMarkInfo, out);
return out.toByteArray();
}
/**
* 給pdf文件每頁(yè)添加水印
*
* @param source pdf文件的字節(jié)數(shù)組形式
* @param waterMarkInfo 水印信息
* @param out 輸出流,pdf文件用此流輸出,需要pdf文檔關(guān)閉后流中才會(huì)有數(shù)據(jù)
*/
@Override
@SneakyThrows
public void addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo, OutputStream out) {
@Cleanup PdfReader reader = new PdfReader(source);
// 這里需要關(guān)閉PdfStamper才能讓生成的pdf字節(jié)數(shù)據(jù)刷到輸出流中
@Cleanup PdfStamper pdfStamper = new PdfStamper(reader, out);
BaseFont font = BaseFont.createFont(waterMarkInfo.getFontName(), waterMarkInfo.getEncoding(), BaseFont.EMBEDDED);
PdfGState gs = new PdfGState();
gs.setFillOpacity(waterMarkInfo.getOpacity());
// 給每頁(yè)pdf生成水印
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
PdfContentByte waterMarker = pdfStamper.getUnderContent(i);
waterMarker.beginText();
// 設(shè)置水印透明度
waterMarker.setGState(gs);
// 設(shè)置水印字體和大小
waterMarker.setFontAndSize(font, waterMarkInfo.getFontSize());
// 設(shè)置水印位置、內(nèi)容、旋轉(zhuǎn)角度
float X = reader.getPageSize(i).getWidth() * waterMarkInfo.getX() / 100;
float Y = reader.getPageSize(i).getHeight() * waterMarkInfo.getY() / 100;
waterMarker.showTextAligned(Element.ALIGN_CENTER, waterMarkInfo.getWaterMark(), X, Y, waterMarkInfo.getRotation());
// 設(shè)置水印顏色
waterMarker.setColorFill(BaseColor.GRAY);
waterMarker.endText();
}
}
}
3.3、整合實(shí)現(xiàn)
@Slf4j
@Service("generatePdfService")
public class GeneratePdfServiceImpl implements RestService {
@Autowired
private FreeMarkerService freeMarkerService;
@Autowired
private PdfService pdfService;
@Override
@SneakyThrows
public GeneratePdfResp service(GeneratePdfReq generatePdfReq) {
log.info("開始生成pdf文件,請(qǐng)求報(bào)文:{}", JSONUtil.toJsonPrettyStr(generatePdfReq));
/*
1.根據(jù)freemarker模板填充業(yè)務(wù)數(shù)據(jù)獲取完整的html字符串
*/
String html = freeMarkerService.getHtml(generatePdfReq.getTemplateName(), generatePdfReq.getDataModel());
/*
2.生成pdf文件(內(nèi)存)
*/
byte[] bytes = pdfService.html2Pdf(html, generatePdfReq.getWidth(), generatePdfReq.getHeight(), generatePdfReq.getWaterMarkInfo());
/*
3.本地保存pdf文件
*/
File targetFile = new File(generatePdfReq.getAbsolutePath());
// 上級(jí)目錄不存在則創(chuàng)建
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
// 根據(jù)不同文件名后綴生成對(duì)應(yīng)文件
if (generatePdfReq.getAbsolutePath().endsWith("pdf")) {
FileUtils.writeByteArrayToFile(targetFile, bytes);
} else {
@Cleanup PDDocument document = PDDocument.load(bytes);
PDFRenderer renderer = new PDFRenderer(document);
BufferedImage bufferedImage = renderer.renderImageWithDPI(0, 150);// 只打第一頁(yè),dpi越大圖片越高清也越耗時(shí)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "jpg", baos);
FileUtils.writeByteArrayToFile(targetFile, baos.toByteArray());
}
log.info("文件本地保存完成,文件路徑:[{}]", targetFile.getAbsolutePath());
/*
4.組織返回
*/
GeneratePdfResp generatePdfResp = new GeneratePdfResp();
generatePdfResp.setAbsolutePath(targetFile.getAbsolutePath());
return generatePdfResp;
}
}
3.4、controller
@Slf4j
@RestController
public class PdfController {
@Autowired
private RestService generatePdfService;
@PostMapping(value = "/html2Pdf")
public GeneratePdfResp html2Pdf(@RequestBody @Validated GeneratePdfReq req) {
GeneratePdfResp resp = generatePdfService.service(req);
return resp;
}
}
4、應(yīng)用
4.1、freemarker模板(html模板)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<style>
body {
font-family: SimSun
}
</style>
<title>html模板</title>
</head>
<body>
<div>
<p style="margin:0pt; orphans:0; text-align:center; widows:0">
<span style="font-family:SimSun; font-size:16pt">html模板</span><br/>
</p>
<p>姓名:${name}</p>
<p>證件號(hào)碼:${cardNo}</p>
<p>日期:${date}</p>
</div>
</body>
</html>
4.2、接口調(diào)用生成pdf


5、說明
1、根據(jù)參數(shù)后綴名可以生成pdf或jpg文件,生成的pdf文件默認(rèn)為A4大小,也可以通過請(qǐng)求參數(shù)設(shè)置大小。
2、pdf文件會(huì)根據(jù)html模板內(nèi)容大小自動(dòng)分頁(yè)。
3、如果生成圖片,多頁(yè)不會(huì)生成多張圖片,可以把高度設(shè)置大一些,最后會(huì)生成長(zhǎng)圖。
4、水印每頁(yè)都會(huì)自動(dòng)添加。
5、為了提高代碼的復(fù)用性和可維護(hù)性,工程內(nèi)渲染html模板、生成pdf文件、添加水印都有單獨(dú)的接口實(shí)現(xiàn)。
代碼地址
github:https://github.com/senlinmu1008/spring-boot/tree/master/html2pdf
gitee:https://gitee.com/ppbin/spring-boot/tree/master/html2pdf
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
springboot 自定義異常并捕獲異常返給前端的實(shí)現(xiàn)代碼
在開發(fā)中,如果用try catch的方式,每個(gè)方法都需要單獨(dú)實(shí)現(xiàn),為了方便分類異常,返回給前端,采用了@ControllerAdvice注解和繼承了RuntimeException的方式來實(shí)現(xiàn),具體實(shí)現(xiàn)內(nèi)容跟隨小編一起看看吧2021-11-11
IDEA Maven Mybatis generator 自動(dòng)生成代碼(實(shí)例講解)
下面小編就為大家分享一篇IDEA Maven Mybatis generator 自動(dòng)生成代碼的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12
SpringBoot項(xiàng)目中只執(zhí)行一次的任務(wù)寫法實(shí)現(xiàn)
有時(shí)候我們需要進(jìn)行初始化工作,就說明只要進(jìn)行一次的工作,本文主要介紹了SpringBoot項(xiàng)目中只執(zhí)行一次的任務(wù)寫法實(shí)現(xiàn),感興趣的可以了解一下2023-12-12
java中數(shù)組list map三者之間的互轉(zhuǎn)介紹
java中 數(shù)組 list map之間的互轉(zhuǎn)一張圖清晰呈現(xiàn)并附有代碼,不懂的朋友可以參考下2013-10-10
SpringBoot中處理的轉(zhuǎn)發(fā)與重定向方式
這篇文章主要介紹了SpringBoot中處理的轉(zhuǎn)發(fā)與重定向方式,分別就轉(zhuǎn)發(fā)和重定向做了概念解說,結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11
淺談Spring Cloud中的API網(wǎng)關(guān)服務(wù)Zuul
這篇文章主要介紹了淺談Spring Cloud中的API網(wǎng)關(guān)服務(wù)Zuul,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10

