注:demo保存在码云项目中
1、设置maven依赖
这里我们集成knife4j方便测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.8</version>
</dependency>
<!--knife4j start-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.9</version>
</dependency>
<!--knife4j end-->
<!--上传下载 start-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.20</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!--上传下载 end-->
2、设置上传的相关配置
application.yml
spring:
application:
name: demo
profiles:
active: dev
servlet:
multipart:
max-request-size: 300MB
max-file-size: 300MB
server:
servlet:
context-path: /demo
port: 8000
knife4j:
enable: true
application-dev.yml
spring:
servlet: #上传文件使用
multipart:
max-file-size: 100MB #单个文件最大上传大小
max-request-size: 1024MB #每次请求上传文件大小最大值
#自定义参数,获取值@Value("${define..uploadPath}")
define:
uploadPath: C:\Users\admin\Desktop\file_upload\ #文件上传根路径:用户上传的文件 存储到这里
downPath: C:\Users\admin\Desktop\file_upload\ #文件下载根路径
knife4j配置
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
@Bean(value = "defaultApi2")
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
//.title("swagger-bootstrap-ui-demo RESTful APIs")
.description("# swagger-bootstrap-ui-demo RESTful APIs")
.termsOfServiceUrl("http://www.xx.com/")
.contact("xx@qq.com")
.version("1.0")
.build())
//分组名称
.groupName("2.X版本")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.sc.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurationSupport {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(responseBodyConverter());
}
}
3、上传下载工具类,可以在任意的项目中进行使用
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 文件上传下载工具类
*/
public class FileUploadDownUtils {
/**
* 文件上传:用户将自己电脑上的的文件 上传到服务器filePath路径下
* @param file :文件
* @param filePath:文件上传地址
* @param fileName:文件名称
*/
public static void saveFile(byte[] file, String filePath, String fileName) throws Exception {
File targetFile = new File(filePath);
if (!targetFile.exists()) {
targetFile.mkdirs();
}
FileOutputStream out = new FileOutputStream(filePath + fileName);
out.write(file);
out.flush();
out.close();
}
/**
* 本地单个文件下载
* @param file
* @param request
* @param response
* @throws UnsupportedEncodingException
*/
public static void downloadFile(File file, HttpServletRequest request,HttpServletResponse response) {
if (!file.exists() || file.length() <= 0L) {
throw new RuntimeException("文件为空或不存在!");
}
FileInputStream fis = null;
BufferedInputStream bis = null;
OutputStream os = null;
try {
boolean isPreview = "preview".equalsIgnoreCase(request.getParameter("source"));
String s = (!isPreview ? "attachment;" : "") + "filename=" + URLEncoder.encode(file.getName(), "UTF-8");
response.setHeader("Content-Disposition", s.trim());
os = response.getOutputStream();
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
byte[] buffer = new byte[bis.available()];
int i = bis.read(buffer);
while(i != -1){
os.write(buffer, 0, i);
i = bis.read(buffer);
}
} catch (Exception e) {
throw new RuntimeException("单个文件下载异常");
}finally {
try {
if (bis!=null){
bis.close();
}
if (fis!=null){
fis.close();
}
if (os!=null){
os.close();
}
} catch (IOException e) {
throw new RuntimeException("关闭流处理异常");
}
}
}
/**
* 本地多个文件下载(打包zip)
* @param fileList
* @param response
*/
public static void downloadFileZip(String zipName,List<File> fileList, HttpServletResponse response) {
for (File file : fileList) {
if (file == null || !file.exists() || file.length() <= 0L) {
throw new RuntimeException("文件为空或不存在!");
}
}
ZipArchiveOutputStream zout = null;
try {
//1、设置response参数并且获取ServletOutputStream
zout = getServletOutputStream(zipName,response);
for (File file : fileList) {
InputStream in = new FileInputStream(file);
//2、设置字节数组输出流,并开始输出
setByteArrayOutputStream(file.getName(),in,zout);
}
} catch (Exception e) {
throw new RuntimeException("多个文件打包成zip,下载异常");
}finally {
try {
if (zout != null) {
zout.close();
}
} catch (IOException e) {
throw new RuntimeException("关闭流处理异常");
}
}
}
/**
* 网络单个文件下载
*
* @param urlStr
* @param request
* @param response
* @param fileName
* @return
*/
public static void downloadHttpFile(String urlStr, HttpServletRequest request, HttpServletResponse response, String fileName) {
ServletOutputStream out = null;
InputStream in = null;
try {
//1、通过网络地址获取文件InputStream
in = getInputStreamFromUrl(urlStr);
//2、FileInputStream 转换为byte数组
byte[] getData = inputStreamToByte(in);
out = response.getOutputStream();
long contentLength = getData.length;
// 3、设置Response
setResponse(fileName, contentLength, request, response);
out.write(getData);
out.flush();
} catch (Exception e) {
throw new RuntimeException("下载失败!");
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 网络多个文件下载(打包zip)
*
* @param pathList
* @param request
* @param response
*/
public static void downloadHttpFileZip(String zipName,List<Map<String, String>> pathList, HttpServletRequest request, HttpServletResponse response) {
try {
// 1、设置response参数并且获取ServletOutputStream
ZipArchiveOutputStream zous = getServletOutputStream(zipName,response);
for (Map<String, String> map : pathList) {
String fileName = map.get("name");
//2、通过网络地址获取文件InputStream
InputStream inputStream = getInputStreamFromUrl(map.get("path"));
//3、设置字节数组输出流,并开始输出
setByteArrayOutputStream(fileName, inputStream, zous);
}
zous.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//设置response参数并且获取ServletOutputStream
private static ZipArchiveOutputStream getServletOutputStream(String zipName,HttpServletResponse response){
String outputFileName = zipName + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
response.reset();
response.setHeader("Content-Type", "application/octet-stream");
try {
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(outputFileName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("设置Content-Disposition失败");
}
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
ServletOutputStream out = null;
try {
out = response.getOutputStream();
} catch (IOException e) {
throw new RuntimeException("获取ServletOutputStream失败");
}
ZipArchiveOutputStream zout = new ZipArchiveOutputStream(out);
zout.setUseZip64(Zip64Mode.AsNeeded);
return zout;
}
//通过网络地址获取文件InputStream
private static InputStream getInputStreamFromUrl(String path) {
URL url = null;
InputStream is = null;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream();
} catch (IOException e) {
throw new RuntimeException("网络地址获取文件失败:"+ url);
}
return is;
}
//FileInputStream 转换为byte数组
public static byte[] inputStreamToByte(InputStream inputStream) {
try {
byte[] buffer = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
return bos.toByteArray();
} catch (Exception e) {
throw new RuntimeException("文件转换失败!");
}
}
/**
* @param fileName
* @param contentLength
* @param request
* @param response
* @return
*/
public static void setResponse(String fileName, long contentLength, HttpServletRequest request, HttpServletResponse response) {
try {
boolean isPreview = "preview".equalsIgnoreCase(request.getParameter("source"));
response.addHeader("Content-Disposition", (!isPreview ? "attachment;" : "") + "filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setHeader("Accept-Ranges", "bytes");
String range = request.getHeader("Range");
if (range == null) {
response.setHeader("Content-Length", String.valueOf(contentLength));
} else {
response.setStatus(206);
long requestStart = 0L;
long requestEnd = 0L;
String[] ranges = range.split("=");
if (ranges.length > 1) {
String[] rangeDatas = ranges[1].split("-");
requestStart = Long.parseLong(rangeDatas[0]);
if (rangeDatas.length > 1) {
requestEnd = Long.parseLong(rangeDatas[1]);
}
}
long length = 0L;
if (requestEnd > 0L) {
length = requestEnd - requestStart + 1L;
response.setHeader("Content-Length", String.valueOf(length));
response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
} else {
length = contentLength - requestStart;
response.setHeader("Content-Length", String.valueOf(length));
response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1L) + "/" + contentLength);
}
}
} catch (Exception e) {
throw new RuntimeException("response响应失败!");
}
}
//设置字节数组输出流,并开始输出
private static void setByteArrayOutputStream(String fileName, InputStream in, ZipArchiveOutputStream zout){
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.flush();
byte[] bytes = baos.toByteArray();
//设置文件名
ArchiveEntry entry = new ZipArchiveEntry(fileName);
zout.putArchiveEntry(entry);
zout.write(bytes);
zout.closeArchiveEntry();
baos.close();
in.close();
} catch (Exception e) {
throw new RuntimeException("设置字节数组输出流失败!");
}
}
}
4、编写controller测试一下
import cn.hutool.core.collection.CollectionUtil;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.sc.utils.uploadDown.FileUploadDownUtils;
import io.swagger.annotations.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/upload")
@Api(tags = "上传下载模块")
public class UploadController {
@Value("${define.uploadPath}")
private String uploadPath;
@Value("${define.downPath}")
private String downPath;
@Resource
private HttpServletResponse response;
@Resource
private HttpServletRequest request;
/**
* 单文件上传
* @param file
* @return
*/
@PostMapping("/uploadFile")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "单文件上传")
@ApiImplicitParams(
{@ApiImplicitParam(name = "file", value = "文件流对象", required = true,dataType = "MultipartFile")}
)
public String singleFileUpload(@RequestParam("file") MultipartFile file) {
try {
if (file == null || StringUtils.isBlank(file.getOriginalFilename())) {
return "上传的文件为空";
}
FileUploadDownUtils.saveFile(file.getBytes(), uploadPath, file.getOriginalFilename());
} catch (Exception e) {
return "文件上传失败";
}
return "文件上传成功";
}
/**
* 批量文件上传,按住ctrl可以选择多个文件
* @param files
* @return
*/
@PostMapping("/uploadFiles")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "批量文件上传")
@ApiImplicitParams(
{@ApiImplicitParam(name = "file[]", value = "文件流对象,接收数组格式", required = true,dataType = "MultipartFile",allowMultiple = true)}
)
public String multiFileUpload(@RequestParam(value="file[]",required = true) MultipartFile[] files) {
try {
for (int i = 0; i < files.length; i++) {
//check file
if (StringUtils.isBlank(files[i].getOriginalFilename())) {
continue;
}
FileUploadDownUtils.saveFile(files[i].getBytes(), uploadPath, files[i].getOriginalFilename());
}
} catch (Exception e) {
return "文件批量上传失败";
}
return "文件批量上传成功";
}
/**
* 本地单个文件下载
* @throws UnsupportedEncodingException
*/
@GetMapping(value = "/download/file")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "本地单个文件下载",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadFile(@ApiParam(name = "fileName", value = "本地单个文件名称", required = true) @RequestParam(value = "fileName") String fileName){
//文件本地位置
String filePath = downPath+fileName;
File file = new File(filePath);
//文件下载
FileUploadDownUtils.downloadFile(file, request, response);
}
/**
* 本地多个文件下载(打包zip)
*/
@PostMapping(value = "/download/zip")
@ApiOperationSupport(order = 4)
@ApiOperation(value = "本地多个文件下载",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadFileZip(@ApiParam(name = "strs", value = "本地多个文件名称", required = true) @RequestBody List<String> strs) {
//设置输出文件名字
String zipName = "test";
//文件本地位置
if (CollectionUtil.isEmpty(strs)){
return;
}
List<File> fileList = new ArrayList<>();
for (String fileName : strs) {
fileList.add(new File(downPath+fileName));
}
//文件下载
FileUploadDownUtils.downloadFileZip(zipName,fileList,response);
}
/**
* 网络单个文件下载
* 测试地址 https://code.jquery.com/jquery-3.6.0.js
*/
@GetMapping(value = "/download/httpFile")
@ApiOperationSupport(order = 5)
@ApiOperation(value = "网络单个文件下载", notes = "网络单个文件下载",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadHttpFile(@ApiParam(name = "urlStr", value = "网络单个文件名称", required = true) @RequestParam(value = "urlStr") String urlStr) {
// 文件名称
String fileName = "测试文件.js";
FileUploadDownUtils.downloadHttpFile(urlStr, request, response, fileName);
}
/**
* 网络多个文件下载(打包zip)
* https://code.jquery.com/jquery-3.6.0.js
* https://code.jquery.com/jquery-3.6.0.js
*/
@PostMapping(value = "/download/httpFile/zip")
@ApiOperationSupport(order = 6)
@ApiOperation(value = "网络多个文件下载(打包zip)",produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadHttpFileZip(@ApiParam(name = "strs", value = "本地多个文件名称", required = true) @RequestBody List<String> strs) {
if (CollectionUtil.isEmpty(strs)){
return;
}
List<Map<String, String>> mapList = new ArrayList<>();
for (int i =0 ;i<strs.size();i++) {
Map<String, String> map = new HashMap<>();
map.put("path", strs.get(i));
map.put("name", "测试文件"+i+".js");
mapList.add(map);
}
String zipName = "test";
FileUploadDownUtils.downloadHttpFileZip(zipName,mapList, request, response);
}
}
注:下载时,Knife4j的一个小问题
经过测试Content-Disposition filename前带空格会导致文件下载始终是Knife4j.txt
查看了下UI的源码,filename 前有空格,就会解析不到文件名
@ApiOperation(value = "Excel下载[流下载]", produces = ApiEnum.PRODUCES_FILE)
public void downloadExcel2() throws IOException {
HttpServletResponse response = this.getResponse();
File file = new File(PathKit.getWebRootPath() + "/temp/test.xlsx");
response.setContentType("application/octet-stream");
// UI点下载文件 正常下载文件名
// response.addHeader("Content-Disposition", "attachement;filename=test.xlsx");
// UI点下载文件, 始终为Knife4j.txt(filename前面有空格 )
// response.addHeader("Content-Disposition", "attachement; filename=test.xlsx");
// UI点下载文件, 下载文件名为 _test.xlsx_(filename 文件名带双引号)
// response.addHeader("Content-Disposition", "attachement;filename=\"test.xlsx\"");
// UI点下载文件, 始终为Knife4j.txt (JFinal renderFile 的格式)
response.addHeader("Content-Disposition", "attachement; filename=\"test.xlsx\"");
OutputStream outputStream = response.getOutputStream();
FileInputStream fileInputStream = new FileInputStream(file);
int i = -1;
byte[] b = new byte[1024 * 1024];
while ((i = fileInputStream.read(b)) != -1) {
outputStream.write(b, 0, i);
}
fileInputStream.close();
outputStream.close();
this.renderNull();
}