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

news2024/9/23 3:15:13

文章目录

  • 前言
  • 一、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


在这里插入图片描述

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

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

相关文章

基于MetaGPT构建LLM多智能体

前言 你好&#xff0c;我是GISer Liu&#xff0c;在上一篇文章中&#xff0c;我们用了两万多字详细拆解了单个Agent的组成&#xff0c;并通过Github Trending订阅智能体理解MetaGPT框架的订阅模块如何解决应用问题&#xff0c;但是对于复杂&#xff0c;并行的任务&#xff0c;单…

Java进阶学习笔记20——枚举

认识枚举&#xff1a; 枚举是一种特殊的类。 枚举类的格式&#xff1a; 说明&#xff1a; 第一行是罗列枚举的对象名称。只能写合法的标识符&#xff08;名称&#xff09;&#xff0c;多个名称用逗号隔开。 这些名称本质上都是常量&#xff0c;每个变量都会记住枚举类的一个…

HIVE3.1.3+ZK+Kerberos+Ranger2.4.0高可用集群部署

目录 一、集群规划 二、介质下载 三、基础环境准备 1、解压文件 2、配置环境变量 四、配置zookeeper 1、创建主体 2、修改zoo.cfg 3、新增jaas.conf 4、新增java.env 5、重启ZK 6、验证ZK 五、配置元数据库 六、安装HIVE 1、创建Hiver的kerberso主体 2…

U盘引导盘制作Rufus v4.5.2180

软件介绍 Rufus小巧实用开源免费的U盘系统启动盘制作工具和格式化U盘的小工具&#xff0c;它可以快速将ISO镜像文件制作成可引导的USB启动安装盘&#xff0c;支持Windows或Linux启动&#xff0c;堪称写入镜像速度最快的U盘系统制作工具。 软件截图 更新日志 github.com/pbat…

Digital Image Processing System(DIPS)

数字图像处理系统 Digital Image Processing System&#xff08;DIPS&#xff09; 早前版本&#xff1a; ​​​​​​​DIPS_YTPC OCR-CSDN博客

(南京观海微电子)——TFT LCM的作用

VCOM介绍 VCOM是液晶分子偏转的参考电压 &#xff0c;要求要稳定&#xff0c;对液晶显示有直接影响&#xff0c;具体的屏不同的话 也是不同的。 电压的具体值是根据输入的数据以及Vcom电压大小来确定的&#xff0c;用来显示各种不同灰阶&#xff0c;也就是实现彩色显示GAMMA简…

【知识蒸馏】deeplabv3 logit-based 知识蒸馏实战,对剪枝的模型进行蒸馏训练

本文将对【模型剪枝】基于DepGraph(依赖图)完成复杂模型的一键剪枝 文章中剪枝的模型进行蒸馏训练 一、逻辑蒸馏步骤 加载教师模型定义蒸馏loss计算蒸馏loss正常训练 二、代码 1、加载教师模型 教师模型使用未进行剪枝&#xff0c;并且已经训练好的原始模型。 teacher_mod…

Strategy设计模式

Strategy设计模式举例。 看图&#xff1a; 代码实现&#xff1a; #include <iostream>using namespace std;class FlyBehavior { public:virtual void fly() 0; };class QuackBehavior { public:virtual void quack() 0; };class FlyWithWings :public FlyBehavior …

新人攻略:避开这3大坑,让老员工主动带你飞!

进入职场的新人们&#xff0c;常常会感到困惑和挑战。他们可能会发现自己在与老员工的交流中遇到难题&#xff0c;甚至发现老员工并不愿意花费时间和精力去指导他们。这背后的原因是什么呢&#xff1f;又该如何改善这一现象呢&#xff1f;本文将从新员工的角度出发&#xff0c;…

无人机飞手:ASFC无人机和航模爱好者证书详解

ASFC无人机和航模爱好者证书是由中国航空运动协会&#xff08;ASFC&#xff09;颁发的一种无人机操作资格认证。这种证书在无人机和航模爱好者群体中享有广泛的认可度&#xff0c;并被视为操作无人机的一种重要资质。 ASFC证书的定义和用途十分明确。它是民航局颁发的民用无人驾…

C++中的继承详解

1.继承的概念及定义 1.1继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象 程序设计的…

产品数据特性驱动设计

一、什么是数据特性 一个产品在宏观的视角下,是不同功能模块的有机组合;在微观的视角上,是千丝万缕的数据连接。 基于模块化设计思想,对产品进行业务化梳理,对业务进行模块化拆分出功能模块,功能模块就是产品的“逻辑”,而功能中的数据就是“特性”。 业务:比较固定…

马尔可夫和比奈梅-切比雪夫不等式

目录 一、说明 二、自然界的极限性 三、马尔可夫不等式 3.1 最早提出 3.2 马尔可夫不等式的证明 四、 Bienaym–Chebyshev 不等式 4.1 简要回顾Bienaym–Chebyshev 不等式的历史 4.2 Bienaym — Chebyshev 不等式的证明 五、弱大数定律&#xff08;及其证明&#xff09;…

C语言——⾼位优先与低位优先的不同之处是什么?

一、问题 C语⾔的最⼤特⾊就是可移植性好。根据机器类型的不同&#xff0c;⾼位优先与低位优先也不同。那么&#xff0c;最好的可移植的 C 程序应该同时适⽤这两种类型的计算机。下⾯了解⼀下⾼位优先与低位优先的不同之处。 二、解答 所谓的⾼位优先&#xff0c;就是最低的地…

实现排行榜之Mysql的 OrderBy方法

排行榜之Mysql OrderBy实现 1、排行榜系统的功能点 数据收集与计算 排名规则 实时性 可视化展示 周期性更新 2、排行榜系统基本功能要素 MySQL实现方案 数据量较小&#xff0c;业务场景比较简单。可直接使用 新建表 CREATE TABLE leaderboard( id BIGINT UNSIGNED NOT …

21.Happens-Before原则

文章目录 Happens-Before原则1.Happens-Before规则介绍2.规格介绍2.1.顺序性规则(as-if-serial)2.2.volatile规则2.3.传递性规则2.4.监视锁规则2.5.start规则2.6.join()规则 Happens-Before原则 JVM内存屏障指令对Java开发工程师是透明的&#xff0c;是JMM对JVM实现的一种规范和…

基于51单片机温度报警系统—数码管显示

基于51单片机温度报警系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.DS18B20采集温度&#xff0c;数码管显示温度&#xff1b; 2.温度测量范围&#xff1a;0-99度&#xff1b; 3.当温度低于…

Qt for android 获取USB设备列表(二)JNI方式 获取

简介 基于上篇 [Qt for android 获取USB设备列表&#xff08;一&#xff09;Java方式 获取]&#xff0c; 这篇就纯粹多了&#xff0c; 直接将上篇代码转换成JNI方式即可。即所有的设备连接与上篇一致。 (https://listentome.blog.csdn.net/article/details/139205850) 关键代码…

FPGA实现多路并行dds

目录 基本原理 verilog代码 仿真结果​ 基本原理 多路并行dds&#xff0c;传统DDS的局限性在于输出频率有限。根据奈奎斯特采样定理&#xff0c;单路DDS的输出频率应小于系统时钟频率的一半。但是在很多地方&#xff0c;要使采样率保持一致&#xff0c;所以&#xff0c;为了…

逻辑这回事(一)----FPGA安全编码规范

安全编码的背景、定义 FPGA攻击方式和攻击目的 安全编码价值 2020年4月&#xff0c;来自德国的研究者披露了一个名为“StarBleed”的漏洞&#xff0c;当时引起了业内一片轰动。这种漏洞存在于赛灵思的Virtex、Kintex、Artix、Spartan 等全部7系列FPGA中。通过这个漏洞&#…