当前位置: 首页 > 工具软件 > flying > 使用案例 >

SpringBoot+FreeMarker+flying-saucer-pdf实现PDF预览、分页需求

傅长恨
2023-12-01

SpringBoot+FreeMarker+flying-saucer-pdf实现PDF预览、分页需求

需求说明

  1. MicroSoft Word文档转换PDF文档
  2. 实际工作场景中,类似于业务部门提供合同的Word文档范本,预览合同、签署合同、下载合同均需求使用PDF文档。

程序示例

程序示例说明

示例程序通过两种方式实现了FreeMarker+flying-saucer-pdf的PDF预览、分页需求

  1. FreeMarker模板位于应用程序资源目录下:/resources/templates/freemarkers
  2. FreeMarker模板存储于其它存储单元,例如数据库

添加依赖包

<!-- xml 将html模板文件转换成pdf -->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.22</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

FreeMarker模板文件编写

<html>

<head>
    <style>
        span{
            border-bottom: 1px solid black;
        }
        p{
            line-height: 1.5;
        }
        @page {
            size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/
            margin-bottom: 1cm;

            padding: 1em;

            @top-center {
                content: "页眉中间位置";
                font-family: SimSun;
                font-size: 15px;
                color:#000;
            };

            @bottom-center{
                content: "页脚中间位置";
                font-family: SimSun;
                font-size: 15px;
                color:#000;
            };

            @bottom-right{
                content: "第" counter(page) "页 共" counter(pages) "页";
                font-family: SimSun;
                font-size: 15px;
                color:#000;
            };
        }

        #pagenumber:before {
            content: counter(page);
        }

        #pagecount:before {
            content: counter(pages);
        }

    </style>
</head>

<body style="font-family: SimSun; ">
    <p>
        <h1 style="text-align: center;">公司授权委托书</h1>
    </p>
    <br /><br />
    <p>致:<span>${tenEntName!}</span></p>
    <p>&#160;&#160;&#160;&#160;我单位现委托<span>${userName!}</span>作为我单位合法委托代理人,授权其代表我单位进行<span>${indexTitle!}</span>账户相关管理工作。该委托代理人的授权范围为:代表我单位在<span>${indexTitle!}</span>上注册、签署文件、使用<span>${indexTitle!}</span>、资金交易、融资等与<span>${indexTitle!}</span>有关的一切事务。在整个<span>${indexTitle!}</span>使用过程中,该代理人的一切行为,均代表本单位,与本单位的行为具有同等法律效力。本单位将承担该代理人行为带来的全部法律后果和法律责任。</p>
    <p>&#160;&#160;&#160;&#160;以上授权委托有效期自盖章之日(含当日)起至<span>${indexTitle!}</span>账户注销和/或<span>${indexTitle!}</span>全部融资款项结算完毕终止。</p>
    <p>&#160;&#160;&#160;&#160;代理人无权转换代理权。</p>
    <p>&#160;&#160;&#160;&#160;特此委托。</p>
    <p>&#160;&#160;&#160;&#160;代理人姓名:<span>${userName!}</span></p>
    <p>&#160;&#160;&#160;&#160;身份证号码:<span>${userNum!}</span></p>
    <br /><br />
    <p>&#160;&#160;&#160;&#160;委托人(盖章):</p>
    <p>&#160;&#160;&#160;&#160;日期:<span>${year!}</span>年<span>${month!}</span>月<span>${day!}</span>日</p>
    <br /><br />    <br /><br />    <br /><br />    <br /><br />    <br /><br />    <br /><br />
    <br /><br />    <br /><br />    <br /><br />
    <p>附:委托代理人身份证复印件(<span>正反面、</span>加盖公章)</p>

    <div id = "header"></div>

</body>

</html>

注意事项:

  1. 在body标签中指定模板文件字体。本文是宋体:font-family: SimSun;
  2. FreeMarker文件对Html标签要求很严格,尽量使用正确的标签。
  3. 模板文件位置应用程序资源目录下:/resources/templates/freemarkers

工具类编写

  • FreeMarker工具类
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;

public class FreemarkerUtil {


    /**
     * 从模板文件中加载
     * @param templateFileName
     * @param data
     * @return
     * @throws IOException
     * @throws TemplateException
     */
    public static String loadTemplateFromFile(String templateFileName, Map<String, String> data) throws IOException, TemplateException {

        // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 指定FreeMarker模板文件的位置
        cfg.setClassForTemplateLoading(FreemarkerUtil.class,"/templates/freemarkers");

        // 设置模板的编码格式
        cfg.setEncoding(Locale.CHINA, "UTF-8");
        // 获取模板文件
        Template template = cfg.getTemplate(templateFileName, "UTF-8");
        StringWriter writer = new StringWriter();

        // 将数据输出到html中
        template.process(data, writer);
        writer.flush();

        String html = writer.toString();

        return html;
    }

    /**
     * 从存储单元中中加载
     * @param templateFileContent
     * @param data
     * @return
     * @throws IOException
     * @throws TemplateException
     */
    public static String loadTemplateFromStorage(String templateFileContent, Map<String, String> data) throws IOException, TemplateException {

        String templateName = "自定义模板名称";

        StringWriter stringWriter = new StringWriter();

        StringTemplateLoader loader = new StringTemplateLoader();
        loader.putTemplate(templateName, templateFileContent);

        Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
        cfg.setTemplateLoader(loader);
        cfg.setDefaultEncoding("UTF-8");
        Template template = cfg.getTemplate(templateName);
        template.process(data, stringWriter);

        String html = stringWriter.toString();

        return html;
    }


}
  • PDF工具类
import com.lowagie.text.pdf.BaseFont;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

public class PDFUtil {

    public static ByteArrayOutputStream createPDFFromHtml(String html) throws Exception {

        ITextRenderer renderer = new ITextRenderer();
        OutputStream out = new ByteArrayOutputStream();

        // 设置 css中 的字体样式(暂时仅支持宋体和黑体) 必须,不然中文不显示
        renderer.getFontResolver().addFont("/font/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        // 把html代码传入渲染器中
        renderer.setDocumentFromString(html);

//            // 设置模板中的图片路径 (这里的images在resources目录下) 模板中img标签src路径需要相对路径加图片名 如<img src="images/xh.jpg"/>
//            String url = PDFTemplateUtil.class.getClassLoader().getResource("images").toURI().toString();
//            renderer.getSharedContext().setBaseURL(url);
        renderer.layout();

        renderer.createPDF(out, false);
        renderer.finishPDF();
        out.flush();

        ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) out;

        return byteArrayOutputStream;
    }

}

程序示例控制器编写

import cn.tyrone.springboot.freemarker.util.FreemarkerUtil;
import cn.tyrone.springboot.freemarker.util.PDFUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/freemarker/pdf")
public class Freemarker2PDFController {

    /**
     * 从模板文件中加载
     * @param response
     * @throws Exception
     */
    @GetMapping("/template/file")
    public void loadFromTemplate(HttpServletResponse response) throws Exception {

        String templateFileName = "demo.ftl";

        Map<String, String> data = new HashMap<>();
        data.put("userName", "孙悟空");
        data.put("userNum", "110101199003074071");
        data.put("year", "2022");
        data.put("month", "01");
        data.put("day", "24");
        data.put("siteName", "帅链平台");
        data.put("ticketTitle", "帅票");
        data.put("tenEntName", "郑州市公共交通集团有限公司");
        data.put("indexTitle", "帅链科技");

        String html = FreemarkerUtil.loadTemplateFromFile(templateFileName, data);

        ByteArrayOutputStream baos = PDFUtil.createPDFFromHtml(html);
        response.setContentType("application/pdf");
        OutputStream out = response.getOutputStream();
        baos.writeTo(out);
        baos.close();

    }

    /**
     * 从存储单元中加载
     * @param response
     * @throws Exception
     */
    @GetMapping("/template/storage")
    public void loadFromStorage(HttpServletResponse response) throws Exception {

        Map<String, String> data = new HashMap<>();
        data.put("userName", "孙悟空");
        data.put("userNum", "110101199003074071");
        data.put("year", "2022");
        data.put("month", "01");
        data.put("day", "24");
        data.put("siteName", "帅链平台");
        data.put("ticketTitle", "帅票");
        data.put("tenEntName", "郑州市公共交通集团有限公司");
        data.put("indexTitle", "帅链科技");

        String templateContent = "<html><head><style>span{border-bottom:1px solid black}p{line-height:1.5}@page{size:210mm 297mm;margin-bottom:1cm;padding:1em;@top-center{content:\"页眉中读取存储单元间位置\";font-family:SimSun;font-size:15px;color:#000};@bottom-center{content:\"页脚中间位置\";font-family:SimSun;font-size:15px;color:#000};@bottom-right{content:\"第\"counter(page)\"页 共\"counter(pages)\"页\";font-family:SimSun;font-size:15px;color:#000}}#pagenumber:before{content:counter(page)}#pagecount:before{content:counter(pages)}</style></head><body style=\"font-family: SimSun; \"><p><h1 style=\"text-align: center;\">公司授权委托书</h1></p><br/><br/><p>致:<span>${tenEntName!}</span></p><p>&#160;&#160;&#160;&#160;我单位现委托<span>${userName!}</span>作为我单位合法委托代理人,授权其代表我单位进行<span>${indexTitle!}</span>账户相关管理工作。该委托代理人的授权范围为:代表我单位在<span>${indexTitle!}</span>上注册、签署文件、使用<span>${indexTitle!}</span>、资金交易、融资等与<span>${indexTitle!}</span>有关的一切事务。在整个<span>${indexTitle!}</span>使用过程中,该代理人的一切行为,均代表本单位,与本单位的行为具有同等法律效力。本单位将承担该代理人行为带来的全部法律后果和法律责任。</p><p>&#160;&#160;&#160;&#160;以上授权委托有效期自盖章之日(含当日)起至<span>${indexTitle!}</span>账户注销和/或<span>${indexTitle!}</span>全部融资款项结算完毕终止。</p><p>&#160;&#160;&#160;&#160;代理人无权转换代理权。</p><p>&#160;&#160;&#160;&#160;特此委托。</p><p>&#160;&#160;&#160;&#160;代理人姓名:<span>${userName!}</span></p><p>&#160;&#160;&#160;&#160;身份证号码:<span>${userNum!}</span></p><br/><br/><p>&#160;&#160;&#160;&#160;委托人(盖章):</p><p>&#160;&#160;&#160;&#160;日期:<span>${year!}</span>年<span>${month!}</span>月<span>${day!}</span>日</p><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><p>附:委托代理人身份证复印件(<span>正反面、</span>加盖公章)</p><div id=\"header\"></div></body></html>";

        String html = FreemarkerUtil.loadTemplateFromStorage(templateContent, data);

        ByteArrayOutputStream baos = PDFUtil.createPDFFromHtml(html);
        response.setContentType("application/pdf");
        OutputStream out = response.getOutputStream();
        baos.writeTo(out);
        baos.close();

    }


}

PDF分页实现

flying-saucer-pdf是通过css样式实现分页的,如下:

<style>
        span{
            border-bottom: 1px solid black;
        }
        p{
            line-height: 1.5;
        }
        @page {
            size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/
            margin-bottom: 1cm;

            padding: 1em;

            @top-center {
                content: "页眉中间位置";
                font-family: SimSun;
                font-size: 15px;
                color:#000;
            };

            @bottom-center{
                content: "页脚中间位置";
                font-family: SimSun;
                font-size: 15px;
                color:#000;
            };

            @bottom-right{
                content: "第" counter(page) "页 共" counter(pages) "页";
                font-family: SimSun;
                font-size: 15px;
                color:#000;
            };
        }

        #pagenumber:before {
            content: counter(page);
        }

        #pagecount:before {
            content: counter(pages);
        }

    </style>

参考链接

https://www.iteye.com/blog/wuxiangxin-1550349

源代码地址

https://github.com/myNameIssls/springboot-study/tree/master/springboot-freemarker

 类似资料: