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;
	//文件存储的路径(把所有文件保存到数据库里,文件路径之间以逗号相隔)
	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();
		//压缩包:系统桌面路径+随机数+_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

教程会持续更新

发布了74 篇原创文章 · 获赞 37 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/exodus3/article/details/103905013