相关推荐recommended
SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我)
作者:mmseoamin日期:2023-12-25

背景

最近有一个需求是需要动态导出合同、订单等信息,导出一个word文档供客户进行下载查看。

需要导出的word文件,主要可以分为两种类型。

  1. 导出固定内容和图片的word文档
  2. 导出表格内容不固定的word文档

经过对比工具,我实践过两种实现方式。第一种是FreeMarker模板来进行填充;第二种就是文中介绍的POI-TL。

这里我推荐使用POI-TL。

介绍

POI-TL是word模板引擎,基于Apache POI,提供更友好的API。

目前最新的版本是1.12.X,POI对应版本是5.2.2。

这里需要注意的是POI和POI-TL有一个对应的关系。

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第1张

准备工作

我使用的POI-TL版本是1.10.0

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第2张


            com.deepoove
            poi-tl
            1.10.0
        
        
            org.apache.poi
            poi
            4.1.2
        
        
            org.apache.poi
            poi-ooxml
            4.1.2
        
        
            org.apache.poi
            poi-ooxml-schemas
            4.1.2
        
        
            commons-io
            commons-io
            2.7
        

快速开始

流程:制作模板->提供数据->渲染模板->下载word

注意:需要填充的数据需要使用{{}}来表示。

1. 导出固定内容和图片的word文档

准备模板

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第3张

模板保存为docx格式,存放在resource目录下

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第4张

提供数据

private Map assertMap() {
        Map params = new HashMap<>();
        params.put("name", "努力的蚂蚁");
        params.put("age", "18");
        params.put("image", Pictures.ofUrl("http://deepoove.com/images/icecream.png").size(100, 100).create());
        return params;
    }

工具方法

/**
     * 将项目中的模板文件拷贝到根目录下
     * @return
     */
    private String copyTempFile(String templeFilePath) {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(templeFilePath);
        String tempFileName = System.getProperty("user.home") + "/" + "1.docx";
        File tempFile = new File(tempFileName);
        try {
            FileUtils.copyInputStreamToFile(inputStream, tempFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return tempFile.getPath();
    }
private void down(HttpServletResponse response, String filePath, String realFileName) {
        String percentEncodedFileName = null;
        try {
            percentEncodedFileName = percentEncode(realFileName);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        StringBuilder contentDispositionValue = new StringBuilder();
        contentDispositionValue.append("attachment; filename=").append(percentEncodedFileName).append(";").append("filename*=").append("utf-8''").append(percentEncodedFileName);
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
        response.setHeader("Content-disposition", contentDispositionValue.toString());
        response.setHeader("download-filename", percentEncodedFileName);
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
             // 输出流
             BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());) {
            byte[] buff = new byte[1024];
            int len = 0;
            while ((len = bis.read(buff)) > 0) {
                bos.write(buff, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/**
     * 百分号编码工具方法
     * @param s 需要百分号编码的字符串
     * @return 百分号编码后的字符串
     */
    public static String percentEncode(String s) throws UnsupportedEncodingException {
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
        return encode.replaceAll("\+", "%20");
    }

编写接口

@RequestMapping("genera")
    public void genera(HttpServletResponse response) {
        //1.组装数据
        Map params = assertMap();
        //2.获取根目录,创建模板文件
        String path = copyTempFile("word/1.docx");
        String fileName = System.currentTimeMillis() + ".docx";
        String tmpPath = "D:\\" + fileName;
        try {
            //3.将模板文件写入到根目录
            //4.编译模板,渲染数据
            XWPFTemplate template = XWPFTemplate.compile(path).render(params);
            //5.写入到指定目录位置
            FileOutputStream fos = new FileOutputStream(tmpPath);
            template.write(fos);
            fos.flush();
            fos.close();
            template.close();
            //6.提供前端下载
            down(response, tmpPath, fileName);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.删除临时文件
            File file = new File(tmpPath);
            file.delete();
            File copyFile = new File(path);
            copyFile.delete();
        }
    }

对于图片的格式,POI-TL也提供了几种方式来提供支撑。

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第5张

测试

请求接口:http://127.0.0.1:1000/file/genera

效果如下:

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第6张

2. 导出表格内容不固定的word文档

表格动态内容填充,POI-TL提供了3种方式。

  1. 表格行循环
  2. 表格列循环
  3. 动态表格。

第二种和第三种都可以实现表格填充,但我个人感觉第一种更方便一点,这里我只介绍【表格行循环】实现方式。

LoopRowTableRenderPolicy 是一个特定场景的插件,根据集合数据循环表格行。

注意:

  1. 模板中有两个list,这两个list需要置于循环行的上一行。
  2. 循环行设置要循环的标签和内容,注意此时的标签应该使用[]

准备模板

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第7张

提供数据

学生实体类

public class Student {
    private String name;
    private String age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
}

学生word类

public class StudentTable {
    private String title;
    private List studentList;
    private List studentList1;
    public List getStudentList1() {
        return studentList1;
    }
    public void setStudentList1(List studentList1) {
        this.studentList1 = studentList1;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public List getStudentList() {
        return studentList;
    }
    public void setStudentList(List studentList) {
        this.studentList = studentList;
    }
}

表格数据

private StudentTable assertData() {
        StudentTable table = new StudentTable();
        table.setTitle("我是标题");
        List studentList = new ArrayList<>();
        Student student = new Student();
        student.setName("张三");
        student.setAge("18");
        studentList.add(student);
        Student student1 = new Student();
        student1.setName("李四");
        student1.setAge("20");
        studentList.add(student1);
        Student student2 = new Student();
        student2.setName("王五");
        student2.setAge("21");
        studentList.add(student2);
        Student student3 = new Student();
        student3.setName("马六");
        student3.setAge("19");
        studentList.add(student3);
        table.setStudentList(studentList);
        table.setStudentList1(studentList);
        return table;
    }

编写接口

@RequestMapping("dynamicTable")
    public void dynamicTable(HttpServletResponse response) {
        //1.组装数据
        StudentTable table = assertData();
        //2.获取根目录,创建模板文件
        String path = copyTempFile("word/2.docx");
        //3.获取临时文件
        String fileName = System.currentTimeMillis() + ".docx";
        String tmpPath = "D:\\" + fileName;
        try {
            //4.编译模板,渲染数据
            LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy();
            Configure config =
                    Configure.builder().bind("studentList", hackLoopTableRenderPolicy).bind("studentList1", hackLoopTableRenderPolicy).build();
            XWPFTemplate template = XWPFTemplate.compile(path, config).render(table);
            //5.写入到指定目录位置
            FileOutputStream fos = new FileOutputStream(tmpPath);
            template.write(fos);
            fos.flush();
            fos.close();
            template.close();
            //6.提供下载
            down(response, tmpPath, fileName);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7.删除临时文件
            File file = new File(tmpPath);
            file.delete();
            File copyFile = new File(path);
            copyFile.delete();
        }
    }

测试

请求接口:http://127.0.0.1:1000/file/dynamicTable

效果如下:

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我),在这里插入图片描述,第8张