生成pdf base64字符串

业务场景

最近接到业务需求:把查询的数据转成指定模板的pdf base64字符串。开始接到任务的时候想的是先生成excel文件再转成pdf,最后转换成base64字符串,后续发现编码过程中,excel转pdf出现了问题,后面选择通过html生成pdf base64的方案一样可以满足业务需求,大致实现过程如下:

一、pom文件配置

        <!--pdf配置-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-tools</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.1.20</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

二、工具类编写

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.pdf.BaseFont;
import com.lowagie.text.Image;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.apache.velocity.tools.generic.DateTool;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Base64Utils;
import org.w3c.dom.Element;
import org.xhtmlrenderer.extend.FSImage;
import org.xhtmlrenderer.extend.ReplacedElement;
import org.xhtmlrenderer.extend.ReplacedElementFactory;
import org.xhtmlrenderer.extend.UserAgentCallback;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextFSImage;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextImageElement;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;

import java.io.*;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;

public class PdfUtil {

    private static final String HTML_TEMPLATE_FILE = "htmltemplate/template.html";
    private static final String FONT = "font/simsun.ttc";
    private static final String _BUSINESS_TEMPLATE_ = "_BUSINESS_TEMPLATE_";

    /**
     * 通过html生成pdf,返回字节数组
     * @param dataMap
     * @return
     */
    public static String generatePdfByHtmlForByteArray(Map<String, Object> dataMap, String businessTemplate) throws IOException {
        String templateFileString = html2String();
        if (businessTemplate.contains("<html>") && businessTemplate.contains("</html>")){
            //1.完整写法的反馈表
            //body,需要加上字符集,例如:<body style="font-family:SimSun">
            templateFileString = businessTemplate;
        } else {
            //2.只写<body></body>中内容的反馈表
            if (StringUtils.isEmpty(businessTemplate)) {
                templateFileString = templateFileString.replace(_BUSINESS_TEMPLATE_, "");
            } else {
                templateFileString = templateFileString.replace(_BUSINESS_TEMPLATE_, businessTemplate);
            }
        }

        // 添加时间日期格式化dateTool
        dataMap.put("businessTemplate", StringUtils.isNotEmpty(businessTemplate));
        dataMap.put("dateTool", new DateTool());
        dataMap.put("nowTime", Timestamp.valueOf(LocalDateTime.now()));

        String html = getHtmlByVelocity(templateFileString, dataMap);
        html = html.replace("&nbsp;", "  ");
        return StringUtils.replaceBase64Blank(Base64Utils.encodeToString(htmlTopdfForByteArray(html)));
    }

    private static String html2String() throws IOException {
        ClassPathResource classPathResource = new ClassPathResource(HTML_TEMPLATE_FILE);
        return IOUtils.toString(classPathResource.getInputStream(), "utf-8");
    }

    /**
     * 使用velocity生成html
     *
     * @param dataMap
     * @return
     * @throws Exception
     */
    private static String getHtmlByVelocity(String html, Map<String, Object> dataMap) throws IOException {
        VelocityEngine ve = new VelocityEngine();
        ve.setProperty(Velocity.ENCODING_DEFAULT, "UTF-8");
        ve.setProperty(Velocity.INPUT_ENCODING, "UTF-8");//指定编码格式,避免生成模板就造成乱码,影响到转pdf后的文件
//        ve.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8");
        ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");// 这是模板所在路径
        ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        ve.init();
        return mergeTemplateIntoString(ve, html, "UTF-8", dataMap);
    }

    /**
     * html转pdf返回字节数组
     * @param html
     * @return
     */
    public static byte[] htmlTopdfForByteArray(String html) {
        OutputStream os = null;
        InputStream insm = null;
        byte[] byteArray = null;
        //临时文件路径,将pdf字节数组返回之后,就将临时文件删除
        try {
            ITextRenderer renderer = new ITextRenderer();

            // step 3 解决中文支持
            ITextFontResolver fontResolver = renderer.getFontResolver();
            fontResolver.addFont(FONT, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

            SharedContext sharedContext = renderer.getSharedContext();
            // 解决base64图片支持问题
            sharedContext.setReplacedElementFactory(new B64ImgReplacedElementFactory());
            sharedContext.getTextRenderer().setSmoothingThreshold(0);

            File tempFile = File.createTempFile(UUID.randomUUID().toString().replace("-", ""), "pdf");
            os = new FileOutputStream(tempFile);

            renderer.setDocumentFromString(html);
            renderer.layout();
            renderer.createPDF(os);
            renderer.finishPDF();

            os.close();
            insm = new FileInputStream(tempFile);
            byteArray = new byte[(int) tempFile.length()];
            IOUtils.readFully(insm, byteArray);
            insm.close();
            //删除临时文件
            tempFile.delete();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                }
            }
            if (insm != null) {
                try {
                    insm.close();
                } catch (IOException e) {
                }
            }
        }
        return byteArray;
    }

    public static String mergeTemplateIntoString(VelocityEngine velocityEngine, String html, String encoding, Map<String, Object> data)
            throws VelocityException {
        StringWriter result = new StringWriter();
        VelocityContext velocityContext = new VelocityContext(data);
        velocityEngine.evaluate(velocityContext, result, encoding, html);
        return result.toString();
    }

    static class B64ImgReplacedElementFactory implements ReplacedElementFactory {
        /**
         * 实现createReplacedElement 替换html中的Img标签
         *
         * @param c 上下文
         * @param box 盒子
         * @param uac 回调
         * @param cssWidth css宽
         * @param cssHeight css高
         * @return ReplacedElement
         */
        @Override
        public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac,
                                                     int cssWidth, int cssHeight) {
            Element e = box.getElement();
            if (e == null) {
                return null;
            }
            String nodeName = e.getNodeName();
            // 找到img标签
            if (nodeName.equals("img")) {
                String attribute = e.getAttribute("src");
                FSImage fsImage;
                try {
                    // 生成itext图像
                    fsImage = buildImage(attribute, uac);
                } catch (IOException e1) {
                    fsImage = null;
                }
                if (fsImage != null) {
                    // 对图像进行缩放
                    if (cssWidth != -1 || cssHeight != -1) {
                        fsImage.scale(cssWidth, cssHeight);
                    }
                    return new ITextImageElement(fsImage);
                }
            }

            return null;
        }

        /**
         * 将base64编码解码并生成itext图像
         *
         * @param srcAttr 属性
         * @param uac 回调
         * @return FSImage
         * @throws IOException io异常
         * @throws BadElementException BadElementException
         */
        protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException {
            FSImage fsImage = null;
            if (srcAttr.startsWith("data:image/")) {
                String b64encoded = srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length());
                // 解码
                byte[] decodedBytes = Base64Utils.decodeFromString(b64encoded);
                try {
                    fsImage = new ITextFSImage(Image.getInstance(decodedBytes));
                } catch (com.lowagie.text.BadElementException e) {
                    e.printStackTrace();
                }
            } else {
                fsImage = uac.getImageResource(srcAttr).getImage();
            }
            return fsImage;
        }

        /**
         * 实现reset
         */
        @Override
        public void reset() {
        }

        @Override
        public void remove(Element arg0) {
        }

        @Override
        public void setFormSubmissionListener(FormSubmissionListener arg0) {
        }
    }
}

三、测试demo

 /**
     * 根据数据生成pdf base64字符串
     *
     *
     * */
    public String getPdfBase64Str(SendDataFileVO vo) throws IOException {

        //html
        String html = "<p align=\"center\">\n" +
                "    <span style='font-size:15.0pt;font-weight: bold;'>xxxxx表</span>\n" +
                "</p>\n" +
                "<table style=\"table-layout:fixed; width: 95%; \">\n" +
                "    <tr>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"left\"></td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"center\">$!data.bbrq00</td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"right\"></td>\n" +
                "    </tr>\n" +
                "    <tr>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"left\">名称(盖章):</td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"center\">类型</td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"right\">名称2:$!data.bankName</td>\n" +
                "    </tr>\n" +
                "</table>\n" +
                "<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\" style=\"table-layout:fixed;width: 95%;\">\n" +
                "    <tr>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">文件名称</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">批次号</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">流水号</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">方式</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">笔数</span></td>\n" +
                "\t\t<td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">金额</span></td>\n" +
                "    </tr>\n" +
                "     <tr>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.fileName</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.busiBatch</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.busiNo</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.zffs00</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.cnt</span></td>\n" +
                "\t\t<td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.sum</span></td>\n" +
                "    </tr>\n" +
                "     <tr>\n" +
                "        <td height=\"50\" colspan=\"4\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">合计</span></td>\n" +
                "        <td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.cnt</span></td>\n" +
                "\t\t<td height=\"50\" colspan=\"1\" rowspan=\"1\" align=\"center\" style=\"word-wrap : break-word\"><span\n" +
                "                align=\"center\">$!data.sum</span></td>\n" +
                "    </tr>\n" +
                "</table>\n" +
                "\n" +
                "<table style=\"table-layout:fixed; width: 90%; padding: 10px\">\n" +
                "    <tr>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"left\">业务经办人:$!data.ywjb00</td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"center\">财务经办人:$!data.cwjb00</td>\n" +
                "    </tr>\n" +
                "    <tr>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"left\">业务负责人:$!data.ywfh00</td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"center\">财务负责人:$!data.cwfh00</td>\n" +
                "        <td colspan=\"1\" width=\"33%\" align=\"right\">日期:$!data.dyrq00</td>\n" +
                "    </tr>\n" +
                "</table>\n" +
                "<p style=\"text-align: left; font-weight: 700\">注:报盘文件名称须按照支付方式、日期、序号顺序排列。</p>";


        Map<String, Object> resultData = new HashMap<>();
        //接口数据data
        JSONObject params = new JSONObject();
        params.put("busiNo",vo.getBusiNo());
        params.put("fileName",vo.getFileName());
        params.put("bankName",vo.getBankName());
        params.put("cnt",vo.getCnt());
        params.put("sum",vo.getSum());
        params.put("busiBatch",vo.getBusiBatch());
        params.put("ywjb00",vo.getYwjb00());
        params.put("ywfh00",vo.getYwfh00());
        params.put("cwjb00",vo.getCwjb00());
        params.put("cwfh00",vo.getCwfh00());
        params.put("zffs00",vo.getZffs00());
        params.put("bbrq00",vo.getBbrq00());
        //获取当前年月日
        Integer nowdate = Integer.parseInt(DateUtil.dateNoSymbol().substring(0, 8));
        params.put("dyrq00", DateFormatUtil.stringToYMDFormat(nowdate.toString()));
        resultData.put("data", params);

        String base64File = PdfUtil.generatePdfByHtmlForByteArray(resultData, html);
        log.info("===================================================报盘文件字符串《"+vo.getBankName()+"》");
        log.info("==================================================="+base64File);
        return base64File;
    }

四、base64字符串测试地址

https://www.ec95.com/

尾言

以上就是大致的实现过程,唯一不方便的是,对应的模板都需用html画出来才能进行使用,后续有更好的方式也会同步到文档上,感谢阅读。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值