当前位置: 首页 > news >正文

SpringBoot整合POI-TL动态生成Word文档

1、背景

最近在项目开发过程中,遇到需要动态生成word文档的需求,特意研究了下,最后选择了比较好实现的POI-TL动态生成word文档,POI-TL使用Word模板来进行填充。poi-tl官网。

2、word模板文件

3、项目中pom.xml需要用到的依赖

<!-- poi-tl是基于Apache POI的Word模板引擎 --><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.10.0</version></dependency><!--  上面需要的依赖--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-schemas</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-scratchpad</artifactId><version>4.1.2</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.7</version></dependency>

4、生成文件需要用到的对象

        4.1  授信单信息

                

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;@Schema(description = "管理后台 - 授信单打印 Response VO")
@Data
public class AmountCreditApplyPrintRespVO {@Schema(description = "申请单位名称")private String applyUnitName;@Schema(description = "申请日期")private String applyDateStr;@Schema(description = "额度类型(字典编码:amount_credit_apply_type):INVESTMENT:投资类;MINERAL_CATEGORY:矿产类;FINANCIAL_CATEGORY:财务类;HUMAN_RESOURCES_CATEGORY:人力资源类;SAFETY_AND_ENVIRONMENTAL_PROTECTION_CATEGORY:安全环保类;TECHNOLOGY:科技类;INFORMATIONIZATION_CATEGORY:信息化类;MARKETING:营销类;PRODUCTION_CATEGORY:生产类;OTHERS_TYPE:其他类;")private String typeName;@Schema(description = "企业性质(字典编码:amount_credit_apply_company_type)(STATE_OWNED_ENTERPRISE:国企;PRIVATE_ENTERPRISE:私企;GOVERNMENT_AGENCY:政府机构;OTHERS_TYPE:其他类;)")private String companyTypeName;@Schema(description = "申请事由")private String applyCause;@Schema(description = "额度使用情况及财务账务处理计划")private String plan;@Schema(description = "不纳入授信管理依据")private String excludingCreditDescription;@Schema(description = "附件路径")private String attachmentPath;@Schema(description = "授信申请明细信息")List<AmountCreditApplyDetailPrintRespVO> acadetailList;@Schema(description = "经办人")private String hdlr;@Schema(description = "手机号码")private String hdlrPhn;@Schema(description = "承办单位部门负责人意见")private String organizerDepartOpinion;@Schema(description = "承办单位信用管理部门意见")private String organizerCdOpinion;@Schema(description = "承办单位分管领导意见")private String organizerDeputyOpinion;@Schema(description = "承办单位总经理意见")private String organizerDirectorOpinion;@Schema(description = "逐级单位业务部门意见")private String stepUnitBusinessOpinion;@Schema(description = "逐级单位信用管理部门意见")private String stepUnitCreditOpinion;@Schema(description = "逐级单位分管领导意见")private String stepUnitDeputyChargeOpinion;@Schema(description = "逐级单位总经理意见")private String stepUnitDirectorOpinion;@Schema(description = "云钢股份业务归口管理部门承办意见")private String stepUnitCentralizedCbOpinion;@Schema(description = "云钢股份业务归口管理部门逐级意见")private String stepUnitCentralizedZjOpinion;
}

        4.2 授信单详细信息

        

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;@Schema(description = "管理后台 - 授信单明细信息打印 Response VO")
@Data
public class AmountCreditApplyDetailPrintRespVO {@Schema(description = "序号")private String orderNum;@Schema(description = "客户/供应商类别(字典编码:sply_amt_crdt_appl_dtl_ptnrTp)(CUSTOMER:客户;SUPPLIER:供应商)")private String ptnrTpName;@Schema(description = "客商编码")private String partnerNumber;@Schema(description = "客商名称")private String partnerName;@Schema(description = "实际占用额(万元)")private BigDecimal actualAmount;@Schema(description = "原额度(万元)")private BigDecimal originalAmount;@Schema(description = "申请额度(万元)")private BigDecimal applyAmount;@Schema(description = "使用期限(开始和结束)")private String useDeadline;}

5、Controller层

@GetMapping("/printAmountCreditApply")
@Operation(summary = "打印授信单")
@PreAuthorize("@ss.hasPermission('sply:amount-credit-apply:export')")
@ApiAccessLog(operateType = EXPORT)
public void printAmountCreditApply(@RequestParam("id") Long id,HttpServletResponse response) throws Exception {amountCreditApplyService.printAmountCreditApply(id,response);
}

6、Service层逻辑

@Overridepublic void printAmountCreditApply(Long id, HttpServletResponse response) throws Exception {if (ObjectUtils.isEmpty(id)) {throw exception(AMOUNT_CREDIT_APPLY_ID_NOT_EXISTS);}AmountCreditApplyDO amountCreditApplyDO = amountCreditApplyMapper.selectById(id);List<AmountCreditApplyDetailDO> acadetialList = amountCreditApplyDetailMapper.getAmountCreditApplyDetailListByApplyId(id);//获得指定流程实例的任务列表List<BpmTaskRespDTO> btdtoList = bpmTaskApi.getTaskListByProcessInstanceId(amountCreditApplyDO.getProcessInstanceId()).getData();if (ObjectUtils.isEmpty(amountCreditApplyDO)) {throw exception(AMOUNT_CREDIT_APPLY_NOT_EXISTS);} else if (CollectionUtils.isEmpty(acadetialList)) {throw exception(AMOUNT_CREDIT_APPLY_DETAIL_NOT_EXISTS);}// 先封装填充word的数据AmountCreditApplyPrintRespVO acaprvo = BeanUtils.toBean(amountCreditApplyDO,AmountCreditApplyPrintRespVO.class);acaprvo.setApplyDateStr(DateUtils.localDateTimeToStr(amountCreditApplyDO.getApplyDate(),DateUtils.YYYY_MM_DD));acaprvo.setTypeName(AmountCreditApplyTypeEnum.getEnumByCode(amountCreditApplyDO.getType()).getName());acaprvo.setCompanyTypeName(AmountCreditApplyCompanyTypeEnum.getEnumByCode(amountCreditApplyDO.getCompanyType()).getName());checkAndInitAmountCreditApplyPrintRespVO(acaprvo); // 检查属性是否为nullif (CollectionUtils.isNotEmpty(btdtoList)) {for (BpmTaskRespDTO item : btdtoList) {//UserSimpleBaseDTO usbdto = item.getAssigneeUser();if (item.getName().contains("承办单位部门负责人审批") && StringUtils.isBlank(acaprvo.getOrganizerDepartOpinion())) {acaprvo.setOrganizerDepartOpinion(initApprovalOpinion(item.getReason(),"测试","测试",item.getEndTime())); // 承办单位部门负责人意见} else if (item.getName().contains("承办单位信用管理部门审批") && StringUtils.isBlank(acaprvo.getOrganizerCdOpinion())) {acaprvo.setOrganizerCdOpinion(initApprovalOpinion(item.getReason(),"测试1","测试1",item.getEndTime())); // 承办单位信用管理部门意见} else if (item.getName().contains("承办单位分管领导审批") && StringUtils.isBlank(acaprvo.getOrganizerDeputyOpinion())) {acaprvo.setOrganizerDeputyOpinion(initApprovalOpinion(item.getReason(),"测试2","测试2",item.getEndTime())); // 承办单位分管领导意见} else if (item.getName().contains("承办单位总经理审批") && StringUtils.isBlank(acaprvo.getOrganizerDirectorOpinion())) {acaprvo.setOrganizerDirectorOpinion(initApprovalOpinion(item.getReason(),"测试3","测试3",item.getEndTime())); // 承办单位总经理意见}else if (item.getName().contains("云钢股份业务归口管理部门承办审批") && StringUtils.isBlank(acaprvo.getStepUnitCentralizedCbOpinion())) {acaprvo.setStepUnitCentralizedCbOpinion(initApprovalOpinion(item.getReason(),"测试4","测试4",item.getEndTime())); // 云钢股份业务归口管理部门承办意见}else if (item.getName().contains("逐级单位业务部门审批") && StringUtils.isBlank(acaprvo.getStepUnitBusinessOpinion())) {acaprvo.setStepUnitBusinessOpinion(initApprovalOpinion(item.getReason(),"测试5","测试5",item.getEndTime())); // 逐级单位业务部门意见} else if (item.getName().contains("逐级单位信用管理部门审批") && StringUtils.isBlank(acaprvo.getStepUnitCreditOpinion())) {acaprvo.setStepUnitCreditOpinion(initApprovalOpinion(item.getReason(),"测试6","测试6",item.getEndTime())); // 逐级单位信用管理部门意见} else if (item.getName().contains("逐级单位分管领导审批") && StringUtils.isBlank(acaprvo.getStepUnitDeputyChargeOpinion())) {acaprvo.setStepUnitDeputyChargeOpinion(initApprovalOpinion(item.getReason(),"测试7","测试7",item.getEndTime())); // 逐级单位分管领导意见}else if (item.getName().contains("逐级单位总经理审批") && StringUtils.isBlank(acaprvo.getStepUnitDirectorOpinion())) {acaprvo.setStepUnitDirectorOpinion(initApprovalOpinion(item.getReason(),"测试8","测试8",item.getEndTime())); // 逐级单位总经理意见}else if (item.getName().contains("云钢股份业务归口管理部门逐级审批") && StringUtils.isBlank(acaprvo.getStepUnitCentralizedZjOpinion())) {acaprvo.setStepUnitCentralizedZjOpinion(initApprovalOpinion(item.getReason(),"测试9","测试9",item.getEndTime())); // 云钢股份业务归口管理部门逐级意见}}}// 设置授信单详情List<AmountCreditApplyDetailPrintRespVO> acadprBeans = new ArrayList<>();for (int i = 0; i < acadetialList.size();i++) {AmountCreditApplyDetailDO item = acadetialList.get(i);AmountCreditApplyDetailPrintRespVO bean = BeanUtils.toBean(item, AmountCreditApplyDetailPrintRespVO.class);bean.setOrderNum(String.valueOf(i+1));bean.setPtnrTpName(AmountCreditApplyDetailPtnrTpEnum.getEnumByCode(item.getPtnrTp()).getName());bean.setUseDeadline(initUseDeadline(item.getStartDate(),item.getEndDate(),"至")); // 使用期限(开始和结束)checkAndInitAmountCreditApplyDetailPrintRespVO(bean); // 检查授信单详情对象属性是否为nullacadprBeans.add(bean);}acaprvo.setAcadetailList(acadprBeans);Map<String,Object> dataMap = new ConcurrentHashMap<>();List<Map<String,Object>> detailMap = new ArrayList<>();dataMap = JsonUtils.parseObject(JSON.toJSONString(acaprvo, SerializerFeature.DisableCircularReferenceDetect),Map.class);detailMap = (List<Map<String, Object>>) JSON.parse(JSON.toJSONString(acadprBeans));dataMap.put("acadetailList",detailMap);/*** 4. Configure类是该库中的一个配置类,其作用是提供了一些全局的配置选项* (1) useSpringEL() 开启El表达式{{ }}  word模板中的数据就以这个表达式传递数据 例如:{{companyName}};也可以调用buildGramer("${", "}") 可以修改模板为${}* (2) bind()  绑定标记需要循环的数据* (3) 实现表格行循环的策略 HackLoopTableRenderPolicy 不同poi版本实现行循环的策略不一样;当前案例是poi-tl 1.9.1版本*      1.9.x版本:HackLoopTableRenderPolicy  1.10.x以后的版本:LoopRowTableRenderPolicy*/ConfigureBuilder configureBuilder = Configure.builder().useSpringEL().bind("acadetailList", new LoopRowTableRenderPolicy());Configure config = configureBuilder.build();String fileName = "云南铜业股份有限公司调整额度申请表" + ".docx";ExportWordUtil.exportWordDocx(response, fileName, "printAmountCreditApplyTemplate.docx","static"+ File.separator +"templates", dataMap, config,"UTF-8");}/*** 初始化审批意见内容* @param reason 审批意见* @param nickname 处理人* @param deptName 部门名称* @param endTime 处理时间* @return*/private String initApprovalOpinion(String reason, String nickname, String deptName,LocalDateTime endTime) {String res = "";if (StringUtils.isNotBlank(reason) && StringUtils.isNotBlank(nickname) && StringUtils.isNotBlank(deptName) && ObjectUtils.isNotEmpty(endTime)) {res = reason + System.lineSeparator() + deptName + "  " + nickname + "  " + DateUtils.localDateTimeToStr(endTime,DateUtils.YYYY_MM_DD_HH_MM_SS);}return res;}/*** 对使用期限进行格式处理* @param startDate 使用期限开始时间* @param endDate 使用期限结束时间* @param splice 中间的拼接字符* @return*/private String initUseDeadline(LocalDateTime startDate,LocalDateTime endDate,String splice) {String res = "";if (StringUtils.isBlank(splice)) {splice = "至";}if (ObjectUtils.isNotEmpty(startDate) && ObjectUtils.isNotEmpty(endDate)) {res = DateUtils.localDateTimeToStr(startDate,DateUtils.YYYY_MM_DD) + "  " + splice + "  " + DateUtils.localDateTimeToStr(endDate,DateUtils.YYYY_MM_DD);}return res;}/*** 检查授信单属性是否有值,没有值赋值为空字符,word表达式中如果属性没有值会报错* @param acaprvo 打印的word属性对象*/private void checkAndInitAmountCreditApplyPrintRespVO(AmountCreditApplyPrintRespVO acaprvo) {if (StringUtils.isBlank(acaprvo.getApplyUnitName())) {acaprvo.setApplyUnitName("");}if (StringUtils.isBlank(acaprvo.getApplyDateStr())) {acaprvo.setApplyDateStr("");}if (StringUtils.isBlank(acaprvo.getTypeName())) {acaprvo.setTypeName("");}if (StringUtils.isBlank(acaprvo.getCompanyTypeName())) {acaprvo.setCompanyTypeName("");}if (StringUtils.isBlank(acaprvo.getApplyCause())) {acaprvo.setApplyCause("");}if (StringUtils.isBlank(acaprvo.getPlan())) {acaprvo.setPlan("");}if (StringUtils.isBlank(acaprvo.getExcludingCreditDescription())) {acaprvo.setExcludingCreditDescription("");}if (StringUtils.isBlank(acaprvo.getAttachmentPath())) {acaprvo.setAttachmentPath("");}if (StringUtils.isBlank(acaprvo.getHdlr())) {acaprvo.setHdlr("");}if (StringUtils.isBlank(acaprvo.getHdlrPhn())) {acaprvo.setHdlrPhn("");}if (StringUtils.isBlank(acaprvo.getOrganizerDepartOpinion())) {acaprvo.setOrganizerDepartOpinion("");}if (StringUtils.isBlank(acaprvo.getOrganizerCdOpinion())) {acaprvo.setOrganizerCdOpinion("");}if (StringUtils.isBlank(acaprvo.getOrganizerDeputyOpinion())) {acaprvo.setOrganizerDeputyOpinion("");}if (StringUtils.isBlank(acaprvo.getOrganizerDirectorOpinion())) {acaprvo.setOrganizerDirectorOpinion("");}if (StringUtils.isBlank(acaprvo.getStepUnitBusinessOpinion())) {acaprvo.setStepUnitBusinessOpinion("");}if (StringUtils.isBlank(acaprvo.getStepUnitCreditOpinion())) {acaprvo.setStepUnitCreditOpinion("");}if (StringUtils.isBlank(acaprvo.getStepUnitDeputyChargeOpinion())) {acaprvo.setStepUnitDeputyChargeOpinion("");}if (StringUtils.isBlank(acaprvo.getStepUnitDirectorOpinion())) {acaprvo.setStepUnitDirectorOpinion("");}if (StringUtils.isBlank(acaprvo.getStepUnitCentralizedCbOpinion())) {acaprvo.setStepUnitCentralizedCbOpinion("");}if (StringUtils.isBlank(acaprvo.getStepUnitCentralizedZjOpinion())) {acaprvo.setStepUnitCentralizedZjOpinion("");}}/*** 检查授信单详情对象属性是否有为null的,如果有则重新赋值为空字符* @param bean 授信单详情对象*/private void checkAndInitAmountCreditApplyDetailPrintRespVO(AmountCreditApplyDetailPrintRespVO bean) {if (StringUtils.isBlank(bean.getOrderNum())) {bean.setOrderNum("");}if (StringUtils.isBlank(bean.getPtnrTpName())) {bean.setPtnrTpName("");}if (StringUtils.isBlank(bean.getPartnerNumber())) {bean.setPartnerNumber("");}if (StringUtils.isBlank(bean.getPartnerName())) {bean.setPartnerName("");}if (ObjectUtils.isEmpty(bean.getActualAmount())) {bean.setActualAmount(BigDecimal.ZERO);}if (ObjectUtils.isEmpty(bean.getOriginalAmount())) {bean.setOriginalAmount(BigDecimal.ZERO);}if (ObjectUtils.isEmpty(bean.getApplyAmount())) {bean.setApplyAmount(BigDecimal.ZERO);}if (StringUtils.isBlank(bean.getUseDeadline())) {bean.setUseDeadline("");}}

7、工具类方法

package com.zt.plat.module.capital.utils.wordUtil;import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.util.PoitlIOUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;@Slf4j
public class ExportWordUtil {private String encoding = "UTF-8";private String exportPath = "D:\\data";/*** poi-tl导出word* @param response 响应设置* @param fileName 导出文件名称* @param tplName 模板名称* @param tplPath 模版路径* @param data 设置的数据* @param config 配置* @param encoding 编码方式* @throws Exception*/public static void exportWordDocx(HttpServletResponse response, String fileName, String tplName,String tplPath, Map<String, Object> data, Configure config, String encoding) throws Exception {response.reset();response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Expose-Headers","content-disposition"); // 设置方便前端获取文件名称response.setCharacterEncoding("UTF-8");response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));//获取文件的路径String path = tplPath + File.separator + tplName;String filePathD = URLDecoder.decode(path, "UTF-8");//如果路径中带有中文会被URLEncoder,因此这里需要解码InputStream inputStream = ExportWordUtil.class.getClassLoader().getResourceAsStream(filePathD);XWPFTemplate template = XWPFTemplate.compile(inputStream, config).render(data);OutputStream out = response.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(out);template.write(bos);bos.flush();out.flush();PoitlIOUtils.closeQuietlyMulti(template, bos, out);}}

总结:1、word模版一定、一定、一定要使用Microsoft Word来进行生成,不能使用WPS或其他工具生成。2、渲染模板绑定的对象,对象属性值不能为null,值为null渲染模板会失败。

http://www.dtcms.com/a/407651.html

相关文章:

  • MyComic v1.10.2 集动漫、漫画、小说三合一的娱乐软
  • 时间轴网站设计江苏省 前置审批 网站
  • C++ 类的默认成员函数详解:构造、析构与拷贝构造
  • 网站建设在360属于什么类目在线教育网站源码
  • 企业微信官方网站有做医学手术视频的网站
  • nssctf篇
  • 《代码的“言外之意”:从词源学透彻理解编程》Python 字符串的两个重要性质
  • java面试:可以讲一讲sychronized和ReentrantLock的异同点吗
  • 网站建设江苏网站开发文档下载
  • 阿里云服务器建站个人创建微信小程序
  • 免拔卡刷 TikTok 国际版教程|小米手机+电信卡完整指南
  • 【精品资料鉴赏】194页电力行业DeepSeek大模型的财务智能化应用设计方案
  • 部分网站为什么网页打不开的原因及解决方法wordpress frp穿透
  • 网站建设和运营的课程wordpress账号注册
  • FineReport自定义登录系统技术
  • 网站建设广告平台推广做自己的网站多少钱
  • SyncTV+cpolar:跨地域同步追剧的远程方案
  • Redis 面试宝典
  • 【LeetCode_21】合并两个有序链表
  • 大庆建设工程交易中心网站提供网站建设管理
  • VSCode编译器测试yolo环境配置
  • 网站建设类国外企业招聘网站
  • tp做网站房地产培训网站建设
  • php网站做分享到朋友圈网站设置搜索框是什么知识点
  • 免费psd图片素材网站ui设计培训大概多少钱
  • 《C++程序设计》笔记p6
  • 安徽同济建设集团网站公司搭建网站模板
  • 【读书笔记】《大国大成》
  • C++笔记(基础)引用 inline内联函数
  • 焦作网站建设公司哪家好dz整站网站建设