背景
在Ruoyi框架中,虽然也提供了基于fileinput的文件上传示例,加入企业在真实业务中有大文件的上传,比如上GB的文件,那使用fileinput的用户体验不怎么友好,因而在大容量文件上传处理时,就有必要进行切片,断点续传,重复文件判断等。因此本文将使用百度开源的WebUploader上传组件,对文件上传业务提供统一的封装和扩展,可以满足所有业务场景的覆盖。
本文将重点说明ruoyi使用的基础技术,简单介绍webuploader,webuploader如何在Ruoyi中进行集成。Ruoyi的示例例子采用的是Ruoyi的单体集成框架,不是前后端分离版,不过技术的思路是类似的,可以作为参考。
一、Ruoyi的实现
1、ruoyi的前端实现
ruoyi的前端是依赖于fileinput来实现的,其官方的文档手册地址可以参见:bootstrap-fileinput
实现的效果大致是这样的:
2、Ruoyi后端实现
Ruoyi使用了最简单的文件接收方式,没有文件切片,这样设计的目的,个人猜测是因为不考虑大文件的这种场景,当然在互联网里,确实遇到大文件的情况也不多,使用这样的方案也可以应对。Ruoyi的后台处理类代码如下:
package com.hngtghy.project.common;import java.util.ArrayList;import java.util.List;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import com.hngtghy.common.constant.Constants;import com.hngtghy.common.utils.StringUtils;import com.hngtghy.common.utils.file.FileUploadUtils;import com.hngtghy.common.utils.file.FileUtils;import com.hngtghy.framework.config.HngtghyConfig;import com.hngtghy.framework.config.ServerConfig;import com.hngtghy.framework.web.domain.AjaxResult;/** * 通用请求处理 ** @author wuzuhu */@Controller@RequestMapping("/common")public class CommonController{private static final Logger log = LoggerFactory.getLogger(CommonController.class);@Autowiredprivate ServerConfig serverConfig;private static final String FILE_DELIMETER = ",";/** * 通用上传请求(单个) */@PostMapping("/upload")@ResponseBodypublic AjaxResult uploadFile(MultipartFile file) throws Exception{try{// 上传文件路径String filePath = HngtghyConfig.getUploadPath();// 上传并返回新文件名称String fileName = FileUploadUtils.upload(filePath, file);String url = serverConfig.getUrl() + fileName;AjaxResult ajax = AjaxResult.success();ajax.put("url", url);ajax.put("fileName", fileName);ajax.put("newFileName", FileUtils.getName(fileName));ajax.put("originalFilename", file.getOriginalFilename());return ajax;}catch (Exception e){return AjaxResult.error(e.getMessage());}}/** * 通用上传请求(多个) */@PostMapping("/uploads")@ResponseBodypublic AjaxResult uploadFiles(List files) throws Exception{try{// 上传文件路径String filePath = HngtghyConfig.getUploadPath();List urls = new ArrayList();List fileNames = new ArrayList();List newFileNames = new ArrayList();List originalFilenames = new ArrayList();for (MultipartFile file : files){// 上传并返回新文件名称String fileName = FileUploadUtils.upload(filePath, file);String url = serverConfig.getUrl() + fileName;urls.add(url);fileNames.add(fileName);newFileNames.add(FileUtils.getName(fileName));originalFilenames.add(file.getOriginalFilename());}AjaxResult ajax = AjaxResult.success();ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));return ajax;}catch (Exception e){return AjaxResult.error(e.getMessage());}}}
二、基于WebUploader的切片处理
1、关于webuploader
正是由于Ruoyi天生的大文件处理能力比较差的,经过对开源组件的比较,我们选定了百度开源的百度Webuploader,它具有以下的能力:
2、Webuploader集成
在官网上下载最新的webuploader资源包后,将相应的资源文件拷贝到Ruoyi的工程目录中,
3、在上传页面中引用webuploader.js
在这里,我们设计了统一的文件存储服务,因此,将文件的查询、上传、编辑、删除功能都封装在一个界面中,对外提供单个文件上传功能,也提供批量管理功能。所以,有必要对文件进行统一封装。下面是基于Thymeleaf的一个简单封装:
try{ace.settings.loadState('main-container')}catch(e){} 文件名称
选择
var prefix = [[@{/uploadfile}]];var fileUploadIndex = 0;$(document).ready(function() {$("#btn-search").on("click",doSearch);initTable();});function doSearch(){table.reload('dataTable',{where : {name :$("input[name='name']").val(),}});}var uploadSuccessCallback = function(file,fileArray){var allFinished = true;for(i in fileArray){ var obj = fileArray[i];if(obj.status != '上传失败' && obj.status != '上传成功'){ allFinished = false; break;} }if(allFinished){doSearch();parent.layer.close(fileUploadIndex);modal = null;}}function initTable(){var bid = [[${bid}]];var temp_b_id = [[${temp_b_id}]];var b_ids =bid == null " /> 0){$(".filepicker_btn").hide();}else{$(".filepicker_btn").show();}}},cols: [[//表头{type: 'checkbox',fixed: 'left'},{field: 'name', title: '文件名',sort: true, fixed: 'left'},{field: 'createTime', title: '创建时间',sort: true,width:170,align: 'center'},{field: 'size', title: '文件大小',sort: true,width:110,align: 'center',templet: function(data){return WebUploader.Base.formatSize(data.size); }},{field:'title', title: '操作',width:120,templet: function(data){var actions = [];actions.push('下载 ');actions.push('删除 ');return actions.join('');} }]] }); //监听工具条table.on('toolbar(dataTable)', function(obj){var layEvent = obj.event;if(layEvent == 'create'){}if(layEvent == "del"){}});//监听排序事件table.on('sort(dataTable)',function(obj){table.reload('dataTable',{initSort: obj,where:{orderByColumn:obj.field,isAsc:obj.type}});});});}function deleteFile(fid){$.ajax({type:"POST",url:[[@{/uploadfile/deleteByFid}]],data:{fid : fid,},dataType:"json",success:function(response){doSearch();parent.layer.msg("操作成功",{time:1500,icon:6});},error:function(){}});}function downloadFile(fid){window.location.href=[[@{/uploadfile/download}]]+"?fid="+ fid;}
4、Webuploader功能定制
这里我们放在百度网盘的样子对WebUpload的样式进行改造,同时需要将文件上传的列表展示出来,同时可以对文件进行上传、暂停、删除等操作,因此需要对webuploader进行定制化开发。相关代码如下:
function initUploader(){bindFileListeners();var fileNumLimit = [[${fileNumLimit}]];//文件数量限制var acceptType = [[${acceptType}]];//支持文件类型var auto = [[${autoUpload}]];//是否自动上传0否1是var multipleMode = [[${multipleMode}]];//多选模式add by wuzuhu on 2022-07-18uploader = WebUploader.create({auto: auto==0 ? false : true, swf: [[@{/uploader/Uploader.swf}]], server: [[@{/uploadfile/bigUploader}]],pick: {id:'#filePicker_'+[[${temp_b_id}]],multiple: multipleMode == "single" ? false : true},dnd: '#filePicker_'+[[${temp_b_id}]],method:'POST', resize: false ,chunked : true,chunkRetry:false,formData : { fid : '', name : '', size : 0, md5code : '', tablename : tablename, temp_b_id : temp_b_id, bizType : [[${bizType}]], bid : b_id }, compress : false, duplicate:true, prepareNextFile: true, disableGlobalDnd:true, }); uploader.on('beforeFileQueued', function(file) { if(file.size == 0){ var error = "文件不能为空!"; parent.layer.msg(error); return false; } if (fileNumLimit != null && fileNumLimit block_size) {obj.status = '99.99%';if(modal){modal.updateStatus(obj.f_id,'99.99%')}}} else {percentage = (percentage * 100).toFixed(2);if (percentage + "%" === obj.status) {return;}obj.status = percentage + "%";if(modal){modal.updateStatus(obj.f_id,percentage + "%")}}} }); uploader.on( 'uploadBeforeSend', function( block,data,headers ) {var obj = getFileObjById(block.file.id); data.md5code = obj.md5code; data.fid = obj.f_id;data.name = obj.f_name; data.size = obj.f_size; data.chunk = block.chunk; data.chunkSize = block.end-block.start; });}function addFiles(files){for(i in files){var file = files[i];addFile(file);}}
由于篇幅有限,这里不把所有的代码都列出来,仅将部分代码列出来。
三、WebUploader与Ruoyi集成总结
这里讲解了Webuploader与Ruoyi的简单集成,这是第一个部分,如果需要详细了解的,可以深入交流,这里有涉及数据分片的具体实现,还有后端的服务端支持等等,关于后端的设计和业务表的设计,打算在后续再进行说明。
Webuploader与Ruoyi的集成效果图如下图所示:
通过观察网络请求可以看到,前端往服务端提交数据时,数据是已经进行了分片: