EasyPoi:java导出excel,并从OSS下载附件打包zip,excel中每条记录用超链接关联附件目录
使用esaypoi生成Excel文件,并将附件打包成ZIP。Excel内的每个Sheet包含超链接,指向ZIP内的附件文件夹。程序首先创建Excel工作簿,设置表头,然后遍历数据,为每一项创建超链接到附件目录。
效果图
下载解压后的目录
注意:Window系统在解压缩软件内是打不开附件的,提示失败,需要解压缩后才可以正常打开
EasyPoi简介
⚠️ EasyPoi 已基本停止维护,新项目建议使用 Alibaba EasyExcel
Java代码实现
/*** @Description: 导出政策材料以及附件* @author: ly* @param materialFileDto* @param response* @return void**/public void downloadMaterialFile(MaterialFileDto materialFileDto, HttpServletResponse response) throws Exception {List<MaterialFileDto> materialList = policyLibraryMapper.downloadMaterialFile(materialFileDto);List<MaterialFileDto> materialSampleList = policyLibraryMapper.downloadMaterialSample(materialFileDto);//创建临时目录:用于存放附件和生成 ZIPPath tempDir = Files.createTempDirectory("政策库");Path attachmentsDir = tempDir.resolve("附件目录");Files.createDirectory(attachmentsDir);//循环下载附件for (MaterialFileDto dto : materialSampleList) {downloadAndSetAttachment(dto, attachmentsDir);}for (MaterialFileDto dto : materialList) {downloadAndSetAttachment(dto, attachmentsDir);}// 创建 WorkbookWorkbook workbook = new XSSFWorkbook(); // 或 SXSSFWorkbook 用于大数据// 使用 ExcelExportService 支持多 sheetExcelExportService exportService = new ExcelExportService();// 第一个 Sheet:材料文件ExportParams exportParams1 = new ExportParams();exportParams1.setSheetName("材料表单");exportParams1.setType(ExcelType.XSSF);exportService.createSheet(workbook, exportParams1, MaterialFileDto.class, materialList);// 第二个 Sheet:材料样例ExportParams exportParams2 = new ExportParams();exportParams2.setSheetName("材料样例");exportParams2.setType(ExcelType.XSSF);exportService.createSheet(workbook, exportParams2, MaterialFileDto.class, materialSampleList);// 写入 Excel 到临时文件Path excelFile = tempDir.resolve("政策库信息.xlsx");try (FileOutputStream fos = new FileOutputStream(excelFile.toFile())) {workbook.write(fos);}//添加超链接addHyperlinksToExcel(excelFile, "附件目录");// 创建打包目录(Excel + 附件)Path zipFilePath = Files.createTempFile("政策库信息", ".zip");ZipCompressor zipCompressor = new ZipCompressor(zipFilePath.toString());zipCompressor.compressExe(tempDir.toString());ResponseUtils.downZip(response, "政策库信息.zip", zipFilePath);ZipCompressor.deleteFile(tempDir.toFile());ZipCompressor.deleteFile(zipFilePath.toFile());log.info("===============成功下载政策库附件=================");}/*** @Description: 手动添加超链接* 1、(isHyperlink = true 在 EasyPoi 4.x 及更早版本中存在严重 bug,即使字段有值也会报 空指针异常(NPE)目前最新的版本也没有修复)* 2、EasyPoi 没有标准 CellWriteHandler* 3、推荐使用阿里的easyExcel* @author: ly* @param excelFile* @param attachmentDirName* @return void**/private void addHyperlinksToExcel(Path excelFile, String attachmentDirName) throws IOException {try (FileInputStream fis = new FileInputStream(excelFile.toFile());Workbook workbook = WorkbookFactory.create(fis)) {CreationHelper helper = workbook.getCreationHelper();for (Sheet sheet : workbook) {for (Row row : sheet) {if (row.getRowNum() == 0) continue; // 跳过表头Cell cell = row.getCell(5); // 假设“附件目录”是第6列(索引5)if (cell == null) continue;String value = cell.getStringCellValue();if (value == null || value.isEmpty() ||value.startsWith("无附件") || value.contains("失败")) {continue;}// 创建超链接Hyperlink hyperlink = helper.createHyperlink(HyperlinkType.FILE);hyperlink.setAddress(value); // 相对路径cell.setHyperlink(hyperlink);// 设置样式CellStyle style = workbook.createCellStyle();Font font = workbook.createFont();font.setColor(IndexedColors.BLUE.getIndex());font.setUnderline(Font.U_SINGLE);style.setFont(font);// 设置居中对齐style.setAlignment(HorizontalAlignment.CENTER); // 水平居中style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中cell.setCellStyle(style);}}// 保存try (FileOutputStream fos = new FileOutputStream(excelFile.toFile())) {workbook.write(fos);}}}/*** @Description: 下载文件并设置 attachmentDir 为相对路径* @author: ly* @param dto* @param attachmentsDir* @return void**/private void downloadAndSetAttachment(MaterialFileDto dto, Path attachmentsDir) throws IOException {String ossPath = dto.getFilePath();if (ossPath != null && !ossPath.isEmpty()) {try {String fileName = Paths.get(ossPath).getFileName().toString();// 防止路径穿越if (fileName.contains("..")) {fileName = "invalid_file";}Path targetFile = attachmentsDir.resolve(fileName);// 调用阿里云 SDK 下载(示例伪代码)downloadFromOss(ossPath, targetFile.toFile(), fileName);// 设置相对路径(Windows 风格,Excel 更兼容)dto.setAttachmentDir("附件目录/" + fileName);} catch (Exception e) {// 处理异常,避免中断整个导出dto.setAttachmentDir("下载失败: ");}}}/*** @Description: 获取文件* @author: ly* @param filePath* @param targetFile* @param fileName* @return void**/private void downloadFromOss(String filePath, File targetFile, String fileName) {ossClientUtil.getZipFile(targetFile, filePath , fileName);}
工具类方法以及实体
public void getZipFile(File zipFilePath, String filePath, String fileName) {OSS ossClient = getOssClient();String fileUrl = getObjectKeyFromUrl(filePath);//String fName = getFileName(filePath);//String folder = FileUtils.getFileFolder(type);// OSSObject ossObject = ossClient.getObject(new GetObjectRequest(bucketName, folder+fileName));ossClient.getObject(new GetObjectRequest(bucketName, fileUrl), zipFilePath);ossClient.shutdown();
}public static String getObjectKeyFromUrl(String url) {try {URI uri = new URI(url);return uri.getPath().substring(1);} catch (Exception e) {throw new IllegalArgumentException("Invalid URL: " + url, e);}
}@Getter
@Setter
public class MaterialFileDto {private Long id;@Excel(name="城市",orderNum="1",width=10)private String cityName;@Excel(name="地区",orderNum="5",width=10)private String areaName;@Excel(name="服务类型",orderNum="8",width=15)private String businessName;@Excel(name="服务项目",orderNum="15",width=20)private String serviceName;@Excel(name="材料描述",orderNum="25",width=40, isWrap=true)private String material;@Excel(name = "附件目录",orderNum="35",width=40)private String attachmentDir="";private String filePath;private String fileName;}
到这里可能就会有同学有疑问,为什么不使用 easypoi 提供的便捷功能isHyperlink = true
- EasyPoi 确实支持通过注解 @Excel 的 isHyperlink属性来自动生成超链接单元格,能自动将字段渲染为超链接样式。
❌ isHyperlink = true 在 EasyPoi 4.x 及更早版本中存在严重 bug,即使字段有值也会报 空指针异常(NPE)
✅ 你遇到的问题是 EasyPoi 的已知缺陷,不是你的代码问题
为什么 isHyperlink = true 会空指针?EasyPoi 源码中处理超链接的逻辑如下:
if (isHyperlink) {Hyperlink link = workbook.getCreationHelper().createHyperlink(type);link.setAddress(obj.toString()); // obj 可能为 null → NPEcell.setHyperlink(link);
}
即使你打印 dto.getAttachmentDir() 有值,但在反射取值时可能为 null,导致 NPE。