SpringBoot文件上传下载以及优化过程 -- 个人笔记
Java IO/NIO/AIO的知识体系图

博主最开始是用IO实现文件上传下载功能,但发现效率慢,于是使用了NIO
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
实体类
@Entity
public class DpOrder {
@Id
@GeneratedValue
private Integer id;
//订单号
private String orderNo;
//文件存储的路径(把所有文件保存到数据库里,文件路径之间以逗号相隔)
//数据库字段类型为text
@Column(columnDefinition = "text")
private String filePath;
//、、、get、set方法
}
file实体类
public class FileBean implements Serializable {
private String filePath;// 文件保存路径
private String fileName;// 文件保存名称
public FileBean() {
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
控制层
@RestController
@RequestMapping("api/orderdatamanagement/dpordermanagement")
public class DpOrderController extends ExceptionResponse {
@Autowired
private DpOrderService dpOrderService;
//文件上传功能,支持多文件上传(这里传了orderNo参数,是为了在对象中,把文件上传的路径保存到数据库)
//根据自己的业务需求,来决定参数,但MultipartFile是一定要的
@RequestMapping(value = "/uploadFile", method = RequestMethod.POST, produces = "multipart/form-data;charset=utf8")
@ResponseBody
public void uploadFile(@RequestParam("orderNo") String orderNo, @RequestParam("files") MultipartFile[] files)
throws IOException {
dpOrderService.uploadFile(orderNo, files);
}
//校验有没有文件
@RequestMapping(value = "/valid-dp-order-download", method = RequestMethod.GET, produces = "multipart/form-data;charset=utf8")
public void validDpOrderDownload(@RequestParam("orderNo") String orderNo) {
dpOrderService.validDpOrderDownload(orderNo);
}
//下载文件,把所有文件打包成压缩包下载
@RequestMapping(value = "/download-files-list", method = RequestMethod.GET)
public ResponseEntity<byte[]> downFilesList(@RequestParam("orderNo") String orderNo, HttpServletResponse response) {
return dpOrderService.downFilesList(orderNo, response);
}
//查看所有的文件(查看上传后的文件名称)
@RequestMapping(value = "/dp-order-file-paths", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
public List<String> getDpOrderByFilePaths(@RequestParam String orderNo) {
return dpOrderService.getDpOrderByFilePaths(orderNo);
}
}
业务逻辑层
//业务逻辑层
@Service
@Transactional
public class DpOrderServiceImpl implements DpOrderService {
private static final Logger logger = LoggerFactory.getLogger(DpOrderServiceImpl.class);
@Autowired
private DpOrderRepository dpOrderRepository;
//上传文件
@Override
public void uploadFile(String orderNo, MultipartFile[] files) throws IOException {
///下面步骤是校验orderNo是否为空,并获取orderNo
if (StringUtils.isEmpty(orderNo)) {
throw new DpOrderException("订单号为空.");
}
DpOrder dpOrder = dpOrderRepository.findByOrderNo(no[0]);
if (dpOrder == null) {
logger.error("订单号不存在.");
throw new DpOrderException("订单号不存在.");
}
String[] no = orderNo.split(",");
//
InputStream in;//定义一个输出流
OutputStream out;//定义一个输入流
//orderFileProperties.getFilePath()是文件存储路径
//I:\\Develop\\postgres\\orderfile为自己本地路径
//也可以改成服务器路径,不过,要写给个读写的权限
String path = orderFileProperties.getFilePath();
File fileDir = new File(path);
if (!fileDir.exists()) {
fileDir.setWritable(true);
boolean mkdir = fileDir.mkdirs();
if (!mkdir) {
logger.error("创建文件夹失败.");
throw new DpOrderException("创建文件夹失败.");
}
}
//遍历上传的文件
for (MultipartFile file : files) {
//获得完整文件名,包括拓展名
String fileName = file.getOriginalFilename();
//获得文件名
String originalName = fileName.substring(0, fileName.lastIndexOf("."));
//获得拓展名
String fileExtensionName = fileName.substring(fileName.lastIndexOf(".") + 1);
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
//拼接组成新的文件名
String combinationFileName = originalName + "_" + sdf.format(new Date()) + "." + fileExtensionName;
//路径+完整的文件名
File serverFile = new File(fileDir.getAbsolutePath() + "/" + combinationFileName);
//输出流,读出数据
in = file.getInputStream();
//输入流,写入数据
out = new FileOutputStream(serverFile);
byte[] b = new byte[1024];
int len;
//把读出的数据赋予len
while ((len = in.read(b)) > 0) {
//输出数据
out.write(b, 0, len);
}
//关闭流
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
//不管FilePath以前有没有值,都要加上,避免存值的时候覆盖以前的值
dpOrder.setFilePath(dpOrder.getFilePath() + combinationFileName + ",");
}
}
//查看上传的文件
@Override
public List<String> getDpOrderByFilePaths(String orderNo) {
///下面步骤是校验orderNo是否为空,并获取orderNo
DpOrder dpOrder = dpOrderRepository.findByOrderNo(orderNo);
String str = dpOrder.getFilePath();
if (StringUtils.isEmpty(str)) {
return null;
}
String[] ids = str.split(",");//对存储值以逗号进行分割,以获得每个文件的名称
//
//定义集合,存储每个文件名称
ArrayList<String> filePathList = new ArrayList<>();
for (int i = 0; i < ids.length; i++) {
filePathList.add(ids[i]);
}
return filePathList;
}
//校验文件是否为空
@Override
public void validDpOrderDownload(String orderNo) {
//从传过来的参数查出该对象,判断FilePath字段是否有值,有值表示有文件
DpOrder dpOrder = dpOrderRepository.findByOrderNo(orderNo);
String str = dpOrder.getFilePath();
if (StringUtils.isEmpty(str)) {
throw new DpOrderException("没有文件可下载.");//抛出异常
}
}
//下载文件,把所有文件以压缩包的形式进行下载
@Override
public ResponseEntity<byte[]> downFilesList(String orderNo, HttpServletResponse response) {
///下面步骤是校验orderNo是否为空,并获取orderNo
DpOrder dpOrder = dpOrderRepository.findByOrderNo(orderNo);
String str = dpOrder.getFilePath();
String[] ids = str.split(",");
//
ArrayList<FileBean> fileList = new ArrayList<>();
//遍历循环得到文件名和文件路径
for (int i = 0; i < ids.length; i++) {
FileBean file = new FileBean();
file.setFileName(ids[i]);
file.setFilePath(orderFileProperties.getFilePath());
fileList.add(file);
}
//压缩包名称
String zipName = "download.zip";
//设置压缩包的类型
response.setContentType("application/x-zip-compressed");
response.setHeader("Content-Disposition", "attachment; filename=" + zipName);
//设置压缩流:直接写入response,实现边压缩边下载
ZipOutputStream zipos = null;
try {
zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
//设置压缩方法
zipos.setMethod(ZipOutputStream.DEFLATED);
} catch (Exception e) {
e.printStackTrace();
}
DataOutputStream os = null;
//循环将文件写入压缩流
for (int i = 0; i < fileList.size(); i++) {
String filePath = fileList.get(i).getFilePath();
String fileName = fileList.get(i).getFileName();
File file = new File(filePath + "/" + fileName);
try {
//添加ZipEntry,并ZipEntry中写入文件流
zipos.putNextEntry(new ZipEntry(fileName));
os = new DataOutputStream(zipos);
InputStream is = new FileInputStream(file);
byte[] b = new byte[100];
int length;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
zipos.closeEntry();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
//关闭流
os.flush();
os.close();
zipos.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
application.properties
#单个文件最大
spring.servlet.multipart.max-file-size=20MB
#设置总上传数据总大小
spring.servlet.multipart.max-request-size=400MB
效果如下

查看文件

选择上传文件


点击上传

再次查看文件

点击下载


DpOrder对象中,filePath属性存储文件

file_path里面的文件名以逗号相隔

文件上传下载功能已经完成了,但发现效率慢,尤其在服务器上,下载文件要几秒钟,大文件甚至更久
于是,就用效率更快的NIO
优化后的代码
@Override
public ResponseEntity<byte[]> downFilesList(String orderNo) {
// 循环开始时的当前时间
long starttime = System.currentTimeMillis();
DpOrder dpOrder = dpOrderRepository.findByOrderNo(orderNo);
String str = dpOrder.getFilePath();
FileUtil.downZipFile(str, orderFileProperties);//orderFileProperties为路径I:\\Develop\\postgres\\orderfile,这个改成自己实际上的路径
// 循环结束的时间
long endtime = System.currentTimeMillis();
logger.info("下载文件花费的时间为:" + (endtime - starttime) + "毫秒");
return null;
}
public class FileUtil {
public static void downZipFile(String str, OrderFileProperties orderFileProperties) {
String[] ids = str.split(",");
//获得系统桌面路径
//FileSystemView fsv = FileSystemView.getFileSystemView();
//File home = fsv.getHomeDirectory();
//String savePath = home.getPath();
String savePath = "D:\\download_files";
// 创建不同的文件夹目录
File downloadFile = new File(savePath);
// 判断文件夹是否存在
if (!downloadFile.exists()) {
// 如果文件夹不存在,则创建新的的文件夹
downloadFile.mkdirs();
downloadFile.setWritable(true);
}
//压缩包:路径+随机数+_download
String zipName = savePath + "\\" + FileUtil.random() + "_download.zip";
File zipFile = new File(zipName);
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
WritableByteChannel writableByteChannel = Channels.newChannel(zipOut)) {
zipOut.setMethod(ZipOutputStream.DEFLATED);
//遍历循环文件
for (int i = 0; i < ids.length; i++) {
File file = new File(orderFileProperties.getFilePath() + "/" + ids[i]);
try (FileChannel fileChannel = new FileInputStream(file).getChannel()) {
zipOut.putNextEntry(new ZipEntry(ids[i]));
fileChannel.transferTo(0, file.length(), writableByteChannel);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
经测试,下载速度确实快多了,10M的大小的文件需要500左右的ms
教程会持续更新
本文来自博客园,作者:暗影月色程序猿,转载请注明原文链接:https://www.cnblogs.com/Geneling/p/15249157.html

浙公网安备 33010602011771号