SpringBoot - FileStorage Starter场景启动器
Posted 小小工匠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot - FileStorage Starter场景启动器相关的知识,希望对你有一定的参考价值。
文章目录
Pre
Spring Boot - 手把手教小师妹自定义Spring Boot Starter
需求
系统中,文件存储是个非常常规的需求,大家都需要重复开发,何不封装一个starter支持多协议文件存储的呢?
目前规划了如下的功能:
- 支持 多种存储, FTP , SFTP ,本地存储 , S3协议客户端(MINIO、 阿里云等)
- 支持自定义属性配置
- 开箱即用
使用步骤
各位看官,先看看符不符合你的需要,先演示下开发完成后的如何集成到自己的业务系统中。
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 机器学习函数需要 CvFileStorage* 而不是 cv::FileStorage*