Java后端
pom依赖
<!--poi-tl--> <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.12.1</version> </dependency> <!--springEL--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>6.0.10</version> </dependency> <!--aspose 破解 word转pdf--> <dependency> <groupId>com.aspose</groupId> <artifactId>aspose-words</artifactId> <version>15.8.0-jdk16</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/lib/aspose-words-15.8.0-jdk16.jar</systemPath> </dependency>
1、controller类
@Slf4j @RestController @RequestMapping("/searchInfo") public class SearchInfoController { @Resource private ISearchInfoService searchInfoService; /** * @param searchId 寻源id * 寻源比价记录导出 */ @PostMapping("/searchDetailExport") public void searchDetailExport(Long searchId){ searchInfoService.searchDetailExport(searchId); } }
2、service
public interface ISearchInfoService extends IService<SearchInfo> { /** * @param searchId 寻源id * 寻源比价记录导出 */ void searchDetailExport(Long searchId); }
3、serviceImpl
@Slf4j public class SearchInfoServiceImpl extends ServiceImpl<SearchInfoMapper, SearchInfo> implements ISearchInfoService { @SneakyThrows @Override public void searchDetailExport(Long searchId) { //判断寻源比价状态,仅限已完成的寻源比价可以下载导出 Integer state = this.getById(searchId).getState(); if (!Objects.equals(SearchStateEnum.COMPLETE.getCode(), state)) { return; } //寻源比价基本信息 ResponsesDetailVO responsesDetailVO = this.winResultDetail(searchId).getData(); //附件 String files = responsesDetailVO.getFiles(); List<SearchInfoFileDTO> searchInfoFileList = JSON.parseArray(files).toJavaList(SearchInfoFileDTO.class); List<String> fileNameList = searchInfoFileList.stream().map(SearchInfoFileDTO::getName).collect(Collectors.toList()); //格式化响应类型 Integer responseTypeInter = responsesDetailVO.getResponseType(); String responseType = ""; if (responseTypeInter == 0) { responseType = "全部响应"; } else if (responseTypeInter == 1){ responseType = "部分响应"; } //格式化报价截止时间 String deadLineTime = responsesDetailVO.getDeadline().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); SearchInfoExportVO searchInfoExportVO = SearchInfoExportVO.builder() .responsesDetailVO(responsesDetailVO) .otherRemark(responsesDetailVO.getRemark()) .attachmentFileNameList(fileNameList) .responseType(responseType) .deadline(deadLineTime) .productInfoVO(responsesDetailVO.getProductInfoVO()) .build(); //导出 HttpServletResponse response = ServletUtils.getResponse(); PDFUtils.exportSearchToPdf(searchInfoExportVO, response); } }
4、需要导出数据的VO类
@Data @Builder public class SearchInfoExportVO { /** * 寻源比价基本信息 */ private ResponsesDetailVO responsesDetailVO; /** * 采购需求清单 */ private List<ProductInfoVO> productInfoVO; /** * 寻源比价备注 */ private String otherRemark; /** * 附件 */ private List<String> attachmentFileNameList; /** * 附件集合转化为指定格式列表 */ private NumberingRenderData attachmentFileNameData; /** * 报价响应类型 */ private String responseType; /** * 报价截止时间 */ private String deadline; }
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class ResponsesDetailVO { /** * 需求标题 */ private String demandTitle; /** * 报价截止时间 */ @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")//存到数据库 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //从数据库读出 private LocalDateTime deadline; /** * 采购周期开始时间 */ @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")//存到数据库 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //从数据库读出 private LocalDateTime periodStartTime; /** * 采购周期结束时间 */ @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")//存到数据库 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //从数据库读出 private LocalDateTime periodEndTime; /** * 采购周期 */ private String procurementCycle; /** * 采购单位 */ private String procurementUnit; /** * 报价响应类型 0:全部响应 1:部分响应 */ private Integer responseType; /** * 预算总价 */ private BigDecimal totalBudgetPrice; /** * 商品件数 */ private BigDecimal productNum; /** * 公示天数 */ private Integer publicityDay; /** * 采购需求清单 */ private List<ProductInfoVO> productInfoVO; /** * 供应商响应报价-全部响应 */ private List<SupResponseAll> supResponseAll; /** * 供应商响应报价-部分响应 */ private List<SupResponsePart> supResponsePart; /** * 中选结果 */ private List<SelectedResult> selectedResults; /** * 送货方式 */ private String deliveryWay; /** * 送货期限 */ private String deliveryDeadline; /** * 送货地址 */ private String deliveryAddress; /** * 备注 */ private String remark; /** * 附件 */ private String files; }
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class ProductInfoVO { /** * 商品名称 */ private String productName; /** * 品牌 */ private String brand; /** * 型号 */ private String model; /** * 单位 */ private String unit; /** * 数量 */ private BigDecimal num; /** * 预算单价 */ private BigDecimal price; /** * 备注 */ private String remark; }
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class SupResponseAll { /** * 供应商名称 */ private String supplierName; /** * 联系人 */ private String linkman; /** * 联系人手机号码 */ private String phone; /** * 报价时间 */ @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")//存到数据库 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //从数据库读出 private LocalDateTime offerTime; /** * 中选总价 */ private BigDecimal totalPrice; /** * 中选商品数量 */ private BigDecimal productNum; /** * 排名 */ private Integer ranking; /** * 报价明细 */ private List<QuotationDetail> quotationDetail; }
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class QuotationDetail { /** * 商品名称 */ private String productName; /** * 品牌 */ private String brand; /** * 型号 */ private String model; /** * 单位 */ private String unit; /** * 数量 */ private BigDecimal num; /** * 单价报价 */ private BigDecimal unitPrice; /** * 商品sku */ private String productSku; }
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class SelectedResult { /** * 中选供应商 */ private String supplierName; /** * 中选总价 */ private BigDecimal totalPrice; /** * 中选商品数量 */ private BigDecimal productNum; /** * 中选商品 */ private List<SelectedProduct> selectedProducts; }
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class SelectedProduct { /** * 商品名称 */ private String productName; /** * 品牌 */ private String brand; /** * 型号 */ private String model; /** * 单位 */ private String unit; /** * 数量 */ private BigDecimal num; /** * 中选单价 */ private BigDecimal selectedPrice; }
5、导出工具类
public class PDFUtils { private final static String SEARCH_TEMPLATE_RESOURCE = "D:/projects/dev/cx-mall-cloud/cx-modules/cx-mall/src/main/resources/templates/search.docx"; /** * poi-tl 寻源比价word导出PDF * @throws Exception 异常 */ public static void exportSearchToPdf(SearchInfoExportVO data, HttpServletResponse response) throws Exception { //表格行循环插件 LoopRowTableRenderPolicy loopRowTableRenderPolicy = new LoopRowTableRenderPolicy(); NumberingRenderData numberingRenderData = Numberings.ofDecimal(data.getAttachmentFileNameList().toArray(new String[0])).create(); data.setAttachmentFileNameData(numberingRenderData); Configure config = Configure.builder().useSpringEL(false) .bind("productInfoVO", loopRowTableRenderPolicy) .bind("quotationDetail", loopRowTableRenderPolicy) .bind("selectedProducts", loopRowTableRenderPolicy) .build(); try (XWPFTemplate template = XWPFTemplate.compile(SEARCH_TEMPLATE_RESOURCE, config).render(data)) { ByteArrayOutputStream fos = new ByteArrayOutputStream(); template.write(fos); Word2PdfAsposeUtil.doc2pdf(fos, response); } } }
@Slf4j public class Word2PdfAsposeUtil { private static boolean getLicense() { boolean result = false; try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("license.xml")) { // License的包路径必须为com.aspose.words.License License license = new License(); license.setLicense(in); result = true; } catch (Exception e) { log.info(e.getMessage()); } return result; } public static void doc2pdf(OutputStream outputStream, HttpServletResponse response) throws IOException { if (!getLicense()) { // 验证License 若不验证则转化出的pdf文档会有水印产生 return; } OutputStream out = response.getOutputStream(); FileInputStream fileInputStream = null; try { long old = System.currentTimeMillis(); response.setCharacterEncoding("utf-8"); response.setHeader( "Content-Disposition", "attachment;filename=".concat(URLEncoder.encode("test.pdf", StandardCharsets.UTF_8))); response.setContentType("application/pdf"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byteArrayOutputStream = (ByteArrayOutputStream) outputStream; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); Document doc = new Document(byteArrayInputStream); // Address是将要被转化的word文档 doc.save(out, SaveFormat.PDF);// 全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, IOUtils.copy(byteArrayInputStream, out); // EPUB, XPS, SWF 相互转换 long now = System.currentTimeMillis(); System.out.println("pdf转换成功,共耗时:" + ((now - old) / 1000.0) + "秒"); // 转化用时 } catch (Exception e) { log.info(e.getMessage()); }finally { if (out != null && fileInputStream != null) { try { out.flush(); PoitlIOUtils.closeQuietlyMulti(out, fileInputStream); } catch (IOException e) { log.info(e.getMessage()); } } } } }
6、word模板文件(wps/office创建都行,创建好之后放在业务代码对应模块的/resources/templates路径下,例如:src/main/resources/templates/search.docx)
模板中页眉设置插入页码,导出可自动分页:
license放到resources下面 license.xml(破解PDF水印等)
<?xml version="1.0" encoding="UTF-8" ?> <License> <Data> <Products> <Product>Aspose.Total for Java</Product> <Product>Aspose.Words for Java</Product> </Products> <EditionType>Enterprise</EditionType> <SubscriptionExpiry>20991231</SubscriptionExpiry> <LicenseExpiry>20991231</LicenseExpiry> <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber> </Data> <Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature> </License>
导出效果:
注:注意往word模板中填充数据对象字段层级间的关系
poi-tl官方文档:Poi-tl Documentation
参考博客:使用poi-tl填充word模板,并转化为pdf输出(兼容word输出)_poi-tl转成pdf返回前端预览-CSDN博客poi-tl导出复杂word/pdf文档(去除aspose转pdf水印,多张图片、表格循环行合并列、表格带图片、循环多个模板导出,多个文档合并)_poi-tl导出pdf-CSDN博客
破解PDF资源aspose-words.zip(0积分下载):https://download.csdn.net/download/m0_49605579/59212077