SPRINGBOOT实现大文件上传,断点续传,秒传功能
首先要理解,为什么大文件上传跟小文件上传不一样。假设我有个1G的文件或者更大需要上传,如果直接上传的话会有什么弊端?
1.上传过程时间较长
2.中途不能间断,间断之后需要重新上传等
简单来说:大文件上传其实就是前端对文件做一个分片处理,将一个大文件分成很多份小文件上传,后端将小文件进行一个存储合并的过程。
码农最喜欢的搬砖来了,前端基于vue的一个开源插件。大家可以去github上下载。下载地址:https://github.com/pseudo-god/vue-simple-upload
后端代码(仅供参考,实际根据业务进行处理即可,重在理解):
两个处理文件路径和名称的工具类
与数据库对应的持久类
处理前端请求controller类
public class BigFileApi {
@Autowired
private FileSectionService fileSectionService; //操作数据库表的service类
@Value("${custom.upload.dir}")
private String upLoadDir ; //文件的存储路径
@Autowired
private IACLNodeApi aclNodeApi ;
/**
* 大文件上传 超过500M 理论无上限
* @param request
* @param multipartFile 文件块
* @return
* @throws IllegalStateException
* @throws IOException
* */
@ApiOperation(value="大文件上传 超过500M 理论无上限")
@RequestMapping(value = "/fileChunk/uploadChunkFile" , method = RequestMethod.POST)
public APIResult<Map<String,Object>> uploadBigFile(HttpServletRequest request, @RequestParam(value = "file",required = false) MultipartFile multipartFile) {
UserBean user = UserHolder.getUser();
if(user == null ){
return new APIResult<Map<String,Object>>("请先登陆" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
Map<String,Object> map = Maps.newHashMap();
String bigFileId = request.getParameter("id");
String md5 = request.getParameter("md5"); //分片的md5 和整个文件的md5相同 这里可以忽略
String fileName = request.getParameter("fileName"); //
int index = Integer.parseInt(fileName);
FileSection recordById = fileSectionService.findById(bigFileId);
if (recordById == null){
return new APIResult<Map<String,Object>>("上传失败,参数错误!" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
if (recordById.getFileStatus() ==1){
return new APIResult<Map<String,Object>>("文件已经存在,请勿重复上传" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
//文件夹路径
String saveDirectory= FileOperaHelper.CreatePath(upLoadDir,recordById.getFileDate(),recordById.getUuid());
//分片文件
File path = new File(saveDirectory);
if (!path.exists()) {
path.mkdirs();
}
File file = new File(saveDirectory, recordById.getUuid() + "_" + index);
FileSection fileSectionRecordUpload = new FileSection();
//先删除后上传
if (file.exists()) {
file.delete();
}
try {
multipartFile.transferTo(file.getAbsoluteFile());
} catch (IOException e) {
e.printStackTrace();
fileSectionRecordUpload.setFileIndex(index);
map.put("status", 4);
return new APIResult<Map<String,Object>>("成功!" , SystemConst.SYSTEM_STATUS_TRUE.getState() , map);
}
fileSectionRecordUpload = fileSectionService.findById(bigFileId);
if (fileSectionRecordUpload == null){
return new APIResult<Map<String,Object>>("上传失败,md5值错误!" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}else{
if(index > fileSectionRecordUpload.getTotal()){ //文件下标数超过了总数 上传失败
return new APIResult<Map<String,Object>>("上传失败,分片数大于总数!" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
fileSectionRecordUpload.setFileIndex(index);
fileSectionRecordUpload.setMd5(md5);
fileSectionRecordUpload.setPath(saveDirectory);
fileSectionRecordUpload.setFileIndex(index);
fileSectionRecordUpload.setFileStatus(0);
fileSectionService.modify(fileSectionRecordUpload);
}
map.put("record",fileSectionRecordUpload);
map.put("status", 2);
return new APIResult<Map<String,Object>>("成功!" , SystemConst.SYSTEM_STATUS_TRUE.getState() , map);
}
/**
* 最终分片文件合并
* */
@ApiOperation(value="最终分片文件合并")
@RequestMapping(value = "/fileChunk/merge" , method = RequestMethod.POST)
public APIResult<Map<String,Object>> fileChunkMerge(@RequestBody FileSection sectionRecord){
Map<String,Object> map = Maps.newHashMap();
UserBean user = UserHolder.getUser();
if(user == null ){
return new APIResult<Map<String,Object>>("请先登陆" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
String md5 = sectionRecord.getMd5();
if (StringUtils.isEmpty(md5)){
return new APIResult<Map<String,Object>>("md5参数不能为空" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
FileSection selectBean = new FileSection();
selectBean.setMd5(md5);
selectBean.setFileStatus(0);
List<FileSection> fileSectionRecordList = fileSectionService.getListByFileSection(selectBean);
if (CollectionUtils.isEmpty(fileSectionRecordList)){
return new APIResult<Map<String,Object>>("md5参数错误" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
FileSection fileSectionRecord = fileSectionRecordList.get(0);
if (fileSectionRecord.getFileStatus() == 1){
return new APIResult<Map<String,Object>>("文件已经存在,请勿重复上传" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
File path = new File(fileSectionRecord.getPath());
if (path.isDirectory()) {
File[] fileArray = path.listFiles();
if (fileArray != null && fileArray.length == fileSectionRecord.getTotal()) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
MergeFilesThread mergeFilesThread = new MergeFilesThread();
mergeFilesThread.setFileSectionRecord(fileSectionRecord);
mergeFilesThread.setFileSectionService(fileSectionService);
mergeFilesThread.setUpLoadDir(upLoadDir);
cachedThreadPool.submit(mergeFilesThread);
map.put("record",fileSectionRecord);
map.put("status", 1);
return new APIResult<Map<String,Object>>("成功!" , SystemConst.SYSTEM_STATUS_TRUE.getState() , map);
}else{
fileSectionService.removeById(fileSectionRecord.getId());
return new APIResult<Map<String,Object>>("文件上传失败,请重新上传" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
}
map.put("record",fileSectionRecord);
map.put("status", 0);
return new APIResult<Map<String,Object>>("成功!" , SystemConst.SYSTEM_STATUS_TRUE.getState() , map);
}
/**
* 上传文件前校验
*
* @param sectionRecord
* @return
* @throws IOException
*/
@ApiOperation(value="上传文件前校验")
@RequestMapping(value = "/file/isUpload" , method = RequestMethod.POST)
public APIResult<Map<String,Object>> isUpload(@RequestBody FileSection sectionRecord){
UserBean user = UserHolder.getUser();
if(user == null ){
return new APIResult<Map<String,Object>>("请先登陆" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
Site site = SiteHolder.getSite() ;
ResourceBean rootNode = ACLNodeApiHelper.getRootNode(aclNodeApi, site.getAppId()) ;
if (rootNode == null) {
return new APIResult<Map<String,Object>>("非法请求!", SystemConst.SYSTEM_STATUS_FALSE.getState());
}
if (StringUtils.isEmpty(sectionRecord.getMd5())){
return new APIResult<Map<String,Object>>("md5参数不能为空" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
String fileName = sectionRecord.getFileName();
FileSection fileSectionRecord = new FileSection();
fileSectionRecord.setFileMd5(sectionRecord.getMd5());
List<FileSection> list = fileSectionService.getListByFileSection(fileSectionRecord);
Map<String,Object> map = FileOperaHelper.FileExit(list);
if ((int)map.get("status") == 1){
FileSection record = new FileSection();
String uuid = GuidUtil.genGuid();
record.setFileDate(DateHelper.getNowDateStr());
record.setSuffix(NameHelper.getExtensionName(fileName));
record.setName(NameHelper.getFileNameNoEx(fileName));
record.setCreator(user.getUsername());
record.setCreatorId(user.getId());
record.setFileMd5(sectionRecord.getMd5());
record.setModifierId(user.getId());
record.setModifier(user.getUsername());
record.setFileIndex(0);
record.setAppId(sectionRecord.getAppId());
record.setRefId(sectionRecord.getRefId());
record.setTotal(sectionRecord.getTotal());
record.setSize(sectionRecord.getSize());
record.setUuid(uuid);
record.setFileName(fileName);
record.setFileStatus(0);
record.setId(GuidUtil.genGuid());
FileSection save = fileSectionService.save(record);
map.put("record",save);
}
if ((int)map.get("status") == 3){
String id = GuidUtil.genGuid();
FileSection record = (FileSection) map.get("record");
/**
* 查询是否存在文件MD5,论文id,创建人id相同的记录 ,如果相同则不存储记录,不同则储存记录
* */
FileSection section = new FileSection();
section.setCreatorId(user.getId());
section.setRefId(sectionRecord.getRefId());
section.setMd5(sectionRecord.getMd5());
List<FileSection> sectionList = fileSectionService.getListByFileSection(section);
if (CollectionUtils.isNotEmpty(sectionList)){
record.setId(id);
record.setRefId(sectionRecord.getRefId());
record.setFileName(fileName);
record.setSuffix(NameHelper.getExtensionName(fileName));
record.setName(NameHelper.getFileNameNoEx(fileName));
record.setCreator(user.getUsername());
record.setCreatorId(user.getId());
record.setModifierId(user.getId());
record.setModifier(user.getUsername());
fileSectionService.save(record);
}
}
return new APIResult<Map<String,Object>>("成功!" , SystemConst.SYSTEM_STATUS_TRUE.getState() , map);
}
/**
* 查询视频列表
* */
@ApiOperation(value="查询视频列表")
@RequestMapping(value = "/file/getList" , method = RequestMethod.POST)
public APIResult<PageInfo<FileSectionBean>> getList(@RequestBody FileSectionQuery sectionRecordQuery){
UserBean user = UserHolder.getUser();
if(user == null ){
return new APIResult<PageInfo<FileSectionBean>>("请先登陆" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
Site site = SiteHolder.getSite() ;
ResourceBean rootNode = ACLNodeApiHelper.getRootNode(aclNodeApi, site.getAppId()) ;
if (rootNode == null) {
return new APIResult<PageInfo<FileSectionBean>>("非法请求!", SystemConst.SYSTEM_STATUS_FALSE.getState());
}
PageInfo<FileSection> pageInfo = fileSectionService.getList(sectionRecordQuery);
PageInfo<FileSectionBean> info = fileSectionService.getBeanToList(pageInfo);
return new APIResult<PageInfo<FileSectionBean>>("成功", SystemConst.SYSTEM_STATUS_TRUE.getState(),info);
}
@ApiOperation(value="删除大文件")
@RequestMapping(value = "/file/deleteFile" , method = RequestMethod.POST)
public APIResult<String> deleteFile(@RequestBody FileSectionBean sectionRecord){
UserBean user = UserHolder.getUser();
if(user == null ){
return new APIResult<String>("请先登陆" , SystemConst.SYSTEM_STATUS_FALSE.getState());
}
Site site = SiteHolder.getSite() ;
ResourceBean rootNode = ACLNodeApiHelper.getRootNode(aclNodeApi, site.getAppId()) ;
if (rootNode == null) {
return new APIResult<String>("非法请求!", SystemConst.SYSTEM_STATUS_FALSE.getState());
}
fileSectionService.deleteById(sectionRecord.getId());
return new APIResult<String>("删除成功!", SystemConst.SYSTEM_STATUS_TRUE.getState());
}
}
参考文章:http://blog.ncmem.com/wordpress/2023/11/16/springboot%e5%ae%9e%e7%8e%b0%e5%a4%a7%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0%ef%bc%8c%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0%ef%bc%8c%e7%a7%92%e4%bc%a0%e5%8a%9f%e8%83%bd/
欢迎入群一起讨论
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2020-11-16 js+php分片上传大文件源码
2020-11-16 js+php分片上传大文件插件
2020-11-16 js+php分片上传大文件控件
2020-11-16 js+php分片上传大文件组件
2020-11-16 js+php分片上传大文件工具
2020-11-16 js+Nginx分片上传大文件
2020-11-16 js+百度WebUploader分片上传大文件