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

springboot响应文件流文件给浏览器+前端下载

springboot响应文件流文件给浏览器+前端下载

在这里插入图片描述
在这里插入图片描述

1.controller:

@Api(tags = {"【样本提取系统】-api"})
@RestController("YbtqYstbtqController")
@RequiredArgsConstructor
@RequestMapping("/ybtq-ystbtq")
@Slf4j
public class YbtqYstbtqController {private final YbService ybService;@ApiOperation(value = "【样本集管理】-样本集导出", notes = "export", produces = "application/octet-stream")@PostMapping("/ybgl-set-cloud-download")@OpLog("【样本集管理】-样本集导出")public void ybGlYbSetDownload(@RequestBody List<String> pkIds, HttpServletResponse response) throws Exception {FileDownloadVo fileDownloadVo = tileSetService.ybGlYbSetDownload(pkIds);if (null != fileDownloadVo && fileDownloadVo.getFileStream() != null) {try (InputStream inputStream = fileDownloadVo.getFileStream();OutputStream outputStream = response.getOutputStream()) {//先将字符串以UTF-8转换成字节数组,再将字节数组以ISO-8859-1转换字符串(标准)String fileName = new String(fileDownloadVo.getFileName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName,"utf-8"));response.setContentType(fileDownloadVo.getContentType());response.setCharacterEncoding("UTF-8");// 响应文件给浏览器IOUtils.copy(inputStream, outputStream);} catch (Exception e) {e.printStackTrace();}} else{response.setContentType("application/json;charset=utf-8");response.getWriter().write(JSONUtil.toJsonStr(Result.error("所选样本集无有效文件!")));}}}

2.serviceImpl:

@Overridepublic FileDownloadVo ybGlYbSetDownload(List<String> pkIds) {// 文件根目录FileDownloadVo ret = new FileDownloadVo();// 获取样本集信息List<TileSet> ybSetList = tileSetMapper.selectList(new LambdaQueryWrapper<TileSet>().in(TileSet::getPkId, pkIds));// 查询到的样本集,影像压缩成一个zipif (ybSetList.size() > 0) {try {// 获取当前日期并格式化为 yyyyMMddString currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date());// 创建临时 ZIP 文件File tempZipFile = Files.createTempFile("样本集_" + currentDate + "_", ".zip").toFile();// 创建临时根文件夹用于存放样本文件夹File tempRootDir = Files.createTempDirectory("样本集_" + currentDate + "_").toFile();for (TileSet tileSet : ybSetList) {// 为每个样本集创建一个文件夹File sampleDir = new File(tempRootDir, tileSet.getTileName() + "_" + tileSet.getPkId());sampleDir.mkdirs();// 获取文件复制到临时目录下setTitleSetFile(tileSet, sampleDir);}try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile))) {// 遍历 tempRootDir 下的所有子文件夹并添加到 ZIP 文件中File[] subDirs = tempRootDir.listFiles();if (subDirs != null) {for (File subDir : subDirs) {UnZipUtils.zipFiles(subDir, subDir.getName(), zipOut); // 将每个子文件夹递归添加到 ZIP}}} finally {// 删除临时文件夹及其内容UnZipUtils.deleteDirectory(tempRootDir);}try {// 将 ZIP 文件转为 FileInputStreamret.setFileStream(new FileInputStream(tempZipFile));} catch (IOException e) {e.printStackTrace();}// 异步任务:300秒后删除文件UnZipUtils.deleteAsyncFiles(tempZipFile);} catch (IOException e) {e.printStackTrace();}}ret.setContentType("application/zip");ret.setFileName(String.valueOf(new Date().getTime()));return ret;}// 获取文件复制到临时目录下private void setTitleSetFile(TileSet tileSet, File sampleDir) {String tileSetPath = tileSet.getTilePath();boolean isTileSetPath = tileSetPath != null && !Objects.equals(tileSetPath, "");if (isTileSetPath) {// 获取样本切片fileList<File> tileSetFileList = UnZipUtils.getFileByImagePaths(tileSetPath);for (File file : tileSetFileList) {try {Files.copy(file.toPath(), new File(sampleDir, file.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);} catch (Exception e) {System.out.print("前时项影像文件路径无效:" + file.getPath());}}}}/*** 辅助方法,用于从ZIP输入流中提取文件** @param zis ZIP输入流* @param filePath 文件的完整路径* @throws IOException 如果发生I/O错误*/public static void extractFile(ZipInputStream zis, String filePath) throws IOException {BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));byte[] bytesIn = new byte[4096];int read = 0;while ((read = zis.read(bytesIn)) != -1) {bos.write(bytesIn, 0, read);}bos.close();}public static void zipFiles(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {if (fileToZip.isHidden()) return;if (fileToZip.isDirectory()) {if (fileName.endsWith("/")) {zipOut.putNextEntry(new ZipEntry(fileName));zipOut.closeEntry();} else {zipOut.putNextEntry(new ZipEntry(fileName + "/"));zipOut.closeEntry();}File[] children = fileToZip.listFiles();for (File childFile : children) {zipFiles(childFile, fileName + "/" + childFile.getName(), zipOut);}return;}try (FileInputStream fis = new FileInputStream(fileToZip)) {ZipEntry zipEntry = new ZipEntry(fileName);zipOut.putNextEntry(zipEntry);byte[] bytes = new byte[1024];int length;while ((length = fis.read(bytes)) >= 0) {zipOut.write(bytes, 0, length);}}}public static void deleteDirectory(File file) {File[] contents = file.listFiles();if (contents != null) {for (File f : contents) {deleteDirectory(f);}}file.delete();}// 异步任务-删除临时文件public static void deleteAsyncFiles(File tempZipFile) {executorService.submit(() -> {try {TimeUnit.SECONDS.sleep(300);Files.deleteIfExists(tempZipFile.toPath());System.out.println("临时压缩包已删除:" + tempZipFile.toPath());} catch (Exception e) {System.err.println("临时压缩包删除失败:" + e.getMessage());}});}public static List<File> getFileByImagePaths(String imagePaths) {List<File> ret = new ArrayList<>();if (imagePaths != null && !imagePaths.equals("")) {List<String> preImagePathList = Arrays.asList(imagePaths.split(","));if (preImagePathList.size() > 0) {for (String preImagePath : preImagePathList) {// 根据path获取文件File file = new File(preImagePath);ret.add(file);}}}return ret;}

3.前端下载:


/*** 【样本集管理】-样本集导出*/ybGlYbSetDownload(context = undefined, params = undefined, fileName) {const url = `${this.interfaceUrl}/ybtq-ystbtq/ybgl-set-cloud-download`;return ajaxHelperInstance.downloadFilePost(url, fileName, {}, params);}async downloadFilePost(url, fileName, urlParam, data = {}, requestMethod = 'POST', vueContext = undefined, spinName = 'spinning', useStreamSaver = false) {url = this.prepareUrl(url, {...urlParam, ...data});data = convertDateParam(data);setVueContextSpinState(vueContext, spinName);const requestOptions = this.setupRequestOptions(requestMethod, data);try {let response;if (useStreamSaver) {await this.downloadWithStream(url, fileName, requestOptions);} else {response = await axios({url, ...requestOptions});await this.handleResponse(response, fileName);}} catch (error) {this.handleError(error);} finally {resetVueContextSpinState(vueContext, spinName);}}prepareUrl(url, params) {const reqData = _.map(params, (val, prop) => ({name: prop, value: val}));const queryString = abp.utils.buildQueryString(reqData);return url + queryString;}/*** 转换时间参数,主要处理表单传入参数含有moment对象的问题* @param obj 传入对象* @constructor*/
function convertDateParam(obj) {_.each(obj, (val, prop) => {if (val instanceof moment) {obj[prop] = (val as any).format('YYYY-MM-DD HH:mm:ss');}if (val instanceof Date) {obj[prop] = moment(val).format('YYYY-MM-DD HH:mm:ss');}});return obj;
}function setVueContextSpinState(vueContext, spinName: string) {if (vueContext && vueContext[spinName] != undefined) {vueContext[spinName] = true;}
}setupRequestOptions(method, data = null) {const headers = {'Content-Type': 'application/json','Authorization': 'Bearer ' + window.abp.auth.getToken(),};return {method: method,headers: headers,responseType: 'blob',data: data ? JSON.stringify(data) : undefined,};}/*** 通过fetch Api进行流式下载* @param url 下载地址* @param fileName 文件名称* @param options 下载选项*/async downloadWithStream(url, fileName, options: any = {}) {const {timeout = 1000 * 60 * 180} = options;const response = await this.fetchWithTimeout(url, options, timeout);if (!response.ok) {notification.warning({message: '提示',description: '下载失败',});throw new Error('下载失败');}const fileStream = StreamSaver.createWriteStream(fileName);const writer = fileStream.getWriter();if (response.body.pipeTo) {writer.releaseLock();return response.body.pipeTo(fileStream);}const reader = response.body.getReader();const pump = () =>reader.read().then(({value, done}) => (done ? writer.close() : writer.write(value).then(pump)));return pump();}async handleResponse(response, fileName) {const contentType = response.headers['content-type'];console.log(contentType);if (contentType && contentType.includes('application/json')) {const reader = new FileReader();reader.onload = () => {try {const jsonData = JSON.parse(reader.result as string);if (jsonData && jsonData.message) {notification.error({message: '提示',description: jsonData.message,});}} catch (error) {console.error('解析json失败:', error);}};reader.readAsText(response.data);} else {const blob = new Blob([response.data]);const link = document.createElement('a');link.href = URL.createObjectURL(blob);link.download = fileName;link.click();URL.revokeObjectURL(link.href);}}handleError(error) {console.error('下载失败:', error);}function resetVueContextSpinState(vueContext, spinName: string) {if (vueContext && vueContext[spinName] == true) {vueContext[spinName] = false;}
}

http://www.mrgr.cn/news/61663.html

相关文章:

  • 【电子通识】四线制电阻屏怎么判断是哪一路出现异常?
  • 【计网】UDP Echo Server与Client实战:从零开始构建简单通信回显程序
  • 数据结构————map,set详解
  • GCC及GDB的使用
  • JDK17的新增特性-Java
  • 代码随想录训练营Day10 | 二叉树的遍历
  • Windows Terminal终端美化
  • CSDN粉丝服务功能
  • 钣金结构的防水设计简介
  • redis:基本全局命令-键管理(1)
  • 【2023工业图像异常检测文献】DiAD: 基于扩散模型的多类异常检测方法
  • CMake 同时编译两个程序
  • CentOS下Redis简洁安装(无坑版)
  • 【C++题解】1186. 数字走向III
  • Kafka 物理存储机制
  • 浏览器HTTP缓存解读(HTTP Status:200 304)
  • 大东方码险引领彩票行业新纪元:全球保险巨头携手共创新篇章
  • 【Python】元组、字典与集合详解:数据容器的实战应用
  • 【STM32 Blue Pill编程实例】-4位7段数码管使用
  • 干货:零基础Google免费的GPU Colab+手把手理解扩散模型
  • 火语言RPA流程组件介绍--鼠标长按
  • 关于我的数据结构与算法——初阶第二篇(排序)
  • 如何设置示波器为二通道示波器,示波器显示范围如何调整
  • sicp每日一题[2.62]
  • docker中使用ros2humble的rviz2不显示问题
  • SELS-SSL/TLS