背景:項目需要使用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