本次需求需要将表格不够的位置另起一页展示
在根据数据动态拼接完HTML语句后使用wkhtmltopdf将HTML转为PDF文件
发现本地成功但是部署到测试环境却失效
经排查是由于服务器上版本为12.4版本,本地是12.6版本,再结果多次尝试后发现为12.4版本存在bug,12.5.X版本以上都能正常分页展示
如图 12.4版本:
如图 12.5版本:
代码部分:
@Override
public void exprot(String supervisionLogId) throws BussinesException {
String htmlString = "此处拼接HTML代码";
WkhtmltopdfUtil.start(htmlString);
}
public static void start(String content) {
final String path = CreateHtmlFileUtil.getHtmlPath(content, null);
final File file = new File(path);
final String pdfPath = file.getParentFile().toString() + File.separator + System.currentTimeMillis() + ".pdf";
final String commandPath = "";
WkhtmltopdfUtil.convert(null, path, pdfPath, commandPath);
file.getAbsoluteFile().delete();
}
public static boolean convert(final String headerHtmlPath, final String htmlPath, String pdfPath, String commandPath) {
if (System.getProperty("os.name").contains("Windows")) {
// commandPath 填写wkhtmltopdf的exe地址
commandPath = "D:\\wkhtmltopdf\\bin\\wkhtmltopdf.exe";
// pdfPath 填写你需要存放导出pdf的文件地址及名称
pdfPath = "D:\\htmlDir\\test.pdf";
}
return convert2pdf(commandPath, headerHtmlPath, htmlPath, pdfPath);
}
/**
* HTML文件转为PDF文件
*
* @param commandPath wkhtmltopdf命令完整目录
* @param headerHtmlPath 页眉html文件(其源码必须是以<!DOCTYPE html>打头的html字符串)
* @param htmlPath html文件路径(比如:/app/ifs/contract.html,可以是本地或网络完整路径,本地文件则需含文件名和后缀)
* @param pdfPath pdf存储路径(比如:/app/ifs/contract.pdf,包含文件名和后缀的完整路径)
* @return 转换成功或失败
*/
public static boolean convert2pdf(final String commandPath, final String headerHtmlPath, final String htmlPath, final String pdfPath) {
// PDF存储目录不存在,则新建
final File parent = new File(pdfPath).getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
// 组装命令
final StringBuilder cmd = new StringBuilder();
cmd.append(commandPath);
cmd.append(" --footer-center 第[page]页/共[topage]页");
cmd.append(" --margin-top 30mm");
cmd.append(" --margin-bottom 20mm");
cmd.append(" --footer-spacing 5");
cmd.append(" --header-spacing 5");
if (StringUtils.isNotBlank(headerHtmlPath)) {
cmd.append(" --header-html ").append(headerHtmlPath);
}
final Process process;
try {
// 执行命令(根据源HTML为网络路径或本地路径,来决定是否设置工作目录)
final String workPath = FilenameUtils.getFullPath(htmlPath);
if (!htmlPath.startsWith("/") || StringUtils.isBlank(workPath)) {
cmd.append(" ").append(htmlPath);
cmd.append(" ").append(pdfPath);
process = Runtime.getRuntime().exec(cmd.toString());
} else {
cmd.append(" ").append(FilenameUtils.getName(htmlPath));
cmd.append(" ").append(pdfPath);
process = Runtime.getRuntime().exec(cmd.toString(), null, new File(workPath));
}
final ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(new ClearBufferThread(process.getErrorStream()));
threadPool.execute(new ClearBufferThread(process.getInputStream()));
process.waitFor();
threadPool.shutdown();
} catch (final Exception e) {
// LogUtil.getLogger().error("HTML转PDF异常", e);
return false;
}
return true;
}
完整的WkhtmltopdfUtil文件
package yeshen.dto.SupervisionLog;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author: YeShen
* @Date: 2023/9/26 9:46
* @Description: wkhtmltopdf工具类
*/
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveClassLength"})
public class WkhtmltopdfUtil {
private static final Logger logger = LoggerFactory.getLogger(CreateHtmlFileUtil.class);
public static boolean convert(final String headerHtmlPath, final String htmlPath, String pdfPath, String commandPath) {
if (System.getProperty("os.name").contains("Windows")) {
commandPath = "D:\\wkhtmltopdf\\bin\\wkhtmltopdf.exe";
pdfPath = "D:\\htmlDir\\test.pdf";
}
return convert2pdf(commandPath, headerHtmlPath, htmlPath, pdfPath);
}
/**
* HTML文件转为PDF文件
*
* @param commandPath wkhtmltopdf命令完整目录
* @param headerHtmlPath 页眉html文件(其源码必须是以<!DOCTYPE html>打头的html字符串)
* @param htmlPath html文件路径(比如:/app/ifs/contract.html,可以是本地或网络完整路径,本地文件则需含文件名和后缀)
* @param pdfPath pdf存储路径(比如:/app/ifs/contract.pdf,包含文件名和后缀的完整路径)
* @return 转换成功或失败
*/
public static boolean convert2pdf(final String commandPath, final String headerHtmlPath, final String htmlPath, final String pdfPath) {
// PDF存储目录不存在,则新建
final File parent = new File(pdfPath).getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
// 组装命令
final StringBuilder cmd = new StringBuilder();
cmd.append(commandPath);
cmd.append(" --footer-center 第[page]页/共[topage]页");
cmd.append(" --margin-top 30mm");
cmd.append(" --margin-bottom 20mm");
cmd.append(" --footer-spacing 5");
cmd.append(" --header-spacing 5");
if (StringUtils.isNotBlank(headerHtmlPath)) {
cmd.append(" --header-html ").append(headerHtmlPath);
}
final Process process;
try {
// 执行命令(根据源HTML为网络路径或本地路径,来决定是否设置工作目录)
final String workPath = FilenameUtils.getFullPath(htmlPath);
if (!htmlPath.startsWith("/") || StringUtils.isBlank(workPath)) {
cmd.append(" ").append(htmlPath);
cmd.append(" ").append(pdfPath);
process = Runtime.getRuntime().exec(cmd.toString());
} else {
cmd.append(" ").append(FilenameUtils.getName(htmlPath));
cmd.append(" ").append(pdfPath);
process = Runtime.getRuntime().exec(cmd.toString(), null, new File(workPath));
}
final ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(new ClearBufferThread(process.getErrorStream()));
threadPool.execute(new ClearBufferThread(process.getInputStream()));
process.waitFor();
threadPool.shutdown();
} catch (final Exception e) {
// LogUtil.getLogger().error("HTML转PDF异常", e);
return false;
}
return true;
}
public static boolean convert2pdf2(final String commandPath, final String headerHtmlPath, final String htmlPath, final String pdfPath) {
// PDF存储目录不存在,则新建
final File parent = new File(pdfPath).getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
// 组装命令
final StringBuilder cmd = new StringBuilder();
if (StringUtils.isNotBlank(headerHtmlPath)) {
cmd.append(" --header-html ").append(headerHtmlPath);
}
final Process process;
try {
// 执行命令(根据源HTML为网络路径或本地路径,来决定是否设置工作目录)
final String workPath = FilenameUtils.getFullPath(htmlPath);
if (!htmlPath.startsWith("/") || StringUtils.isBlank(workPath)) {
cmd.append(" ").append(htmlPath);
cmd.append(" ").append(pdfPath);
process = Runtime.getRuntime().exec(cmd.toString());
} else {
cmd.append(" ").append(FilenameUtils.getName(htmlPath));
cmd.append(" ").append(pdfPath);
process = Runtime.getRuntime().exec(cmd.toString(), null, new File(workPath));
}
final ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(new ClearBufferThread(process.getErrorStream()));
threadPool.execute(new ClearBufferThread(process.getInputStream()));
process.waitFor();
threadPool.shutdown();
} catch (final Exception e) {
logger.info(e.getMessage() + "html转pdf失败");
return false;
}
return true;
}
/**
* 清理输入流缓存的线程 ------------------------------------------------------------------------------
* jdk-1.6:process.waitFor()不能正常执行,会阻塞,1.7无此问题 所以1.6在接收Process的输入和错误信息时,需要创建另外的线程,否则当前线程会一直等待
*/
static class ClearBufferThread implements Runnable {
private final InputStream is;
ClearBufferThread(final InputStream is) {
this.is = is;
}
@Override
public void run() {
try {
final BufferedReader br = new BufferedReader(new InputStreamReader(this.is));
for (String line; (line = br.readLine()) != null; ) {
logger.info("message =========== {}", line);
}
} catch (final Exception e) {
logger.info(e.getMessage());
}
}
}
public static void start(String content) {
final String path = CreateHtmlFileUtil.getHtmlPath(content, null);
final File file = new File(path);
final String pdfPath = file.getParentFile().toString() + File.separator + System.currentTimeMillis() + ".pdf";
final String commandPath = "";
WkhtmltopdfUtil.convert(null, path, pdfPath, commandPath);
file.getAbsoluteFile().delete();
}
public ByteArrayOutputStream addWaterMark(final InputStream inputFile, final String waterMarkName,
final float opacity, final int fontsize, final int angle, final int heightdensity, final int widthdensity, final String userId, final String date, final String phone, final String createdName, final String companyName) {
PdfReader reader = null;
PdfStamper stamper = null;
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
// final LoginAuthDto loginAuthDto = LoginAuthUtil.getLoginAuthDto();
try {
//int interval = -5;
reader = new PdfReader(inputFile);
stamper = new PdfStamper(reader, baos);
final BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
Rectangle pageRect = null;
final PdfGState gs = new PdfGState();
//这里是透明度设置
gs.setFillOpacity(opacity);
//这里是条纹不透明度
gs.setStrokeOpacity(0.2f);
final int total = reader.getNumberOfPages() + 1;
//System.out.println("合同页数:" + reader.getNumberOfPages());
final JLabel label = new JLabel();
final FontMetrics metrics;
int textH = 0;
int textW = 0;
label.setText(waterMarkName);
metrics = label.getFontMetrics(label.getFont());
textH = metrics.getHeight(); //字符串的高, 只和字体有关
textW = metrics.stringWidth(label.getText()); //字符串的宽
PdfContentByte under;
//这个循环是确保每一张PDF都加上水印
for (int i = 1; i < total; i++) {
pageRect = reader.getPageSizeWithRotation(i);
under = stamper.getOverContent(i); //在内容上方添加水印
//under = stamper.getUnderContent(i); //在内容下方添加水印
under.saveState();
under.setGState(gs);
under.beginText();
under.setColorFill(new BaseColor(4, 0, 0)); //添加文字颜色 不能动态改变 放弃使用
under.setFontAndSize(base, fontsize); //这里是水印字体大小
int time = 1;
for (float height = pageRect.getHeight() - textH * 4; height > textH; height = height - textH * heightdensity) {
int time2 = 1;
for (int width = textW; width < pageRect.getWidth() * 1.5 + textW; width = width + textW * widthdensity) {
if (time % 2 == 1 && time2 % 2 == 1) {
under.showTextAligned(Element.ALIGN_CENTER, "中国移动计划建设管理系统", width - 15, height + 35, 30);
under.showTextAligned(Element.ALIGN_CENTER, this.dataToUpper(new Date()), width - 10, height + 20, 30);
under.showTextAligned(Element.ALIGN_CENTER, companyName, width - 5, height + 5, 30);
under.showTextAligned(Element.ALIGN_CENTER, createdName + " " + phone, width + 5, height - 10, 30);
} else if (time % 2 == 0 && time2 % 2 == 0) {
under.showTextAligned(Element.ALIGN_CENTER, "中国移动计划建设管理系统", width - 15, height + 35, 30);
under.showTextAligned(Element.ALIGN_CENTER, this.dataToUpper(new Date()), width - 10, height + 20, 30);
under.showTextAligned(Element.ALIGN_CENTER, companyName, width - 5, height + 5, 30);
under.showTextAligned(Element.ALIGN_CENTER, createdName + " " + phone, width + 5, height - 10, 30);
}
// rotation:倾斜角度
time2++;
}
time++;
}
under.setFontAndSize(base, 20);
under.showTextAligned(Element.ALIGN_LEFT, userId + " " + date, 0, textH, 0);
//添加水印文字
under.endText();
}
// System.out.println("水印添加成功");
logger.info("水印添加成功");
return baos;
} catch (final Exception e) {
return null;
} finally {
//关闭流
if (stamper != null) {
try {
stamper.close();
} catch (final Exception e) {
logger.info(e.getMessage());
}
}
if (reader != null) {
reader.close();
}
}
}
// 日期转化为大小写
public String dataToUpper(final Date date) {
final Calendar ca = Calendar.getInstance();
ca.setTime(date);
final int year = ca.get(Calendar.YEAR);
final int month = ca.get(Calendar.MONTH) + 1;
final int day = ca.get(Calendar.DAY_OF_MONTH);
return this.numToUpper(year) + "年" + this.monthToUppder(month) + "月" + this.dayToUppder(day) + "日";
}
// 将数字转化为大写
public String numToUpper(final int num) {
//String u[] = {"零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};
final String[] u = {"〇", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
final char[] str = String.valueOf(num).toCharArray();
String rstr = "";
for (int i = 0; i < str.length; i++) {
rstr = rstr + u[Integer.parseInt(str[i] + "")];
}
return rstr;
}
// 月转化为大写
public String monthToUppder(final int month) {
if (month < 10) {
return this.numToUpper(month);
} else if (month == 10) {
return "十";
} else {
return "十" + this.numToUpper(month - 10);
}
}
// 日转化为大写
public String dayToUppder(final int day) {
if (day < 20) {
return this.monthToUppder(day);
} else {
final char[] str = String.valueOf(day).toCharArray();
if (str[1] == '0') {
return this.numToUpper(Integer.parseInt(str[0] + "")) + "十";
} else {
return this.numToUpper(Integer.parseInt(str[0] + "")) + "十" + this.numToUpper(Integer.parseInt(str[1] + ""));
}
}
}
}