java springboot 写入word文档(word模版: 文字,表格,图片)

前言: 一共找了两个方案,第一个使用比较方便。

第一个方案比较方便,但是局限性也大,适合没用太大变化,只需要替换期中一些文字的模板。

第二个方案比较麻烦,需要编写freemarker模板。 详见 2.3.1 freemarker的参考。
** 可以使用for循环,对象等。。(直接看方案二,2.1和2.2)**

方案一 poi-tl

可以用office,也可用用wps

1.1 依赖

        <!--poi-tl生成word-->
        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.6.0</version>
        </dependency>

1.2 使用

1.2.1 工具类 (先要有模版)

package com.mods.study.lessons;

import com.deepoove.poi.XWPFTemplate;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Map;

public class DocUtils {
   
   
	//map里传的是数据
    public static void generateWord(Map<String, Object> map) throws IOException {
   
   
        String mobanPath  = "D:\\Users\\Administrator\\Desktop\\add.docx"
        
        String outPath = "c:\\word-doc2\\" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSS") + "-" + "一个word文档.docx";//文件输出地址,指定到文件

        //.文件地址的目录  是否存在,不存在新建目录
        File dest = new File(outPath);
        // 检测是否存在目录
        if (!dest.getParentFile().exists()) {
   
   
            dest.getParentFile().mkdirs();// 新建文件夹
        }
        XWPFTemplate render = XWPFTemplate.compile(mobanPath).render(map);
        //这个路径指定的是
        render.writeToFile(outPath);
    }
}

1.2.2 使用

package com.mods.study.controller;

import com.mods.common.result.Result;
import com.mods.study.lessons.DocUtils;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/word-doc")
@Api(tags = "WriteWordController", description = "生成word文件")
public class WriteWordController {
   
   
    @GetMapping
    public Result write() {
   
   
        try {
   
   
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "这是一个标题");
            dataMap.put("author", "这是一个作者名称");
            DocUtils.generateWord(dataMap);
        } catch (IOException e) {
   
   
            e.printStackTrace();
        }
        return new Result();
    }
}

1.3 word模版的创建

新建一个word,正常写内容。需要java替换的地方, 用两层花括号包起来就好了

标题 : {
   
   {
   
   title}}

作者 : {
   
   {
   
   author}}

方案二 freemarker

注意:

需要使用freemarker语法制作模板。。 详见 2.3.1 freemarker的参考

2.1 依赖

注意:和mybatis升成用的可能是同一个东西,都是freemarker引擎。

<!--word写入,需要模版引擎-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.30</version>
</dependency>

2.2 使用

  • 要使用freemarker语法,模板后缀是 .ftl(生成的需要是doc格式)

2.2.1 工具类

注意生成doc,不要docx

package com.mods.common.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.util.Date;
import java.util.Map;
/**
 * @program: world
 * @description: doc工具类
 * @author: leaves
 * @create: 2022-05-09 18:54
 */
public class DocUtils {
   
   

    //生成word
    public static void generateWord(Map<String, Object> map) throws IOException, TemplateException {
   
   
        // ftl模板目录
        String ftlUrl = "d:\\";
        // ftl模板名称
        String ftlName = "template.ftl";
        String filePath = "c:\\word-doc3\\" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSS") + "-" + "一个word文档.doc";//文件输出地址,指定到文件
        //.文件地址的目录  是否存在,不存在新建目录
        File dest = new File(filePath);
        // 检测是否存在目录
        if (!dest.getParentFile().exists()) {
   
   
            dest.getParentFile().mkdirs();// 新建文件夹
        }
        Configuration configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        
        //方式一:指定模板所在文件夹 和 指定模板文件
        configuration.setDirectoryForTemplateLoading(new File(ftlUrl));
        Template template = configuration.getTemplate(ftlName, "utf-8");
        
        //方式二,找项目内的,相对路径...建议方式一
        //configuration.setClassForTemplateLoading(DocUtils.class, "/");//resource下
        //Template template = configuration.getTemplate("templates/template.ftl");
		
		//方式3 自己读文件成字符串,再创建模板template(需要绝对路径)
        //String str = readFile(templatePath + templateName);
        //Template template = new Template("name1", new StringReader(str), configuration);

        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "utf-8"), 10240);
        template.process(map, out);
        out.close();
    }

    //图片转base64
    public static String getImageBase64(MultipartFile file) {
   
   
        InputStream in = null;
        try {
   
   
            in = file.getInputStream();
            byte[] data = new byte[in.available()];
            int read = in.read(data);
            BASE64Encoder encoder = new BASE64Encoder();
            return encoder.encode(data);
        } catch (IOException e) {
   
   
            System.out.println("加载图片未找到,错误原因:" + e);
            e.printStackTrace();
        } finally {
   
   
            try {
   
   
                if (in != null) {
   
   
                    in.close();
                }
            } catch (IOException ignored) {
   
   
            }
        }
        return null;
    }

	//读取文件成字符串
	    private static String readFile(String filePath) {
   
   
        String str = "";
        File file = new File(filePath);
        try {
   
   
            FileInputStream in = new FileInputStream(file);
            int size = in.available();
            byte[] buffer = new byte[size];
            int read = in.read(buffer);
            in.close();
            str = new String(buffer, StandardCharsets.UTF_8);
        } catch (IOException ignore) {
   
   

        }
        return str;
    }
}

2.2.2 使用

package com.mods.study.controller;

import com.mods.common.result.Result;
import com.mods.common.utils.DocUtils;
import freemarker.template.TemplateException;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/word-doc")
@Api(tags = "WriteWordController", description = "生成word文件")
public class WriteWordController {
   
   

    @GetMapping
    public Result write() {
   
   
        try {
   
   
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "这是一个标题");
            dataMap.put("author", "这是一个作者名称");
            	//缩进部分是table里的内容。如果列也要变动,要两层list,双层循环
            	List<Map<String, Object>> tableData = new ArrayList<>();
            	Map<String, Object> line1 = new HashMap<>();
            	line1.put("cent1", "1行1列");
            	line1.put("cent2", "1行2列");
            	line1.put("cent3", "1行3列");
            	line1.put("cent4", "1行4列");
            	line1.put("cent5", "1行5列");
            	tableData.add(line1);
            	Map<String, Object> line2 = new HashMap<>();
            	line2.put("cent1", "2行1列");
            	line2.put("cent3", "2行3列");
            	line2.put("cent4", "2行4列");//特地没放满
            	tableData.add(line2);
            dataMap.put("tableData", tableData);
            //dataMap.put("picture",pictureBase64)//如果要放图片,需要放base64
            DocUtils.generateWord(dataMap);
        } catch (IOException | TemplateException e) {
   
   
            e.printStackTrace();
        }
        return new Result();
    }
}

2.3 模板编辑

2.3.1 freemarker 在线编辑

测试步骤:填template模板->填model data -> 点Evaluate 出现result

2.3.2 freemarker 语法示例

  • 示例模板:template

如果 是图片,则传base64(也是字符串,不过外边包的标签不一样)

这是一个字符串: ${word}

这是一个数组: 
<#if list?? && (list?size > 0)>
   list不为空,内容是
      <#list list as item>
       ${item}<#sep>,</#sep>
      </#list>
<#else>
   list为空
</#if>

这是一个对象:

<#if obj??>
   obj不为空
   ${obj.name}
   ${obj.age}
<#else>
   obj为空
</#if>
  • 参数:model data
word = "alice",
list = [1,2,3],
obj = {
   
   "name":"obj名称","age":12}

  • 效果:result
这是一个字符串: alice

这是一个数组: 
   list不为空,内容是
       1
       2
       3

这是一个对象:

   obj不为空
   obj名称
   12

2.3.3 ftl 模板的创建

注意小心使用格式化,别把freemarker的内容格式化没了

  • 用office或者wps新建一个docx文档,在里边填充一些文字、表格、等内容(图表太难了,慢慢研究)
    如下:

    在这里插入图片描述

  • 将word保存成xml文件(文件->另存为 -> 格式选(xml文档: word2003.xml,wps直接选xml))

  • vscode打开xml文档,里边内容是被压缩了的代码,格式化一下。(用编辑器打开即可,vscode开源,可以使用插件xml,可以格式化一下)

  • 找到需要替换的内容(ctrl+f查找然后替换即可),用freemarker语法替换处理即可。。之后尽量别格式化,不被识别的xml语法(freemarker)会被删掉

    # 标题   ->     <#if title??>${title}<#else>无标题</#if>
    
    # 作者名称 ->    <#if author??>${author}<#else>匿名</#if>
    
    # 图片, 后边的一大坨  -> ${picture}    (找这个标签就行:``<w:pict><w:binData w:name=`` ,后边一大串字符串,就是base64。最好在最外层判断一下,没有图片,整个标签都不要)
    
    # 表格, 需要在w:tr 外边套一个list循环(跟html标签很类似,就是前边多个w:),里边就是正常item(如果是对象,可以用点的形式,使用对象的值)
    
  • 最后重命名成ftl格式。(不改好像也没事,文件名和后缀正确,能找到文件即可)

2.4 案例中的模板

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" xmlns:v="urn:schemas-microsoft-com:vml"
                xmlns:w10="urn:schemas-microsoft-com:office:word"
                xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
                xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
                xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
                xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
                w:macrosPresent="no" w:embeddedObjPresent="no" w:ocxPresent="no" xml:space="preserve"
                xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData">
    <o:DocumentProperties>
        <o:Author>Administrator</o:Author>
        <o:LastAuthor>Administrator</o:LastAuthor>
        <o:Created>2022-05-12T01:35:00Z</o:Created>
        <o:LastSaved>2022-05-12T02:15:50Z</o:LastSaved>
        <o:TotalTime>14400</o:TotalTime>
        <o:Pages>1</o:Pages>
        <o:Words>41</o:Words>
        <o:Characters>41</o:Characters>
        <o:Lines>0</o:Lines>
        <o:Paragraphs>0</o:Paragraphs>
        <o:CharactersWithSpaces>41</o:CharactersWithSpaces>
        <o:Version>14</o:Version>
    </o:DocumentProperties>
    <o:CustomDocumentProperties>
        <o:KSOProductBuildVer dt:dt="string">2052-11.1.0.11636</o:KSOProductBuildVer>
        <o:ICV dt:dt="string">AA494BCD86734597A35EAE4E6BC1D982</o:ICV>
    </o:CustomDocumentProperties>
    <w:fonts>
        <w:defaultFonts w:ascii="Calibri" w:fareast="宋体" w:h-ansi="Calibri" w:cs="Times New Roman"/>
        <w:font w:name="Times New Roman">
            <w:panose-1 w:val="02020603050405020304"/>
            <w:charset w:val="00"/>
            <w:family w:val="Auto"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="E0002EFF" w:usb-1="C000785B" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="400001FF"
                   w:csb-1="FFFF0000"/>
        </w:font>
        <w:font w:name="宋体">
            <w:panose-1 w:val="02010600030101010101"/>
            <w:charset w:val="86"/>
            <w:family w:val="Auto"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="00000203" w:usb-1="288F0000" w:usb-2="00000006" w:usb-3="00000000" w:csb-0="00040001"
                   w:csb-1="00000000"/>
        </w:font>
        <w:font w:name="Wingdings">
            <w:panose-1 w:val="05000000000000000000"/>
            <w:charset w:val="02"/>
            <w:family w:val="Auto"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="00000000" w:usb-1="00000000" w:usb-2="00000000" w:usb-3="00000000" w:csb-0="80000000"
                   w:csb-1="00000000"/>
        </w:font>
        <w:font w:name="Arial">
            <w:panose-1 w:val="020B0604020202020204"/>
            <w:charset w:val="01"/>
            <w:family w:val="SWiss"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="E0002EFF" w:usb-1="C000785B" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="400001FF"
                   w:csb-1="FFFF0000"/>
        </w:font>
        <w:font w:name="黑体">
            <w:panose-1 w:val="02010609060101010101"/>
            <w:charset w:val="86"/>
            <w:family w:val="Auto"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="800002BF" w:usb-1="38CF7CFA" w:usb-2="00000016" w:usb-3="00000000" w:csb-0="00040001"
                   w:csb-1="00000000"/>
        </w:font>
        <w:font w:name="Courier New">
            <w:panose-1 w:val="02070309020205020404"/>
            <w:charset w:val="01"/>
            <w:family w:val="Modern"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="E0002EFF" w:usb-1="C0007843" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="400001FF"
                   w:csb-1="FFFF0000"/>
        </w:font>
        <w:font w:name="Symbol">
            <w:panose-1 w:val="05050102010706020507"/>
            <w:charset w:val="02"/>
            <w:family w:val="Roman"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="00000000" w:usb-1="00000000" w:usb-2="00000000" w:usb-3="00000000" w:csb-0="80000000"
                   w:csb-1="00000000"/>
        </w:font>
        <w:font w:name="Calibri">
            <w:panose-1 w:val="020F0502020204030204"/>
            <w:charset w:val="00"/>
            <w:family w:val="SWiss"/>
            <w:pitch w:val="Default"/>
            <w:sig w:usb-0="E4002EFF" w:usb-1="C000247B" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="200001FF"
                   w:csb-1="00000000"/>
        </w:font>
    </w:fonts>
    
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值