springboot使用xdoc-report包导出word

背景:项目需要使用xdoc-report.jar根据设置好的word模版,自动填入数据 导出word

框架使用

我的需求是我做一个模板然后往里面填充内容就导出我想要的word文件,问了下chatgpt还有百度,最后选用了xdocreport这个框架,主要它使用docx模板以及freemarker模板引擎就可以做导出,不用各种转换,而且文档demo也很齐全,虽然最新的版本已经两年没更新了!!参考文档

制作模板

模板效果图如下

文字显示

比如说,我们需要显示一些文档的元数据,例如 文档的标题,文档的时间等等可以单独显示的属性。我们可以使用一个对象来封装这些属性,现在我封装了一个Project对象

class ExportProject() {  var title: String? = null  var type: String? = null  var car: String? = null  var className: String? = null  var date: String? = null  var problem: String? = null  var range: String? = null  var signature: IImageProvider? = null  constructor(vo: DriverCheckVo) : this() {  type = vo.spec  car = vo.usageCode  className = vo.shift  date = "${vo.date?.year} 年 ${vo.date?.monthValue} 月 ${vo.date?.dayOfMonth} 日"  xproblem = vo.problemSummary  }  
}

生成word文档时,只要new一个对象实例,扔到xdocreport的context中,就可以使用啦。当然具体展示还是需要在docx模板上做特殊处理的。比如我想控制我生成的docx文件中的标题,我直接在标题处,生成一个域即可,具体操作步骤如下,(下列例子中使用了wps),另外模版的文件格式名一定是docx

step-》 将光标放在标题处-》点击插入 -》选择文档部件-》点击域-》选择邮件合并-》输入变量 ${project.title} -》 点击确定 

效果如下!

其他的地方,例如年份,也是一样 输入 变量 ${project.range}即可。这样我们就可以将需要单独显示的文字,控制在xdocreport context里面 project变量里面了,当然具体怎么取名,放在哪,都随便。

列表嵌套

上面说了一些单独显示的文字显示,那列表控制如何显示呢,比如下图

首先还是定义控制对象 

class DriverCheckData {  
/**  
* 大检查项  
*/  
var name: String? = null  /**  
* 是否有小检查项  
*/  
var hasLittleCheck: Boolean? = false  /**  
* 小检查项列表  
*/  
var items: List<DriverCheckItem>? = null  
}  class DriverCheckItem {  
/**  
* 小检查项列表  
*/  
var name: String? = null  /**  
* 序号  
*/  
var sequence: Int? = null  /**  
* 检查内容  
*/  
var content: String? = null  /**  
* 检查结果  
*/  
var result: String? = null  
}

定义了两个类,大检查项类以及小检查项类,大检查项内嵌小检查项。这样我们在输出列表数据时,只需要定义一个大检查项列表就可以展示了,因为是展示的还是文本,所以定义还是跟上面一样,使用域来关联变量,写法么,就跟mybatis,el表达式差不多。

即使用 [#list noTag as data] 这个将 notag 这个list 里面的子元素 定义变量名为 data,[/#list] 代表列表结束,就跟html的标签对一样,然后是,data的数据展示了,直接使用 ${data.name} 这样的域就可以展示data里面的属性,可以看到,我这个图里面 用的不是[#list noTag as data] ,而是加了前缀,list的结尾也加了后缀,这是为了处理docx里面的表格而做的处理。照猫画虎即可,如果不是在表格里展示,直接使用

以下案例即可

«[#list developers as developer]»Name: «${developer.name}»Mail : [${developer.mail}]Mail2 : [${developer.mail}]«[/#list]»

图片展示

例如我们展示文本,使用了域,对于图片呢,需要使用书签,如果我说的不清楚,直接看官方demo

流程如下:

step-》添加一个图片(啥图片都行)当做模板,选中图片-》点击插入-》书签-》添加书签名-》添加

这个书签名非常重要,他的名字对应了我们在context里面设置的变量。如果是单独展示,直接设置一级变量名,与书签名对应即可,但是如果是图片列表,而且我们放在对象里面,怎么办呢?使用官方示例,不用[#list noTag as data],而是在java/kotlin程序里面对该列表进行处理,使用列表名中对应的图片属性即可。详见 官方示例

代码展示

springboot加载模板实例

@Component  
@Data  
class ExportInstanceConfig(  
private val resourceLoader: ResourceLoader  
) {  
private var driverExport: IXDocReport? = null  
private var checkExport: IXDocReport? = null  
@PostConstruct  
fun init(){  
driverExport = getInstance("classpath:check.docx")  
checkExport = getInstance("classpath:driver.docx")  
}  private fun getInstance(path:String):IXDocReport{  
var inputStream :InputStream? = null  
try{  
val res = resourceLoader.getResource(path)  
inputStream = res.inputStream  
return XDocReportRegistry  
.getRegistry()  
.loadReport(  
inputStream,  
TemplateEngineKind.Freemarker  
)  
}catch (e:IOException){  
throw e  
}finally {  
inputStream?.close()  
}  
}

将导出的数据转成二进制数组

private fun exportProcess(report: IXDocReport, context: IContext): ByteArray {  val bos = ByteArrayOutputStream()  val res: ByteArray  try {  // 导入模板  report.process(context, bos)  res = bos.toByteArray()  } catch (e: IOException) {  throw e  } finally {  bos.close()  }  return res  
}

进行单独图片导出

private fun exportDriverCheckDocx(param: DriverCheckVo, title: String): ByteArray {  val report = exportInstanceConfig.getDriverExport()  val metadata = report.createFieldsMetadata()  val context = report.createContext()  val exportProject = ExportProject(param).apply { this.title = title }  // 这里是对上传的图片的base64编码 进行解码val image = param.checkPeopleDocumentary?.split(",")?.let { decoder.decode(it[1]) }  if (image != null) {  // 对应模板中,单独显示的图片metadata.addFieldAsImage("signature")  // 导出图片时,图片对应的类 的格式context.put("signature",ByteArrayImageProvider(ByteArrayInputStream(image)))  }  val noTags = param.dataList?.filter { it.hasLittleCheck == false }?.toList()  val hasTags = param.dataList?.filter { it.hasLittleCheck == true }?.toList()  context.put("project", exportProject)  context.put("noTag", noTags)  context.put("hasTag", hasTags)  return exportProcess(report, context)  
}  

图片迭代导出

private fun exportCheckDocx(param: CheckVo, title: String, range: String?): ByteArray {  val report = exportInstanceConfig.getCheckExport()  val context = report.createContext()  val metadata = report.createFieldsMetadata()  // 对带图片列表的对象进行load处理,方便模板识别metadata.load("re",CheckRecord::class.java,true)  val exportProject = ExportProject().apply {  this.title = title  this.range = range  }  val noTags = param.dataList?.filter { it.hasLittleCheck == false }?.toList()  param.recordList?.forEach { it ->  if(it.documentary!=null){  val image = it.documentary?.split(",")?.let { decoder.decode(it[1]) }  if (image != null) {  it.signature = ByteArrayImageProvider(image).apply {  this.setSize(100f,100f)  }  }  }  }  val hasTags = param.dataList?.filter { it.hasLittleCheck == true }?.toList()  context.put("project", exportProject)  context.put("noTag", noTags)  context.put("hasTag", hasTags)  // 将带图片的列表加载进上下文context.put("re", param.recordList)  return exportProcess(report, context)  
}

带图片的迭代对象定义

class CheckRecord {  
/**  
* 设备型号  
*/  
var spec: String? = null  /**  
* 车号  
*/  
var usageCode: String? = null  /**  
* 检查日期  
*/  
var checkDate: String? = null  /**  
* 检查情况  
*/  
var checkContent: String? = null  /**  
* 整改要求及完成日期  
*/  
var require: String? = null  /**  
* 检查人签名  
*/  
var documentary: String? = null  
/**  
* 图片实体  
*/  
@get:FieldMetadata(images = [ ImageMetadata(name = "signature", behaviour = NullImageBehaviour.RemoveImageTemplate) ])  
var signature: IImageProvider? = null  
}

文件压缩

因为导出docx文件有多个,要求压缩成一个压缩包,这边使用的是

<dependency>  
<groupId>org.apache.commons</groupId>  
<artifactId>commons-compress</artifactId>  
<version>1.23.0</version>  
</dependency>

代码如下,将得到的docx二进制数组转成zip

// 不同类型的文件对应不同的MIME类型  
response.apply {  characterEncoding = "UTF-8" // 设置编码字符  setHeader("Content-disposition", "attachment;filename=${URLEncoder.encode("下载文件" + ".zip", "utf-8")}")  contentType = "application/zip"  
}
val zipOutputStream = ZipArchiveOutputStream(response.outputStream) 
try {   var sequence = 0  taskExportVo.forEach {  // 实例化 ZipEntry 对象,源文件数组中的当前文件  sequence++  val date = it.date  val fileName = when (query.inspType) {  EqpInspPlanType.日常检查 ->  "${it.eqpCategoryName}${it.exportType}${date?.year}${date?.monthValue}${date?.dayOfMonth}-$sequence.docx"  EqpInspPlanType.点检员点检 -> "${it.eqpCategoryName}${it.exportType}.docx"  EqpInspPlanType.专检组专检 -> "${it.eqpCategoryName}${it.exportType}.docx"  }  zipOutputStream.putArchiveEntry(ZipArchiveEntry(fileName)) // 导出单个docx文件val data = memEqpInspCheckRecordService.exportDocx(it)  // 写入zip流data?.let { it1 -> zipOutputStream.write(it1, 0, it1.size) }  zipOutputStream.closeArchiveEntry()  }  
} catch (i: IOException) {  i.printStackTrace()  
} finally{zipOutputStream.close()
}

其他:可视化Word模板设计和导出可以使用NopReport引擎,它不依赖于poi库,直接使用Word进行模板设计。​编辑juejin.cn

附录:Java版本读取数据库数据生成word模板并压缩成Zip中返回

参考:https://juejin.cn/post/7265673876032766015

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

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

相关文章

CodeBuddy实现pdf批量加密

本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 在信息爆炸的时代&#xff0c;PDF 格式因其跨平台性和格式稳定性&#xff0c;成为办公、学术、商业等领域传递信息的重要载体。从机密合同到个人隐私文档&#xff0c…

如何在PyCharm2025中设置conda的多个Python版本

前言 体验的最新版本的PyCharm(Community)2025.1.1&#xff0c;发现和以前的版本有所不同。特别是使用Anaconda中的多个版本的Python的时候。 关于基于Anaconda中多个Python版本的使用&#xff0c;以及对应的Pycharm&#xff08;2023版&#xff09;的使用&#xff0c;可以参考…

STM32F103 HAL多实例通用USART驱动 - 高效DMA+RingBuffer方案,量产级工程模板

导言 《STM32F103_LL库寄存器学习笔记12.2 - 串口DMA高效收发实战2&#xff1a;进一步提高串口接收的效率》前阵子完成的LL库与寄存器版本的代码&#xff0c;有一个明显的缺点是不支持多实例化。最近&#xff0c;计划基于HAL库系统地梳理一遍bootloader程序开发。在bootloader程…

【数据结构】栈和队列(上)

目录 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、栈的概念及结构 2、栈的底层结构分析 二、代码实现 1、定义一个栈 2、栈的初始化 3、入栈 3、增容 4、出栈 5、取栈顶 6、销毁栈 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、…

Vue 3 官方 Hooks 的用法与实现原理

Vue 3 引入了 Composition API&#xff0c;使得生命周期钩子&#xff08;hooks&#xff09;在函数式风格中更清晰地表达。本篇文章将从官方 hooks 的使用、实现原理以及自定义 hooks 的结构化思路出发&#xff0c;全面理解 Vue 3 的 hooks 系统。 &#x1f4d8; 1. Vue 3 官方生…

大语言模型 17 - MCP Model Context Protocol 介绍对比分析 基本环境配置

MCP 基本介绍 官方地址&#xff1a; https://modelcontextprotocol.io/introduction “MCP 是一种开放协议&#xff0c;旨在标准化应用程序向大型语言模型&#xff08;LLM&#xff09;提供上下文的方式。可以把 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 提供了一种…

云原生安全之PaaS:从基础到实践的技术指南

🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 云原生安全之PaaS:从基础到实践的技术指南 一、基础概念 PaaS(Platform as a Service)平台 PaaS是一种云计算服务模型,为开发者提供应用程序的开发、部署和运行环境,涵…

Chrome中http被强转成https问题

原因&#xff1a;2023年11月1日&#xff0c;chrome发布HTTPS-Upgrades功能&#xff0c;在用户访问 http:// 的旧链接之后&#xff0c;会自动尝试跳转到通过加密的 https:// 协议&#xff0c;访问该网站。且探测到 https 服务存在也会自动改成 https。 亲测两种方案可行&#x…

Linux 操作文本文件列数据的常用命令

文章目录 Linux 操作文本文件列数据的常用命令基本列处理命令高级列处理列数据转换和排序列数据统计和分析 Linux 操作文本文件列数据的常用命令 Linux 提供了多种强大的命令来处理文本文件中的列数据&#xff0c;以下是一些最常用的命令和工具&#xff1a; 基本列处理命令 c…

如何理解线性判别分析(LDA)算法?

在高维数据空间中,特征变量呈指数级增长,信息分布密集且复杂。研究者在面对海量特征时,仿佛置身于一幅结构高度抽象且维度交织的多变量图景之中,其解析与建模犹如在一幅复杂的数据宇宙图谱中导航,既需理论框架的指引,也依赖于算法工具的精确刻画。如何从众多维度中筛选出…

鸿蒙UI开发——Builder函数的封装

1、问题引入 我们在开发中可能会遇到这样一个问题&#xff1a;将一个Builder修饰后的函数用变量或者数组记录下来&#xff0c;在业务其他地方使用这些Builder函数。 举个例子&#xff0c;有下面一段代码&#xff1a; Builderfunction builderElement() {}let builderArr: Fu…

ARM笔记-ARM指令集

第三章 ARM指令集 3.1 ARM指令集简介 ARM微处理器的ARM指令集 &#xff0c;所有的指令长度都是32位 &#xff0c;并且大多数指令都在一个单独指令周期内执行。 主要特点&#xff1a; 指令是条件执行的ARM微处理器的指令集是加载/存储型的在多寄存器操作指令中一次最多可以完成…

Spring Boot接口通用返回值设计与实现最佳实践

一、核心返回值模型设计&#xff08;增强版&#xff09; package com.chat.common;import com.chat.util.I18nUtil; import com.chat.util.TraceUtil; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter;import java.io.Serializable;/*** 功能: 通…

2025年上半年软件架构师考试回忆版【持续更新】

文章目录 案例分析1、端AI相对于云AI的优势2、redis持久化&#xff0c;主从库3、解释器架构风格4、知识图谱5、区块链 论文1、基于事件驱动的模型2、多模型数据库及其应用3、负载均衡设计方法4、论软件测试理论及其应用 考试感受 2025年软件考试架构考试于5月24日如期举行&…

Windows下编译Zipios

本文记录在Windows下编译Zipios的流程。 注1&#xff1a;文章内容会不定期更新。 零、环境 操作系统Windows 11VS Code1.92.1Git2.34.1Visual StudioVisual Studio Community 2022CMake3.22.1 一、安装依赖 二、编译 2.1 下载代码 git clone https://github.com/Zipios/Zi…

SOC-ESP32S3部分:11-任务创建

飞书文档https://x509p6c8to.feishu.cn/wiki/EH3owsPahisvl6kL6k3cqaQ3n0g 在我们学习单片机的时候&#xff0c;main函数入口中一般有一个while大循环在不停轮询&#xff0c;如果我们需要实现多种不同的业务&#xff0c;就需要用到状态机&#xff0c;根据不同时刻的要求执行不…

[Git] 如何进行版本回退

版本控制系统最重要的能力之一&#xff0c;就是能够轻松地在项目的不同历史版本之间切换。有时&#xff0c;你可能发现最近的修改引入了严重问题&#xff0c;或者需要回到之前的某个节点重新开始。这时&#xff0c;“版本回退”功能就派上用场了。 版本回退&#xff1a;反方向…

易贝平台关键字搜索技术深度解析

一、核心搜索机制 关键词匹配原理 采用TF-IDF算法计算关键词权重 支持同义词扩展&#xff08;如"phone"匹配"cellphone"&#xff09; 标题权重 > 副标题 > 商品描述 搜索排序因素 # 搜索权重模拟计算 def calculate_rank(keyword, item): title…

深度剖析 MCP SDK 最新版:Streamable HTTP 模式

好记忆不如烂笔头&#xff0c;能记下点东西&#xff0c;就记下点&#xff0c;有时间拿出来看看&#xff0c;也会发觉不一样的感受. 目录 一、概述 二、快速上手&#xff1a;开启 Streamable HTTP 服务端开启 客户端连接 三、深入两个核心参数 stateless_http json_resp…

树莓派开箱上手教程(无需显示器版)

树莓派开箱上手教程&#xff08;无需显示器版&#xff09; 硬件准备 名称参数电源适配器5V电源适配器&#xff0c;至少需要3A的额定电流&#xff0c;配备USB Type-C输出接头microSD卡用来将树莓派的操作系统安装到上边&#xff0c;至少需要8GB容量&#xff0c;一般建议16GB及以…