松鼠的博客

导航

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/

欢迎入群一起讨论

 

 

posted on 2023-11-16 11:53  Xproer-松鼠  阅读(179)  评论(0编辑  收藏  举报