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>
pom中还需加入以下依赖,不然部署到Linux服务器上请求接口会报错
参考:将word文档转换成pdf格式【使用Aspose技术实现:亲测可用】_aspose word转pdf-CSDN博客
// 引入aspose-words-15.8.0-jdk16依赖包
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>15.8.0</version>
<scope>system</scope>
<!--路径是aspose-words-15.8.0-jdk16包放的路径-->
<systemPath>${project.basedir}/src/main/resources/lib/aspose-words-15.8.0-jdk16.jar</systemPath>
</dependency>
<!--需要在pom中加入一下代码服务器上才能正常使用 不然打包时候报程序包com.aspose.words不存在-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--将第三方包引入不加这个是linux上是没有的-->
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
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 {
//该路径win开发环境能访问到,Linux服务器不行
/**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();
InputStream templateUrl = getTemplateUrl("search");
try (XWPFTemplate template = XWPFTemplate.compile(templateUrl , config).render(data)) {
ByteArrayOutputStream fos = new ByteArrayOutputStream();
template.write(fos);
Word2PdfAsposeUtil.doc2pdf(fos, response);
}
}
}
private static InputStream getTemplateUrl(String templateFlag) throws URISyntaxException {
String flagSuffix = "";
if ("order".equals(templateFlag)){
flagSuffix = "/order.docx";
} else if ("search".equals(templateFlag)) {
flagSuffix = "/search.docx";
}
/*ClassLoader classLoader = PDFUtils.class.getClassLoader();
URL resource = classLoader.getResource("templates");
String templateDirectory = Objects.requireNonNull(resource).toURI().getPath();*/
//此方式获取的路径在开发环境和正式Linux环境都能访问的到
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("templates/" + flagSuffix);
//linux环境文件路径地址
// String templateUrl = templateDirectory;
//windows环境文件路径地址
// String templateUrl = templateDirectory.replaceFirst("/", "");
// return templateUrl + flagSuffix;
return resourceAsStream;
}
@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