基于Ruoyi和WebUploader的统一附件管理扩展(上)

简介: 本文将重点说明ruoyi使用的基础文件处理技术,简单介绍了webuploader,webuploader如何在Ruoyi中进行集成,最后给出了集成的页面效果,可以作为技术参考。

背景

     在Ruoyi框架中,虽然也提供了基于fileinput的文件上传示例,加入企业在真实业务中有大文件的上传,比如上GB的文件,那使用fileinput的用户体验不怎么友好,因而在大容量文件上传处理时,就有必要进行切片,断点续传,重复文件判断等。因此本文将使用百度开源的WebUploader上传组件,对文件上传业务提供统一的封装和扩展,可以满足所有业务场景的覆盖。

     本文将重点说明ruoyi使用的基础技术,简单介绍webuploader,webuploader如何在Ruoyi中进行集成。Ruoyi的示例例子采用的是Ruoyi的单体集成框架,不是前后端分离版,不过技术的思路是类似的,可以作为参考。

一、Ruoyi的实现

1、ruoyi的前端实现

        ruoyi的前端是依赖于fileinput来实现的,其官方的文档手册地址可以参见:bootstrap-fileinput

实现的效果大致是这样的:

2、Ruoyi后端实现

       Ruoyi使用了最简单的文件接收方式,没有文件切片,这样设计的目的,个人猜测是因为不考虑大文件的这种场景,当然在互联网里,确实遇到大文件的情况也不多,使用这样的方案也可以应对。Ruoyi的后台处理类代码如下:

packagecom.hngtghy.project.common;
importjava.util.ArrayList;
importjava.util.List;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.http.MediaType;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.PostMapping;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.ResponseBody;
importorg.springframework.web.multipart.MultipartFile;
importcom.hngtghy.common.constant.Constants;
importcom.hngtghy.common.utils.StringUtils;
importcom.hngtghy.common.utils.file.FileUploadUtils;
importcom.hngtghy.common.utils.file.FileUtils;
importcom.hngtghy.framework.config.HngtghyConfig;
importcom.hngtghy.framework.config.ServerConfig;
importcom.hngtghy.framework.web.domain.AjaxResult;
/*** 通用请求处理* * @author wuzuhu*/@Controller@RequestMapping("/common")
publicclassCommonController{
privatestaticfinalLoggerlog=LoggerFactory.getLogger(CommonController.class);
@AutowiredprivateServerConfigserverConfig;
privatestaticfinalStringFILE_DELIMETER=",";
/*** 通用上传请求(单个)*/@PostMapping("/upload")
@ResponseBodypublicAjaxResultuploadFile(MultipartFilefile) throwsException    {
try        {
// 上传文件路径StringfilePath=HngtghyConfig.getUploadPath();
// 上传并返回新文件名称StringfileName=FileUploadUtils.upload(filePath, file);
Stringurl=serverConfig.getUrl() +fileName;
AjaxResultajax=AjaxResult.success();
ajax.put("url", url);
ajax.put("fileName", fileName);
ajax.put("newFileName", FileUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
returnajax;
        }
catch (Exceptione)
        {
returnAjaxResult.error(e.getMessage());
        }
    }
/*** 通用上传请求(多个)*/@PostMapping("/uploads")
@ResponseBodypublicAjaxResultuploadFiles(List<MultipartFile>files) throwsException    {
try        {
// 上传文件路径StringfilePath=HngtghyConfig.getUploadPath();
List<String>urls=newArrayList<String>();
List<String>fileNames=newArrayList<String>();
List<String>newFileNames=newArrayList<String>();
List<String>originalFilenames=newArrayList<String>();
for (MultipartFilefile : files)
            {
// 上传并返回新文件名称StringfileName=FileUploadUtils.upload(filePath, file);
Stringurl=serverConfig.getUrl() +fileName;
urls.add(url);
fileNames.add(fileName);
newFileNames.add(FileUtils.getName(fileName));
originalFilenames.add(file.getOriginalFilename());
            }
AjaxResultajax=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));
returnajax;
        }
catch (Exceptione)
        {
returnAjaxResult.error(e.getMessage());
        }
    }
}

二、基于WebUploader的切片处理

1、关于webuploader

       正是由于Ruoyi天生的大文件处理能力比较差的,经过对开源组件的比较,我们选定了百度开源的百度Webuploader,它具有以下的能力:

2、Webuploader集成

       在官网上下载最新的webuploader资源包后,将相应的资源文件拷贝到Ruoyi的工程目录中,

3、在上传页面中引用webuploader.js

      在这里,我们设计了统一的文件存储服务,因此,将文件的查询、上传、编辑、删除功能都封装在一个界面中,对外提供单个文件上传功能,也提供批量管理功能。所以,有必要对文件进行统一封装。下面是基于Thymeleaf的一个简单封装:

<!DOCTYPE html><htmllang="zh"xmlns:th="http://www.thymeleaf.org"><headth:include="include :: webupload"><bodyclass="no-skin"><divclass="main-container ace-save-state"id="main-container"><scripttype="text/javascript">try{ace.settings.loadState('main-container')}catch(e){}
</script><divclass="main-content"><divclass="main-content-inner"><divclass="page-content"style="padding: 8px 10px 0px;"><divclass="widget-box"><div><formclass="form-search"><divclass="row"><divclass="col-xs-12 col-sm-12"><tableclass="table"style="margin-bottom: 0px;"><tbody><tr><tdstyle="border: 0px;vertical-align: middle;width:10%;"><pclass="text-right">文件名称</p></td><tdstyle="border: 0px;vertical-align: middle;width:50%;"><inputtype="text"name="name"class="form-control"placeholder="请输入文件名称"/></td><tdstyle="border: 0px;vertical-align: middle;width:30%;"><buttontype="button"class="btn btn-primary btn-xs"id="btn-search"><spanclass="fa fa-search "></span>                              查询
</button><ahref="#"class="btn btn-success btn-xs filepicker_btn"th:id="'filePicker_'+${temp_b_id}"><iclass="ace-icon fa fa-upload"></i>                              选择
</a><divth:include="include-upload-js :: header"></div></td></tr></tbody></table></div></div></form></div></div><divclass="table-responsive"><tableid="dataTable"lay-filter="dataTable"cellspacing="0"></table></div></div></div></div></div><scriptth:inline="javascript">varprefix= [[@{/uploadfile}]];varfileUploadIndex=0;
$(document).ready(function() {
$("#btn-search").on("click",doSearch);
initTable();
    });
functiondoSearch(){
table.reload('dataTable',{
where : {
name :$("input[name='name']").val(),
        }
      });
    }
varuploadSuccessCallback=function(file,fileArray){
varallFinished=true;
for(iinfileArray){
varobj=fileArray[i];     
if(obj.status!='上传失败'&&obj.status!='上传成功'){
allFinished=false;
break;
         }
       }
if(allFinished){
doSearch();
parent.layer.close(fileUploadIndex);
modal=null;
      }
    }
functioninitTable(){
varbid= [[${bid}]];
vartemp_b_id= [[${temp_b_id}]];
varb_ids=bid==null?temp_b_id : bid;
vartablename= [[${tablename}]];
varbizType= [[${bizType}]];
varmultipleMode= [[${multipleMode}]];
layui.use('table', function(){
table=layui.table;
table.render({
elem: '#dataTable',
height: "full",
url: prefix+"/list?b_id="+b_ids,
method : "post",
page: true,
//toolbar:"#toolbar",defaultToolbar:[],
where:{orderByColumn:'createTime',isAsc:'desc',tablename:tablename,bizType:bizType},
done: function(res, curr, count){
if(multipleMode=="single"){//单选模式下需要进行设置 add by wuzuhu on 2022-07-18if(count>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){
returnWebUploader.Base.formatSize(data.size);
                 }},
              {field:'title', title: '操作',width:120,templet: function(data){
varactions= [];
actions.push('<a class="btn btn-purple btn-xs" href="#" onclick="downloadFile(\''+data.fid+'\')"><i class="fa fa-download">下载</i></a> ');
actions.push('<a class="btn btn-danger btn-xs" href="#" onclick="deleteFile(\''+data.fid+'\')"><i class="fa fa-remove">删除</i></a> ');
returnactions.join('');
                  }
             }
            ]] }); 
//监听工具条table.on('toolbar(dataTable)', function(obj){
varlayEvent=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}
          });
        });
      });
    }
functiondeleteFile(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(){
            }
        });
    }
functiondownloadFile(fid){
window.location.href=[[@{/uploadfile/download}]]+"?fid="+fid;
    }
</script></body></html>

4、Webuploader功能定制

      这里我们放在百度网盘的样子对WebUpload的样式进行改造,同时需要将文件上传的列表展示出来,同时可以对文件进行上传、暂停、删除等操作,因此需要对webuploader进行定制化开发。相关代码如下:

functioninitUploader(){
bindFileListeners();
varfileNumLimit= [[${fileNumLimit}]];//文件数量限制varacceptType= [[${acceptType}]];//支持文件类型varauto= [[${autoUpload}]];//是否自动上传0否1是varmultipleMode= [[${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){
varerror="文件不能为空!";
parent.layer.msg(error);
returnfalse;
         }
if (fileNumLimit!=null&&fileNumLimit<=fileArray.length) {
message.info("文件数量不能超过"+fileNumLimit);
returnfalse;
               }
varfile_name=file.name;
varfile_type=file_name.substring(file_name.lastIndexOf(".") +1);
if (acceptType!=null&&acceptType.length!==0) {
if (acceptType.indexOf(file_type) ==-1) {
message.info("文件类型只能是"+acceptType.toString());
returnfalse;
                  }
               }
returntrue;
       });
uploader.on('fileQueued', function(file) {
/* for(i in fileArray){           var obj = fileArray[i];                if(obj.name == file.name){             if(obj.status == '上传失败'){              modal.removeFile(obj.f_id);             }           }         }           var uuid = WebUploader.Base.guid('');         var file_upload = new FileObj(file.id,uuid,file.name,file.size,'','等待上传','','');           fileArray.push(file_upload);         openProcessModalFile(file); */        }); 
uploader.on('filesQueued', function(files) {
for(jinfiles){
varfile=files[j];
for(iinfileArray){
varobj=fileArray[i];     
if(obj.name==file.name){
if(obj.status=='上传失败'&&modal!=null){
modal.removeFile(obj.f_id);
               }
             }
           }
varuuid=WebUploader.Base.guid('');
varfile_upload=newFileObj(file.id,uuid,file.name,file.size,'','等待上传','','');
fileArray.push(file_upload);
         }
openProcessModalFiles(files);
        }); 
uploader.on( 'uploadProgress', function( file, percentage ) {
varobj=getFileObjById(file.id);
if(obj.status=='暂停'){
return;
         }
if (obj.id===file.id) {
if (percentage===1) {
if (obj.status==='99.99%') {
return;
                        }
if (file.size>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 ) { 
varobj=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;
       });
    }
functionaddFiles(files){
for(iinfiles){
varfile=files[i];
addFile(file);
      }
    }

由于篇幅有限,这里不把所有的代码都列出来,仅将部分代码列出来。

三、WebUploader与Ruoyi集成总结

      这里讲解了Webuploader与Ruoyi的简单集成,这是第一个部分,如果需要详细了解的,可以深入交流,这里有涉及数据分片的具体实现,还有后端的服务端支持等等,关于后端的设计和业务表的设计,打算在后续再进行说明。

      Webuploader与Ruoyi的集成效果图如下图所示:

通过观察网络请求可以看到,前端往服务端提交数据时,数据是已经进行了分片:

目录
相关文章
|
2月前
|
JavaScript 前端开发 Java
SpringBoot 引入 smart-doc 接口文档管理插件,以及统一接口返回,最后推送到 Torna,进行统一管理
本文介绍了如何在SpringBoot项目中整合smart-doc接口文档管理插件,实现接口文档的生成和统一管理,并展示了如何将文档推送到Torna接口文档管理系统进行进一步的集中管理。
125 0
SpringBoot 引入 smart-doc 接口文档管理插件,以及统一接口返回,最后推送到 Torna,进行统一管理
|
3月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理
循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理
|
3月前
|
存储 JavaScript Java
若依修改,如何安装wangEditor,图片上传接口编写。建议暴露专门写一个图片存储的接口
若依修改,如何安装wangEditor,图片上传接口编写。建议暴露专门写一个图片存储的接口
若依修改,如何安装wangEditor,图片上传接口编写。建议暴露专门写一个图片存储的接口
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的基于保信息学科平台系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的基于保信息学科平台系统附带文章和源代码设计说明文档ppt
40 10
基于springboot+vue.js的基于保信息学科平台系统附带文章和源代码设计说明文档ppt
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的校园体育场馆(设施)使用管理网站附带文章和源代码设计说明文档ppt
基于springboot+vue.js的校园体育场馆(设施)使用管理网站附带文章和源代码设计说明文档ppt
55 12
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的企业资产管理系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的企业资产管理系统附带文章和源代码设计说明文档ppt
63 8
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的企业OA管理系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的企业OA管理系统附带文章和源代码设计说明文档ppt
74 8
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的基于工程教育认证的计算机课程管理平台附带文章和源代码设计说明文档ppt
基于springboot+vue.js的基于工程教育认证的计算机课程管理平台附带文章和源代码设计说明文档ppt
46 6
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的政府管理的系统附带文章和源代码设计说明文档ppt
基于springboot+vue.js的政府管理的系统附带文章和源代码设计说明文档ppt
30 2
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js的校园资产管理附带文章和源代码设计说明文档ppt
基于springboot+vue.js的校园资产管理附带文章和源代码设计说明文档ppt
30 0
下一篇
无影云桌面