Freemarker word导出教程

本文详细介绍了如何使用Freemarker模板技术,结合XML文档,实现自定义内容的Word文档导出。从创建Word模板到配置FreeMarker,再到处理图片和数据填充,最后通过Controller和Service层实现导出功能,整个过程清晰易懂,适用于需要动态生成Word报告的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简要描述
  由于 easypoi对 word导出支持有限,也不能完成多记录导出。因此本次选择使用 freemarker来实现按自定义模板导出word。

  使用 freemarker生成 word文档,基本流程如使用 easypoi一样简单,唯一的难点在于掌握如何配置 word模板,修改模板内容!

第一步:创建Word模板

(1)建议使用 office创建 word文档。原因:创建完成后,可轻松转为 Word 2003 XML文档(*.xml) 格式。
在这里插入图片描述
在这里插入图片描述
(2)xml 内容修改(重点)

1.模板制作成功后,可使用 Visual Studio Code 或者 IDEA等工具打开 xml文件,打开后如下图:在这里插入图片描述
红色部分:由于 xml文档内容非常多,为了便于修改,可在 Visual Studio Code 商店安装 xml格式化插件。

绿色部分:xml 文档内容虽多,但很多内容是 word相关信息自带,无需关注。

2、直接往下翻,找到 <w:body> </w:body> 标签,有点类似于 HTML,这里面存放的是模板内容,重要标签解释如下图:
在这里插入图片描述注:上图中,不是"图片转成 base64",而是"图片内容的16进制字符串,失误,懒得改图了,特此注明!"

在这里插入图片描述
关于图片内容填充,此处我举个多图片例子,代码如下:

<w:p wsp:rsidR="00FE3676" wsp:rsidRDefault="009F529F">
	<#list image as img>
		<w:r>
			<w:pict>
				<w:binData w:name="${"wordml://img_"+img_index+".jpg"}" xml:space="preserve">${
   img.picture}</w:binData>
				<v:shape id="_x0000_i1026" o:spid="_x0000_i1025" type="#_x0000_t75"
                                         style="width:384pt;height:216pt;visibility:visible;mso-wrap-style:square">
					<v:imagedata src="${"wordml://img_"+img_index+".jpg"}" o:title="2"/>
				</v:shape>
 			</w:pict>
		</w:r>
	</#list>
</w:p>

在这里插入图片描述
在这里插入图片描述

(在博客底部,我会附上一份完整的 xml文件。该文件内容为 `多条记录循环,每条记录里会有多张图片的模板`

(3)将 xml文件后缀改为 ftl文件,至此模板制作成功。

第二步:导入 Jar包

	<!-- freemarker -->
	<dependency>
		<groupId>org.freemarker</groupId>
		<artifactId>freemarker</artifactId>
		<version>2.3.30</version>
	</dependency>

第三步:Controller 层接口

	@ApiOperation(value = "导出 word文档")
    @PostMapping("/word/export")
    @ApiImplicitParams({
   @ApiImplicitParam(name = "id", value = "用户id")})
    public void wordExport(@RequestBody Long id,HttpServletRequest request, HttpServletResponse response) throws Exception {
   
        service.wordExport(id, request, response);
    }

第四步:Service 层业务

	@Override
    public void wordExport(Long id, HttpServletRequest request, HttpServletResponse response) throws IcmsException, IOException {
   
        // 1.查询数据
        User user = userDao.selectList(new QueryWrapper<User>().eq("id",id));

        // 2.创建 map
        Map<String,Object> data = new HashMap<>();
        data.put("name",StringUtils.isEmpty(user.getName()) ? " ":user.getName()); // 姓名
        data.put("age", null == user.getAge() ? " ":user.getAge()); // 年龄
        data.put("sex",StringUtils.isEmpty(user.getSex()) ? " ":user.getSex()); // 性别

        StringBuilder builder = new StringBuilder();
        builder.append("<w:p></w:p>水果名称:草莓");
        builder.append("<w:p></w:p>产地:海南");
        data.put("content",builder.toString()); // 动态内容

        // 3.处理图片
        List<Map<String, Object>> resultPic = new ArrayList<>();

        List<FileInfoModel> files = user.getFiles();
        if(null != files && files.size() > 0){
   
            for (int i = 0; i < files.size(); i++) {
   
                // 判断图片路径是否为空
                if(StringUtils.isEmpty(files.get(i).getUrl()))
                    continue;

                Map<String, Object> pic = new HashMap<>();
                String httpPath = request.getScheme() + "://" + request.getServerName() + files.get(i).getUrl();  // 图片路径
                pic.put("picture", StringUtils.isEmpty(FileUtils.getImageString(httpPath)) ? " ":FileUtils.getImageString(httpPath));
                resultPic.add(pic);
            }
        }else{
   
            Map<String, Object> pic = new HashMap<>();
            pic.put("picture", " ");
            resultPic.add(pic);
        }

        map.put("image",resultPic);

        // 4.执行导出
        FileUtils.freemarkerExport("C:/Users/Administrator/Desktop","test.ftl",data,"C:/Users/Administrator/Desktop","test.docx",response);
    }

关于业务描述,需要注意的点,和使用 easypoi一致。
我的另一篇博客:easypoi word 导出教程

第五步:FileUtils

通用工具类

public class FileUtils {
   

    /**
     * 使用 freemarker 生成word文档
     *
     * @param templateDir  模板所在目录路径
     * @param templateName 模板 例如:xxx.ftl
     * @param data         数据
     * @param fileSavePath 文档生成后,存放的路径
     * @param filename     生成后的文件名称,使用英文
     * @param response
     * @throws Exception
     */
    public static void freemarkerExport(String templateDir, String templateName, Map<String, Object> data, String fileSavePath, String filename, HttpServletResponse response) throws Exception {
   
        // 1.设置 freeMarker的版本和编码格式
        Configuration configuration = new Configuration();
        configuration.setDefaultEncoding("UTF-8");

        // 2.设置 freeMarker生成Word文档,所需要的模板的路径
        configuration.setDirectoryForTemplateLoading(new File(templateDir));

        // 3.设置 freeMarker生成Word文档所需要的模板 ---> xxx.ftl
        Template t = null;
        try {
   
            t = configuration.getTemplate(templateName); // 模板文件名称
        } catch (IOException e) {
   
            throw new IOException("获取 ftl模板失败!" + e.getMessage());
        }

        // 4.生成 Word文档的全路径名称
        File outFile = new File(fileSavePath + filename);

        // 5.创建一个 Word文档的输出流
        Writer writer = null;
        try {
   
            writer = new OutputStreamWriter(new FileOutputStream(outFile), "utf-8");
        } catch (Exception e) {
   
            throw new Exception(e.getMessage());
        }

        try {
   
            // 6.装载数据
            t.process(data, writer);

            response.setCharacterEncoding("utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + filename);
            response.setContentType("application/force-download");

            // 7.读取生成好的 Word文档
            File file = new File(fileSavePath + filename);
            FileInputStream is = new FileInputStream(file);
            OutputStream os = response.getOutputStream();
            byte[] b = new byte[1024];
            int length;
            while ((length = is.read(b)) > 0) {
   
                os.write(b, 0, length);
            }
            os.flush();
            os.close();
            writer.flush();
            writer.close();
        } catch (IOException e) {
   
            throw new IOException(e.getMessage());
        } finally {
   
            deleteTempFile(fileSavePath + filename);
        }
    }

    /**
     * 删除临时生成的文件
     */
    public static void deleteTempFile(String filePath) {
   
        File f = new File(filePath);
        f.delete();
    }
}

至此,按照以上顺序,即可完成 freemarker 导出 word!

二、扩展

1、图片转16进制字符串,并使用 base64编码。本地开发环境

	public static String getImageString(String path) throws Exception {
   
        InputStream input = new FileInputStream(path);
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int numBytesRead = 0;
        while ((numBytesRead = input.read(buf)) != -1) {
   
            output.write(buf, 0, numBytesRead);
        }
        byte[] data = output.toByteArray();
        output.close();
        input.close();
        BASE64Encoder ecoder = new BASE64Encoder();
        return ecoder.encode(data);
    }

2、图片转16进制字符串,并使用 base64编码。Linux 生产环境

	/**
     * 将图片转换为 String存储
     * @param path 文件 http路径
     * @return String
     * @throws IOException
     */
    public static String getImageString(String path) throws IOException {
   
        byte[] data = null;
        URL url = null;
        InputStream input = null;
        try{
   
            url = new URL(path);
            HttpURLConnection httpUrl = (HttpURLConnection) url.openConnection();
            httpUrl.connect();
            httpUrl.getInputStream();
            input = httpUrl.getInputStream();
        }catch (Exception e) {
   
            e.printStackTrace();
            return null;
        }
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int numBytesRead = 0;
        while ((numBytesRead = input.read(buf)) != -1) {
   
            output.write(buf, 0, numBytesRead);
        }
        data = output.toByteArray();
        output.close();
        input.close();
        BASE64Encoder ecoder = new BASE64Encoder();
        return ecoder.encode(data);
    }

三、xml模板

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:aml="http://schemas.microsoft.com/aml/2001/core" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wsp="http://schemas.microsoft.com/office/word/2003/wordml/sp2" xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core" w:macrosPresent="no" w:embeddedObjPresent="no" w:ocxPresent="no" xml:space="preserve">
    <w:ignoreSubtree w:val="http://schemas.microsoft.com/office/word/2003/wordml/sp2"/>
    <o:DocumentProperties>
        <o:Author>Windows 用户</o:Author>
        <o:LastAuthor>Windows 用户</o:LastAuthor>
        <o:Revision>1</o:Revision>
        <o:TotalTime>1</o:TotalTime>
        <o:Created>2021-03-25T11:03:00Z</o:Created>
        <o:LastSaved>2021-03-25T11:04:00Z</o:LastSaved>
        <o:Pages>1</o:Pages>
        <o:Words>0</o:Words>
        <o:Characters>1</o:Characters>
        <o:Company>Microsoft</o:Company>
        <o:Lines>1</o:Lines>
        <o:Paragraphs>1</o:Paragraphs>
        <o:CharactersWithSpaces>1</o:CharactersWithSpaces>
        <o:Version>16</o:Version>
    </o:DocumentProperties>
    <w:fonts>
        <w:defaultFonts w:ascii="等线" w:fareast="等线" w:h-ansi="等线" 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="Roman"/>
            <w:pitch w:val=
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值