一、需求

总体需求是根据word模板和数据,生成对应的word文件。经过技术调查后,确定poi-tl是最合适,最方便的框架。于是参考了官方文档和一些文章,很快就掌握了基本用法,这里附一下官方文档,写的清晰透彻,简单明了。

官方文档:http://deepoove.com/poi-tl/#

源码:https://github.com/Sayi/poi-tl

但是,我的需求中有一个动态生成带小标题的表格数据的问题,如图:

如图中红色框中就是动态的数据,然后绿色框中就是小标题,小标题和下面的数据都需要根据传来的数据,动态的生成。

二、分析

一开始参考了官方文档里的例子,根据数据,动态的生成行和列。这种方式数据是可以动态显示了,但是会出现列和表头不对齐的问题。原因是因为建模板的时候,表格整体是使用拆分单元格的方式建的,导致表格真实的列数和看到的列数不一样。如图所示:

如上图,我们把word文件复制到excel中,就会发现表格总共有11个列。所以,我们可以在生成数据行时,每行生成11个列,然后按照上一行的样式,把列再进行合并。这个思路是没问题,我也实现了,但是和我的需求还是有点不符合。因为这种方法需要知道模板的全部列数,以及模板中每个单元格所占的列数,这两个参数我是取不到的。

于是我又参考了文档里区块对的例子,发现如果把小标题和下面的记录作为一个循环单元,进行动态的循环,最后形成的结果拼起来就是需求里满足的样子。模板如下:

三、代码

这里为了省事,我就把我的接口和测试类直接粘过来了,可以自己做个记录,如果能帮到需要的人,给出一些启发就更好了。

/*** 生成报表的接口* @param file word模板* @param* @param response* @param data* @return* @throws IOException*/@RequestMapping(value = "/getReport", method = RequestMethod.POST)public String doCreateWordByTemplateAndData(MultipartFile file ,String type,HttpServletResponse response,@RequestParam("data") String data) throws IOException {Map<String,Object> renderData = JSONObject.parseObject(data);//增加对条形码的处理System.out.println(data);Set<String> sets = renderData.keySet();for (String key :sets) {//条形码if(key.endsWith("barCode")){FileInputStream barCode = ImgRenderUtil.getBarCode((String) renderData.get(key));renderData.put(key,new PictureRenderData(150,70, ".png",barCode));}//图片if(key.endsWith("img")){FileInputStream imgByBase64 = ImgRenderUtil.getImgByBase64((String) renderData.get(key));renderData.put(key,new PictureRenderData(150,70, ".png",imgByBase64));}if("cycleDatas".equals(key)){List<Map> list = (List<Map>)renderData.get(key);for (Map tableData : list) {for (String tableDataKey : (Set<String>)tableData.keySet()) {//列表项if (tableDataKey.endsWith("_list")){List<String> tableDataList = (List<String>)tableData.get(tableDataKey);ArrayList<TextRenderData> textRenderDatas = new ArrayList<>();for (String str :tableDataList) {textRenderDatas.add(new TextRenderData(str));}tableData.put(tableDataKey, new NumbericRenderData(textRenderDatas));}}}}//列表项if (key.endsWith("_list")){List<String> list = (List<String>)renderData.get(key);ArrayList<TextRenderData> textRenderDatas = new ArrayList<>();for (String str :list) {textRenderDatas.add(new TextRenderData(str));}renderData.put(key, new NumbericRenderData(textRenderDatas));}}//根据类型选择渲染方式类 如果遇到特殊的模板无法直接渲染 可以自定义渲染的策略类RenderPolicy policy = null;if("1".equals(type)){//普通表格列policy = new HackLoopTableRenderPolicy();}Configure config = Configure.newBuilder().bind("tableDatas", policy).build();XWPFTemplate template = XWPFTemplate.compile(file.getInputStream(), config).render(renderData);System.out.println(renderData);ServletOutputStream outputStream = response.getOutputStream();response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment;filename=".concat("result.docx"));try {template.write(outputStream);outputStream.flush();outputStream.close();template.close();} catch (Exception e) {e.printStackTrace();}return "";}

调用接口的测试方法:

  /*** 测试的方法 远程调用生成报表的接口* @param response* @return* @throws IOException*/@RequestMapping(value = "/doTest5", method = RequestMethod.GET)public String doTest5(HttpServletResponse response) throws IOException {List<Map> cycle = new ArrayList<Map>();List<Map> datas1 = new ArrayList<Map>();Map<String,Object> map0 = new HashMap<String, Object>();map0.put("childIndex",1.1);map0.put("checkName","启动会在获得伦理批件和签署协议之后");map0.put("yes","");map0.put("no","");map0.put("NA","");map0.put("problem","");datas1.add(map0);Map<String,Object> map1 = new HashMap<String ,Object>();map1.put("index",1);map1.put("childTitle","启动会");map1.put("tableDatas",datas1);List<Map> datas2 = new ArrayList<Map>();Map<String,Object> map01 = new HashMap<String, Object>();map01.put("childIndex",2.1);map01.put("checkName","研究者文件夹齐全");map01.put("yes","");map01.put("no","");map01.put("NA","");map01.put("problem","");datas2.add(map01);Map<String,Object> map3 = new HashMap<String ,Object>();map3.put("index",2);map3.put("childTitle","研究者文件夹");map3.put("tableDatas",datas2);List<Map> datas3 = new ArrayList<Map>();Map<String,Object> map03 = new HashMap<String, Object>();map03.put("childIndex",3.1);map03.put("checkName","使用的试验方案也伦理批准的版本一致(填写试验方案版本号)");map03.put("yes","");map03.put("no","");map03.put("NA","");map03.put("problem","");datas3.add(map03);Map<String,Object> map06 = new HashMap<String ,Object>();map06.put("childIndex",3.2);map06.put("checkName","使用的知情同意书与伦理批准的版本一致(请注明知情同意书版本号))");map06.put("yes","");map06.put("no","");map06.put("NA","");map06.put("problem","");datas3.add(map06);Map<String,Object> map5 = new HashMap<String ,Object>();map5.put("index",3);map5.put("childTitle","试验方案、知情同意书、研究病历和CRF");map5.put("tableDatas",datas3);cycle.add(map1);cycle.add(map3);cycle.add(map5);Map<String,Object> renderMap = new HashMap<String, Object>();renderMap.put("cycleDatas",cycle);renderMap.put("title","北京某某医院临床试验质量检查报告");renderMap.put("tableName","2015年度1季度器械试验临时检查计划");renderMap.put("num","2020-017");renderMap.put("trialName","xxx的临床研究");renderMap.put("member","xxx");renderMap.put("projectNum","2015-118");renderMap.put("shenbanzhe","xxx有限公司");renderMap.put("checkNum","0");renderMap.put("joinNum","0");renderMap.put("doingNum","0");renderMap.put("finishNum","0");renderMap.put("stopNum","0");Gson gson = new Gson();Map<String, String> params = new HashMap<>();params.put("data", gson.toJson(renderMap));String fileUrl = "xxx.docx";params.put("type","1");File wordTemplate = new File(fileUrl);String postUrl = "http://192.168.131.51:8081/report/getReport";CloseableHttpResponse postResponse = HttpClientUtil.doPostFileAndData(postUrl, wordTemplate, params);InputStream inputStream = null;inputStream = postResponse.getEntity().getContent();int len = 0;byte[] buffer = new byte[1024];ServletOutputStream outputStream = response.getOutputStream();response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment;filename=".concat("result.docx"));while ((len = inputStream.read(buffer,0,1024))!=-1){outputStream.write(buffer,0,len);}outputStream.flush();outputStream.close();inputStream.close();return "ok";}

工具类:

/*** 根据条形码内容 返回条码文件流* @param data* @return* @throws IOException*/public static FileInputStream getBarCode(String data) throws IOException {BarcodeSettings settings = new BarcodeSettings();settings.setType(BarCodeType.Code_128);settings.setData(data);settings.setData2D(data);//设置底部显示文本settings.setShowTextOnBottom(true);settings.setShowText(true);settings.setBarHeight(4);BarCodeGenerator barCodeGenerator = new BarCodeGenerator(settings);BufferedImage bufferedImage = barCodeGenerator.generateImage();ImageIO.write(bufferedImage, "png", new File("Barcode.png"));return new FileInputStream("Barcode.png");}/*** 根据base64的图片编码 获得图片的文件流* @param base64Str* @return*/public static FileInputStream getImgByBase64(String base64Str)  {String base64Img = base64Str.replace("data:image/png;base64,", "");String base64ImgNew = base64Img.replace("data:image/jpg;base64,", "");FileOutputStream fileOutputStream = null;try{byte[] bytes = new BASE64Decoder().decodeBuffer(base64ImgNew.trim());fileOutputStream =new FileOutputStream("img.png");fileOutputStream.write(bytes);return new FileInputStream("img.png");}catch (Exception e){e.printStackTrace();}finally {try {fileOutputStream.close();}catch (IOException e){e.printStackTrace();}}return null;}

旧系统可能会使用httpclient调用接口,方法如下:

/**** @param url 接口地址* @param file 模板文件* @param param 数据参数* @return* @throws ClientProtocolException* @throws IOException*/public static CloseableHttpResponse doPostFileAndData(String url,File file, Map<String,String> param) throws ClientProtocolException, IOException {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;try {HttpPost httpPost = new HttpPost(url);// 相当于<input type="file" name="file"/>MultipartEntityBuilder builder = MultipartEntityBuilder.create();builder.addBinaryBody("file",new FileInputStream(file),ContentType.MULTIPART_FORM_DATA,file.getName());for (Map.Entry<String,String> entry :param.entrySet()) {String key = entry.getKey();// 相当于<input type="text" name="userName" value=userName>StringBody value = new StringBody(entry.getValue(), ContentType.create("text/plain", Consts.UTF_8));builder.addPart(key, value);}HttpEntity entity = builder.build();httpPost.setEntity(entity);response = httpClient.execute(httpPost);HttpEntity entity1 = response.getEntity();} catch (IOException e) {e.printStackTrace();throw new RuntimeException("传出文件及数据是出现异常",e);}return response;}

还可以使用restTemplate模拟表单提交,上传文件和数据,如下:

  MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();params.add("data", renderMap);params.add("file", new FileSystemResource("xxx.docx"));HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.MULTIPART_FORM_DATA);HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(params, headers);String url = "http://192.168.131.51:8081/report/getReport";
//        String url = "http://localhost:8081/report/getReport";ResponseEntity<Resource> httpResponse = restTemplate.exchange(url, HttpMethod.POST, httpEntity, Resource.class);if (httpResponse.getStatusCode().equals(HttpStatus.OK)) {InputStream inputStream = null;OutputStream outputStream = null;inputStream = httpResponse.getBody().getInputStream();outputStream = response.getOutputStream();response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment;filename=".concat("result.docx"));int len = 0;byte[] buffer = new byte[1024];while ((len = inputStream.read(buffer,0,1024))!=-1){outputStream.write(buffer,0,len);}outputStream.flush();outputStream.close();inputStream.close();}

使用poi-tl根据word模板生成word文件——解决生成的表格里数据行有小标题的这种需求相关推荐

  1. 关于最近word模板以及word转pdf的总结

    主要的学习和踩坑 word模板1类型只有文字的只要用这种方式实现非常好,没有图片的word模板;特别注意的是支持.doc的模板 2 第二种带图片的word模板,只支持 docx的 4 word转pdf ...

  2. Java 使用word模板创建word文档报告教程

    上面是java 利用word模板生成的一个word报告文档,利用的是第三方类库Poi-tl 是实现的. poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你 ...

  3. java 导出word,java根据提供word模板导出word文档

    本文主要讲解,利用poi-tl在word中动态生成表格行,进行文字.图片填充.一共提供了两种方式,1.基于本地文件 2.基于网络文件 本文讲解思路,1.先看示例,2. 示例对应的代码展示 3. 基本概 ...

  4. 基于Easypoi+jfree,使用SpringBoot架构,Java编程实现word模板导出Word报表

    目录 1.项目目录结构 2.pom.xml添加的依赖 3.编写jfreeutil工具类 4.编写wordutil工具类 5.编写word模板 7.运行效果 8.复杂布局实现 8.1如何实现图片并排 S ...

  5. poi-tl,根据word模板导出word(表格行循环,表格无表头的情况)

    最近项目里要做一个根据客户提供的word模板导出word的功能,方法有很多,比如easyPoi(对word的支持并不是很好),freeMark(太麻烦不想研究),以及poi-tl, 最后研究了半天发现 ...

  6. 用好Word模板 提高Word操作效率(转)

    用好Word模板 提高Word操作效率(转)[@more@] 巧妙地利用Office模板可以大大方便我们的操作.Word中更是添加了众多好用的模板文件,但是你知道它们到底怎样用吗?如何才能够让它们用得 ...

  7. idea actiBPM插件生成png文件 (解决没有Diagrams或Designer选项问题)

    idea actiBPM插件生成png文件 (解决没有Diagrams或Designer选项问题) 参考文章: (1)idea actiBPM插件生成png文件 (解决没有Diagrams或Desig ...

  8. PowerDesigner经验,sql文件生成pdm文件,并生成中文comment描述

    PowerDesigner经验,sql文件生成pdm文件,并生成中文comment描述 返回导航页 打开pdm工程,使用反向工程 选择这个 选择sql脚本文件,或删除已选 将commen字段映射到na ...

  9. xcode5打包不生成ipa文件而生成文件夹 及 app文件转成ipa

    xcode5打包不生成ipa文件而生成文件夹 小菜我在用xcode5打包ipa时,遇到如下情况 在oganizer 里的distribute 里没有 share选项 由于没有share选项,小菜着实捉 ...

最新文章

  1. [转] Nodejs 进阶:Express 常用中间件 body-parser 实现解析
  2. GGNN(Gated Graph Sequence Neural Networks)
  3. linux命令之创建符号连接-ln
  4. BI工具升级动态增量新功能,让大数据量入集市更便捷
  5. putty, puttycm区别
  6. Win32动态库 Lib文件哪去了
  7. 动态规划之图像压缩问题
  8. python列表删除行_Python DataFrame – 删除具有属于值列表的列值的行
  9. c语言编程学生活动安排表,C语言作业安排表(18学时-周学时2...).doc
  10. Windows系统如何关闭防火墙保姆式教程,超详细
  11. 项目管理知识体系系指南学习总结(一)
  12. 卷积、卷积核的维数、尺寸
  13. PythonStock(37)股票系统:Python股票系统发布V2.0版本,改个名字吧,叫Python全栈股票系统2.0,可以实现数据的抓取(akshare),统计分析,数据报表展示。
  14. 风险度量、马科维茨模型的求解与衍生
  15. 用 vs 跑 lvgl 模拟器
  16. xml与txt文件格式互换
  17. 搬运视频怎么做成原创 | 短视频批量伪原创
  18. 数控计算机实习小结,数控机床实习心得体会
  19. WebRtc以Trickle ICE形式去进行pair
  20. 数据挖掘实战—餐饮行业的数据挖掘之挖掘建模

热门文章

  1. BotVS开发基础—2.11 API绘制图表
  2. AS5045B/AS5145 零位编程 OTP
  3. Python学生公寓管理系统的设计与实现 毕业设计-附源码181047
  4. 1287真的是刘翔的命劫吗
  5. 水利电力专业和计算机专业,水利电力类大学学科排名
  6. 决定我们人生的不是能力,而是选择
  7. 如何投资新零售 新零售未来的发展方向有哪些?
  8. 安卓ROM包改为ZIP格式刷机包
  9. dedecms响应式汽车制造公司网站模板
  10. web开发实战,华为商城网页html源代码