当前位置: 首页 > news >正文

Spring File Storage的详细文档

快速入门

  1. 配置

pom.xml引入依赖

<dependencies><!-- spring-file-storage 必须要引入 --><dependency><groupId>cn.xuyanwu</groupId><artifactId>spring-file-storage</artifactId><version>0.7.0</version></dependency><!-- 华为云 OBS 不使用的情况下可以不引入 --><dependency><groupId>com.huaweicloud</groupId><artifactId>esdk-obs-java</artifactId><version>3.22.3.1</version></dependency><!-- 阿里云 OSS 不使用的情况下可以不引入 --><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version></dependency><!-- 七牛云 Kodo 不使用的情况下可以不引入 --><dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>7.11.0</version></dependency><!-- 腾讯云 COS 不使用的情况下可以不引入 --><dependency><groupId>com.qcloud</groupId><artifactId>cos_api</artifactId><version>5.6.98</version></dependency><!-- 百度云 BOS 不使用的情况下可以不引入 --><dependency><groupId>com.baidubce</groupId><artifactId>bce-java-sdk</artifactId><version>0.10.218</version></dependency><!-- 又拍云 USS 不使用的情况下可以不引入 --><dependency><groupId>com.upyun</groupId><artifactId>java-sdk</artifactId><version>4.2.3</version></dependency><!-- MinIO 不使用的情况下可以不引入 --><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.4.3</version></dependency><!-- AWS S3 不使用的情况下可以不引入 --><dependency><groupId>com.amazonaws</groupId><artifactId>aws-java-sdk-s3</artifactId><version>1.12.272</version></dependency><!-- FTP 不使用的情况下可以不引入 --><dependency><groupId>commons-net</groupId><artifactId>commons-net</artifactId><version>3.8.0</version></dependency><!-- SFTP 不使用的情况下可以不引入 --><dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version></dependency><!--糊涂工具类扩展,如果要使用 FTP、SFTP 则必须引入,否则不用引入--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-extra</artifactId><version>5.8.5</version></dependency><!-- WebDAV 不使用的情况下可以不引入 --><dependency><groupId>com.github.lookfirst</groupId><artifactId>sardine</artifactId><version>5.10</version></dependency><!-- 谷歌云 Google Cloud Storage--><dependency><groupId>com.google.cloud</groupId><artifactId>google-cloud-storage</artifactId><version>2.14.0</version></dependency><!--因 guava 存在较多冲突版本导致谷歌云存储无法使用,故引入独立版本--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version></dependency></dependencies>

application.yml配置文件中添加以下相关配置(不使用的平台可以不配置)

spring:file-storage: #文件存储配置default-platform: local-1 #默认使用的存储平台thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】local: # 本地存储(不推荐使用),不使用的情况下可以不写- platform: local-1 # 存储平台标识enable-storage: true  #启用存储enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)domain: "" # 访问域名,例如:“http://127.0.0.1:8030/test/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名base-path: D:/Temp/test/ # 存储地址path-patterns: /test/file/** # 访问路径,开启 enable-access 后,通过此路径可以访问到上传的文件local-plus: # 本地存储升级版,不使用的情况下可以不写- platform: local-plus-1 # 存储平台标识enable-storage: true  #启用存储enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名base-path: local-plus/ # 基础路径path-patterns: /** # 访问路径storage-path: D:/Temp/ # 存储路径huawei-obs: # 华为云 OBS ,不使用的情况下可以不写- platform: huawei-obs-1 # 存储平台标识enable-storage: false  # 启用存储access-key: ??secret-key: ??end-point: ??bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.obs.com/base-path: hy/ # 基础路径aliyun-oss: # 阿里云 OSS ,不使用的情况下可以不写- platform: aliyun-oss-1 # 存储平台标识enable-storage: false  # 启用存储access-key: ??secret-key: ??end-point: ??bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/base-path: hy/ # 基础路径qiniu-kodo: # 七牛云 kodo ,不使用的情况下可以不写- platform: qiniu-kodo-1 # 存储平台标识enable-storage: false  # 启用存储access-key: ??secret-key: ??bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.hn-bkt.clouddn.com/base-path: base/ # 基础路径tencent-cos: # 腾讯云 COS- platform: tencent-cos-1 # 存储平台标识enable-storage: true  # 启用存储secret-id: ??secret-key: ??region: ?? #存仓库所在地域bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.cos.ap-nanjing.myqcloud.com/base-path: hy/ # 基础路径baidu-bos: # 百度云 BOS- platform: baidu-bos-1 # 存储平台标识enable-storage: true  # 启用存储access-key: ??secret-key: ??end-point: ?? # 例如 abc.fsh.bcebos.combucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.fsh.bcebos.com/abc/base-path: hy/ # 基础路径upyun-uss: # 又拍云 USS- platform: upyun-uss-1 # 存储平台标识enable-storage: true  # 启用存储username: ??password: ??bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.test.upcdn.net/base-path: hy/ # 基础路径minio: # MinIO,由于 MinIO SDK 支持 AWS S3,其它兼容 AWS S3 协议的存储平台也都可配置在这里- platform: minio-1 # 存储平台标识enable-storage: true  # 启用存储access-key: ??secret-key: ??end-point: ??bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/base-path: hy/ # 基础路径aws-s3: # AWS S3,其它兼容 AWS S3 协议的存储平台也都可配置在这里- platform: aws-s3-1 # 存储平台标识enable-storage: true  # 启用存储access-key: ??secret-key: ??region: ?? # 与 end-point 参数至少填一个end-point: ?? # 与 region 参数至少填一个bucket-name: ??domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.hn-bkt.clouddn.com/base-path: s3/ # 基础路径ftp: # FTP- platform: ftp-1 # 存储平台标识enable-storage: true  # 启用存储host: ?? # 主机,例如:192.168.1.105port: 21 # 端口,默认21user: anonymous # 用户名,默认 anonymous(匿名)password: "" # 密码,默认空domain: ?? # 访问域名,注意“/”结尾,例如:ftp://192.168.1.105/base-path: ftp/ # 基础路径storage-path: /www/wwwroot/file.abc.com/ # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/”sftp: # SFTP- platform: sftp-1 # 存储平台标识enable-storage: true  # 启用存储host: ?? # 主机,例如:192.168.1.105port: 22 # 端口,默认22user: root # 用户名password: ?? # 密码或私钥密码private-key-path: ?? # 私钥路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等,例如:classpath:id_rsa_2048domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/base-path: sftp/ # 基础路径storage-path: /www/wwwroot/file.abc.com/ # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/”webdav: # WebDAV- platform: webdav-1 # 存储平台标识enable-storage: true  # 启用存储server: ?? # 服务器地址,例如:http://192.168.1.105:8405/user: ?? # 用户名password: ?? # 密码domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/base-path: webdav/ # 基础路径storage-path: / # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/”google-cloud: # 谷歌云存储- platform: google-1 # 存储平台标识enable-storage: true  # 启用存储project-id: ?? # 项目 idbucket-name: ??credentials-path: file:/deploy/example-key.json # 授权 key json 路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等domain: ?? # 访问域名,注意“/”结尾,例如:https://storage.googleapis.com/test-bucket/base-path: hy/ # 基础路径

注意配置每个平台前面都有个-号,通过以下方式可以配置多个

local:- platform: local-1 # 存储平台标识enable-storage: trueenable-access: truedomain: ""base-path: D:/Temp/test/path-patterns: /test/file/**- platform: local-2 # 存储平台标识,注意这里不能重复enable-storage: trueenable-access: truedomain: ""base-path: D:/Temp/test2/path-patterns: /test2/file/**

2、编码

在启动类上加上@EnableFileStorage注解

@EnableFileStorage
@SpringBootApplication
public class SpringFileStorageTestApplication {public static void main(String[] args) {SpringApplication.run(SpringFileStorageTestApplication.class, args);}}

3、开始使用


@RestController
public class FileDetailController {@Autowiredprivate FileStorageService fileStorageService;//注入实列/*** 上传文件,成功返回文件 url*/@PostMapping("/upload")public String upload(MultipartFile file) {FileInfo fileInfo = fileStorageService.of(file).setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写.setObjectId("0")   //关联对象id,为了方便管理,不需要可以不写.setObjectType("0") //关联对象类型,为了方便管理,不需要可以不写.putAttr("role","admin") //保存一些属性,可以在切面、保存上传记录、自定义存储平台等地方获取使用,不需要可以不写.upload();  //将文件上传到对应地方return fileInfo == null ? "上传失败!" : fileInfo.getUrl();}/*** 上传图片,成功返回文件信息* 图片处理使用的是 https://github.com/coobird/thumbnailator*/@PostMapping("/upload-image")public FileInfo uploadImage(MultipartFile file) {return fileStorageService.of(file).image(img -> img.size(1000,1000))  //将图片大小调整到 1000*1000.thumbnail(th -> th.size(200,200))  //再生成一张 200*200 的缩略图.upload();}/*** 上传文件到指定存储平台,成功返回文件信息*/@PostMapping("/upload-platform")public FileInfo uploadPlatform(MultipartFile file) {return fileStorageService.of(file).setPlatform("aliyun-oss-1")    //使用指定的存储平台.upload();}
}

如果想使用删除、下载等功能,请阅读 保存上传记录 章节

基础功能

上传

多种上传方式

of方法有很多个重载方法,支持 File、MultipartFile、byte[]、InputStream、URL、URI、String

// 直接上传
fileStorageService.of(file).upload();// 如果要用 InputStream、URL、URI、String 等方式上传,暂时无法获取 originalFilename 属性,最好手动设置
fileStorageService.of(inputStream).setOriginalFilename("a.jpg").upload();// 上传到指定路径下
fileStorageService.of(file).setPath("upload/") // 保存到相对路径下,为了方便管理,不需要可以不写.upload();// 关联文件参数并上传
fileStorageService.of(file).setObjectId("0")   // 关联对象id,为了方便管理,不需要可以不写.setObjectType("0") // 关联对象类型,为了方便管理,不需要可以不写.putAttr("role","admin") //保存一些属性,可以在切面、保存上传记录、自定义存储平台等地方获取使用,不需要可以不写.putAttr("username","007").upload();// 上传到指定的存储平台
fileStorageService.of(file).setPlatform("aliyun-oss-1")    // 使用指定的存储平台.upload();// 对图片进行处理并上传,有多个重载方法。图片处理使用的是 https://github.com/coobird/thumbnailator
fileStorageService.of(file).setThumbnailSuffix(".jpg") //指定缩略图后缀,必须是 thumbnailator 支持的图片格式,默认使用全局的.setSaveThFilename("thabc") //指定缩略图的保存文件名,注意此文件名不含后缀,默认自动生成.image(img -> img.size(1000,1000))  // 将图片大小调整到 1000*1000.thumbnail(th -> th.size(200,200))  // 再生成一张 200*200 的缩略图.upload();// 其它更多方法以实际 API 为准

保存上传记录

如果还想使用除了保存文件之外的其它功能,例如删除、下载文件,还需要实现 FileRecorder 这个接口,把文件信息保存到数据库中。

/*** 用来将文件上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类*/
@Service
public class FileDetailService extends ServiceImpl<FileDetailMapper, FileDetail> implements FileRecorder {/*** 保存文件信息到数据库*/@SneakyThrows@Overridepublic boolean record(FileInfo info) {FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"attr");//这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中if (info.getAttr() != null) {detail.setAttr(new ObjectMapper().writeValueAsString(info.getAttr()));}boolean b = save(detail);if (b) {info.setId(detail.getId());}return b;}/*** 根据 url 查询文件信息*/@SneakyThrows@Overridepublic FileInfo getByUrl(String url) {FileDetail detail = getOne(new QueryWrapper<FileDetail>().eq(FileDetail.COL_URL,url));FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"attr");//这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用if (StrUtil.isNotBlank(detail.getAttr())) {info.setAttr(new ObjectMapper().readValue(detail.getAttr(),Dict.class));}return info;}/*** 根据 url 删除文件信息*/@Overridepublic boolean delete(String url) {return remove(new QueryWrapper<FileDetail>().eq(FileDetail.COL_URL,url));}
}

数据库表结构推荐如下,你也可以根据自己喜好在这里自己扩展

-- 这里使用的是 mysql
CREATE TABLE `file_detail`
(`id`                varchar(32)  NOT NULL COMMENT '文件id',`url`               varchar(512) NOT NULL COMMENT '文件访问地址',`size`              bigint(20)   DEFAULT NULL COMMENT '文件大小,单位字节',`filename`          varchar(256) DEFAULT NULL COMMENT '文件名称',`original_filename` varchar(256) DEFAULT NULL COMMENT '原始文件名',`base_path`         varchar(256) DEFAULT NULL COMMENT '基础存储路径',`path`              varchar(256) DEFAULT NULL COMMENT '存储路径',`ext`               varchar(32)  DEFAULT NULL COMMENT '文件扩展名',`content_type`      varchar(32)  DEFAULT NULL COMMENT 'MIME类型',`platform`          varchar(32)  DEFAULT NULL COMMENT '存储平台',`th_url`            varchar(512) DEFAULT NULL COMMENT '缩略图访问路径',`th_filename`       varchar(256) DEFAULT NULL COMMENT '缩略图名称',`th_size`           bigint(20)   DEFAULT NULL COMMENT '缩略图大小,单位字节',`th_content_type`   varchar(32)  DEFAULT NULL COMMENT '缩略图MIME类型',`object_id`         varchar(32)  DEFAULT NULL COMMENT '文件所属对象id',`object_type`       varchar(32)  DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片',`attr`              text COMMENT '附加属性',`create_time`       datetime     DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDBDEFAULT CHARSET = utf8ROW_FORMAT = DYNAMIC COMMENT ='文件记录表';

下载

多种下载方式

// 获取文件信息
FileInfo fileInfo = fileStorageService.getFileInfoByUrl("http://file.abc.com/test/a.jpg");// 下载为字节数组
byte[] bytes = fileStorageService.download(fileInfo).bytes();// 下载到文件
fileStorageService.download(fileInfo).file("C:\\a.jpg");// 下载到 OutputStream 中
ByteArrayOutputStream out = new ByteArrayOutputStream();
fileStorageService.download(fileInfo).outputStream(out);// 获取 InputStream 手动处理
fileStorageService.download(fileInfo).inputStream(in -> {//TODO 读取 InputStream
});// 直接通过文件信息中的 url 下载,省去手动查询文件信息记录的过程
fileStorageService.download("http://file.abc.com/test/a.jpg").file("C:\\a.jpg");// 下载缩略图
fileStorageService.downloadTh(fileInfo).file("C:\\th.jpg");

监听下载进度

// 方式一
fileStorageService.download(fileInfo).setProgressMonitor(progressSize ->System.out.println("已下载:" + progressSize)
).file("C:\\a.jpg");// 方式二
fileStorageService.download(fileInfo).setProgressMonitor((progressSize,allSize) ->System.out.println("已下载 " + progressSize + " 总大小" + allSize)
).file("C:\\a.jpg");// 方式三
fileStorageService.download(fileInfo).setProgressMonitor(new ProgressListener() {@Overridepublic void start() {System.out.println("下载开始");}@Overridepublic void progress(long progressSize,long allSize) {System.out.println("已下载 " + progressSize + " 总大小" + allSize);}@Overridepublic void finish() {System.out.println("下载结束");}
}).file("C:\\a.jpg");

删除

//获取文件信息
FileInfo fileInfo = fileStorageService.getFileInfoByUrl("http://file.abc.com/test/a.jpg");//直接删除
fileStorageService.delete(fileInfo);//条件删除
fileStorageService.delete(fileInfo,info -> {//TODO 检查是否满足删除条件return true;
});//直接通过文件信息中的 url 删除,省去手动查询文件信息记录的过程
fileStorageService.delete("http://file.abc.com/test/a.jpg");

判断文件是否存在

//获取文件信息
FileInfo fileInfo = fileStorageService.getFileInfoByUrl("http://file.abc.com/test/a.jpg");//判断文件是否存在
boolean exists = fileStorageService.exists(fileInfo);//直接通过文件信息中的 url 判断文件是否存在,省去手动查询文件信息记录的过程
boolean exists2 = fileStorageService.exists("http://file.abc.com/test/a.jpg");

存储平台

目前支持多种存储平台,你也可以根据需要自行扩展

支持的存储平台

平台

支持

本地

FTP

SFTP

WebDAV

平台

官方 SDK

AWS S3 SDK

S3 兼容说明

AWS S3

-

MinIO

查看

阿里云 OSS

查看

华为云 OBS

查看

七牛云 Kodo

查看

腾讯云 COS

查看

百度云 BOS

查看

又拍云 USS

×

-

金山云 KS3

×

查看

美团云 MSS

×

查看

京东云 OSS

×

查看

天翼云 OOS

×

查看

移动云 EOS

×

查看

沃云 OSS

×

查看

网易数帆 NOS

×

查看

Ucloud US3

×

查看

青云 QingStor

×

查看

平安云 OBS

×

查看

首云 OSS

×

查看

IBM COS

×

查看

谷歌云存储

×

-

其它兼容 S3 协议的平台

×

-

自定义存储平台

想要自定义存储平台就要实现 FileStorage 这个接口,并进行实例化,注意返回的 bean 是个 list

这里拿 LocalFileStorage 举例


/*** 实现 FileStorage 接口,这里使用了 Lombok 和 Hutool 工具类*/
@Getter
@Setter
public class LocalFileStorage implements FileStorage {/* 本地存储路径*/private String basePath;/* 存储平台 */private String platform;/* 访问域名 */private String domain;/*** 释放资源*/@Overridepublic void close() {}/*** 保存文件*/@Overridepublic boolean save(FileInfo fileInfo,UploadPretreatment pre) {String path = fileInfo.getPath();File newFile = FileUtil.touch(basePath + path,fileInfo.getFilename());fileInfo.setBasePath(basePath);fileInfo.setUrl(domain + path + fileInfo.getFilename());try {pre.getFileWrapper().transferTo(newFile);byte[] thumbnailBytes = pre.getThumbnailBytes();if (thumbnailBytes != null) { //上传缩略图fileInfo.setThUrl(fileInfo.getUrl() + pre.getThumbnailSuffix());FileUtil.writeBytes(thumbnailBytes,newFile.getPath() + pre.getThumbnailSuffix());}return true;} catch (IOException e) {FileUtil.del(newFile);throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e);}}/*** 删除文件*/@Overridepublic boolean delete(FileInfo fileInfo) {if (fileInfo.getThFilename() != null) {   //删除缩略图FileUtil.del(new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getThFilename()));}return FileUtil.del(new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getFilename()));}/*** 文件是否存在*/@Overridepublic boolean exists(FileInfo fileInfo) {return new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getFilename()).exists();}/*** 下载文件*/@Overridepublic void download(FileInfo fileInfo,Consumer<InputStream> consumer) {try (InputStream in = FileUtil.getInputStream(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename())) {consumer.accept(in);} catch (IOException e) {throw new FileStorageRuntimeException("文件下载失败!platform:" + fileInfo,e);}}/*** 下载缩略图文件*/@Overridepublic void downloadTh(FileInfo fileInfo,Consumer<InputStream> consumer) {if (StrUtil.isBlank(fileInfo.getThFilename())) {throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo);}try (InputStream in = FileUtil.getInputStream(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename())) {consumer.accept(in);} catch (IOException e) {throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e);}}
}/*** 初始化*/
@Configuration
public class LocalFileStorageAutoConfiguration {/*** 这里拿本地存储做个演示,注意返回的是个List*/@Beanpublic List<LocalFileStorage> localFileStorageList() {ArrayList<LocalFileStorage> list = new ArrayList<>();LocalFileStorage localFileStorage = new LocalFileStorage();localFileStorage.setPlatform("my-local-1");//平台名称localFileStorage.setBasePath("");localFileStorage.setDomain("");list.add(localFileStorage);return list;}
}

动态增减存储平台

//获得存储平台 List
CopyOnWriteArrayList<FileStorage> list = fileStorageService.getFileStorageList();//增加
LocalFileStorage storage = new LocalFileStorage();
storage.setPlatform("my-local-1");//平台名称
storage.setBasePath("");
storage.setDomain("");
list.add(storage);//删除
FileStorage myLocal = fileStorageService.getFileStorage("my-local-1");
list.remove(myLocal);
myLocal.close();//释放资源

获取对应存储平台

AliyunOssFileStorage storage = (AliyunOssFileStorage) fileStorageService.getFileStorage("aliyun-oss-1");
OSS client = storage.getClient();
//获取到对应SDK的原生客户端对象,可以进行更多高级操作

切面

通过切面,可以对文件的上传和删除等进行干预

自定义切面

只需要实现FileStorageAspect接口

不需要的方法可以不用实现,此接口里的方法全部都有默认实现

/*** 使用切面打印文件上传和删除的日志*/
@Slf4j
@Component
public class LogFileStorageAspect implements FileStorageAspect {/*** 上传,成功返回文件信息,失败返回 null*/@Overridepublic FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) {log.info("上传文件 before -> {}",fileInfo);fileInfo = chain.next(fileInfo,pre,fileStorage,fileRecorder);log.info("上传文件 after -> {}",fileInfo);return fileInfo;}/*** 删除文件,成功返回 true*/@Overridepublic boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) {log.info("删除文件 before -> {}",fileInfo);boolean res = chain.next(fileInfo,fileStorage,fileRecorder);log.info("删除文件 after -> {}",res);return res;}/*** 文件是否存在*/@Overridepublic boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) {log.info("文件是否存在 before -> {}",fileInfo);boolean res = chain.next(fileInfo,fileStorage);log.info("文件是否存在 after -> {}",res);return res;}/*** 下载文件*/@Overridepublic void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer<InputStream> consumer) {log.info("下载文件 before -> {}",fileInfo);chain.next(fileInfo,fileStorage,consumer);log.info("下载文件 after -> {}",fileInfo);}/*** 下载缩略图文件*/@Overridepublic void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer<InputStream> consumer) {log.info("下载缩略图文件 before -> {}",fileInfo);chain.next(fileInfo,fileStorage,consumer);log.info("下载缩略图文件 after -> {}",fileInfo);}
}

动态增减切面

//获得切面 List
CopyOnWriteArrayList<FileStorageAspect> list = fileStorageService.getAspectList();//增加
FileStorageAspect aspect = new LogFileStorageAspect();
list.add(aspect);//删除
list.remove(aspect);//条件删除
list.removeIf(item -> item instanceof LogFileStorageAspect);

常见问题

为什么我在application.yml 中已经添加对应的存储平台配置,上传时却提示我“没有找到对应的存储平台!”

检查pom.xml,是否引入对应平台的依赖,例如阿里云

<!-- 阿里云 OSS 不使用的情况下可以不引入 -->
<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.6.0</version>
</dependency>

如何根据数据库配置加载存储平台?

参考 动态增减存储平台

如何自定义缩略图后缀?

参考 多种上传方式 中的缩略图上传方式,可以自己调整缩略图后缀

相关文章:

Spring File Storage的详细文档

快速入门配置pom.xml引入依赖<dependencies><!-- spring-file-storage 必须要引入 --><dependency><groupId>cn.xuyanwu</groupId><artifactId>spring-file-storage</artifactId><version>0.7.0</version></dependen…...

Java软件开发好学吗?学完好找工作吗?

互联网高速发展的当下&#xff0c;Java语言无处不在&#xff1a;手机APP、Java游戏、电脑应用&#xff0c;都有它的身影。作为最热门的开发语言之一&#xff0c;Java在编程圈的地位不可撼动。可是&#xff0c;听名字就很专业的样子。Java语言到底好学吗&#xff1f;刚入坑编程圈…...

【独家C】华为OD机试提供C语言题解 - 优秀学员统计

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明优秀…...

数据仓库、数据中台、数据湖都是什么?

相信很多人都在最近的招聘市场上看到过招聘要求里提到了数据仓库、数据中台&#xff0c;甚至还有数据湖&#xff0c;这些层出不穷的概念让人困扰。今天我就来跟大家讲一讲数据仓库、数据中台以及数据湖的概念及区别。 数据库 在了解数据仓库、数据中台以及数据湖之前&#xff…...

0099 MySQL02

1.简单查询 查询一个字段 select 字段名 from 表名; 查询多个字段&#xff0c;使用“&#xff0c;”隔开 select 字段名,字段名 from 表名; 查询所有字段 1.把每个字段都写上 select 字段名,字段名,字段名.. from 表名; 2.使用*(效率低&#xff0c;可读性差) select *…...

应急响应-ubuntu系统cpu飙高

这里写目录标题一、排查过程二、处置过程三、溯源总结一、排查过程 1、查看CPU使用情况 top -c2、查看异常进程的具体参数 ps -aux3、通过微步查询域名信息 4、查看异常进程的监听端口 netstat -anlpt5、查找服务器内的异常文件 ls cat run.sh cat mservice.sh6、查看脚本…...

MDK软件使用技巧

本文主要汇总MDK软件使用技巧 一、字体大小及颜色修改 第一步点击工具栏的这个小扳手图标 进去后显示如下&#xff0c;先设置 Encoding 为&#xff1a;Chinese GB2312(Simplified)&#xff0c;然后设置 Tab size 为&#xff1a;4 以更好的支持简体中文&#xff0c;否则&…...

3 333333

全部 答对 答错 单选题 1. 一个项目来取代目前公司的文件存储系统已经获批。外部供应商提供硬件&#xff0c;内部团队开发软件。这个团队是自组织的&#xff0c;由一般的专家组成。团队建议迭代地与供应商合作&#xff0c;但供应商表示拒绝。因此&#xff0c;只有软件将被迭代…...

1528. 重新排列字符串

1528. 重新排列字符串https://leetcode.cn/problems/shuffle-string/ 难度简单52收藏分享切换为英文接收动态反馈 给你一个字符串 s 和一个 长度相同 的整数数组 indices 。 请你重新排列字符串 s &#xff0c;其中第 i 个字符需要移动到 indices[i] 指示的位置。 返回重新…...

【8】【用户操作日志】操作日志SpringBootStarter

操作日志 此版本操作日志主要就是通过AOP拦截器实现的&#xff0c;整体主要分为AOP拦截器、自定义函数、日志上下文、扩展接口&#xff1b;组件提供了6个扩展点&#xff0c;自定义函数、日志上下文、用户信息获取&#xff0c;日志保存&#xff0c;自定义异常获取&#xff0c;入…...

【游戏逆向】寻路函数隐藏检测点分析

案例&#xff1a; 某游戏出现调用寻路函数失败异常崩溃。 基本情况分析&#xff1a; 在刚登陆游戏的时候直接调用寻路函数崩溃。 手动寻路以后再调用寻路不崩溃。(排除了函数编写错误的可能) 猜测可能检测方法&#xff1a; 有某一个标志位(全局类型)在游戏刚登陆的时候没…...

【Zabbix实战之运维篇】Zabbix监控Docker容器配置方法

【Zabbix实战之运维篇】Zabbix监控Docker容器配置方法 一、检查Zabbix监控平台状态1.检查Zabbix各组件容器状态2.奸诈Zabbix-server状态二、下载监控模板1.进入Zabbix官网下载页面2.查看下载模板三、创建一个测试容器1.创建一个测试容器2.检查测试容器状态3.访问测试web服务四、…...

这款 Python 工具进行数据分析及数据可视化真的很棒啊

前言 大家好&#xff0c;今天我们以全国各地区衣食住行消费数据为例&#xff0c;来分析2022年中国统计年鉴数据&#xff0c;统计全国各地人民的消费地图&#xff0c;看看&#xff1a; 哪个省份的人最能花钱 哪个省份的人最舍得花钱 哪个省份的人最抠门 全国各地区人民在吃、穿…...

visual Studio Code常用快捷键

1、向上/向下移动代码行 alt 下箭头/上箭头 2、向上/向下复制一行代码 shiftalt 下箭头/上箭头 3、选定多个相同的单词 ctrl d 4、全局替换 ctrl h 5、快速定位到某一行 ctrl g 6、放大缩小整个编辑器界面 ctrl / - 7、添加多个光标 Ctrl Alt 上箭头/下箭头…...

基础(一)十六进制转八进制

试题 基础练习 十六进制转八进制 资源限制 内存限制&#xff1a;512.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述   给定n个十六进制正整数&#xff0c;输出它们对应的八进制数。输入格式   输入的第…...

梯度提升算法决策过程的逐步可视化

梯度提升算法是最常用的集成机器学习技术之一&#xff0c;该模型使用弱决策树序列来构建强学习器。这也是XGBoost和LightGBM模型的理论基础&#xff0c;所以在这篇文章中&#xff0c;我们将从头开始构建一个梯度增强模型并将其可视化。 梯度提升算法介绍 梯度提升算法&#x…...

Linux系统调用之文件属性操作函数

前言 如果&#xff0c;想要深入的学习Linux系统调用中access&#xff0c;chmod&#xff0c;chown&#xff0c;truncate这些有关于文件属性的操作函数&#xff0c;还是需要去自己阅读Linux系统中的帮助文档。 具体输入命令&#xff1a; man 2 access/chmod/chown/truncate 即可…...

VMware 安装 银河麒麟高级服务器操作系统 V10 + QT 开发环境搭建

下载并安装vmware 下载银河麒麟操作烯烃服务器版v10的镜像文件从官网下载&#xff0c;因为是x86的电脑芯片&#xff0c;选择AMD64版&#xff0c;即vmare 安装麒麟操作系统注意事项&#xff1a;安装位置选择自动分区网络和主机名设置打开网络&#xff0c;ip4就不用再设置了创建一…...

2023年疫情开放,国内程序员薪资涨了还是跌了?大数据告诉你答案

自从疫情开放&#xff0c;国内各个行业都开始有复苏的迹象&#xff0c;尤其是旅游行业更是空前暴涨&#xff0c;那么互联网行业如何&#xff1f; 有人说今年好找工作多了&#xff0c;有人说依然是内卷得一塌糊涂&#xff0c;那么今年开春以来&#xff0c;各个岗位的程序员工资…...

太赫兹频段耦合器设计相关经验总结

1拿到耦合器的频段后&#xff0c;确定中心频率和波导的宽度和高度 此处贴一张不同频段对应的波导尺寸图 需要注意的是1英寸 2.54厘米&#xff0c;需注意换算 具体网址&#xff1a;矩形波导尺寸 | 扩维 (qualwave.com) 仅列举我比较常用的太赫兹频段部分 2.以220~320GHz频段&a…...

反弹shell数据不回显带外查询pikaqiu靶场搭建

P1 文件上传下载&#xff08;解决无图形化和解决数据传输&#xff09; 解决无图形化&#xff1a; 当我们想下载一个文件时&#xff0c;通常是通过浏览器的一个链接直接访问网站点击下载的&#xff0c;但是在实际的安全测试中&#xff0c;我们获取的权限只是一个执行命令的窗口…...

按键修改阈值功能、报警功能、空气质量功能实现

按键修改阈值功能 要使用按键&#xff0c;首先要定义按键。通过查阅资料&#xff0c;可知按键的引脚如图所示&#xff1a;按键1&#xff08;S1&#xff09;通过KEY0与PA0连接&#xff0c;按键2&#xff08;S2&#xff09;通过KEY1与PE2连接&#xff0c;按键3&#xff08;S3&…...

spring重点整理篇--springMVC(嘿嘿,开心哟)

Spring MVC是的基于JavaWeb的MVC框架&#xff0c;是Spring框架中的一个组成部分(WEB模块) MVC设计模式&#xff1a; Controller&#xff08;控制器&#xff09; Model&#xff08;模型&#xff09; View&#xff08;视图&#xff09; 重点来了&#x1f604; SpringMVC的工作机制…...

图像融合评估指标Python版

图像融合评估指标Python版 这篇博客利用Python把大部分图像融合指标基于图像融合评估指标复现了&#xff0c;从而方便大家更好的使用Python进行指标计算&#xff0c;以及一些I/O 操作。除了几个特征互信息的指标没有成功复现之外&#xff0c;其他指标均可以通过这篇博客提到的P…...

20230303----重返学习-函数概念-函数组成-函数调用-形参及匿名函数及自调用函数

day-019-nineteen-20230303-函数概念-函数组成-函数调用-形参及匿名函数及自调用函数 变量 变量声明 变量 声明定义(赋值) var num;num 100; 声明与赋值分开var num 100; 声明时就赋值 赋值只能声明一次&#xff0c;可以赋值无数次 变量声明关键词 varconstletclassfunctio…...

Java面试题总结

文章目录前言1、JDK1.8 的新特性有哪些&#xff1f;2、JDK 和 JRE 有什么区别&#xff1f;3、String&#xff0c;StringBuilder&#xff0c;StringBuffer 三者的区别&#xff1f;4、为什么 String 拼接的效率低&#xff1f;5、ArrayList 和 LinkedList 有哪些区别&#xff1f;6…...

深圳大学计软《面向对象的程序设计》实验7 拷贝构造函数与复合类

A. Point&Circle(复合类与构造) 题目描述 类Point是我们写过的一个类&#xff0c;类Circle是一个新的类&#xff0c;Point作为其成员对象&#xff0c;请完成类Circle的成员函数的实现。 在主函数中生成一个圆和若干个点&#xff0c;判断这些点与圆的位置关系&#xff0c;…...

Java的JVM(Java虚拟机)参数配置

JVM原理 &#xff08;1&#xff09;jvm是java的核心和基础&#xff0c;在java编译器和os平台之间的虚拟处理器&#xff0c;可在上面执行字节码程序。 &#xff08;2&#xff09;java编译器只要面向jvm&#xff0c;生成jvm能理解的字节码文件。java源文件经编译成字节码程序&a…...

leetcode 困难 —— 数据流的中位数(优先队列)

题目&#xff1a; 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFinder() 初始化…...

7个常用的原生JS数组方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 7个常用的原生JS数组方法一、Array.map()二、Array.filter三、Array.reduce四、Array.forEach五、Array.find六、Array.every七、Array.some总结一、Array.map() 作用&#…...

自已做好的网站怎么做后台/色盲眼中的世界

先做些准备工作&#xff0c; 先建立一个 posts 的migration文件&#xff0c;并成功该表到数据库&#xff0c; 内容如下&#xff1a; public function up(){Schema::create(posts, function (Blueprint $table) {$table->increments(id);$table->integer(user_id)->un…...

做餐饮的餐具网站有哪些/互联网营销外包推广

背景&#xff1a;SVN的项目文件被普通用户误删了&#xff0c;这是个非常严重的错误&#xff0c;还好恢复的及时&#xff0c;不然的话&#xff0c;后果不堪设想。但是由于删除的文件比较多&#xff0c;注释的内容简单&#xff0c;恢复的时候需要一个个的保存到本地&#xff0c;然…...

企业网站优化推广公司/app推广引流

面试题 17.11. 单词距离【中等题】【每日一题】 思路&#xff1a;【哈希表】 建立一个哈希map&#xff0c;以单词为key&#xff0c;以列表为value&#xff0c;将每个单词的下标存入单词key对应的value列表中。 取出题目要求的两个单词对应的列表&#xff0c;遍历两个列表&…...

国内外做的比较好的家装网站/关键词完整版

2019独角兽企业重金招聘Python工程师标准>>> 一.RabbitMq的术语解释 exchange:交换机&#xff0c;一个交换机可以有多个路由&#xff08;而每一个路由分别指向一个队列&#xff09;。 queue&#xff1a;队列&#xff0c;一个队列可以对应多个交换机。 route-key:路由…...

自己做物流网站/域名买卖交易平台

在本文中&#xff0c;我将向你展示如何使用ASP.NET AJAX框架对添加可点击的热点的HTML Map控件进行扩展。经扩展后&#xff0c;当我们的鼠标移动到这些热点上后&#xff0c;即弹出关于这些热点的详细信息;但是&#xff0c;这些详细信息都是通过AJAX异步方式从远程服务中取得的。…...

镇江网站建设联系思创/sem是什么电镜

在总部位于新加坡的员工福利初创公司CXA Group对我们的核心Web平台进行评估的过程中&#xff0c;我们决定将方向从陈旧的现有体系结构转向&#xff0c;从头开始重建前端。 该平台面临的挑战之一是创建一个在CXA Group的12个目标国家/地区&#xff08;在整个亚洲&#xff09;都能…...