From 79af94e648a2bfd29e3506ae099da3cd3d554434 Mon Sep 17 00:00:00 2001 From: zy <82248909@qq.com> Date: Thu, 13 Nov 2025 19:01:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E6=96=B9=E6=B3=95=E6=94=B9?= =?UTF-8?q?=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jeecg/common/util/ResponseOutputFile.java | 326 ++++++++++++++++++ .../system/controller/CommonController.java | 103 +++++- 2 files changed, 410 insertions(+), 19 deletions(-) create mode 100644 jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ResponseOutputFile.java diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ResponseOutputFile.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ResponseOutputFile.java new file mode 100644 index 0000000..0567126 --- /dev/null +++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ResponseOutputFile.java @@ -0,0 +1,326 @@ +package org.jeecg.common.util; + +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Workbook; +import org.jeecg.common.exception.JeecgBootException; +import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; + +/** + * @Author wgx + */ +@Slf4j +public class ResponseOutputFile { + public static void output(HttpServletRequest request, HttpServletResponse response, Workbook sheets, + String fileName) { + ServletOutputStream outputStream = null; + try { + //设置下载模式 + String contentType = getContentType(fileName); + response.setContentType(contentType); + //设置下载模式 + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ""); + outputStream = response.getOutputStream(); + sheets.write(outputStream); + } catch (IOException e) { + e.printStackTrace(); + } finally { + + if(outputStream != null){ + try { + outputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + /** + * 导出文件 + * @param request + * @param response + * @param in + * @param fileSize + * @param fileName + */ + public static void output(HttpServletRequest request, HttpServletResponse response, InputStream in, + long fileSize, String fileName) { + ServletOutputStream outputStream = null; + BufferedInputStream bufIn = null; + try { + //设置下载模式 + String contentType = getContentType(fileName); + response.setContentLengthLong(fileSize); + response.setContentType(contentType); + // 设置强制下载不打开 +// response.setContentType("application/force-download"); + // 在线打开方式 文件名应该编码成UTF-8 +// response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(filename, "UTF-8")); + //设置下载模式 + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ""); + + // 读到流中 + bufIn = new BufferedInputStream(in); + byte[] buffer = new byte[2048]; + int num; + outputStream = response.getOutputStream(); + while ((num = bufIn.read(buffer)) != -1) { + outputStream.write(buffer, 0, num); + } + response.flushBuffer(); + } catch (IOException e) { + e.printStackTrace(); + }finally { + if(bufIn != null){ + try { + bufIn.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if(in != null){ + try { + in.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if(outputStream != null){ + try { + outputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + } + + + //断点续传 + public static void output(HttpServletRequest request, HttpServletResponse response, File file, boolean del){ + if(file == null || !file.exists()){ + throw new JeecgBootException("文件错误"); + } + InputStream inputStream = null; + RandomAccessFile in = null; + ServletOutputStream outputStream = null; + try { + + //设置下载模式 + String fileName = file.getName(); +// String type = fileName.substring(fileName.indexOf(".") + 1); + String contentType = getContentType(fileName); + String range = request.getHeader("Range"); + long fileSize = file.length(); + long downloadSize = fileSize; + long fromPos = 0, toPos = fileSize - 1; + if(range == null){ + response.setContentLengthLong(downloadSize); + }else{// 断点续传 + // https://blog.csdn.net/liang19890820/article/details/53215087#%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E7%9A%84%E7%94%A8%E9%80%94 + // https://blog.csdn.net/qq_33868430/article/details/83308315 + // 若客户端传来Range,说明之前下载了一部分,设置206状态(SC_PARTIAL_CONTENT) + //Range 头部的格式有以下几种情况: + //Range: bytes=0-499 表示第 0-499 字节范围的内容 + //Range: bytes=500-999 表示第 500-999 字节范围的内容 + //Range: bytes=-500 表示最后 500 字节的内容 + //Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容 + //Range: bytes=0-0,-1 表示第一个和最后一个字节 + //Range: bytes=500-600,601-999 同时指定几个范围 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + RangeDTO rangeDTO = executeRangeInfo(range, fileSize); + if(rangeDTO == null){ + throw new JeecgBootException("range error"); + } + fromPos = rangeDTO.getStartByte(); + toPos = rangeDTO.getEndByte(); +// String bytes = range.replaceAll("bytes=", ""); +// String[] ary = bytes.split("-"); +// fromPos = Long.parseLong(ary[0]); +// if (ary.length == 2) { +// toPos = Long.parseLong(ary[1]); +// } + int size; + if (toPos > fromPos) { + size = (int) (toPos - fromPos + 1); + } else { + size = (int) (fileSize - fromPos); + } + response.setContentLengthLong(size); + //Content-Range 说明 + //Content-Range: bytes 0-499/22400 + //0-499 是指当前发送的数据的范围,而 22400 则是文件的总大小。 + response.setHeader("Content-Range", "bytes " + fromPos + "-" + toPos + "/" + fileSize); + downloadSize = size; + } + response.setContentType(contentType); + // 设置强制下载不打开 +// response.setContentType("application/force-download"); + // 在线打开方式 文件名应该编码成UTF-8 +// response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(filename, "UTF-8")); + //设置下载模式 + if(!(contentType.contains("image") || contentType.contains("video"))){ + //非图片或视频则下载 + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(file.getName(), "UTF-8") + ""); + } + response.setHeader("Accept-Ranges", "bytes"); + // 读到流中 + in = new RandomAccessFile(file, "rw"); + // 设置下载起始位置 + if (fromPos > 0) { + in.seek(fromPos); + } + byte[] buffer = new byte[2048]; + int num; + outputStream = response.getOutputStream(); + while ((num = in.read(buffer)) != -1) { + outputStream.write(buffer, 0, num); + } + response.flushBuffer(); + +// inputStream = new BufferedInputStream(new FileInputStream(file)); +// outputStream = response.getOutputStream(); +// byte[] buf = new byte[1024]; +// int len; +// while ((len = inputStream.read(buf)) > 0) { +// outputStream.write(buf, 0, len); +// } +// response.flushBuffer(); + } catch (IOException e) { + e.printStackTrace(); + }finally { + if(inputStream != null){ + try { + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if(in != null){ + try { + in.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if(outputStream != null){ + try { + outputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + if(del){ + try { + file.delete(); + }catch (Exception e){ + log.error(e.getMessage()); + } + } + } + } + private static RangeDTO executeRangeInfo(String range, Long fileSize) { + + if (StrUtil.isEmpty(range) || !range.contains("bytes=") || !range.contains("-")) { + + return null; + } + + long startByte = 0; + long endByte = fileSize - 1; + + range = range.substring(range.lastIndexOf("=") + 1).trim(); + String[] ranges = range.split("-"); + + if (ranges.length <= 0 || ranges.length > 2) { + + return null; + } + + try { + if (ranges.length == 1) { + if (range.startsWith("-")) { + + //1. 如:bytes=-1024 从开始字节到第1024个字节的数据 + endByte = Long.parseLong(ranges[0]); + } else if (range.endsWith("-")) { + + //2. 如:bytes=1024- 第1024个字节到最后字节的数据 + startByte = Long.parseLong(ranges[0]); + } + } else { + //3. 如:bytes=1024-2048 第1024个字节到2048个字节的数据 + startByte = Long.parseLong(ranges[0]); + endByte = Long.parseLong(ranges[1]); + } + } catch (NumberFormatException e) { + startByte = 0; + endByte = fileSize - 1; + } + + if (startByte >= fileSize) { + + log.error("range error, startByte >= fileSize. " + + "startByte: {}, fileSize: {}", startByte, fileSize); + return null; + } + + return new RangeDTO(startByte, endByte); + } + + + public static String getContentType(String fileName){ + return MediaTypeFactory.getMediaType(fileName) + .orElse(MediaType.APPLICATION_OCTET_STREAM).toString(); + +// //String contentType = "application/force-download" +// if(StrUtil.isBlank(fileName)){ +// return contentType; +// } +// MediaTypeFactory.getMediaType(fileName); +// // fileName 文件名 +// +// String type = fileName.substring(fileName.indexOf(".") + 1); +// if (Objects.equals(type, "jpg") || Objects.equals(type, "JPG")) { +// contentType = "image/jpeg"; +// }else if(Objects.equals(type, "png") || Objects.equals(type, "PNG")) { +// contentType = "image/png"; +// }else if(Objects.equals(type, "xlsx") || Objects.equals(type, "xls")){ +// contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; +// }else if(Objects.equals(type, "txt")){ +// contentType = "text/plain; charset=utf-8"; +// }else if(Objects.equals(type, "pdf")){ +// contentType = "application/pdf"; +// }else if(Objects.equals(type, "doc")){ +// contentType = "application/msword"; +// }else if(Objects.equals(type, "docx")){ +// contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; +// }else if(Objects.equals(type, "zip")){ +// contentType = "application/zip"; +// } +// return contentType; + } + + @Data + + static class RangeDTO { + private long startByte; + private long endByte; + + public RangeDTO(long startByte, long endByte){ + this.startByte = startByte; + this.endByte = endByte; + } + } + +} diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/CommonController.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/CommonController.java index 21e4bdf..060d8fc 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/CommonController.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/CommonController.java @@ -7,11 +7,8 @@ import org.jeecg.common.api.vo.Result; import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.exception.JeecgBootException; -import org.jeecg.common.util.CommonUtils; -import org.jeecg.common.util.RestUtil; -import org.jeecg.common.util.TokenUtils; +import org.jeecg.common.util.*; import org.jeecg.common.util.filter.FileTypeFilter; -import org.jeecg.common.util.oConvertUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -205,6 +202,69 @@ public class CommonController { // return result; // } +// /** +// * 预览图片&下载文件 +// * 请求地址:http://localhost:8080/common/static/{user/20190119/e1fe9925bc315c60addea1b98eb1cb1349547719_1547866868179.jpg} +// * +// * @param request +// * @param response +// */ +// @GetMapping(value = "/static/**") +// public void view(HttpServletRequest request, HttpServletResponse response) { +// // ISO-8859-1 ==> UTF-8 进行编码转换 +// String imgPath = extractPathFromPattern(request); +// if(oConvertUtils.isEmpty(imgPath) || CommonConstant.STRING_NULL.equals(imgPath)){ +// return; +// } +// // 其余处理略 +// InputStream inputStream = null; +// OutputStream outputStream = null; +// try { +// imgPath = imgPath.replace("..", "").replace("../",""); +// if (imgPath.endsWith(SymbolConstant.COMMA)) { +// imgPath = imgPath.substring(0, imgPath.length() - 1); +// } +// String filePath = uploadpath + File.separator + imgPath; +// File file = new File(filePath); +// if(!file.exists()){ +// response.setStatus(404); +// throw new RuntimeException("文件["+imgPath+"]不存在.."); +// } +// // 设置强制下载不打开 +// response.setContentType("application/force-download"); +// response.addHeader("Content-Disposition", "attachment;fileName=" + new String(file.getName().getBytes("UTF-8"),"iso-8859-1")); +// inputStream = new BufferedInputStream(new FileInputStream(filePath)); +// outputStream = response.getOutputStream(); +// byte[] buf = new byte[1024]; +// int len; +// while ((len = inputStream.read(buf)) > 0) { +// outputStream.write(buf, 0, len); +// } +// response.flushBuffer(); +// } catch (IOException e) { +// log.error("预览文件失败" + e.getMessage()); +// response.setStatus(404); +// e.printStackTrace(); +// } finally { +// if (inputStream != null) { +// try { +// inputStream.close(); +// } catch (IOException e) { +// log.error(e.getMessage(), e); +// } +// } +// if (outputStream != null) { +// try { +// outputStream.close(); +// } catch (IOException e) { +// log.error(e.getMessage(), e); +// } +// } +// } +// +// } + + /** * 预览图片&下载文件 * 请求地址:http://localhost:8080/common/static/{user/20190119/e1fe9925bc315c60addea1b98eb1cb1349547719_1547866868179.jpg} @@ -216,35 +276,40 @@ public class CommonController { public void view(HttpServletRequest request, HttpServletResponse response) { // ISO-8859-1 ==> UTF-8 进行编码转换 String imgPath = extractPathFromPattern(request); - if(oConvertUtils.isEmpty(imgPath) || CommonConstant.STRING_NULL.equals(imgPath)){ + if (oConvertUtils.isEmpty(imgPath) || CommonConstant.STRING_NULL.equals(imgPath)) { return; } // 其余处理略 InputStream inputStream = null; OutputStream outputStream = null; try { - imgPath = imgPath.replace("..", "").replace("../",""); + imgPath = imgPath.replace("..", "").replace("../", ""); if (imgPath.endsWith(SymbolConstant.COMMA)) { imgPath = imgPath.substring(0, imgPath.length() - 1); } String filePath = uploadpath + File.separator + imgPath; File file = new File(filePath); - if(!file.exists()){ + if (!file.exists()) { response.setStatus(404); - throw new RuntimeException("文件["+imgPath+"]不存在.."); + log.info("文件[" + imgPath + "]不存在.."); + //throw new RuntimeException("文件["+imgPath+"]不存在.."); + return; } + ResponseOutputFile.output(request, response, file, false); // 设置强制下载不打开 - response.setContentType("application/force-download"); - response.addHeader("Content-Disposition", "attachment;fileName=" + new String(file.getName().getBytes("UTF-8"),"iso-8859-1")); - inputStream = new BufferedInputStream(new FileInputStream(filePath)); - outputStream = response.getOutputStream(); - byte[] buf = new byte[1024]; - int len; - while ((len = inputStream.read(buf)) > 0) { - outputStream.write(buf, 0, len); - } - response.flushBuffer(); - } catch (IOException e) { +// response.setContentType("application/force-download"); +// response.setContentType(ResponseOutputFile.getContentType(file.getName())); +// response.setContentLengthLong(file.length()); +// response.addHeader("Content-Disposition", "attachment;fileName=" + new String(file.getName().getBytes("UTF-8"),"iso-8859-1")); +// inputStream = new BufferedInputStream(new FileInputStream(filePath)); +// outputStream = response.getOutputStream(); +// byte[] buf = new byte[1024]; +// int len; +// while ((len = inputStream.read(buf)) > 0) { +// outputStream.write(buf, 0, len); +// } +// response.flushBuffer(); + } catch (Exception e) { log.error("预览文件失败" + e.getMessage()); response.setStatus(404); e.printStackTrace();