前言
最近在做工作流的事情,正好有个需求,要添加一个附件上传的功能,曾找过不少上传插件,都不是特别满意。无意中发现一个很好用的开源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>
小结
总体来说个人使用还是非常不错的,当然对于一些成熟的网盘系统还是有一些差距。