3.Java中根据用户需求将业务数据详情中的文件用压缩包的形式导出
继上一个需求后呢,最近又在另一个项目中遇到了一个相似的需求,就是用户要求将业务数据详情中的所有文件全部下载下来方便查看。
需求描述:
a、业务详情文件
b、实现效果
此时业务详情页面有导出按钮,要求导出时,要求所有文件打包成zip压缩包返回给用户,实现效果如下:
c、实现过程
1.添加pom依赖
<dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-JAXB-MOXy</artifactId> <version>8.3.13</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.5</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.13.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.32</version> <scope>compile</scope> </dependency>
2.代码实现
a、文件操作工具类
package com.yinhai.stagetwo.util; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ZipUtil; import com.yinhai.stagetwo.entity.export.ProjectData; import org.apache.poi.xwpf.usermodel.*; import org.docx4j.jaxb.Context; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.exceptions.InvalidFormatException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.PartName; import org.docx4j.openpackaging.parts.WordprocessingML.AlternativeFormatInputPart; import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart; import org.docx4j.relationships.Relationship; import org.docx4j.wml.CTAltChunk; import javax.xml.bind.JAXBException; import java.io.*; import java.lang.reflect.Field; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; /** * @author gc * @date 2025/1/6 14:11 */ public class GenerateOnWordTemplateKit { public static final String TEMPLATE_PATH = "本地项目地址 + /src/main/resources/template/"; public static final String FILE_PATH = TEMPLATE_PATH + "file/"; public static final String OUTPUT_PATH = TEMPLATE_PATH + "output/"; public static void main(String[] args) throws Exception { // 程序入口,主要作用是为多个项目分别根据模版组装数据后合并成一个word // 1. 准备数据(模拟从数据库中查询,key为项目名称,value为项目数据) Map<String, ProjectData> dataMap = queryData(); // 2. 为每个项目生成一个以项目名为名称的文件夹,文件夹内包括word文档、附件 // 2.1 创建一个临时文件夹 String tempDirPath = FileUtil.mkParentDirs("temp/" + UUID.randomUUID()).getAbsolutePath(); // 2.2 在这个临时文件夹下面,根据数据生成多个项目文件夹及word文档、附件 generateAllWordDocument(dataMap, tempDirPath); // 3. 打包整个临时文件夹为zip压缩包 File projectZipFile = compressIntoAZipFile(tempDirPath); System.out.println("压缩包路径保存:" + projectZipFile.getAbsolutePath()); // 4. 删除所有临时文件 deleteAllTempFiles(tempDirPath); } /** * 打包整个临时文件夹为zip压缩包 * * @param tempDirPath 需要打包的文件夹目录 * @return 压缩包文件 */ public static File compressIntoAZipFile(String tempDirPath) { return ZipUtil.zip(tempDirPath, OUTPUT_PATH + "慈善奖附件_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN) + ".zip"); } /** * 删除所有临时文件 * * @param tempDirPath 临时文件夹路径 */ public static void deleteAllTempFiles(String tempDirPath) { FileUtil.del(tempDirPath); } /** * 生成所有项目文档 * * @param dataMap 所有项目信息,key为项目名称,value为项目数据 * @param tempDirPath 临时目录,用于存储多个临时Word文档 * @throws Exception 异常 */ public static void generateAllWordDocument(Map<String, ProjectData> dataMap, String tempDirPath) throws Exception { for (Map.Entry<String, ProjectData> entry : dataMap.entrySet()) { // 2.2 创建一个以项目名为名称的文件夹 File projectDir = new File(tempDirPath + "/" + entry.getKey()); if (!projectDir.exists() && !projectDir.mkdirs()) { throw new RuntimeException("文件夹[" + projectDir.getAbsolutePath() + "]创建失败"); } // 2.3 为每个项目生成一个word文档 ProjectData projectData = entry.getValue(); generateWordDocument(projectData, projectDir); } } /** * 生成项目word文档 * * @param projectData 项目信息 * @param projectDir 临时目录,用于存储多个临时Word文档 * @throws Exception 异常 */ private static void generateWordDocument(ProjectData projectData, File projectDir) throws Exception { // 创建一个列表,用于存储多个临时Word文档地址 List<String> tempWordList = new ArrayList<>(); tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_1.docx", projectData, projectDir)); tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_3.docx", projectData, projectDir)); tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_3.docx", projectData, projectDir)); mergeDocuments(projectData.getProjectName(), tempWordList, projectDir); // 删除临时文件 tempWordList.forEach(FileUtil::del); } /** * 模拟从数据库中查询数据 * * @return 返回一个模拟数据 */ private static Map<String, ProjectData> queryData() { Map<String, ProjectData> dataMap = new HashMap<>(); for (int i = 1; i <= 2; i++) { // 创建模拟项目数据 ProjectData project = new ProjectData(); project.setProjectName("“友邻有爱”社区养老互助服务项目-" + i); dataMap.put(project.getProjectName(), project); } return dataMap; } /** * 生成word文档 * * @param templatePath word模版路径 * @param dataObj 数据对象 * @param tempDirFile 临时目录 * @return 临时文件路径 * @throws Docx4JException 异常 * @throws JAXBException 异常 * @throws IOException 异常 */ private static String createWordMLPackage(String templatePath, Object dataObj, File tempDirFile) throws Docx4JException, JAXBException, IOException { WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(templatePath)); // 填充数据到模板 Map<String, String> dataMap = objectToMap(dataObj); wordMLPackage.getMainDocumentPart().variableReplace(dataMap); // 保存新的Word文档 String templateFileExtension = templatePath.substring(templatePath.lastIndexOf(".") + 1); File tempFile = new File(tempDirFile.getAbsolutePath() + "/" + UUID.randomUUID() + "." + templateFileExtension); if (!tempFile.exists() && !tempFile.createNewFile()) { throw new RuntimeException("文件[" + tempFile.getAbsolutePath() + "]创建失败"); } wordMLPackage.save(tempFile); return tempFile.getAbsolutePath(); } /** * 将对象转换为Map * * @param dataObj 对象 * @return 返回一个Map */ private static Map<String, String> objectToMap(Object dataObj) { if (null == dataObj) { return MapUtil.empty(); } Map<String, String> dataMap = new HashMap<>(); // 使用反射获取dataObj的所有字段 Field[] fields = dataObj.getClass().getDeclaredFields(); for (Field field : fields) { // 设置访问权限,以便可以访问私有字段 field.setAccessible(true); try { // 获取字段值 Object value = field.get(dataObj); // 如果字段值是String类型,则添加到dataMap中 if (value instanceof String) { dataMap.put(field.getName(), (String) value); } } catch (IllegalAccessException e) { // 忽略无法访问的字段 } } return dataMap; } /** * 合并文档 * * @param fileName 合并之后的word文档名称 * @param wordList word列表 * @param tempDirFile 临时目录 * @throws Exception 异常 */ public static void mergeDocuments(String fileName, List<String> wordList, File tempDirFile) throws Exception { File tempFile = new File(tempDirFile.getAbsolutePath() + "/" + fileName + ".docx"); if (!tempFile.exists() && !tempFile.createNewFile()) { throw new RuntimeException("文件[" + tempFile.getAbsolutePath() + "]创建失败"); } // 保存合并后的文档 mergeDoc(wordList, tempFile.getAbsolutePath()); } /** * 模拟插入附件 * * @param fileList 文件地址列表 * @param tempDirFile 临时目录 * @return 返回一个word文档列表地址 * @throws Exception 异常 */ private static List<String> simulateInsertingAttachments(List<String> fileList, File tempDirFile) throws Exception { List<String> wordList = new ArrayList<>(); // 创建一个新的Word文档 XWPFDocument document = new XWPFDocument(); // 创建一个段落 XWPFParagraph paragraph = document.createParagraph(); for (int i = 0; i < fileList.size(); i++) { String filePath = fileList.get(i); String fileName = getFileName(filePath); InputStream inputStream = downloadFile(filePath); String hyperlinkUrl = "./附件/" + fileName; FileUtil.writeFromStream(inputStream, new File(tempDirFile + hyperlinkUrl)); // 超链接地址,可以是网络地址、本地文件相对路径 XWPFHyperlinkRun hyperlinkRun = paragraph.createHyperlinkRun(hyperlinkUrl); hyperlinkRun.setText(fileName); hyperlinkRun.setColor("0000FF"); // 设置下划线 hyperlinkRun.setUnderline(UnderlinePatterns.SINGLE); // 插入一个换行,除了最后一次循环 if (i < fileList.size() - 1) { paragraph.createRun().addBreak(BreakType.TEXT_WRAPPING); } } // 将文档保存到文件 String tempFilePath = tempDirFile.getAbsolutePath() + "/" + UUID.randomUUID() + ".docx"; try (FileOutputStream out = new FileOutputStream(tempFilePath)) { document.write(out); } // 关闭文档 document.close(); wordList.add(tempFilePath); return wordList; } /** * 从文件路径中提取文件名(包括扩展名) * * @param filePath 文件路径 * @return 文件名(包括扩展名) */ public static String getFileName(String filePath) { if (filePath == null || filePath.isEmpty()) { return null; } // 使用 Paths 来解析文件路径 return Paths.get(filePath).getFileName().toString(); } /** * 下载文件 * * @param filePath 文件路径 * @return 输入流 * @throws IOException 异常 */ public static InputStream downloadFile(String filePath) throws IOException { // 这里应该是下载附件的逻辑,为了模拟效果直接从本地读取 return Files.newInputStream(Paths.get(filePath)); } /** * 下载图片到本地 * * @param imageUrl 图片的URL * @param savePath 本地保存路径(包括文件名) * @throws IOException 如果发生I/O错误 */ public static void downloadImage(String imageUrl, String savePath) throws IOException { // 创建URL对象 URL url = new URL(imageUrl); // 打开连接 HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); // 允许输入流(默认为true) httpConn.setDoInput(true); // 不允许输出流(因为我们只是下载) httpConn.setDoOutput(false); // 连接服务器 httpConn.connect(); // 获取响应码 int responseCode = httpConn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // 成功 // 获取输入流 try (InputStream inputStream = httpConn.getInputStream(); OutputStream outputStream = new FileOutputStream(savePath)) { // 创建一个缓冲区 byte[] buffer = new byte[4096]; int bytesRead; // 读取数据并写入文件 while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } } else { throw new IOException("Failed to download image, HTTP response code: " + responseCode); } // 断开连接 httpConn.disconnect(); } /** * 合并word文件 * * @param wordList 待合并文件集合 * @param outFilePath 输出文件路径 */ public static void mergeDoc(List<String> wordList, String outFilePath) throws IOException, Docx4JException { List<InputStream> streamList = new ArrayList<>(); for (String wordPath : wordList) { streamList.add(Files.newInputStream(Paths.get(wordPath))); } mergeDocStream(streamList, new FileOutputStream(outFilePath)); } /** * 合并word文件 * * @param streamList 文件流列表 * @param out 输出流 * @throws Docx4JException 异常 * @throws IOException 异常 */ private static void mergeDocStream(List<InputStream> streamList, FileOutputStream out) throws Docx4JException, IOException { // 创建一个空的临时Word文档作为基础 WordprocessingMLPackage target = WordprocessingMLPackage.createPackage(); int chunkId = 0; // 将streamList中的每个文档插入到target中 for (InputStream inputStream : streamList) { if (inputStream != null) { insertDoc(target.getMainDocumentPart(), inputStream, chunkId++); } } target.save(out); out.close(); } /** * 插入文档 * * @param mainDocumentPart mainDocumentPart * @param inputStream inputStream * @param chunkId chunkId * @throws InvalidFormatException 异常 */ private static void insertDoc(MainDocumentPart mainDocumentPart, InputStream inputStream, int chunkId) throws InvalidFormatException { PartName partName = new PartName("/part" + chunkId + ".docx"); AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(partName); afiPart.setBinaryData(inputStream); Relationship relationship = mainDocumentPart.addTargetPart(afiPart); CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk(); chunk.setId(relationship.getId()); mainDocumentPart.addObject(chunk); } }
b、控制层
@ApiOperation(value = "详情附件统一导出") @GetMapping("export") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "主键"), @ApiImplicitParam(name = "type", value = "设立模板类型") }) public void export(HttpServletResponse response, String id, String type) throws Exception { if (StrUtil.isBlank(id)) { throw new FlowException("申请人编号不能为空!"); } if (StrUtil.isBlank(type)) { throw new FlowException("申请人填报模板类型不能为空!"); } String filePath = kam03Service.export(response, id, type); File file = new File(filePath); // 检查文件是否存在 if (!file.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found"); return; } // 设置响应头 String fileName = file.getName(); String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + encodedFileName); System.out.println("file.getName() =================== " + file.getName()); response.setContentLengthLong(file.length()); // 获取响应输出流 try (OutputStream outputStream = response.getOutputStream(); FileInputStream fileInputStream = new FileInputStream(file)) { // 将文件内容写入响应输出流 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = fileInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } }业务接口层 String export(HttpServletResponse response, String id, String type) throws IOException;
c、业务逻辑层
@Override public String export(HttpServletResponse response, String id, String type) throws IOException { // 1.创建一个临时文件夹 String tempDirPath = FileUtil.mkParentDirs("temp/" + UUID.randomUUID()).getAbsolutePath(); // 2.创建一个存储附件的文件夹 File projectDir = new File(tempDirPath + "/" + "附件"); if (!projectDir.exists() && !projectDir.mkdirs()) { throw new RuntimeException("文件夹[" + projectDir.getAbsolutePath() + "]创建失败"); } // 3.查询出来所有的详情文件信息 List<Kap17> list; // 1.个人捐赠奖 if (StrUtil.equals(type, ZjcsIConstants.BKAM0108_01)) { list = Kap17Mapper.selectList(new LambdaQueryWrapper<Kap17>() .eq(Kap17::getBkap1702, id) .in(Kap17::getBkap1705, ZjcsIConstants.BUSINESS_FILE_TYPE_LIST_01)); // 2.机构捐赠奖 } else if (StrUtil.equals(type, ZjcsIConstants.BKAM0108_02)) { list = Kap17Mapper.selectList(new LambdaQueryWrapper<Kap17>() .eq(Kap17::getBkap1702, id) .in(Kap17::getBkap1705, ZjcsIConstants.BUSINESS_FILE_TYPE_LIST_02)); // 3.慈善项目、信托奖 } else if (StrUtil.equals(type, ZjcsIConstants.BKAM0108_03)) { list = Kap17Mapper.selectList(new LambdaQueryWrapper<Kap17>() .eq(Kap17::getBkap1702, id) .in(Kap17::getBkap1705, ZjcsIConstants.BUSINESS_FILE_TYPE_LIST_03)); // 4.志愿服务奖 } else if (StrUtil.equals(type, ZjcsIConstants.BKAM0108_04)) { list = Kap17Mapper.selectList(new LambdaQueryWrapper<Kap17>() .eq(Kap17::getBkap1702, id) .in(Kap17::getBkap1705, ZjcsIConstants.BUSINESS_FILE_TYPE_LIST_04)); // 5.慈善楷模奖 } else if (StrUtil.equals(type, ZjcsIConstants.BKAM0108_05)) { list = Kap17Mapper.selectList(new LambdaQueryWrapper<Kap17>() .eq(Kap17::getBkap1702, id) .in(Kap17::getBkap1705, ZjcsIConstants.BUSINESS_FILE_TYPE_LIST_05)); // 6.乡村振兴奖 } else if (StrUtil.equals(type, ZjcsIConstants.BKAM0108_06)) { list = Kap17Mapper.selectList(new LambdaQueryWrapper<Kap17>() .eq(Kap17::getBkap1702, id) .in(Kap17::getBkap1705, ZjcsIConstants.BUSINESS_FILE_TYPE_LIST_06)); } else { LOGGER.error("设立模板不匹配,请稍后重试!"); throw new FlowException("设立模板未匹配,请稍后重试!"); } if (CollUtil.isEmpty(list)) { LOGGER.error("附件信息为空!"); throw new FlowException("附件信息为空!"); } // 4.然后将所有的文件下载到本地 List<String> filePathList = new ArrayList<>(); for (Kap17 kap17 : list) { if (StrUtil.isNotBlank(kap17.getBkap1704()) && StrUtil.isNotBlank(kap17.getBkap1703())) { // key : 202501/03/16/1875093026709995520.jpg // 现在访问文件有两种方式,不知道之前怎么设计的,需要考虑两种情况,可能是一期的旧数据迁移导致的 String savePath = GenerateOnWordTemplateKit.FILE_PATH + kap17.getBkap1703(); if (kap17.getBkap1704().contains(ZjcsIConstants.FILE_URL_REGEX)) { String imageUrl = ZjcsIConstants.URL_22 + kap17.getBkap1704(); GenerateOnWordTemplateKit.downloadImage(imageUrl, savePath); } else { String imageUrl = kap17.getBkap1704().replace("/zjcsmsb/local/", ""); // oss阿里云存储服务器上下载 fileReadService.downloadFile(imageUrl, kap17.getBkap1703()); } filePathList.add(savePath); } } // 5.然后将所有本地的文件写入到本地指定文件夹中 for (String filePathStr : filePathList) { String fileName = GenerateOnWordTemplateKit.getFileName(filePathStr); InputStream inputStream = GenerateOnWordTemplateKit.downloadFile(filePathStr); String hyperlinkUrl = "./" + fileName; FileUtil.writeFromStream(inputStream, new File(projectDir + hyperlinkUrl)); } // 6.将本地的文件打包成zip File projectZipFile = GenerateOnWordTemplateKit.compressIntoAZipFile(tempDirPath); System.out.println("压缩包路径保存:" + projectZipFile.getAbsolutePath()); // 7. 删除所有临时文件 GenerateOnWordTemplateKit.deleteAllTempFiles(tempDirPath); return projectZipFile.getAbsolutePath(); }
d、阿里云oss文件下载方法(部分)
/* * @Param fileUrl 文件url * @Param originalName 原文件名 **/ @Override public void downloadFile(String fileUrl, String originalName) { String key = "local/" + fileUrl; File localFile = new File(GenerateOnWordTemplateKit.FILE_PATH, originalName); AliOssCloudUtil ossCloudUtil = new AliOssCloudUtil(); try (OSSObject ossObject = ossCloudUtil.getOssObject(ZjcsIConstants.bucketName, key); OutputStream outputStream = new FileOutputStream(localFile); InputStream inputstream = ossObject.getObjectContent()) { // 使用缓冲流进行拷贝 byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputstream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); System.out.println("localFile.getAbsolutePath() ============== " + localFile.getAbsolutePath()); } catch (IOException e) { log.warn("文件下载失败", e); throw new RuntimeException(e); } }
3.静态文件
4.前端调用示例
<div style="text-align: right;"> <ta-button class="mt-10" @click="openWinExcel" type="primary" icon="to-top">导出附件</ta-button> </div>// 导出所有附件信息 openWinExcel() { const id = this.primaryKey; const type = this.templateId; window.open(faceConfig.basePath + '/s2/kam03/export?id=' + id + '&type=' + type,'_blank'); },
5.实现效果
原文地址:https://blog.csdn.net/qq_61026557/article/details/145160191
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!