SpringBoot开发案例之打造私人云网盘

321 阅读3分钟
原文链接: mp.weixin.qq.com

前言

最近在做工作流的事情,正好有个需求,要添加一个附件上传的功能,曾找过不少上传插件,都不是特别满意。无意中发现一个很好用的开源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

在线演示地址:https://cloud.52itstyle.vip

学习资源

关注公众号:爪哇笔记回复关键词获取

秒杀 :从0到1构建分布式秒杀系统

支付: 支付宝,微信,银联详细代码案例

任务: 基于quartz的CRUD任务管理系统

邮件: 邮件发送服务,文本、附件、模版多种实现,队列,线程定时任务功能