Spring Boot 手把手实现PDF功能

需求:
    在开发的业务系统中我希望能有一个转出PDF功能。比如:转出订单,转出发货单等。

原理:

使用itext 与flying-pdf 对 thymeleaf 模板填充后生成PDF

实现:

1.准备中文字体,用于PDF中关于中文文字的显示。

链接: https://pan.baidu.com/s/1ubf9eWYHgqNgvtk0w7mauw 提取码: qd1i 

存放位置

Spring Boot 手把手实现PDF功能_第1张图片

2.Maven 主要依赖


    com.lowagie
    itext
    ${itext.version}



    org.apache.poi
    poi
    3.9



    org.apache.poi
    poi-ooxml
    3.9



    org.xhtmlrenderer
    flying-saucer-pdf
    9.1.9

3.工具类

import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.xml.xmp.XmpWriter;
import lombok.extern.slf4j.Slf4j;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;


import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.util.List;
import java.util.Map;

/**
 * pdf处理工具类
 *
 */
@Slf4j
public class PdfUtil {

    /**
     * 以文件流形式下载到浏览器
     *
     * @param templateEngine   配置
     * @param templateName 模板名称
     * @param listVars     模板参数集
     * @param response     HttpServletResponse
     * @param fileName     下载文件名称
     */
    public static void download(TemplateEngine templateEngine, String templateName, List> listVars, HttpServletResponse response, String fileName) {
        // 设置编码、文件ContentType类型、文件头、下载文件名
        response.setCharacterEncoding(XmpWriter.UTF8);
        response.setContentType("application/pdf");
        try {
            response.setHeader("Content-Disposition", "attachment;fileName=" +
                    new String(fileName.getBytes("gb2312"), "ISO8859-1"));
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }
        try (ServletOutputStream out = response.getOutputStream()) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * pdf下载到特定位置
     *
     * @param templateEngine   配置
     * @param templateName 模板名称
     * @param listVars     模板参数集
     * @param filePath     下载文件路径
     */
    public static void save(TemplateEngine templateEngine, String templateName, List> listVars, String filePath) {
        try (OutputStream out = new FileOutputStream(filePath);) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * pdf预览
     *
     * @param templateEngine   配置
     * @param templateName 模板名称
     * @param listVars     模板参数集
     * @param response     HttpServletResponse
     */
    public static void preview(TemplateEngine templateEngine, String templateName, List> listVars, HttpServletResponse response) {
        try (ServletOutputStream out = response.getOutputStream()) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 按模板和参数生成html字符串,再转换为flying-saucer识别的Document
     *
     * @param templateName 模板名称
     * @param variables   模板参数
     * @return Document
     */
    private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map variables)  {
        // 声明一个上下文对象,里面放入要存到模板里面的数据
        final Context context = new Context();
        context.setVariables(variables);
        StringWriter stringWriter = new StringWriter();
        try(BufferedWriter writer = new BufferedWriter(stringWriter)) {
            templateEngine.process(templateName,context, writer);
            writer.flush();
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes()));
        }catch (Exception e){
            //ResponseEnum.TEMPLATE_PARSE_ERROR.assertFail(e);
        }
        return null;
    }

    /**
     * 核心: 根据Thymeleaf 模板生成pdf文档
     *
     * @param templateEngine 配置
     * @param templateName 模板名称
     * @param out          输出流
     * @param listVars     模板参数
     * @throws Exception 模板无法找到、模板语法错误、IO异常
     */
    private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List> listVars) throws Exception {
        // 断言参数不为空
        //ResponseEnum.TEMPLATE_DATA_NULL.assertNotEmpty(listVars);
        ITextRenderer renderer = new ITextRenderer();
        //设置字符集(宋体),此处必须与模板中的一致,区分大小写,不能写成汉字"宋体"
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //避免中文为空设置系统字体
        fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        // 如果linux下有问题,可使用以下方式解决。
        // 有一个项目是用docker部署的,一直报错找不到simsun.ttf文件,但需要将simsun.ttf上传到/usr/share/fonts
        //fontResolver.addFont(CommonUtil.isLinux() ? "/usr/share/fonts/simsun.ttf" : "static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        //根据参数集个数循环调用模板,追加到同一个pdf文档中
        //(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容)
        for (int i = 0; i < listVars.size(); i++) {
            Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));
            renderer.setDocument(docAppend, null);
            //展现和输出pdf
            renderer.layout();
            if(i==0){
                renderer.createPDF(out, false);
            }else {
                //写下一个pdf页面
                renderer.writeNextDocument();
            }

        }
        renderer.finishPDF(); //完成pdf写入
    }
}

4.控制层方法 (原理是把对象传过去,由thymeleaf解析)

import com.flo.po.Dto.OrderDetailedDto;
import com.flo.po.Dto.OrderDto;
import com.flo.service.OrderDetailedService;
import com.flo.service.OrderService;
import com.flo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.TemplateEngine;

import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 文档预览、下载
 *
 */
@RestController
@RequestMapping(value = "/document")
@Api(tags = "文档预览、下载API")
public class DocumentController {

    @Autowired
    private TemplateEngine templateEngine;


    @Autowired
    private OrderService orderService;//订单表头

    @Autowired
    private OrderDetailedService orderDetailedService;//订单表身

    /**
     * pdf预览
     * @param response HttpServletResponse
     */
    @GetMapping(value = "/pdf/preview")
    @ApiOperation(value="pdf预览")
    public void preview(HttpServletResponse response,Integer id) {

        OrderDto orderDto=orderService.findById(id);
        List list=orderDetailedService.findAll(new OrderDetailedDto(){{setOrderId(id);}});

        // 构造freemarker模板引擎参数,listVars.size()个数对应pdf页数
        List> listVars = new ArrayList<>();
        Map variables = new HashMap<>(4);
        variables.put("title","订单明细");
        variables.put("custName",orderDto.getCustomName());
        variables.put("adr",orderDto.getAddress());
        variables.put("tel",orderDto.getTel());
        variables.put("list",list);
        listVars.add(variables);
        PdfUtil.preview(templateEngine,"pdfPage",listVars,response);
    }
}

5.PDF模板(pdfPage.html)

注意事项:

1.尽可能把CSS样式放在这个模板里,外部引入可能会存在一些问题。

2.关于图片,切记一定要给完整路径,而非相对路径。如./uploadFile/logo.png 需要加上http://地址/uploadFile/logo.png

3.图片名称不能包含中文,否则会找不到路径(是由于编码为UTF8的原因)

    位置

        Spring Boot 手把手实现PDF功能_第2张图片

效果:

Spring Boot 手把手实现PDF功能_第3张图片

Spring Boot 手把手实现PDF功能_第4张图片

你可能感兴趣的:(Spring,boot,Java,Web,spring,boot,PDF,java,Pdf订单,Java转PDF,JAVA,生成PDF)