freemarker ftl模板 格式、列表、图片

文章目录

  • 前言
  • 一、freemarker实现内容替换
  • 二、ftl 模板
    • 1.word另存ftl
    • 2.编辑ftl文件
      • 2.1 了解一下常用的标记及其说明
      • 2.2 list处理
      • 2.3 红线
      • 2.4 图片
  • 总结


前言

固定内容word生成:freemarker ftl模板
动态表格生成:https://blog.csdn.net/mr_wanter/article/details/126763195

一、freemarker实现内容替换

maven

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>

模板内容替换工具类

import com.spire.doc.Document;
import com.spire.doc.FileFormat;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.Map;
import java.util.UUID;
@Component
public class DocUtils implements InitializingBean {/*** 设置使用的编码格式*/private static final String CHARSET = "UTF-8";/*** 设置使用的版本*/private static final String VERSION = "2.3.0";//加载Word模板的路径@Value("${analysis.report.template.path}")private String analysisReportTemplatePath;//输出Word路径@Value("${analysis.report.res.path}")private String analysisReportResPath;private static String wordModePath;private static String generatePath;@Overridepublic void afterPropertiesSet() {DocUtils.wordModePath = analysisReportTemplatePath;DocUtils.generatePath = analysisReportResPath;}public static String dutyDownloadReport(Map<String, Object> wordData, String wordModeFile, String fileName) {try {// 2. 设置配置内容// 设置版本Configuration configuration = new Configuration(new Version(VERSION));// 指定加载Word模板的路径configuration.setDirectoryForTemplateLoading(new File(wordModePath));// 以UTF-8的编码格式,读取模板文档Template template = configuration.getTemplate(wordModeFile, CHARSET);// 3. 输出文档路径及名称File outFile = new File(generatePath + fileName + UUID.randomUUID() + ".doc");Writer writer = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outFile.toPath()), CHARSET), 10240);// 输出template.process(wordData, writer);writer.flush();writer.close();return outFile.getAbsolutePath();} catch (TemplateException | IOException e) {e.printStackTrace();}return null;}public static void downloadFile(HttpServletResponse response, Map<String, Object> dataMap, String template, String fileName) {OutputStream out = null;FileInputStream fileInputStream = null;String filePath="";try {filePath =  DocUtils.dutyDownloadReport(dataMap, template, fileName);String exportName = fileName+System.currentTimeMillis();exportName = URLEncoder.encode(exportName, "utf-8");File file;//预览兼容doc转换docxif (filePath.endsWith("doc")){Document doc = new Document();doc.loadFromFile(filePath);String docName = filePath.substring(0,filePath.lastIndexOf("."));doc.saveToFile(docName+".docx", FileFormat.Docx);file = new File(docName+".docx");}else {file = new File(filePath);}fileInputStream = new FileInputStream(file);response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");response.setHeader("Content-Disposition", "attachment; filename=" + exportName + ".docx");response.setHeader("FileName", exportName + ".docx");response.setHeader("Access-Control-Expose-Headers", "FileName");//5. 创建缓冲区int len = 0;byte[] bytes = new byte[1024];//6. 获取OutputStream()对象out = response.getOutputStream();//7. 将fileOutputStream流写入到buffer缓冲区,并使用OutputStream将缓冲区中的数据输入到客户端while ((len = fileInputStream.read(bytes))>0){out.write(bytes,0,len);}}catch (Exception e){e.printStackTrace();}finally {try {if(fileInputStream !=null){fileInputStream.close();}if(out !=null){out.close();}} catch (IOException e) {e.printStackTrace();}}}
}

调用示例

public FileResp wordCreate(String eventId,String type, HttpServletResponse response, HttpServletRequest request) {//构造替换内容的mapHashMap<String, Object> dataMap = new HashMap<>();String nowDateStr = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), DatePattern.CHINESE_DATE_PATTERN);dataMap.put("nowDate", nowDateStr);String filePath = "";try {filePath = DocUtils.dutyDownloadReport(dataMap, "decision_plan.ftl", "决策方案模板");File file;//预览兼容doc转换docxif (filePath.endsWith("doc")) {Document doc = new Document();doc.loadFromFile(filePath);String docName = filePath.substring(0, filePath.lastIndexOf("."));doc.saveToFile(docName + ".docx", FileFormat.Docx);file = new File(docName + ".docx");} else {file = new File(filePath);}// 附件表保存FileResp fileResp = fileService.upload(file, "decision");// 决策方案业务表保存save(DecisionPlanReq.builder().eventId(eventId).fileId(fileResp.getFileId()).reportName(reportName + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss")).build());return fileResp;} catch (Exception e) {e.printStackTrace();throw new BusinessException("方案生成失败");}}

生成效果
在这里插入图片描述

二、ftl 模板

1.word另存ftl

word另存为xml后更改名称为decision_plan.ftl

2.编辑ftl文件

编辑必要性:

  1. 上述步骤生成的ftl会出现一段内容被分割成多个 <w:p></w:p>,但是对于替换文本也会出现类似情况导致替换时发生找不到并报错的情况${eventTitle},这时需手动更改。
  2. 针对list数据多行展示或表格需手动处理。
  3. 示例中的红线
  4. 图片插入

在这里插入图片描述

2.1 了解一下常用的标记及其说明

  1. <w:wordDocument>: Word文档的根元素,表示整个文档。
  2. <w:body>: 文档的主体部分,包含了段落和其他内容。
  3. <w:p>: 表示一个段落,即文档中的一个文本块或一个文本行。
  4. <w:r>: 表示一个运行(Run),即段落中的一个文本范围,可以具有不同的格式和样式。
  5. <w:t>: 表示文本内容,位于<w:r>标签内部,用于包含实际的文本字符串。
<?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"><w:body><w:p><!--段落样式写这里--><w:pPr><!--居中对齐--><w:jc w:val="center"/></w:pPr><w:r><!--内容样式写这里--><w:rPr><w:rFonts w:ascii="微软雅黑" w:h-ansi="微软雅黑" w:fareast="微软雅黑" w:cs="微软雅黑" w:hint="fareast"/><!--粗体--><w:b/><w:b-cs/><!--字体大小--><w:sz w:val="36"/><w:sz-cs w:val="44"/></w:rPr><w:t>指挥决策方案</w:t></w:r></w:p><w:p><w:pPr><w:jc w:val="center"/></w:pPr><w:r><w:rPr><w:rFonts w:ascii="微软雅黑" w:h-ansi="微软雅黑" w:fareast="微软雅黑" w:cs="微软雅黑" w:hint="fareast"/><w:sz w:val="24"/><w:sz-cs w:val="32"/></w:rPr><w:t>第x期</w:t></w:r></w:p><w:p><w:pPr><w:jc w:val="center"/></w:pPr><w:r><w:rPr><w:rFonts w:ascii="微软雅黑" w:h-ansi="微软雅黑" w:fareast="微软雅黑" w:cs="微软雅黑" w:hint="default"/><w:b/><w:b-cs/><w:sz w:val="28"/><w:sz-cs w:val="36"/></w:rPr><w:t>${eventTitle}</w:t></w:r></w:p></w:body>
</w:wordDocument>

2.2 list处理

<#list approveList as row><w:p><w:r><w:rPr><w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:cs="宋体" w:hint="default"/><w:color w:val="333333"/><w:kern w:val="0"/><w:sz w:val="24"/></w:rPr><w:t>${row.id}</w:t></w:r></w:p>
</#list>

2.3 红线

2.4 图片

  1. html格式的直接img标签读取
  2. Office Open XML文档需要将图片转为base64渲染
<#list process.fileRespList as file>
<w:p>
<w:pPr>
<w:rPr>
<w:rFonts w:fareast="宋体" w:hint="fareast"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:fareast="宋体" w:hint="fareast"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
<w:pict>
<w:binData w:name="wordml://1.png">${file.fileBase64} </w:binData>
<v:shape id="_x0000_s1026" o:spt="75" alt="Snipaste_2024-05-10_17-26-29" type="#_x0000_t75" style="height:113.25pt;width:188.25pt;" filled="f" o:preferrelative="t" stroked="f" coordsize="21600,21600">
<v:path/>
<v:fill on="f" focussize="0,0"/>
<v:stroke on="f"/>
<v:imagedata src="wordml://1.png" o:title="Snipaste_2024-05-10_17-26-29"/>
<o:lock v:ext="edit" aspectratio="t"/>
<w10:wrap type="none"/>
<w10:anchorlock/>
</v:shape>
</w:pict>
</w:r>
</w:p>
</#list>

在这里插入图片描述

总结

  1. 有必要了解一些常用的标记及其说明,不必全懂,基础格式清晰即可。
  2. 不适合直接写ftl文件,用word另存的方式可以省下很多样式和排版的精力,只需另存后修改即可(格式化后发现很多无用的或不满足样式的代码,修剪一下吧)
  3. 如果word的视图不要求,可以采用html的文本“画”一个word,freemarker替换后生成的word打开默认是web视图(常规是页面视图)
  4. 有的图片过大会导致图片渲染失败,需要压缩后转base64
  • thumbnailator 用于图片压缩
  • webp-imageio 用于适配不同的图片格式,否则不支持的图片会报错ImageIO.read(input)是null
<dependency><groupId>net.coobird</groupId><artifactId>thumbnailator</artifactId><version>0.4.8</version></dependency><!-- https://mvnrepository.com/artifact/org.sejda.imageio/webp-imageio 第三方库处理图片--><dependency><groupId>org.sejda.imageio</groupId><artifactId>webp-imageio</artifactId><version>0.1.6</version></dependency>
import lombok.SneakyThrows;
import net.coobird.thumbnailator.Thumbnails;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Base64;public class FileUtils {public static boolean isImageFile(String fileName) {if (fileName == null) {return false;}// 获取文件扩展名String extension = getFileExtension(fileName);if (extension == null) {return false;}// 检查扩展名是否在图片扩展名列表中return extension.matches("(jpg|jpeg|png|gif|bmp|JPG|JPEG|PNG|GIF|BMP)$");}private static String getFileExtension(String fileName) {if (fileName == null) {return null;}int dotIndex = fileName.lastIndexOf('.');if (dotIndex > 0 && dotIndex < fileName.length() - 1) {return fileName.substring(dotIndex + 1).toLowerCase();}return null;}@SneakyThrowspublic static String convertImageToBase64(String imagePath) {File file = new File(imagePath);FileInputStream fileInputStream = null;try {fileInputStream = new FileInputStream(file);} catch (FileNotFoundException e) {throw new RuntimeException(e);}InputStream inputStream = null;try {inputStream = compressFile(fileInputStream);} catch (IOException e) {throw new RuntimeException(e);}return getBase64FromInputStream(inputStream);}//1-压缩图片public static InputStream compressFile(InputStream input) throws IOException {//1-压缩图片BufferedImage bufImg = ImageIO.read(input);// 把图片读入到内存中if(bufImg == null) {return input;}bufImg = Thumbnails.of(bufImg).width(500).keepAspectRatio(true).outputQuality(1f).asBufferedImage();//压缩:宽度100px,长度自适应;质量压缩到0.8ByteArrayOutputStream bos = new ByteArrayOutputStream();// 存储图片文件byte数组ImageIO.write(bufImg, "jpg", bos); // 图片写入到 ImageOutputStreaminput = new ByteArrayInputStream(bos.toByteArray());
//        int available = input.available();
//        //2-如果大小超过50KB,继续压缩
//        if (available > 50000) {
//            compressFile(input);
//        }return input;}//2-InputStream转化为base64public static String getBase64FromInputStream(InputStream in) {// 将图片文件转化为字节数组字符串,并对其进行Base64编码处理byte[] data = null;// 读取图片字节数组try {ByteArrayOutputStream swapStream = new ByteArrayOutputStream();byte[] buff = new byte[100];int rc = 0;while ((rc = in.read(buff, 0, 100)) > 0) {swapStream.write(buff, 0, rc);}data = swapStream.toByteArray();} catch (IOException e) {e.printStackTrace();} finally {if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}}String str = new String(Base64.getEncoder().encode(data));System.out.println("str length: " + str.length() + "  str: " + str);return str;}
}

参考:
https://blog.csdn.net/weixin_45565886/article/details/131659741
https://blog.csdn.net/qq_34412985/article/details/96465187
https://blog.csdn.net/muhuixin123/article/details/135479978
https://blog.csdn.net/weixin_42313773/article/details/136271191
https://blog.csdn.net/qq_36635569/article/details/125223917


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/17639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫入门到进阶:解锁网络数据的钥匙

Python爬虫入门到进阶&#xff1a;解锁网络数据的钥匙 一、Python爬虫基础1.1 爬虫基本概念1.2 Python爬虫必备库1.3 第一个爬虫示例 二、实战爬虫实例2.1 爬取天气数据2.2 高级技巧&#xff1a;异步爬虫 三、反爬机制与应对策略3.1 常见反爬机制3.2 应对策略 四、性能优化与安…

【经验分享】可视化的项目管理,轻松解决资源冲突和协作困难

在数字化时代&#xff0c;高效协同逐步成为提升组织效能的重要着力点&#xff0c;同时也是企业保持竞争力、实现持续发展的关键要素。一方面可以打破部门壁垒&#xff0c;促进信息流通&#xff0c;从而提升整体工作效率&#xff1b;另一方面还能帮助企业优化资源配置和管理流程…

keithely 2401 低压源表

Keithley 2401 低压源表提供精密电压和电流源以及测量功能&#xff08;1V - 20V 和 10pA - 1A&#xff09;。它既是高度稳定的直流电源&#xff0c;也是真正的仪器级 5 位万用表。电源特性包括低噪声、精度和回读。万用表功能包括高重复性和低噪声。结果是紧凑型单通道直流参数…

JavaSE:String类常用方法(巨详细)

1、引言 在学习C语言时&#xff0c;我们就已经了解了字符串&#xff0c;但是在C语言中字符串只能用字符数组或者指针来表示&#xff0c;没有特定的字符串类型&#xff0c;而在Java中&#xff0c;我们有了字符串这一特定的类型---String类。 2、String类的常用方法 2.1 字符串…

第98天:权限提升-WIN 全平台MSF 自动化CS 插件化EXP 筛选溢出漏洞

目录 思维导图 前置知识 案例一&#xff1a; Web&Win2008-人工手动&全自动msf-筛选&下载&利用 手动 全自动msf 案例二: Web&Win2019-CS 半自动-反弹&插件&利用 思维导图 前置知识 提权方式&#xff0c;这里讲的是溢出漏洞 windows权限 常…

Java处理CSV文件示例

Java处理CSV文件示例 1. 导入依赖 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-csv</artifactId><version>1.10.0</version></dependency>文件示例 下面是示例文件文件数据 vscode和idea都有解析…

智慧林业云巡平台 客户端和移动端(支持语音和视频)自动定位巡护,后端离线路线监测

目前现状 无法客观、方便地掌握护林员的到位情况&#xff0c;因而无法有效地保证巡护人员按计划要求&#xff0c;按时按周期对所负责的林区开展巡护&#xff0c;使巡护工作的质量得不到保证。遇到火情、乱砍滥伐等灾情时无法及时上报处理&#xff0c;现场状况、位置等信息描述…

[IM002][Microsoft][ODBC驱动程序管理器]未发现数据源名称并且未指定默认驱动程序(0]

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 使用navicat 连接sql server出现[IM002][Microsoft][ODBC驱动程序管理器]未发现数据源名称并且未指定默认驱动程序&#xff08;0] 二、原因分析 Navicat没有找到电脑安装的SQLServer驱动 三、解决方…

向传音手机学习产品市场定位与产品需求定义

2024 年第一季度全球智能手机发货量同比增长 11%&#xff0c;排在第一名的是三星&#xff0c;占比 21%&#xff0c;苹果占比 17% 排在第二位&#xff0c;小米 14%排在第三名&#xff0c;传音手机10% 排在第四位&#xff0c;OPPO为 9% 排在第五名。 「非洲之王」传音手机表现十…

【Python】 如何从日期中减去一天?

基本原理 在编程中&#xff0c;日期和时间的处理是一个常见的需求&#xff0c;尤其是在处理日志、调度任务、数据分析等场景中。Python 提供了多种方式来处理日期和时间&#xff0c;其中最常用的库是 datetime。datetime 模块包含了日期&#xff08;date&#xff09;、时间&am…

【python脚本】修改目标检测的xml标签(VOC)类别名

需求&#xff1a; 在集成多个数据集一同训练时&#xff0c;可能会存在不同数据集针对同一种目标有不同的类名&#xff0c;可以通过python脚本修改数据内的类名映射&#xff0c;实现统一数据集标签名的目的。 代码&#xff1a; # -*- coding: utf-8 -*- # Time : 2023/9/11 1…

全球排名第一的免费开源ERP:Odoo与微信集成的应用场景解析

概述 本文介绍了世界排名第一的开源免费企业应用软件Odoo ERP和企业微信、个人微信的各种对接功能。包括微信登录的对接、微信公众号的对接、微信消息的对接、微信支付的对接、微信打卡的对接、微信小程序的对接。 微信登录的对接 Odoo的登录&#xff0c;除了标准的用户名/密码…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-23.3,4,5,6 讲 I2C驱动-读取AP3216C传感器​

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

什么是知识中台?为什么企业需要知识中台?

如今市面上的企业数不胜数&#xff0c;企业的任何一个小细节都会产生很大的影响。近几年来一直很热门的知识中台备受企业关注。关于如何高效地管理、整合和运用知识&#xff0c;成为了每一家企业都在重点关注的问题。而知识中台&#xff0c;就是为了解决这一问题而诞生的一个全…

豪赌?远见?浙江东方的量子冒险

今年4月16日&#xff0c;量子通信概念异动&#xff0c;浙江东方&#xff08;600120&#xff09;拉升涨停。 量子和浙江东方&#xff0c;要把这两个词联系起来似乎并不太容易。 浙江东方&#xff0c;即浙江东方金融控股集团股份有限公司&#xff0c;系浙江省国资委下属浙江省国…

地下18米的科技守护:旗晟综合管廊巡检机器人

近日&#xff0c;安徽某业主的地下18米深的地下管廊处&#xff0c;一种先进的巡检机器人正活跃在管廊轨道上&#xff0c;执行着重要的巡检任务&#xff0c;只见机器人在管廊轨道上平稳前行&#xff0c;它搭载着先进的检测设备&#xff0c;对地下管廊内的各种设施进行监测巡检&a…

java -- jar打包成exe -- 携带jre环境

java的项目一般都是以jar发布&#xff0c;很少打包为可执行程序&#xff0c;因此常见的打包方式也不多&#xff0c;且即使打包之后也需要jre环境才能运行&#xff0c;大部分打包都不会携带jre&#xff0c;需要手动添加jre。这里介绍几种我用过的打包方案。 exe4j(不推荐) jpac…

关于已配好java环境但双击无法打开jar包的解决方案

如果你已经装好了 java 环境直接跳到最后看解决方法即可 先说一下你安装的 java 环境&#xff0c;如果完全是默认选项安装&#xff0c;则会安装 jdk 和 jre&#xff0c;并且在安装 jre 时还需要安装目录下为空&#xff0c;其实 jre 的安装是多余的&#xff0c;因为安装的 jdk 里…

Teamcenter 查询包含Assembly的ItemRevision

问题描述 需要得到所有包含Assembly的ItemRevision。 TC OOTB的查询方式没有可用的&#xff0c;需要在Query Builder中进行自定义。 进一步分析&#xff0c;如果ItemRevision包含Assembly&#xff0c;其必定包含BOMViewRevision。 解决方案 自定义如下查下。 注意&#xff1…

vue 微信公众号定时发送模版消息

目录 第一步&#xff1a;公众号设置 网页授权第二步&#xff1a;引导用户去授权页面并获取code第三步&#xff1a;通过code换取网页授权access_token&openid第四步&#xff1a;后端处理绑定用户和发送消息 相关文档链接&#xff1a; 1、微信开发文档 2、订阅号/服务号/企业…