由图可知,我们需要在前端定义四个接口。
检查文件是否存在接口检查分块文件是否存在接口上传分块文件接口合并分块文件接口 0. 准备工作编写一个配置类,向Spring容器中注入一个minio客户端。
@ConfigurationProperties(prefix = "minio")@Datapublic class MinioConfig { private String endpoint; private String accessKey; private String secretKey; @Bean public MinioClient minioClient() { MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); return minioClient; }} 1. 检查文件是否存在 1.1 定义接口 @ApiOperation(value = "文件上传前检查文件")@PostMapping("/upload/checkfile")public RestResponse checkfile(@RequestParam("fileMd5") String fileMd5) throws Exception { return mediaFileService.checkFile(fileMd5);} 1.2 编写实现方法检查文件是否存在,必须同时满足两个条件:
在数据库的文件表中存在记录在文件系统中存在文件只有满足以上两个条件才表示文件存在,任何一项不满足都将返回false。
@Overridepublic RestResponse checkFile(String fileMd5) { //在数据库中存在并且文件系统中也存在,才说明真的才存在 MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5); if (mediaFiles == null) { return RestResponse.success(false); } //查看是否在文件系统存在 GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(mediaFiles.getBucket()).object(mediaFiles.getFilePath()).build(); try { InputStream inputStream = minioClient.getObject(getObjectArgs); if (inputStream == null) { //文件不存在 return RestResponse.success(false); } } catch (Exception e) { e.printStackTrace(); return RestResponse.success(false); } return RestResponse.success(true);} 2. 检查分块文件是否存在 2.1 定义接口 @ApiOperation(value = "分块文件上传前的检测")@PostMapping("/upload/checkchunk")public RestResponse checkchunk(@RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") int chunk) throws Exception { return mediaFileService.checkChunk(fileMd5, chunk);} 2.2 编写实现方法想要查询分块文件是否在文件系统中,我们必须要指导分块文件所在的路径。
如果将所有的文件都存在同一个目录,将会导致IO效率低下。所以我们应该尽可能使文件分撒存储在不同目录(同一个文件的分块文件还得在同一目录下)。
我们指定一个规则:
假设一个文件的MD5值为:1374c8160ea2da8dd33208a9ad369641,那么我们就取第一个数为一级目录的名字,取第二个数为二级目录的名字。那么这个文件的分块文件都存在 video/1/3/1374c8160ea2da8dd33208a9ad369641/chunk 目录下,源文件存储在 video/1/3/1374c8160ea2da8dd33208a9ad369641 目录下。
根据这一个规则,我们可以编写一个获得分块文件所在目录的方法:
//得到分块文件的目录private String getChunkFileFolderPath(String fileMd5) { //将文件MD5值的第一位数作为一级目录,第二位数作为二级目录 return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";}接着编写检查分块文件是否存在的方法:
如果存在则返回true,不存在都返回false。
@Overridepublic RestResponse checkChunk(String fileMd5, int chunkIndex) { //得到分块文件所在目录 String chunkFileFolderPath = getChunkFileFolderPath(fileMd5); //分块文件的路径 String chunkFilePath = chunkFileFolderPath + chunkIndex; //查看是否在文件系统中存在 GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucket_videofiles).object(chunkFilePath).build(); try { InputStream inputStream = minioClient.getObject(getObjectArgs); if (inputStream == null) { //文件不存在 return RestResponse.success(false); } } catch (Exception e) { e.printStackTrace(); return RestResponse.success(false); } return RestResponse.success(true);} 3. 上传分块文件接口 3.1 定义接口 @ApiOperation(value = "上传分块文件")@PostMapping("/upload/uploadchunk")public RestResponse uploadchunk(@RequestParam("file") MultipartFile file, @RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") int chunk) throws Exception { return mediaFileService.uploadChunk(fileMd5,chunk,file.getBytes());} 3.2 编写实现方法实现方法涉及文件上传,但是文件上传不是只有在上传视频时才使用到,图片和文档的上传也同样使用得到。所以我们应该编写一个通用的上传文件方法。
通用上传文件代码:
private void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName) { //资源的媒体类型 String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//默认未知二进制流 if (objectName.indexOf(".") >= 0) { //取objectName中的扩展名 String extension = objectName.substring(objectName.lastIndexOf(".")); ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension); if (extensionMatch != null) { contentType = extensionMatch.getMimeType(); } } try { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucket) .object(objectName) //InputStream stream, long objectSize 对象大小,long partSize 分片大小(-1表示5M,最大不要超过5T,最多10000分片) .stream(byteArrayInputStream, byteArrayInputStream.available(), -1) .contentType(contentType) .build(); //上传到minio minioClient.putObject(putObjectArgs); } catch (Exception e) { e.printStackTrace(); log.debug("上传文件到文件系统出错:{}", e.getMessage()); XueChengPlusException.cast("上传文件出错!"); }}上传分块文件代码:
@Overridepublic RestResponse uploadChunk(String fileMd5, int chunk, byte[] bytes) { //得到分块文件的目录路径 String chunkFileFolderPath = getChunkFileFolderPath(fileMd5); //得到分块文件的路径 String chunkFilePath = chunkFileFolderPath + chunk; try { //将文件存储至minIO addMediaFilesToMinIO(bytes, bucket_videofiles, chunkFilePath); return RestResponse.success(true); } catch (Exception ex) { ex.printStackTrace(); log.debug("上传分块文件:{},失败:{}", chunkFilePath, ex.getMessage()); } return RestResponse.validfail(false, "上传分块失败");} 4. 合并分块文件接口 4.1 定义接口 @ApiOperation(value = "合并文件")@PostMapping("/upload/mergechunks")public RestResponse mergechunks(@RequestParam("fileMd5") String fileMd5, @RequestParam("fileName") String fileName, @RequestParam("chunkTotal") int chunkTotal) throws Exception { Long companyId = 1232141425L; //下载分块 UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto(); uploadFileParamsDto.setFilename(fileName); uploadFileParamsDto.setFileType("001002");//视频 uploadFileParamsDto.setTags("课程视频"); return mediaFileService.mergechunks(companyId, fileMd5, chunkTotal, uploadFileParamsDto);} 4.2 编写实现方法合并分块文件流程:
下载所有分块按顺序合并所有分块将合并完的文件上传至文件系统将文件信息存入数据库关闭流,删除分块文件和临时文件我们先给实现方法写一个大致的框架:
@Overridepublic RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) { //1.下载所有分块 //2.按顺序合并所有分块 //3.将合并完的文件上传至文件系统 //4.将文件信息存入数据库}1)下载所有分块
想要将分块文件合并成新的完整文件必须先将所有的分块文件下载下来,所以我们需要先编写一个下载分块文件的方法:
/*** * @description 下载分块 * @param fileMd5 * @param chunkTotal 分块总数 * @return java.io.File[] 分块文件数组 */private File[] checkChunkStatus(String fileMd5, int chunkTotal) { //得到分块文件所在目录 String chunkFileFolderPath = getChunkFileFolderPath(fileMd5); File[] chunkFiles = new File[chunkTotal]; for (int i = 0; i