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


前言

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

环境搭建

软件地址
SpringBoothttps://spring.io/projects/spring-boot/
elFinderhttps://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://blog.52itstyle.vip

分享是快乐的,也见证了个人成长历程,文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(345849402@qq.com)咨询。