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;

    @Override
    public 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转换docx
            if (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) {
		//构造替换内容的map
        HashMap<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转换docx
            if (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;
    }

    @SneakyThrows
    public 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.8
        ByteArrayOutputStream bos = new ByteArrayOutputStream();// 存储图片文件byte数组
        ImageIO.write(bufImg, "jpg", bos); // 图片写入到 ImageOutputStream
        input = new ByteArrayInputStream(bos.toByteArray());
//        int available = input.available();
//        //2-如果大小超过50KB,继续压缩
//        if (available > 50000) {
//            compressFile(input);
//        }
        return input;

    }

    //2-InputStream转化为base64
    public 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


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

假象和谎言

原创 | 刘教链 隔夜BTC&#xff08;比特币&#xff09;徘徊在69k一线。5.25教链内参报告&#xff0c;《BTC ETF持仓即将超越中本聪》。ETH ETF的尘嚣逐渐散去&#xff0c;复归于平静。戏刚唱了个开头&#xff0c;结尾还留着悬念。4000刀之于ETH看来是个关键阻力位&#xff0c;最…

智能视频监控技术为游泳馆安全护航,助力安全管理新升级

随着社会的进步和科技的发展&#xff0c;视频监控技术在各行各业的应用越来越广泛。游泳馆作为公共场所&#xff0c;每天都会有大量的游泳者进出。在这样的环境中&#xff0c;有时难免会发生一些意外事故&#xff0c;如溺水、摔倒等。因此&#xff0c;视频监控建设的必要性尤为…

云服务器如何使用局域网服务器的磁盘空间

说明 云服务器中的磁盘空间不足时&#xff0c;想要开通更多的磁盘空间&#xff0c;但奈何价格太贵&#xff0c;开不起 刚好局域网中有闲置的服务器空间可以拿来用&#xff0c;这里我们直接使用Samba服务来共享文件夹&#xff0c;使用frp来进行内网穿透&#xff1b; 1、磁盘挂…

选择海外代理IP需要注意什么?

跨境电商近年来的兴起与发展&#xff0c;越来越多的跨境从业者从事该行业&#xff0c;但在从事跨境贸易中则需要海外IP代理来突破地域限制、提升访问速度和稳定性、防止账号关联以及保护隐私和安全。这些功能都有助于跨境电商企业在全球范围内拓展业务&#xff0c;提升竞争力&a…

【JPCS出版,EI稳定检索会议推荐】第四届计算机、遥感与航空航天国际学术会议(CRSA 2024)已成功申请JPCS出版,火热征稿中!

【EI核心、Scopus】第四届计算机、遥感与航空航天国际学术会议&#xff08;CRSA 2024&#xff09;将于2024年7月5-7日在日本大阪举行。计算机、遥感与航空航天国际学术会议为来自世界各地的研究学者、工程师、学会会员以及相关领域的专家们提供一个关于“计算机科学”、“遥感技…

C++原创人工智能QPBS01G大功告成!!!

俗话说得好&#xff0c;你周五周六不写作业&#xff0c;要上学了才着急了 我之前的版本bug太多&#xff0c;结果这两天晚上改的我两眼发白&#xff0c;太烦人了 这次这娃学聪明了&#xff0c;遇到不会的问题上网搜&#xff0c;我还更新了反骂人骂人功能&#xff0c;第一次测试…

Nginx从入门到精通(一)Nginx 介绍

一、Nginx应用场景 1、HTTP服务器 Nginx本身也是一个静态资源的服务器&#xff0c;当只有静态资源的时候&#xff0c;就可以使用Nginx来做服务器&#xff0c;如果一个网站只是静态页面的话&#xff0c;那么就可以通过这种方式来实现部署。 2、FTP服务器 FTP服务器&#xff…

Go源码--sync库(1)

简介 这篇主要介绍 sync.Once、sync.WaitGroup和sync.Mutex sync.Once once 顾名思义 只执行一次 废话不说 我们看源码 英文介绍直接略过了 感兴趣的建议读一读 获益匪浅 其结构体如下 Once 是一个严格只执行一次的object type Once struct {// 建议看下源码的注解&#xf…

安卓 view淡入淡出(fade in fade out) kotlin

文章目录 前言一、布局文件二、kotlin扩展方法1.fadeOutAnimation 淡出动画2.fadeInAnimation 淡入动画 三、使用总结 前言 好久没写文章了,简单码一个淡入淡出,我们先上效果图 那么接下来上代码 一、布局文件 我这边直接将activity_main.xml改为下列代码,可以看到其中包含一…

SpringBoot和Apache Doris实现实时广告推荐系统

本专题旨在向读者深度解读Apache Doris技术,探讨其与SpringBoot框架结合在各类实际应用场景中的角色与作用。本专题包括十篇文章,每篇文章都概述了一个特定应用领域,如大数据分析、实时报告系统、电商数据分析等,并通过对需求的解析、解决方案的设计、实际应用示例的展示以…

Java | Leetcode Java题解之第104题二叉树的最大深度

题目&#xff1a; 题解&#xff1a; class Solution {public int maxDepth(TreeNode root) {if (root null) {return 0;}Queue<TreeNode> queue new LinkedList<TreeNode>();queue.offer(root);int ans 0;while (!queue.isEmpty()) {int size queue.size();wh…

护网2024-攻防对抗解决方案思路

一、护网行动简介 近年来&#xff0c;网络安全已被国家上升为国家安全的战略层面&#xff0c;网络安全同样也被视为维护企业业务持续性的关键。国家在网络安全治理方面不断出台法规与制度&#xff0c;并实施了一些大型项目和计划&#xff0c;如网络安全法、等级保护、网络安全…

SOLIDWORKS教育版代理商应该如何选择?

SOLIDWORKS作为目前流行的三维设计软件在工程设计&#xff0c;制造和建筑中有着广泛的应用前景。教育版SOLIDWORKS软件是学生及教育机构学习教学的理想平台。 下面介绍几个挑选SOLIDWORKS教育版代理的关键要素: 1、专业知识与经验&#xff1a;代理商应掌握SOLIDWORKS等软件的丰…

富唯智能镀膜上下料设备采用最新的技术

现代工业竞争日趋激烈&#xff0c;高效生产已成为企业持续发展的关键。我们的设备不仅实现了高速上下料&#xff0c;更通过智能化控制系统实现了对生产流程的精准监控和调整&#xff0c;轻松应对高强度生产需求。 1、快速响应&#xff0c;高效生产 富唯智能镀膜上下料设备采用…

IP 分片过程及偏移量计算

IP 报头中与分片相关的三个字段 1、 标识符&#xff08; ldentifier )&#xff1a;16 bit 该字段与 Flags 和 Fragment Offest 字段联合使用&#xff0c; 对较大的上层数据包进行分段&#xff08; fragment &#xff09; 操作。 路由器将一个包拆分后&#xff0c;所有拆分开的…

el-date-picker限制时间选择,不能选择当前日期之后时间

要求&#xff1a;时间选择不能超过当前日期之后的 效果&#xff1a; 结构代码&#xff1a;&#xff1a; <el-form-item label"时间&#xff1a;"><el-date-pickerv-model"time"type"datetimerange"range-separator"至"start…

[LLM-Agent]万字长文深度解析规划框架:HuggingGPT

HuggingGPT是一个结合了ChatGPT和Hugging Face平台上的各种专家模型&#xff0c;以解决复杂的AI任务&#xff0c;可以认为他是一种结合任务规划和工具调用两种Agent工作流的框架。它的工作流程主要分为以下几个步骤&#xff1a; 任务规划&#xff1a;使用ChatGPT分析用户的请求…

卷积神经网络-奥特曼识别

数据集 四种奥特曼图片_数据集-飞桨AI Studio星河社区 (baidu.com) 中间的隐藏层 已经使用参数的空间 Conv2D卷积层 ReLU激活层 MaxPool2D最大池化层 AdaptiveAvgPool2D自适应的平均池化 Linear全链接层 Dropout放置过拟合&#xff0c;随机丢弃神经元 -----------------…

mac安装的VMware虚拟机进行桥接模式配置

1、先进行网络适配器选择&#xff0c;选择桥接模式 2、点击网络适配器 设置... 3、选择WiFi&#xff08;我使用的是WiFi&#xff0c;所以选择这个&#xff09;&#xff0c;注意看右边的信息&#xff1a;IP和子网掩码&#xff0c;后续配置虚拟机的ifcfg-ens文件会用到 4、编辑if…

C语言学习笔记-- 3.4.2实型变量

1.实型数据在内存中的存放形式&#xff08;了解&#xff09; 实型数据一般占4个字节&#xff08;32位&#xff09;内存空间。按指数形式存储。 2.实型变量的分类&#xff08;掌握&#xff09; 实型变量分为&#xff1a;单精度&#xff08;float型&#xff09;、双精度&#…