Spring Boot+VUE分片上传大文件到OSS服务器解决方案

本文介绍了如何在VUE前端和Spring Boot后端结合,实现大文件分片上传到阿里云OSS服务器的解决方案。详细讨论了前后端交互、分片上传的难点以及解决方法,包括使用Redis缓存中间状态,以应对异步上传和多用户隔离的问题。此外,还涵盖了前端组件设计和文件上传进度的处理。
摘要由CSDN通过智能技术生成

根据项目需要,在很多地方都需要将超大文件上传到服务器,特别是将视频文件上传到云平台的OSS服务器上,这种需求在项目中已经是十分普遍的需求了。在网上收集了很多资料,基本上都只有JAVA到OSS服务器,或都VUE前端服务端到Spring Boot后端服务器。当前前后端开发已经十分普及了,大文件一般都保存到OSS服务器,不会保存到自己的业务服务器,OSS大文件服务器+tomcat为业务服务器+VUE/Ract/小程序前端服务器的三层后台架构是当前最主流的架构设计,因此必须解决这个问题。
通过几天的反复研究和实践,结合网上找到的资料,需要将VUE服务端到Spring Boot服务器和JAVA到OSS服务器两部分结合起来,形成整体的解决方案。
在这个整体中有两上难点:
1、VUE前端服务器的JS程序是异步的,而大文件分片传输需要大量分次请求,请求执行完所顺序是不一致的;
2、由于是三层请求,涉及到多用户,Spring Boot服务器需要将分原来整体功能,撤分为三个,存在中间缓存信息持久化保存与多用户隔离问题

一、解决方案框架

由于这个功能还是非常复杂,为了解决这个问题,还是绘制了功能框架图,并且在具体编程时,对功能框架图进行了多次修正,最终框架如下图所示。
在这里插入图片描述
1、 框架整体上是前端判断是否大文件,超过10M,如果向Spring Boot服务器,请求大文件传输,Spring Boot向OSS服务器申请创新大文件传递的uploadID.
2、前端对文件进行分片,并产生分片MD5码,根据第1得到的id发起分片传输,Spring Boot收到分片后,上传分片
3、Spring boot在redis中缓存存文件ID,分片信息,以备文件合成用
4、前端检索返回MD5,如果有错,重新传递分片
5、前端检查分片是否全部正确传输完成,如果传递完成,发起文件合并请求
6、Spring boot将缓存信息组合,向OSS提出文件合并请求
7、OSS服务器收到相关信息,检验后合成文件并返回文件访问地址
8、Spring boot将访问地址及相关信息保存到服务器,并清除缓存
9、前端收到信息,显示进展,请除缓存存
10、根据地址使用已经上传的文件,如播放或预览。

二、Spring Boot后台程序

后台程序分为Controller和utils两部分,controller与前端交互,utils与OSS交互。

2.1、前端与用户交互的Controller

由于前后端分离,需要将大文件上传分为三个部分,即大文件上传初始化、上传大文件块、合并已上传文件,分别与VUE前端进行交互。

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author wu jize
 * @since 2020-07-05
 */
@RestController
@RequestMapping("/map/scenic/file")
public class MapBigFileController extends BaseController {
   
    @Autowired
    private IMapScenicFileService iMapScenicFileService;

    @Autowired
    IMapScenicService iMapScenicService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private BigFileUploadUtils bigFileUploadUtils;

    /**
     * 大文件上传初始化
     * 返回文件uploadId
     */
    @ApiOperation("大文件上传初始化")
    @PreAuthorize("@ss.hasPermi('data:scenicfile:edit')")
    @Log(title = "大文件上传初始化", businessType = BusinessType.UPDATE)
    @PostMapping("/initUpload")
    public AjaxResult initBigFileUpload(@RequestParam(name = "fileName", required = true) String fileName,
                                        @RequestParam(name = "scenicId", required = false) Long scenicId) {
   
        //格式化文件路径,按县、景区ID、用户名组织文件
        String adcode = iMapScenicService.selectScenicById(scenicId).getAdcode();
        if (StringUtils.isEmpty(adcode)) {
   
            throw new CustomException("景区行政区域编码为空,不能添加景区多媒体文件!");
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        String author = loginUser.getUsername();
        String objectName = "scenicfile" + "/" + adcode + "/" + scenicId.toString() + "/" + author + '/' + fileName;
        String uploadId = bigFileUploadUtils.initUpload(objectName);

        AjaxResult ajax = AjaxResult.success();
        ajax.put("uploadId", uploadId);
        return ajax;
    }

    /**
     * 上传大文件的Chunk
     * 返回chunk的MD5
     */
    @ApiOperation("上传大文件的Chunk")
    @PreAuthorize("@ss.hasPermi('data:scenicfile:edit')")
    @Log(title = "上传大文件的Chunk", businessType = BusinessType.UPDATE)
    @PostMapping("/uploadChunk")
    public AjaxResult uploadChunk(@RequestParam("chunkFile") MultipartFile chunkFile,
                                  @RequestParam(name = "uploadId", required = true) String uploadId,
                                  @RequestParam(name = "chunkId", required = true) Integer chunkId,
                                  @RequestParam(name = "total", required = true) Integer total) throws IOException {
   

        String md5Str = bigFileUploadUtils.uploadChunk(uploadId, chunkId, chunkFile);
        AjaxResult ajax = AjaxResult.success();

        ajax.put("md5Str", md5Str);
        return ajax;
    }

    /**
     * 大文件上传完成后合并
     * 返回文件访问的URL
     */
    @ApiOperation("大文件上传完成后合并")
    @PreAuthorize("@ss.hasPermi('data:scenicfile:edit')")
    @Log(title = "大文件上传完成后合并", businessType = BusinessType.UPDATE)
    @PostMapping("/mergeFile")
    public AjaxResult mergeFile(@RequestParam(name = "uploadId", required = true) String uploadId) {
   
        AjaxResult ajax = AjaxResult.success();
        String url = bigFileUploadUtils.completeFile(uploadId);
        ajax.put("url", url);
        return ajax;
    }
}

2.2、util包,负责与OSS交互。
在后端与OSS交互中,有一个难点是解决大文件前后端分离后,异步数据上传中该文件相关信息的保存。由于是异步请求,方法分为三段执行,不同请求对于Spring Boot来说就是不同的线程,试了以下几种方法,最后用redis缓存存:
1、用包类静态全局变量,由是不同请求会产生不同线程,此方法行不通
2、用线程变量,静态全局变量不行就考虑过用线程变量,功能实现后现还是不行,进一步研究,发现前端不同请求在后端仍然在不同线程,经如初始化大文件上传的uploadId和objectId,在后面请求访问不到。
3、用session变量,研究了一下,发现更加复杂,还不一定能解决得好并好用户,同一用户不同窗口上传问题
4、反馈给前端,uploadId和objectId还行,但是分片的partETag信息太多,处理起来会十分繁琐
5、用数据库临时表进行持久化,也挺复杂,并且还要引入JPA,如用mybatis工作也不少
6、经过多方比较,还是用redis,简单并且速度快,唯一缺点时redis服务器重启后,没有上传完的片断会变成数据垃圾,还没搞清楚阿里OSS怎么处理,不知道要不要占自己的存储容量。Redis根据uploadId保存信息,不同用户,即使同一用户同时上传不同文件其uploadId是唯一的,这样能做好多用户相互隔离问题。

/**
 * 用于超过10M的大文件分片上传
 *
 * @author Wu Jize
 * @version 1.0
 * @date 2020/7/5 18:28
 */
@Component

public class BigFileUploadUtils {
   
    protected static final Logger log = LoggerFactory.getLogger(OSSFileUtils.class);
	//将OSS访问参数保存到配置文件,并忽略上传GIT,避免有权限的数据泄漏
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    
  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值