wkhtmltopdf从HTML转为PDF文件导致表格分页失效,解决方法及Java实现

文章讲述了在将HTML转换为PDF时,开发人员遇到的问题,本地使用wkhtmltopdf成功,但在测试环境中因服务器上wkhtmltopdf版本(12.4)较低而出现分页失效。升级至12.5及以上版本解决该问题。
摘要由CSDN通过智能技术生成

本次需求需要将表格不够的位置另起一页展示

在根据数据动态拼接完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] + ""));
            }
        }
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值