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

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

一、解决方案框架

由于这个功能还是非常复杂,为了解决这个问题,还是绘制了功能框架图,并且在具体编程时,对功能框架图进行了多次修正,最终框架如下图所示。
Spring Boot+VUE分片上传大文件到OSS服务器解决方案_第1张图片
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前端进行交互。

/**
 * 

* 前端控制器 *

* * @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;

你可能感兴趣的:(java,vue,OSS,阿里云)