前言

最近在做工作流的事情,正好有个需求,要添加一个附件上传的功能,曾找过不少上传插件,都不是特别满意。无意中发现一个很好用的开源web文件管理器插件 elfinder,功能比较完善,社区也很活跃,还方便二次开发。

环境搭建

软件 地址
SpringBoot https://spring.io/projects/spring-boot/
elFinder https://studio-42.github.io/elFinder/

项目截图

周末抽时间做了一个简单的案例,希望对大家有所帮助,下面是简单的项目截图。



项目功能

在线新建目录、文件、附件上传、下载、预览、在线打包,图片在线裁剪、编辑,实现列表试图、图标视图等等一些列功能。

项目配置

项目在第三方插件进行二次开发,基于 SpringBoot 注解配置实现。

application.properties 配置:

# 执行类,内部调用,实现前端相关功能
file-manager.command=com.itstyle.cloud.common.elfinder.command
file-manager.thumbnail.width=80
file-manager.volumes[0].Node=
file-manager.volumes[0].source=fileSystem
file-manager.volumes[0].alias=file
# 文件存放目录,可以自定义
file-manager.volumes[0].path=D:/cloudFile
file-manager.volumes[0]._default=true
file-manager.volumes[0].locale=
file-manager.volumes[0].constraint.locked=false
file-manager.volumes[0].constraint.readable=true
file-manager.volumes[0].constraint.writable=true

ElfinderConfiguration 读取配置:

@Component
@ConfigurationProperties(prefix="file-manager") //接收application.properties中的file-manager下面的属性
public class ElfinderConfiguration { private Thumbnail thumbnail; private String command; private List<Node> volumes; private Long maxUploadSize = -1L; //省略部分代码
}

elfinderStorageFactory 初始化 基础Bean:

@Configuration
public class ElFinderConfig { @Autowired
private ElfinderConfiguration elfinderConfiguration; @Bean(name = "commandFactory")
public CommandFactory getCommandFactory() {
CommandFactory commandFactory = new CommandFactory();
commandFactory.setClassNamePattern(elfinderConfiguration.getCommand()+".%sCommand");
return commandFactory;
} @Bean(name = "elfinderStorageFactory")
public ElfinderStorageFactory getElfinderStorageFactory() {
DefaultElfinderStorageFactory elfinderStorageFactory = new DefaultElfinderStorageFactory();
elfinderStorageFactory.setElfinderStorage(getElfinderStorage());
return elfinderStorageFactory;
} @Bean(name = "elfinderStorage")
public ElfinderStorage getElfinderStorage() {
DefaultElfinderStorage defaultElfinderStorage = new DefaultElfinderStorage(); // creates thumbnail
DefaultThumbnailWidth defaultThumbnailWidth = new DefaultThumbnailWidth();
defaultThumbnailWidth.setThumbnailWidth(elfinderConfiguration.getThumbnail().getWidth().intValue()); // creates volumes, volumeIds, volumeLocale and volumeSecurities
Character defaultVolumeId = 'A';
List<Node> elfinderConfigurationVolumes = elfinderConfiguration.getVolumes();
List<Volume> elfinderVolumes = new ArrayList<>(elfinderConfigurationVolumes.size());
Map<Volume, String> elfinderVolumeIds = new HashMap<>(elfinderConfigurationVolumes.size());
Map<Volume, Locale> elfinderVolumeLocales = new HashMap<>(elfinderConfigurationVolumes.size());
List<VolumeSecurity> elfinderVolumeSecurities = new ArrayList<>(); // creates volumes
for (Node elfinderConfigurationVolume : elfinderConfigurationVolumes) { final String alias = elfinderConfigurationVolume.getAlias();
final String path = elfinderConfigurationVolume.getPath();
final String source = elfinderConfigurationVolume.getSource();
final String locale = elfinderConfigurationVolume.getLocale();
final boolean isLocked = elfinderConfigurationVolume.getConstraint().isLocked();
final boolean isReadable = elfinderConfigurationVolume.getConstraint().isReadable();
final boolean isWritable = elfinderConfigurationVolume.getConstraint().isWritable(); // creates new volume
Volume volume = VolumeSources.of(source).newInstance(alias, path); elfinderVolumes.add(volume);
elfinderVolumeIds.put(volume, Character.toString(defaultVolumeId));
elfinderVolumeLocales.put(volume, LocaleUtils.toLocale(locale)); // creates security constraint
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setLocked(isLocked);
securityConstraint.setReadable(isReadable);
securityConstraint.setWritable(isWritable); // creates volume pattern and volume security
final String volumePattern = Character.toString(defaultVolumeId) + ElFinderConstants.ELFINDER_VOLUME_SERCURITY_REGEX;
elfinderVolumeSecurities.add(new DefaultVolumeSecurity(volumePattern, securityConstraint)); // prepare next volumeId character
defaultVolumeId++;
} defaultElfinderStorage.setThumbnailWidth(defaultThumbnailWidth);
defaultElfinderStorage.setVolumes(elfinderVolumes);
defaultElfinderStorage.setVolumeIds(elfinderVolumeIds);
defaultElfinderStorage.setVolumeLocales(elfinderVolumeLocales);
defaultElfinderStorage.setVolumeSecurities(elfinderVolumeSecurities);
return defaultElfinderStorage;
}
}

CloudDiskController 控制层实现:

@Controller
@RequestMapping("elfinder/connector")
public class CloudDiskController { private static final Logger logger = LoggerFactory.getLogger(CloudDiskController.class); public static final String OPEN_STREAM = "openStream";
public static final String GET_PARAMETER = "getParameter"; @Resource(name = "commandFactory")
private ElfinderCommandFactory elfinderCommandFactory; @Resource(name = "elfinderStorageFactory")
private ElfinderStorageFactory elfinderStorageFactory; @RequestMapping
public void connector(HttpServletRequest request, final HttpServletResponse response) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
request = processMultipartContent(request);
} catch (Exception e) {
throw new IOException(e.getMessage());
} String cmd = request.getParameter(ElFinderConstants.ELFINDER_PARAMETER_COMMAND);
ElfinderCommand elfinderCommand = elfinderCommandFactory.get(cmd); try {
final HttpServletRequest protectedRequest = request;
elfinderCommand.execute(new ElfinderContext() {
@Override
public ElfinderStorageFactory getVolumeSourceFactory() {
return elfinderStorageFactory;
} @Override
public HttpServletRequest getRequest() {
return protectedRequest;
} @Override
public HttpServletResponse getResponse() {
return response;
}
});
} catch (Exception e) {
logger.error("Unknown error", e);
}
}
//省略部分代码
}

最后,前端页面引入:

<div id="elfinder"></div>
<script type="text/javascript" charset="utf-8">
window.onload = function() {
elFinder.prototype.loadCss('/elfinder/jquery-ui-1.12.1.custom/jquery-ui.css');
$('#elfinder').elfinder({
url : '/elfinder/connector',
lang: 'zh_CN',
height : window.innerHeight-20,
commandsOptions: {
edit: {
editors : [
{
info:{
name:'编辑',
urlAsContent: false
},
// ACE Editor
// `mimes` is not set for support everything kind of text file
load : function(textarea) {
var self = this,
dfrd = $.Deferred(),
cdn = './elfinder/ace/',
init = function() {
if (typeof ace === 'undefined') {
console.log(cdn);
this.fm.loadScript([
cdn+'/ace.js',
cdn+'/ext-modelist.js',
cdn+'/ext-settings_menu.js',
cdn+'/ext-language_tools.js'
], start);
} else {
start();
}
},
start = function() {
var editor, editorBase, mode,
ta = $(textarea),
taBase = ta.parent(),
dialog = taBase.parent(),
id = textarea.id + '_ace',
ext = self.file.name.replace(/^.+\.([^.]+)|(.+)$/, '$1$2').toLowerCase(),
// MIME/mode map
mimeMode = {
'text/x-php' : 'php',
'application/x-php' : 'php',
'text/html' : 'html',
'application/xhtml+xml' : 'html',
'text/javascript' : 'javascript',
'application/javascript' : 'javascript',
'text/css' : 'css',
'text/x-c' : 'c_cpp',
'text/x-csrc' : 'c_cpp',
'text/x-chdr' : 'c_cpp',
'text/x-c++' : 'c_cpp',
'text/x-c++src' : 'c_cpp',
'text/x-c++hdr' : 'c_cpp',
'text/x-shellscript' : 'sh',
'application/x-csh' : 'sh',
'text/x-python' : 'python',
'text/x-java' : 'java',
'text/x-java-source' : 'java',
'text/x-ruby' : 'ruby',
'text/x-perl' : 'perl',
'application/x-perl' : 'perl',
'text/x-sql' : 'sql',
'text/xml' : 'xml',
'application/docbook+xml' : 'xml',
'application/xml' : 'xml'
}; // set basePath of ace
ace.config.set('basePath', cdn); // set base height
taBase.height(taBase.height()); // detect mode
mode = ace.require('ace/ext/modelist').getModeForPath('/' + self.file.name).name;
if (mode === 'text') {
if (mimeMode[self.file.mime]) {
mode = mimeMode[self.file.mime];
}
} // show MIME:mode in title bar
taBase.prev().children('.elfinder-dialog-title').append(' (' + self.file.mime + ' : ' + mode.split(/[\/\\]/).pop() + ')');
// TextArea button and Setting button
$('<div class="ui-dialog-buttonset"/>').css('float', 'left')
.append(
$('<button>文本框</button>')
.button()
.on('click', function(){
if (ta.data('ace')) {
ta.removeData('ace');
editorBase.hide();
ta.val(editor.session.getValue()).show().focus();
$(this).text('编辑器');
} else {
ta.data('ace', true);
editorBase.show();
editor.setValue(ta.hide().val(), -1);
editor.focus();
$(this).text('文本框');
}
})
)
.append(
$('<button>Ace editor setting</button>')
.button({
icons: {
primary: 'ui-icon-gear',
secondary: 'ui-icon-triangle-1-e'
},
text: false
})
.on('click', function(){
editor.showSettingsMenu();
})
)
.prependTo(taBase.next()); // Base node of Ace editor
editorBase = $('<div id="'+id+'" style="width:100%; height:100%;"/>').text(ta.val()).insertBefore(ta.hide()); // Ace editor configure
ta.data('ace', true);
editor = ace.edit(id);
ace.require('ace/ext/language_tools');
ace.require('ace/ext/settings_menu').init(editor);
editor.$blockScrolling = Infinity;
editor.setOptions({
theme: 'ace/theme/dawn',
mode: 'ace/mode/' + mode,
fontSize: '14px',
wrap: true,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true
});
editor.commands.addCommand({
name : "saveFile",
bindKey: {
win : 'Ctrl-s',
mac : 'Command-s'
},
exec: function(editor) {
self.doSave();
}
});
editor.commands.addCommand({
name : "closeEditor",
bindKey: {
win : 'Ctrl-w|Ctrl-q',
mac : 'Command-w|Command-q'
},
exec: function(editor) {
self.doCancel();
}
}); editor.resize(); dfrd.resolve(editor);
}; // init & start
init(); return dfrd;
},
close : function(textarea, instance) {
if (instance) {
instance.destroy();
$(textarea).show();
}
},
save : function(textarea, instance) {
instance && $(textarea).data('ace') && (textarea.value = instance.session.getValue());
},
focus : function(textarea, instance) {
instance && $(textarea).data('ace') && instance.focus();
},
resize : function(textarea, instance, e, data) {
instance && instance.resize();
}
}
]
},
quicklook : {
// to enable preview with Google Docs Viewer
googleDocsMimes : ['application/pdf', 'image/tiff', 'application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
}
}
});
};
</script>

小结

总体来说个人使用还是非常不错的,当然对于一些成熟的网盘系统还是有一些差距。

源码:https://gitee.com/52itstyle/spring-boot-CloudDisk

SpringBoot开发案例之打造私有云网盘的更多相关文章

  1. SpringBoot开发案例之打造十万博文Web篇

    前言 通过 Python 爬取十万博文之后,最重要的是要让互联网用户访问到,那么如何做呢? 选型 从后台框架.前端模板.数据库连接池.缓存.代理服务.限流等组件多个维度选型. 后台框架 SpringB ...

  2. FreeNAS插件打造ownCloud私有云网盘

    ownCloud 是一个自由开源的个人云存储解决方案,可以自由获取无需付费,但用户需要自行架设服务器,好在FreeNAS可以通过插件轻松的构建ownCloud服务器. ownCloud 分为服务器端和 ...

  3. KODExplorer可道云-轻松搭建属于自己/团队的私有云网盘服务

    如今国内各大网盘关停的也快差不多,百度网盘限速严重.国外大牌的如 Dropbox 或 Google Drive又在长城之外,在各种VPN都被封禁的大背景下,科学上网也困难重重,麻烦到要死.那么,除了购 ...

  4. SpringBoot开发案例之多任务并行+线程池处理

    前言 前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑.当然了,优化是无止境的,前人栽树后人乘凉.作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序 ...

  5. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 ​最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场 ...

  6. SpringBoot开发案例之整合Activiti工作流引擎

    前言 JBPM是目前市场上主流开源工作引擎之一,在创建者Tom Baeyens离开JBoss后,JBPM的下一个版本jBPM5完全放弃了jBPM4的基础代码,基于Drools Flow重头来过,目前官 ...

  7. SpringBoot开发案例之整合Dubbo分布式服务

    前言 在 SpringBoot 很火热的时候,阿里巴巴的分布式框架 Dubbo 不知是处于什么考虑,在停更N年之后终于进行维护了.在之前的微服务中,使用的是当当维护的版本 Dubbox,整合方式也是使 ...

  8. SpringBoot开发案例之整合Kafka实现消息队列

    前言 最近在做一款秒杀的案例,涉及到了同步锁.数据库锁.分布式锁.进程内队列以及分布式消息队列,这里对SpringBoot集成Kafka实现消息队列做一个简单的记录. Kafka简介 Kafka是由A ...

  9. 利用可道云kodexplorer在树莓派raspbian上搭建私有云网盘

    可道云kodexplorer是一款开源私有云系统,类似于owncloud,Dropbox.SkyDrive,seafile等.将可道云kodexplorer搭建在树莓派上,从而在树莓派上存储.管理家庭 ...

随机推荐

  1. Java原子类操作原理剖析

    ◆CAS的概念◆ 对于并发控制来说,使用锁是一种悲观的策略.它总是假设每次请求都会产生冲突,如果多个线程请求同一个资源,则使用锁宁可牺牲性能也要保证线程安全.而无锁则是比较乐观的看待这个问题,它会假设 ...

  2. Harbor配置https认证

    Harbor配置https认证由于Harbor不附带任何证书,它默认使用HTTP来提供注册表请求.但是,强烈建议为任何生产环境启用安全性.因为测试使用,使用自签名证书: 1.创建CA证书 首先创建个目 ...

  3. 从零开始学习PYTHON3讲义(十)自己做一个“电子记事本”

    <从零开始PYTHON3>第十讲 截至上一讲,我们已经完成了Python语言的基本部分.我们用了三讲来讨论Python语言的控制结构,用了两讲来介绍Python的基本数据类型.可以说仅就语 ...

  4. Unity资源打包学习笔记(二)、如何实现高效的unity AssetBundle热更新

    转载请标明出处:http://www.cnblogs.com/zblade/ 0x01 目的 在实际的游戏开发中,对于游戏都需要进行打补丁的操作,毕竟,测试是有限的,而bug是无法预估的.那么在手游中 ...

  5. 强化学习(十)Double DQN (DDQN)

    在强化学习(九)Deep Q-Learning进阶之Nature DQN中,我们讨论了Nature DQN的算法流程,它通过使用两个相同的神经网络,以解决数据样本和网络训练之前的相关性.但是还是有其他 ...

  6. 深入理解Linux内核 学习笔记(2)

    第二章 :内存寻址 略.基本同计算机组成原理中的讲述 内核代码和数据结构会存储在一个保留的页框中. 常规Linux安装在RAM物理地址0x00100000开始的地方.因为:页框0是由BIOS使用,存放 ...

  7. 基础知识:语言、编程、计算机组成、cpu、存储器

    2019年3月18日一.    语言:一种事物与另外一种事物沟通的介质.              编程语言:程序员与计算机沟通的介质.    编程:把要让计算机做的事用一种编程语言表达出来.    ...

  8. SpringCloud-config分布式配置中心

    为什么要统一管理微服务配置? 随着微服务不断的增多,每个微服务都有自己对应的配置文件.在研发过程中有测试环境.UAT环境.生产环境,因此每个微服务又对应至少三个不同环境的配置文件.这么多的配置文件,如 ...

  9. es6之三个点(...)扩展运算符

    我们看一个语法,你就知道es6对我们码农多友好,毕竟世界在进步 let arr=[1,2,3,4,54,56] console.log(...arr) 结果是????? 没错 ...这个运算符就是把这 ...

  10. Web前端-Ajax基础技术(上)

    Web前端-Ajax基础技术(上) ajax是浏览器提供一套的api,用于向服务器发出请求,接受服务端返回的响应,通过javascript调用,实现通过代码控制请求与响应,实现网络编程. ajax发送 ...