controller
@Api(value = "大文件上传接口",tags="大文件上传接口")
@RestController
public class BigFilesController {
@Autowired
MediaFileService mediaFileService;
@ApiOperation(value = "文件上传前检查文件")
@PostMapping("/upload/checkfile")
public RestResponse<Boolean> checkFile(@RequestParam("fileMd5")String fileMd5)throws Exception{
return mediaFileService.checkFile(fileMd5);
}
@ApiOperation(value = "分块文件上传前的检测")
@PostMapping("/upload/checkchunk")
public RestResponse<Boolean> checkchunk(@RequestParam("fileMd5") String fileMd5,
@RequestParam("chunk") int chunk) throws Exception{
return mediaFileService.checkChunk(fileMd5,chunk);
}
@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());
}
@ApiOperation(value = "合并文件")
@PostMapping("/upload/mergechunks")
public RestResponse mergechunks(@RequestParam("fileMd5") String fileMd5,
@RequestParam("fileMame") String fileName,
@RequestParam("chunkTotal") int chunkTotal)throws Exception{
//测试数据
Long companyId = 1232141425L;
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
uploadFileParamsDto.setFileType("001002");
uploadFileParamsDto.setTags("视频");
uploadFileParamsDto.setRemark("");
uploadFileParamsDto.setFilename(fileName);
return mediaFileService.mergechunks(companyId,fileMd5,chunkTotal,uploadFileParamsDto);
}
}
service
/**当前代理对象调用的入库接口*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);
/**
* @description 检查文件是否存在
*/
public RestResponse<Boolean> checkFile(String fileMd5);
/**
* @description 检查分块是否存在
*/
public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex);
/**
* @description 上传分块
*/
public RestResponse uploadChunk(String fileMd5,int chunk,byte[] bytes);
/**
* @description 合并分块
*/
public RestResponse mergechunks(Long companyId,String fileMd5,int chunkTotal,UploadFileParamsDto uploadFileParamsDto);
}
impl
@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {
@Autowired
MediaFilesMapper mediaFilesMapper;
@Autowired
MinioClient minioClient;
//注入当前代理对象MediaFileService
@Autowired
MediaFileService currentProxy;
//普通文件(照片)存储桶
@Value("${minio.bucket.files}")
private String bucket_Files;
//视频文件存储桶
@Value("${minio.bucket.videofiles}")
private String bucket_videofiles;
/**检查文件是否存在*/
@Override
public RestResponse<Boolean> checkFile(String fileMd5) {
//文件在数据库记录并且也在minio系统才算存在
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
if (mediaFiles==null){
return RestResponse.success(false);
}
//检查是否在minio上是否存在
try {
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(mediaFiles.getBucket()).object(mediaFiles.getFilePath()).build();
InputStream stream = minioClient.getObject(getObjectArgs);
if (stream==null){
return RestResponse.success(false);
}
}catch (Exception e){
XueChengPlusException.cast("系统异常");
}
return RestResponse.success(true);
}
/**检查分块是否存在*/
@Override
public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
//获取分块文件所在目录
String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
//得到分块文件具体路径
String chunkPath=chunkFileFolderPath+chunkIndex;
//查询是否存在
try {
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucket_videofiles).object(chunkPath).build();
InputStream stream = minioClient.getObject(getObjectArgs);
if (stream==null){
return RestResponse.success(false);
}
}catch (Exception e){
XueChengPlusException.cast("系统异常");
}
return RestResponse.success(true);
}
/**获取minio上分块文件目录*/
public String getChunkFileFolderPath(String fileMd5){
return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
//return fileMd5.charAt(0)+"/"+fileMd5.charAt(1)+"/"+fileMd5+"/"+"chunk"+"/";
}
/**上传分块*/
@Override
public RestResponse uploadChunk(String fileMd5, int chunk, byte[] bytes) {
//获取分块文件所在目录
String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
//得到分块文件具体路径
String chunkPath=chunkFileFolderPath+chunk;
try {
//上传至minio
addMediaFilesToMinIO(bytes,bucket_videofiles,chunkPath);
return RestResponse.success(true);
} catch (Exception e) {
log.error(e.getMessage());
XueChengPlusException.cast("上传过程失败");
}
return RestResponse.success(false);
}
/**合并分块*/
@Override
public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
//下载所有分块文件
File[] chunkFiles = checkChunkStatus(fileMd5, chunkTotal);
//扩展名
String fileName = uploadFileParamsDto.getFilename();
String extName = fileName.substring(fileName.lastIndexOf("."));
//创建临时文件作为合并文件
File mergeFile = null;
try {
mergeFile = File.createTempFile(fileMd5, extName);
} catch (IOException e) {
XueChengPlusException.cast("合并文件过程中创建临时文件出错");
}
try {
//开始合并
byte[] b = new byte[1024];
try(RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");) {
for (File chunkFile : chunkFiles) {
try (FileInputStream chunkFileStream = new FileInputStream(chunkFile);) {
int len = -1;
while ((len = chunkFileStream.read(b)) != -1) {
//向合并后的文件写
raf_write.write(b, 0, len);
}
}
}
} catch (IOException e) {
e.printStackTrace();
XueChengPlusException.cast("合并文件过程中出错");
}
log.debug("合并文件完成{}",mergeFile.getAbsolutePath());
uploadFileParamsDto.setFileSize(mergeFile.length());
try (InputStream mergeFileInputStream = new FileInputStream(mergeFile);) {
//对文件进行校验,通过比较md5值
String newFileMd5 = DigestUtils.md5Hex(mergeFileInputStream);
if (!fileMd5.equalsIgnoreCase(newFileMd5)) {
//校验失败
XueChengPlusException.cast("合并文件校验失败");
}
log.debug("合并文件校验通过{}",mergeFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
//校验失败
XueChengPlusException.cast("合并文件校验异常");
}
//将临时文件上传至minio
String mergeFilePath = getFilePathByMd5(fileMd5, extName);
try {
//上传文件到minIO
addMediaFilesToMinIO(mergeFile.getAbsolutePath(), bucket_videofiles, mergeFilePath);
log.debug("合并文件上传MinIO完成{}",mergeFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
XueChengPlusException.cast("合并文件时上传文件出错");
}
//入数据库
MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_videofiles, mergeFilePath);
if (mediaFiles == null) {
XueChengPlusException.cast("媒资文件入库出错");
}
return RestResponse.success();
} finally {
//删除临时文件
for (File file : chunkFiles) {
try {
file.delete();
} catch (Exception e) {
}
}
try {
mergeFile.delete();
} catch (Exception e) {
}
}
}
/**遍历分块并下载*/
private File[] checkChunkStatus(String fileMd5,int chunkTotal){
//获取分块文件目录
String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
//分块文件数组
File[] chunkFiles=new File[chunkTotal];
//遍历分块
for (int i = 0; i < chunkTotal; i++) {
//拼接路径
String chunkFilePath=chunkFileFolderPath + i;
//分块文件
File chunkFile=null;
try {
chunkFile = File.createTempFile("chunk", null);
}catch (Exception e){
log.error(e.getMessage());
XueChengPlusException.cast("创建临时分块文件出错");
}
chunkFile = this.downloadFileFromMinIO(chunkFile, bucket_videofiles, chunkFilePath);
chunkFiles[i]=chunkFile;
}
return chunkFiles;
}
/**下载分块方法*/
public File downloadFileFromMinIO(File file,String bucket,String objectName){
InputStream fileInputStream = null;
OutputStream fileOutputStream = null;
try {
fileInputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucket)
.object(objectName)
.build());
try {
fileOutputStream = new FileOutputStream(file);
IOUtils.copy(fileInputStream, fileOutputStream);
} catch (IOException e) {
XueChengPlusException.cast("下载文件"+objectName+"出错");
}
} catch (Exception e) {
e.printStackTrace();
XueChengPlusException.cast("文件不存在"+objectName);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return file;
}
/**将文件上传到minIO,传入文件绝对路径*/
public void addMediaFilesToMinIO(String filePath, String bucket, String objectName) {
//扩展名
String extension = null;
if(objectName.indexOf(".")>=0){
extension = objectName.substring(objectName.lastIndexOf("."));
}
//获取扩展名对应的媒体类型
String contentType = getMimeTypeByExtension(extension);
try {
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(bucket)
.object(objectName)
.filename(filePath)
.contentType(contentType)
.build());
} catch (Exception e) {
e.printStackTrace();
XueChengPlusException.cast("上传文件到文件系统出错");
}
}
private String getFilePathByMd5(String fileMd5,String fileExt){
return fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
}
/**入库方法
事务加在入库方法,文件上传方法如文件过大上传时间就会变长事务持续时间也会变长*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket_Files,String objectName) {
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
if (mediaFiles==null){
mediaFiles=new MediaFiles();
//拷贝基本信息
BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
mediaFiles.setId(fileMd5);
mediaFiles.setFileId(fileMd5);
mediaFiles.setCompanyId(companyId);
mediaFiles.setUrl("/" + bucket_Files + "/" + objectName);
mediaFiles.setBucket(bucket_Files);
mediaFiles.setCreateDate(LocalDateTime.now());
mediaFiles.setStatus("1");
mediaFilesMapper.insert(mediaFiles);
}
return mediaFiles;
}
}