sprinboot使用freemarker导出word文件过程记录

简介

本文为springboot使用freemarker技术导出word文档的过程记录。

内容

1、代码部分

springboot项目创建

<u>开发工具:idea</u>

<u>java版本:java8</u>

<u>springboot版本:2.1.6.RELEASE</u>

创建一个maven项目,项目名称自定义,例如:ssqxx-word

创建完父类项目之后,删除生成的java文件夹和resources文件夹,新建一个子模块,模块命名自定义,例如:test-export-word-manage

springboot导包

父类pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>ssqxx-word</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <description>导出word测试项目</description>

    <!-- 模块 -->
    <modules>
        <module>test-export-word-manage</module>
    </modules>


    <!-- 属性定义和公共版本定义 -->
    <properties>
        <springboot.version>2.1.6.RELEASE</springboot.version>
    </properties>

    <!-- 管理jar包版本,Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号 -->
    <dependencyManagement>
        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

模块pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ssqxx-word</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>test-export-word-manage</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <!-- springboot web 包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- springboot freemarker 包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <!-- framework 包-->
        <dependency>
            <groupId>org.apache.avalon.framework</groupId>
            <artifactId>avalon-framework-api</artifactId>
            <version>4.3.1</version>
        </dependency>
    </dependencies>
</project>

核心导出代码

创建一个word导出工具类,导出word的所有步骤代码都放在这个类里

WordUtil.java
package com.ssqxx.util;

import freemarker.template.Configuration;
import freemarker.template.Template;
import sun.misc.BASE64Encoder;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @ClassName: WordUtil
 * @Description: word导出工具类
 * @author ssqxx
 * @version 1.0
 * @date 2020-11-19
 */
public class WordUtil {

    private static Configuration configuration = null;
    //classLoader.getResource()只能获取相对路径的资源
//     private static final String templateFolder = WordUtils.class.getClassLoader().getResource("template").getPath();
    //class.getResource()可以获取绝对路径和相对路径
    private static final String templateFolder = com.ssqxx.util.WordUtil.class.getResource("/templates").getPath();


    static {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        try {
            configuration.setDirectoryForTemplateLoading(new File(templateFolder));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private WordUtil() {
        throw new AssertionError();
    }

    /**
     * 导出word文件到浏览器
     * @param request
     * @param response
     * @param map 内容
     * @param title 标题
     * @param ftlFile 模板
     * @throws IOException
     */
    public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map map, String title, String ftlFile) throws IOException {
        Template freemarkerTemplate = configuration.getTemplate(ftlFile);
        File file = null;
        InputStream fin = null;
        ServletOutputStream out = null;
        try {
            // 调用工具类的createDoc方法生成Word文档
            file = createDoc(map, freemarkerTemplate);
            fin = new FileInputStream(file);

            response.setCharacterEncoding("utf-8");
            response.setContentType("application/msword");
            //获取指定格式时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 设置浏览器以下载的方式处理该文件名
            String fileName = title + sdf.format(new Date()) + ".doc";
            //文件名称乱码解决
            String encodedFileName = fileName +";filename*=utf-8''"+URLEncoder.encode(fileName,"UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename="+encodedFileName+"");
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);

            out = response.getOutputStream();
            byte[] buffer = new byte[512];  // 缓冲区
            int bytesToRead = -1;
            // 通过循环将读入的Word文件的内容输出到浏览器中
            while ((bytesToRead = fin.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        } finally {
            if (fin != null) fin.close();
            if (out != null) out.close();
            if (file != null) file.delete(); // 删除临时文件
        }
    }

    /**
     * 创建的doc
     * @param dataMap 数据内容
     * @param template 模板
     * @return
     */
    private static File createDoc(Map dataMap, Template template) {
        String name = "ssqxx.doc";
        File f = new File(name);
        Template t = template;
        try {
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }

    /**
     * 本地文件图片获取base64码
     * @param src
     * @return
     */
    public static String getImageBase(String src){
         if (src == null || src == "")
             return "";
         File file = new File(src);
         if (!file.exists())
             return "";
         InputStream in = null;
         byte[] data = null;
         try {
             in = new FileInputStream(file);
             data = new byte[in.available()];
             in.read(data);
             in.close();
         } catch (IOException e) {
             e.printStackTrace();
            }
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }

    /**
     * 获取Base64码根据文件字节流
     * @param inputStream
     * @return
     */
    public static String getImageBaseByInputStream(InputStream inputStream){
        if (inputStream == null)
            return "";
        byte[] data = null;
        try {
            data = new byte[inputStream.available()];
            inputStream.read(data);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }
}

业务导出代码

controller层
package com.ssqxx.controller;

import com.ssqxx.service.ExportWordService;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @ClassName: ExportWordController
 * @Description: word导出Controller类
 * @author ssqxx
 * @version 1.0
 * @date 2020-11-19
 */
@RestController
@RequestMapping("exportWord")
public class ExportWordController {

    @Autowired
    ExportWordService exportWordService;

    /**
     * 导出word方法
     * @param request
     * @param response
     */
    @GetMapping("export")
    public void exportWord(HttpServletRequest request, HttpServletResponse response){
        exportWordService.exportWord(request,response);
    }
}

服务层

sevice类

package com.ssqxx.service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @ClassName: ExportWordService
 * @Description: word导出服务类
 * @author ssqxx
 * @version 1.0
 * @date 2020-11-19
 */
public interface ExportWordService {

    void exportWord(HttpServletRequest request, HttpServletResponse response);
    
}

service实现类

package com.ssqxx.service.impl;

import com.ssqxx.service.ExportWordService;
import com.ssqxx.util.WordUtil;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: ExportWordServiceImpl
 * @Description: word导出服务实现类
 * @author ssqxx
 * @version 1.0
 * @date 2020-11-19
 */
@Service("exportWordService")
public class ExportWordServiceImpl implements ExportWordService {

    @Override
    public void exportWord(HttpServletRequest request, HttpServletResponse response){
        //添加测试数据
        //声明一个数据Map
        Map map = new HashMap();
        map.put("name","迈克尔");
        map.put("remark","暂无");
        //测试列表数据
        List list = new LinkedList();
        list.add("数据一");
        list.add("数据二");
        list.add("数据三");
        map.put("list",list);
        //测试map数据
        Map mapT = new HashMap();
        mapT.put("one","mapOne");
        mapT.put("two","mapTwo");
        map.put("mapT",mapT);

        //添加图片,地址为图片存放地址,需改成自己的文件地址
        String imageOne = WordUtil.getImageBase("C:/U***s/1****3/D*****p/微信图片_20200915162314.jpg");
        map.put("imageOne", imageOne);

        try {
            //将数据导出
            WordUtil.exportMillCertificateWord(request,response,map,"导出word测试","exportWord.ftl");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

模块配置文件
server:
  port: 7070
spring:
  application:
    name: export-word-manage-service

2、模板部分

模板创建

  • 使用WPS或者微软word进行模板编制
  • 写好模板样式,最后在需要填充数据的地方写上标识,方便之后改成导出模板,例如:“占位”来作为标识
  • 模板编制完成后,另存为,保存格式为xml格式!
  • 建议插入图片占位时尽量使用较小的图片!

例如:

001.jpg

模板修改

项目的模块部分添加存放模板文件夹,在resources文件夹下新建templates文件夹

将生成好的xml模板,导入到项目模块中新建的templates文件夹里

修改xml模板的文件名,更改为:exportWord.ftl(名称可以自定义,在代码里对应上即可,不然会报错找不到文件!)

打开exportWord.ftl,进行格式的调整和修改(ftl用idea打开格式较乱的话,可以用Notepad++进行xml格式调整,这个操作不会的话,之后的文章会出!)

将模板中“占位”数据更改为传入map的key,例如:${name}

003.jpg

模板中list的数据更改,需要加上<#list 原名 as 作用名></#list>

004.jpg

模板中Map的数据更改,直接写map的值加上key,例如${map.key}

005.jpg

模板中图片打印,先找到<w:binData></w:binData>,然后将中间的Base64位字符删除,更换为自己的数据名称,注意格式,不可以换行,否则图片打印不出来。例如${image}

007.jpg

3、导出效果

完成以上步骤之后,就可以进行word导出测试了,我使用postman进行导出接口测试

008.jpg

最终的效果为

009.jpg

数据都已经打印出来了,图片也成功展示,word导出完成!

结论

word导出的开发过程记录是在工作中项目需要用到word导出这个功能,然后去学习和寻找方法的,其实关键的地方在于模板的编制导出核心代码

这就是一个简单的springboot使用freemarker进行word文件导出的开发过程记录,希望能给你们的开发提供一些些帮助!

谢谢支持!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容