java使用IText和wkhtmltopdf把html模板生成pdf文件并部署linux

项目场景:

最近公司需要开发一个新功能,就是需要获取微信的审批信息来生成pdf文件,审批申请单是固定一个类似表格的样式,但是表格的内容会变的,有多有少,并且还有审批节点是动态配置的,比较麻烦。所以当时笔者就想到了用html模板来做,先用html来做一个大概的模板像是,里面的内容用变量来替换。然后生成pdf的插件就用wkhtmltopdf,因为这个之前用过,效果还不错。


开发准备

首先进入wkhtmltopdf官网去下载工具。如图所示,首先开发在自己电脑上就下载Windows版本。
在这里插入图片描述
下载之后打开是一个安装包,点击运行一直下一步就行,安装完成之后需要配置本地环境变量。如下图所示:
在这里插入图片描述
配置好之后,打开cmd命令窗口,输入wkhtmltopdf html路径 pdf保存路径
html路径:可以是本地的html文件路径,也可以是url链接
pdf路径:这个是保存本地的路径或者服务器的路径
输完命令直接回车,然后会显示进度条,完成之后就可以去查看生成的pdf了。


代码实现(wkhtmltopdf版本):

Controller类:

package com.ccic.webapi.controller;

import com.ccic.webapi.service.AuditService;
import com.ccic.webapi.util.Result;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * TOO
 *
 * @author hj
 * @version 1.0
 */
@RestController
@Slf4j
@RequestMapping("/audit/api")
@Api(tags = "审核")
public class AuditController {


    @Autowired
    private AuditService auditService;

    /**
     * 生成pdf
     * @param spNo
     * @param templateId
     * @return
     */
    @GetMapping("/auditToPdf")
    public String auditToPdf(@RequestParam String spNo){
        return auditService.auditToPdf(spNo);
    }
}

Service类:

package com.ccic.webapi.service;

/**
 * TOO
 *
 * @author hj
 * @version 1.0
 */
public interface AuditService {

    String auditToPdf(String spNo);
}

ServiceImpl实现层:

package com.example.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.mapper.AddrsbDepartmentMapper;
import com.example.mapper.TAddrsbEmpMapper;
import com.example.service.AuditService;
import com.example.util.WkhtmltopdfUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.*;

/**
 * TOO
 *
 * @author hj
 * @version 1.0
 */
@Service
@Slf4j
public class AuditServiceImpl implements AuditService {

    //获取审批申请详情
    public static String getapprovaldetail = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=ACCESS_TOKEN";

    private String[] msgtypeStr = {"Text", "Textarea", "Number", "Money", "Date", "Contact", "Tips"};

    @Value("${basePath}")
    private String bathBase;

    @Value("${wechat.cp.httpProxyHost}")
    private String httpProxyHost;

    @Value("${wechat.cp.httpProxyPort}")
    private String httpProxyPort;

    /**
     * 生成pdf
     * @param spNo
     */
    public String auditToPdf(String spNo){
        String access_token = "";//调用凭证
        String url = getapprovaldetail.replaceAll("ACCESS_TOKEN",access_token);
        JSONObject json = new JSONObject();
        json.put("sp_no",spNo);
        //调用详情接口
        String res = HttpRequest.post(url).body(json.toJSONString()).execute().body();
        String res = json;
        //拿到结果
        JSONObject jsonObject = JSON.parseObject(res);
        if(jsonObject.containsKey("errcode") && jsonObject.getInteger("errcode") == 0){
            try {
                JSONObject object = jsonObject.getJSONObject("info");
                //以下都是获取审批详情的内容,放到变量里面,倒是做替换使用
                String spName = object.getString("sp_name");
                String spbh = object.getString("sp_no");
                String empCn = object.getJSONObject("applyer").getString("userid");
                String comeName = object.getJSONObject("applyer").getString("partyid");
                String date = DateUtil.format(DateUtil.date(object.getLong("apply_time")*1000),"yyyy年MM月dd日 HH时mm分ss秒");
                //内容,这个内容根据自己的实际模板情况去拼接,因为拿到的内容是集合,需要自己去处理,自立就不放了
                String content = this.pjtr(object);
                //审批节点,也是一样,同理获取审批内容,自行处理
                Map<String, Object> map = this.getAuditNode(object);
                //抄送人
                String csr = "";
                //备注信息
                String remarks = "";
                //获取模版,这个是获取html的模板内容,方法在下面会贴出
                String mbnr = getResource("/template/shmb.html");
                String str = mbnr;
                //获取章,这个是水印,也可以认为是盖章,把png图片转成base64字符码。
                String zhang = getResource("/template/shtg.png.base64");
				//html中的模板变量,我都用中文【】标记,以便于替换真正的内容
                str = str.replaceAll("【审批模板名字】",isNullToChar(spName));
                str = str.replaceAll("【审批编号】",isNullToChar(spbh));
                str = str.replaceAll("【applyer.userid】",StrUtil.isBlank(isNullToChar(empCn)) ? "涂烔" : isNullToChar(empCn));
                str = str.replaceAll("【applyer.party】",isNullToChar(comeName));
                str = str.replaceAll("【apply_time】",isNullToChar(date));
                //这里我是要替换一大推内容,不确定性,content里面还包含了html代码片段。
                str = str.replaceAll("<content2></content2>",isNullToChar(content));
                str = str.replaceAll("【执行部门姓名】",StrUtil.isBlank(isNullToChar(map.get("zxbmxm"))) ? "涂烔" : isNullToChar(map.get("zxbmxm")));
                str = str.replaceAll("【执行部门意见】",isNullToChar(map.get("zxbmyj")));
                str = str.replaceAll("【归属部门姓名】",StrUtil.isBlank(isNullToChar(map.get("gsbmxm"))) ? "涂烔" : isNullToChar(map.get("gsbmxm")));
                str = str.replaceAll("【归属部门意见】",isNullToChar(map.get("gsbmyj")));
                str = str.replaceAll("【抄送人】",isNullToChar(csr));
                str = str.replaceAll("【备注信息】",isNullToChar(remarks));
                //判断是否审核通过,通过才有章
				if(object.getInteger("sp_status") == 2){
                    //获取章
                    String zhang = getResource("/template/shtg.png.base64");
                    str = str.replaceAll("【chapterSrc】",zhang);
                    //这里在html模板上做了一个隐藏的标记,也可以说是样式
                    str = str.replaceAll("【chapterShow】","inline-block");
                }
                //先生成html文件,并返回html文件路径
                String htmlPath = this.updateFile(str,spName);
                //pdf的保存路径
                String pdfPath = bathBase + spName + "new" + ".pdf";
                //生成pdf操作,具体实现方法后面贴出
                WkhtmltopdfUtil.html2pdf(htmlPath,pdfPath);
                return pdfPath;
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }

    public String getAuditStatus(Integer status){
        String s = "";
        if(1 == status){
            s = "审批中";
        }else if(2 == status){
            s = "已同意";
        }else if(3 == status){
            s = "已驳回";
        }else if(4 == status){
            s = "已转审";
        }else if(11 == status){
            s = "已退回";
        }else if(12 == status){
            s = "已加签";
        }else if (13 == status){
            s = "已同意并加签";
        }
        return s;
    }

	//去除null值
    public static String isNullToChar(Object st){
        st = st == null ? "" : st;
        return String.valueOf(st);
    }


    /**
     * 拼接审批内容<tr><td> 的
     * @param jsonObject
     * @return
     */
    public String pjtr(JSONObject jsonObject){
        List<String> msgTypeList = Arrays.asList(msgtypeStr);
        StringBuffer sb = new StringBuffer("");
        JSONArray jsonArray = jsonObject.getJSONObject("apply_data").getJSONArray("contents");
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject object = jsonArray.getJSONObject(i);
            String control = object.getString("control");
            JSONObject title = object.getJSONArray("title").getJSONObject(0);
            JSONObject value = object.getJSONObject("value");
            if(msgTypeList.contains(control)){
                sb.append("<tr height=\"30px\">");
                sb.append("<td>"+title.getString("text")+"</td>");
                sb.append("<td colspan=\"2\">"+value.getString("text")+"</td>");
                sb.append("</tr>");
            }
        }
        return sb.toString();
    }

	//获取html静态文件内容,这个是在resources下面建了一个文件夹template
    public static String getResource(String path){
        log.info("path:{}",path);
        try {
            InputStream is = AuditServiceImpl.class.getResourceAsStream(path);
            InputStreamReader reader = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(reader);
            String s;
            StringBuffer sb = new StringBuffer("");
            while ((s=br.readLine()) != null){
                sb.append(s+"\n");
            }
            if(StrUtil.isBlank(sb.toString())) throw new RuntimeException("RSA KEY IS NULL");

            return sb.toString();
        }catch (Exception e){
            log.error("获取RSA KEY RESOURCE失败:{}",e.getMessage());
        }
        return null;
    }

	//生成html文件并保存在服务器
    public String updateFile(String html,String fileName){
        String htmlpath = bathBase + "wshtml" + File.separator + fileName + ".html";
        File file = new File(htmlpath);
        try{
            if(!file.getParentFile().exists()){
                file.getParentFile().mkdir();
            }
            if(!file.exists()){
                file.createNewFile();
            }
            FileOutputStream outputStream = new FileOutputStream(file);
            byte[] b = html.getBytes();
            outputStream.write(b);
            outputStream.close();
        }catch (Exception e){
            log.error("异常:{}",e.getMessage());
        }
        return htmlpath;
    }
}

WkhtmltopdfUtil类:主要是生成pdf的方法类

package com.example.util;

import lombok.extern.slf4j.Slf4j;

import java.io.*;

/**
 * html转pdf 注意:运行前需要先安装 wkhtmltopdf 并加入path环境变量
 * 下载 https://wkhtmltopdf.org/downloads.html
 * 调用及参数设置 https://blog.csdn.net/x6582026/article/details/53835835
 * 中文乱码或者空白解决方法:
 * 如果wkhtmltopdf中文显示空白或者乱码方框
 * 打开windows c:\Windows\fonts\simsun.ttc拷贝到linux服务器/usr/share/fonts/目录下,再次生成pdf中文显示正常
 */
@Slf4j
public class WkhtmltopdfUtil {

	private static final String toPdfTool = "wkhtmltopdf";

	private static final String pageSize = "--page-size A4";  //纸张类型尺寸

	private static final String smart="--disable-smart-shrinking";  //smart

	private static final String encode="--encoding UTF-8";   //编码格式
	
	//页边距 左右上下的边距
	private static final String margin = "--margin-left 16mm --margin-right 16mm --margin-top 14mm --margin-bottom 16mm";


	/**
	 * html转pdf
	 * @param htmlpath 本地文件或者网络文件地址
	 * @param pdfpath 目标文件
	 * @param
	 */
	public static String html2pdf(String htmlpath, String pdfpath) {

		StringBuilder cmd = new StringBuilder();
		cmd.append(smart);
		cmd.append(" ");
		cmd.append(encode);
		cmd.append(" ");
		cmd.append(pageSize);
		cmd.append(" ");
		cmd.append(margin);

		html2pdf(htmlpath, pdfpath, cmd.toString());

		return pdfpath;
	}

	//生成方法,它是通过运行cmd命令来实现的
	public static String html2pdf(String htmlpath, String pdfpath, String prop) {

		log.info("html2pdf2方法中传递的htmlpath为:{},pdfpath为:{}",htmlpath,pdfpath);

		File file = new File(pdfpath);

		File parent = file.getParentFile();

		if(!parent.exists()){
			parent.mkdirs();
		}

		StringBuilder cmd = new StringBuilder();
		cmd.append(toPdfTool);
		cmd.append(" ");
		cmd.append(prop);
		cmd.append(" ");
		cmd.append(htmlpath);
		cmd.append(" ");
		cmd.append(pdfpath);
		try {
			log.info("执行的cmd命令为:{}",cmd.toString());

			Process process = Runtime.getRuntime().exec(cmd.toString());

			BufferedInputStream bis = new BufferedInputStream(process.getErrorStream());

			BufferedReader br = new BufferedReader(new InputStreamReader(bis));

			String line;

			while ((line = br.readLine()) != null) {
				System.out.println(line);
			}
			process.waitFor();
			if (process.exitValue() != 0) {
				System.out.println("wkhtmltopdf命令未正常结束" + htmlpath + "   " + pdfpath);
			}
			bis.close();
			br.close();

		} catch (IOException e) {
			log.error("html转换为Pdf/IO流异常,异常信息为:{}", e.getMessage());
		} catch (Exception e) {
			log.error("html转换为Pdf发生异常,异常信息为:{}", e.getMessage());
		}
		return pdfpath;
	}
}

后面部署发现的问题:wkhtmltopdf

开发完之后肯定要部署一版做测试,但是发现部署成了问题。因为笔者在做开发的时候实在windows环境下做的开发,一切都很顺利,但是要部署的话肯定实在linux上的,单纯的linux也不麻烦,按照官网下载linux版本去安装就行了。关键在于我这边的环境部署是OC容器,然后上面的人说要安装软件很麻烦,又要走流程啥的,就让我整其它办法。实在不行再来装这个。没办法,换呗,从网上找来找去,决定试试IText7。


解决方案:换成IText7

引入依赖

		<dependency>
			<groupId>com.itextpdf</groupId>
			<artifactId>html2pdf</artifactId>
			<version>2.1.4</version>
		</dependency>

换成IText7之后发现,需要字体,并且还对html的样式有严格要求,之前的模板转换不过来,没办法,一步一步调,先把字体搞定,再调样式。
容器环境,我也不知道去哪找字体,就想到把字体放在项目里面,通过静态文件去读取。
说干就干,如下:

    private static final String FONT = "./src/main/resources/font/simhei.ttf";

    /**
     * 设置BaseFont
     * @param fontPath  字体路径
     * @return
     */
    private static ConverterProperties creatBaseFont(String fontPath) {
        if(StrUtil.isBlank(fontPath)) {
            fontPath =  FONT;
        }

        ConverterProperties properties = new ConverterProperties();

        FontProvider fontProvider = new DefaultFontProvider();
        FontProgram fontProgram;
        try {
        	//这里在linux死活找不到路径
            fontProgram = FontProgramFactory.createFont(fontPath);
            fontProvider.addFont(fontProgram);
            properties.setFontProvider(fontProvider);

        } catch (IOException e) {
            log.error("creat base font erro" , e );
        }
        return properties;
    }

一开始不管改成什么路径,打包之后在linux都找不到字体路径,但是在windows下又是可以的,一顿百度,然后找到了大佬的博文,把字体路径以文件流的方式传过去。如下代码:

大佬博文:https://blog.csdn.net/x2011lj/article/details/122945172

package com.ccic.webapi.util;


import cn.hutool.core.util.StrUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.layout.font.FontProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * TOO
 *
 * @author hj
 * @version 1.0
 */
@Slf4j
public class Pdf7Kit {

    private static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

    /**
     * 设置BaseFont
     * @param fontPath  字体路径
     * @return
     */
    private static ConverterProperties creatBaseFont(String fontPath) {
        ClassPathResource resource = null;
        if(StrUtil.isBlank(fontPath)) {
            resource = new ClassPathResource("font/simhei.ttf");
        }

        ConverterProperties properties = new ConverterProperties();

        FontProvider fontProvider = new DefaultFontProvider();
        FontProgram fontProgram;
        try {
            fontProgram = FontProgramFactory.createFont(Pdf7Kit.toByteArray(resource.getInputStream()));
            fontProvider.addFont(fontProgram);
            properties.setFontProvider(fontProvider);

        } catch (IOException e) {
            log.error("creat base font erro" , e );
        }
        return properties;
    }

    /**
     * 将html文件转换成pdf
     * @param htmlPath
     * @param pdfPath
     * @param fontPath
     * @throws IOException
     */
    public static void creatPdf(String htmlPath , String pdfPath,String fontPath) throws IOException {
        if(StrUtil.isBlank(htmlPath) || StrUtil.isBlank(pdfPath)) {
            log.warn("html2pdf fail. htmlPath or pdfPath is null .");
            return;
        }
        // 拼接html路径
        String src = htmlPath;

        ConverterProperties properties = creatBaseFont(fontPath);
        HtmlConverter.convertToPdf(new File(src), new File(pdfPath),properties);

    }
}

字体解决之后,html样式就一步一步调整了。最后还是做出来了。
有需要的或者不懂得可以私聊我。

总结

技术无止境,我们要做的就是跟上脚步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
可以使用iTextPdf库来使用HTML模板生成PDF文件。首先,你需要导入iTextPdf库到你的项目中。然后,你可以使用以下代码来实现将HTML模板转换为PDF文件的功能: ```java// 创建一个Document对象Document document = new Document(); // 创建一个PdfWriter对象来将文档写入到文件PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf")); // 打开文档document.open(); //读取HTML模板文件String html = "<html><body>...</body></html>"; // 使用XMLWorkerHelper将HTML转换为PDFInputStream inputStream = new ByteArrayInputStream(html.getBytes()); XMLWorkerHelper.getInstance().parseXHtml(writer, document, inputStream); // 关闭文档document.close(); ``` 在这个例子中,我们首先创建了一个`Document`对象,并创建了一个`PdfWriter`对象,用于将文档写入到文件中。然后,我们打开文档,并读取HTML模板文件。接下来,我们使用`XMLWorkerHelper`类的`parseXHtml`方法将HTML转换为PDF。最后,我们关闭文档。 请注意,使用iTextPdf生成PDF文件时,你可以使用CSS属性来控制页面的分页。例如,你可以在HTML模板使用`page-break-after:always`或`page-break-before:always`属性来控制在特定元素之前或之后分页。 此外,你还可以使用`BaseFont`类来设置字体,并在`fields.setFieldProperty`方法中使用`textfont`和`textsize`属性来设置文本的字体和大小。 希望这个回答对你有帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值