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

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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于构建独立的、基于生产级别的Spring应用程序的框架。而Freemarker是一种模板引擎,用于生成动态内容,特别适合Web应用程序的开发。在Spring Boot中使用Freemarker可以通过引入相应的依赖和配置来实现。 首先,在Spring Boot工程中引入Freemarker依赖。可以通过在pom.xml文件中添以下依赖来实现: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 这样,在工程中就可以使用Freemarker来处理模板了。如果想要修改模板文件的位置等配置,可以在application.properties文件中进行配置。比如可以通过以下配置来指定模板文件的位置: ``` spring.freemarker.allow-request-override=false spring.freemarker.allow-session-override=false spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=true spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=false spring.freemarker.expose-session-attributes=false spring.freemarker.suffix=.ftl spring.freemarker.template-loader-path=classpath:/templates/ ``` 这样,Spring Boot就会根据这些配置来载并解析模板文件,生成相应的动态内容。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Spring Boot 整合 Freemarker](https://blog.csdn.net/yaxuan88521/article/details/117173289)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Springboot整合FreeMarker](https://blog.csdn.net/m0_67402096/article/details/126114796)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值