SpringBoot - FileStorage Starter场景启动器

Posted 小小工匠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot - FileStorage Starter场景启动器相关的知识,希望对你有一定的参考价值。

文章目录


Pre

Spring Boot - 手把手教小师妹自定义Spring Boot Starter


需求

系统中,文件存储是个非常常规的需求,大家都需要重复开发,何不封装一个starter支持多协议文件存储的呢?

目前规划了如下的功能:

  1. 支持 多种存储, FTP , SFTP ,本地存储 , S3协议客户端(MINIO、 阿里云等)
  2. 支持自定义属性配置
  3. 开箱即用


使用步骤

各位看官,先看看符不符合你的需要,先演示下开发完成后的如何集成到自己的业务系统中。

1. 引入pom依赖

	 <dependency>
            <groupId>com.artisan</groupId>
            <artifactId>artisan-filestorage-spring-boot-starter</artifactId>
            <version>1.0</version>
        </dependency>

2. 配置

artisan:
  filestorage:
    storage-type: s3
    ftp:
      host: 192.168.126.140
      port: 21
      username: ftptest
      password: ftptest
      mode: Passive
      base-path: /artisan
    s3:
      endpoint: http://192.168.126.140:9000
      access-key: admin
      access-secret: password
      bucket: artisan-bucket
    sftp:
      base-path: /root/abc
      username: root
      password: artisan
      host: 192.168.126.140
      port: 22
    local:
      base-path: D://test

核心: 根据 storage-type 来决定实例化哪种实例对象。 其它配置为实例对象的属性配置。


2. 使用注解

package com.artisan.doc.controller;

import cn.hutool.core.io.IoUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.zfsy.frame.file.storage.FileStorageFactory;
import net.zfsy.frame.operatelog.core.util.ServletUtils;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Api(tags = "S3文件存储")
@RestController
@RequestMapping("/s3")
@Validated
@Slf4j
public class S3FileController 

    @Resource
    private FileStorageFactory fileStorageFactory;

    @PostMapping("/upload")
    @ApiOperation("上传文件")
    @ApiImplicitParams(
            @ApiImplicitParam(name = "path", value = "文件相对路径", example = "soft", dataTypeClass = String.class),
            @ApiImplicitParam(name = "file", value = "文件附件", required = true, dataTypeClass = MultipartFile.class)
    )
    public String uploadFile(String path, @RequestParam("file") MultipartFile file) throws Exception 

        return fileStorageFactory.getStorage().createFile(path, file.getOriginalFilename(), IoUtil.readBytes(file.getInputStream()));
    

    @DeleteMapping("/delete")
    @ApiOperation("删除文件")
    @ApiImplicitParams(
            @ApiImplicitParam(name = "path", value = "文件相对路径", required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "fileName", value = "文件名称", required = true, dataTypeClass = String.class)
    )
    public void deleteFile(String path, @RequestParam("fileName") String fileName) throws Exception 
        fileStorageFactory.getStorage().deleteFile(path, fileName);
    

    @GetMapping("/get")
    @ApiOperation("下载文件")
    @ApiImplicitParams(
            @ApiImplicitParam(name = "path", value = "文件相对路径", required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "fileName", value = "文件名称", required = true, dataTypeClass = String.class)
    )
    public void getFileContent(HttpServletResponse response,
                               String path, @RequestParam("fileName") String fileName) throws Exception 
        byte[] content = fileStorageFactory.getStorage().getFileContent(path, fileName);
        if (content == null) 
            log.warn("[getFileContent][path() fileName() 文件不存在]", path, fileName);
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        
        ServletUtils.writeAttachment(response, fileName, content);
    



    


实现

自动装配类 和 属性文件

/**
 * @author 小工匠
 * @version 1.0
 * @date 2022/4/16 19:12
 * @mark: show me the code , change the world
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ArtisanFileUploadProperties.class)
@ConditionalOnProperty(prefix = ArtisanFileUploadProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class ArtisanFileUploadAutoConfiguration implements InitializingBean, DisposableBean 

    private static final Logger logger = LoggerFactory.getLogger(ZhongFuFileUploadAutoConfiguration.class);

    private ZhongFuFileUploadProperties config;

    public ZhongFuFileUploadAutoConfiguration(ZhongFuFileUploadProperties config) 
        this.config = config;
    

    /**
     *
     * @return 文件存储工厂对象
     */
    @Bean
    public FileStorageFactory fileStorageFactory()
        return new FileStorageFactory(config);
    

    @Override
    public void destroy() 
        logger.info("<== 【销毁--自动化配置】----多协议文件上传组件【ZhongFuFileUploadAutoConfiguration】");
    

    @Override
    public void afterPropertiesSet() 
        logger.info("==> 【初始化--自动化配置】----多协议文件上传组件【ZhongFuFileUploadAutoConfiguration】");
    

package net.zfsy.frame.file.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
 * @author 小工匠
 * @version 1.0
 * @date 2022/4/18 22:55
 * @mark: show me the code , change the world
 */
@ConfigurationProperties(ArtisanFileUploadProperties.PREFIX)
@Data
public class ArtisanFileUploadProperties 

    /**
     * 属性配置前缀
     */
    public static final String PREFIX = "zf.filestorage";

    /**
     * 文件服务类型
     * 1. file:本地磁盘
     * 2. ftp:FTP 服务器
     * 3. sftp:SFTP 服务器
     * 4. s3:支持 S3 协议的云存储服务,比如 MinIO、阿里云、华为云、腾讯云、七牛云等等
     */
    private StorageType storageType;

    private LocalStorageProperties local;

    private FtpStorageProperties ftp;

    private SftpStorageProperties sftp;

    private S3StorageProperties s3;


    /**
     * Type of Storage  to use.
     */
    public enum StorageType 

        /**
         * 本地存储
         */
        local,

        /**
         * ftp存储
         */
        ftp,

        /**
         * sftp存储
         */
        sftp,

        /**
         * s3协议的存储,比如minio
         */
        s3
    

    /**
     * Local
     */
    @Data
    public static class LocalStorageProperties 

        /**
         * 基础路径
         */
        @NotEmpty(message = "基础路径不能为空")
        private String basePath;

    


    /**
     * FTP
     */
    @Data
    public static class FtpStorageProperties 

        /**
         * 基础路径
         * <p>
         * 1. basePath为null或""上传到当前路径
         * 2. basePath为相对路径则相对于当前路径的子路径
         * 3. basePath为绝对路径则上传到此路径
         */
        @NotEmpty(message = "基础路径不能为空")
        private String basePath;

        /**
         * 主机地址
         */
        @NotEmpty(message = "host 不能为空")
        private String host;

        /**
         * 主机端口
         */
        @NotNull(message = "port 不能为空")
        private Integer port;

        /**
         * 用户名
         */
        @NotEmpty(message = "用户名不能为空")
        private String username;

        /**
         * 密码
         */
        @NotEmpty(message = "密码不能为空")
        private String password;

        /**
         * 连接模式
         * <p>
         * 使用 @link  cn.hutool.extra.ftp.FtpMode 对应的字符串
         * Active 主动模式
         * Passive 被动模式 (推荐)
         */
        @NotEmpty(message = "连接模式不能为空")
        private String mode;
    

    @Data
    public static class SftpStorageProperties 

        /**
         * 基础路径
         */
        @NotEmpty(message = "基础路径不能为空")
        private String basePath;


        /**
         * 主机地址
         */
        @NotEmpty(message = "host 不能为空")
        private String host;

        /**
         * 主机端口
         */
        @NotNull(message = "port 不能为空")
        private Integer port;

        /**
         * 用户名
         */
        @NotEmpty(message = "用户名不能为空")
        private String username;

        /**
         * 密码
         */
        @NotEmpty(message = "密码不能为空")
        private String password;

    

    /**
     * S3协议
     */
    @Data
    public static class S3StorageProperties 

        /**
         * 节点地址 MinIO:http://127.0.0.1:9000
         */
        @NotNull(message = "endpoint 不能为空")
        private String endpoint;


        /**
         * 存储 Bucket
         */
        @NotNull(message = "bucket 不能为空")
        private String bucket;

        /**
         * 访问 Key
         */
        @NotNull(message = "accessKey 不能为空")
        private String accessKey;
        /**
         * 访问 Secret
         */
        @NotNull(message = "accessSecret 不能为空")
        private String accessSecret;

    

    


FileStorageFactory


/**
 * @author 小工匠
 * @version 1.0
 * @date 2022/4/19 18:40
 * @mark: show me the code , change the world
 */

public class FileStorageFactory 

    private Logger logger = LoggerFactory.getLogger(FileStorageFactory.class);
    /**
     * 存储配置信息
     */
    private ZhongFuFileUploadProperties config;

    /**
     * 文件存储类型 和 实例化存储对象 映射关系
     */
    private Map<String, FileStorage> uploader = new ConcurrentHashMap<>();

    /**
     * 构造函数
     *
     * @param config
     */
    public FileStorageFactory(ZhongFuFileUploadProperties config) 
        this.config = config;
    

    /**
     * @return 文件存储对象
     */
    public FileStorage getStorage() 
        // 获取配置文件中配置的存储类型
        String type = config.getStorageType().name();
        // 缓存对象,避免重复创建
        if (ZhongFuFileUploadProperties.StorageType.local.name().equalsIgnoreCase(type) && uploader.get(type) == null) 
            uploader.put(type, new LocalFileStorage(config));
         else if (ZhongFuFileUploadProperties.StorageType.ftp.name().equalsIgnoreCase(type) && uploader.get(type) == null) 
            uploader.put(type, new FtpFileStorage(config));
         else if (ZhongFuFileUploadProperties.StorageType.sftp.name().equalsIgnoreCase(type) && uploader.get(type) == null) 
            uploader.put(type, new SftpFileStorage(config));
         else if (ZhongFuFileUploadProperties.StorageType.s3.name().equalsIgnoreCase(type) && uploader.get(type) == null) 
            uploader.put(type, new S3FileStorage(config));
         else 
            if (uploader.get(type) == null) 
                uploader.put(type, new LocalFileStorage(config));
                logger.warn("未找到配置的文件存储类型, 将使用默认LocalFileStorage");
            
        
        // 返回实例化存储对象
        return uploader.get(type);
    


    

/**
 * 文件 Storage  接口
 * @author artisan
 */
public interface FileStorage 


    /**
     * 保存文件
     *
     * @param path    文件路径
     * @param path    文件名称
     * @param content 文件内容
     * @return 文件路径
     */
    String createFile(String path, String fileName, byte[] content) throws Exception;

    /**
     * 删除文件
     *
     * @param path 相对路径
     * @throws Exception 删除文件时,抛出 Exception 异常
     */
    void deleteFile(String path, String fileName) throws Exception;

    /**
     * 获得文件内容
     *
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件内容
     */
    byte[] getFileContent(String path, String fileName) throws Exception;




/**
 * @author 小工匠
 * @version 1.0
 * @date 2022/4/19 10:42
 * @mark: show me the code , change the world
 */
public abstract class AbstractFileStorage implements FileStorage 

    /**
     * 业务扩展
     */
    public final void ext() 
        doExt();
    

    /**
     * 自定义业务扩展
     */
    protected abstract void doExt();


本地存储实现


/**
 * 本地文件 Storage 实现类
 *
 * @author artisan
 */
public class LocalFileStorage extends AbstractFileStorage 

    private Logger logger = LoggerFactory.getLogger(LocalFileStorage.class);

    private ZhongFuFileUploadProperties config;

    public LocalFileStorage(ZhongFuFileUploadProperties config) 
        this.config = config;
        ZhongFuFileUploadProperties.LocalStorageProperties local = this.config.getLocal();
        Assert.notNull(local, "本地存储配置信息不能为空,请配置 basePath 属性");

        // 补全风格  Linux 是 /,Windows 是 \\
        if (!local.getBasePath().endsWith(File.separatorFileStorage

使用opencv通过Filestorage保存时出错

OpenCV 机器学习函数需要 CvFileStorage* 而不是 cv::FileStorage*

OpenCV Python API 的 FileStorage

如何将向量<Dmatch> 写入 FileStorage

在 FileStorage 中使用路径作为键