# spring-file-storage **Repository Path**: stm/spring-file-storage ## Basic Information - **Project Name**: spring-file-storage - **Description**: 在SpringBoot中通过简单的方式将文件存储到本地、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO。后续即将支持 亚马逊S3、谷歌云存储、FTP、SFTP、WebDAV、Samba、NFS - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 764 - **Created**: 2021-05-26 - **Last Updated**: 2023-10-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

X Spring File Storage

github star star

### 简介 在SpringBoot中通过简单的方式将文件存储到本地、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO 后续即将支持 亚马逊S3、谷歌云存储、FTP、SFTP、WebDAV、Samba、NFS `spring-file-storage` 模块是本体。 `spring-file-storage-test` 模块是测试+使用演示,不需要的情况下可以直接删除。 GitHub:https://github.com/1171736840/spring-file-storage
Gitee:https://gitee.com/XYW1171736840/spring-file-storage 如果你觉得这个项目不错,可以在右上角点个 Star 或捐赠请作者吃包辣条~,在此表示感谢^_^。 点击以下链接,将页面拉到最下方点击“捐赠”即可。 [Gitee上捐赠](https://gitee.com/XYW1171736840/spring-file-storage) ### 使用说明 #### 配置 `pom.xml`引入依赖 ```xml cn.xuyanwu spring-file-storage 0.2.1 com.huaweicloud esdk-obs-java 3.20.6.1 com.aliyun.oss aliyun-sdk-oss 3.6.0 com.qiniu qiniu-java-sdk 7.4.0 com.qcloud cos_api 5.6.38 com.baidubce bce-java-sdk 0.10.162 com.upyun java-sdk 4.2.2 io.minio minio 7.0.2 ``` `application.yml`配置文件中添加以下相关配置(不使用的平台可以不配置) ```yaml 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 后,通过此路径可以访问到上传的文件 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.com bucket-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 - platform: minio-1 # 存储平台标识 enable-storage: true # 启用存储 access-key: ?? secret-key: ?? end-point: ?? bucket-name: ?? domain: ?? # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/ base-path: hy/ # 基础路径 ``` 注意配置每个平台前面都有个`-`号,通过以下方式可以配置多个 ```yaml local: - platform: local-1 # 存储平台标识 enable-storage: true enable-access: true domain: "" base-path: D:/Temp/test/ path-patterns: /test/file/** - platform: local-2 # 存储平台标识,注意这里不能重复 enable-storage: true enable-access: true domain: "" base-path: D:/Temp/test2/ path-patterns: /test2/file/** ``` #### 编码 在启动类上加上`@EnableFileStorage`注解 ```java @EnableFileStorage @SpringBootApplication public class SpringFileStorageTestApplication { public static void main(String[] args) { SpringApplication.run(SpringFileStorageTestApplication.class, args); } } ``` #### 开始使用 ```java @RestController public class FileDetailController { @Autowired private FileStorageService fileStorageService;//注入实列 /** * 上传文件,成功返回文件 url */ @PostMapping("/upload") public String upload(MultipartFile file) { FileInfo fileInfo = fileStorageService.of(file) .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 .setObjectType("0") //关联对象类型,为了方便管理,不需要可以不写 .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(); } } ``` 如果还想使用除了保存文件之前的其它功能,例如删除文件,还需要实现 `FileRecorder` 这个接口,把文件信息保存到数据库中
点击查看详情 ```java /** * 用来将文件上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类 */ @Service public class FileDetailService extends ServiceImpl implements FileRecorder { /** * 保存文件信息到数据库 */ @Override public boolean record(FileInfo info) { FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class); boolean b = save(detail); if (b) { info.setId(detail.getId()); } return b; } /** * 根据 url 查询文件信息 */ @Override public FileInfo getByUrl(String url) { return BeanUtil.copyProperties(getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)),FileInfo.class); } /** * 根据 url 删除文件信息 */ @Override public boolean delete(String url) { return remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); } } ``` 数据库表结构推荐如下,你也可以根据自己喜好在这里自己扩展 ```sql -- 这里使用的是 mysql CREATE TABLE `file_detail` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文件id', `url` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '文件访问地址', `size` bigint(20) NULL DEFAULT NULL COMMENT '文件大小,单位字节', `filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称', `original_filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '原始文件名', `base_path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '基础存储路径', `path` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '存储路径', `ext` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件扩展名', `platform` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '存储平台', `th_url` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '缩略图访问路径', `th_filename` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '缩略图名称', `th_size` bigint(20) NULL DEFAULT NULL COMMENT '缩略图大小,单位字节', `object_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件所属对象id', `object_type` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件记录表' ROW_FORMAT = Dynamic; ```
#### 自定义存储平台
点击查看详情 想要自定义存储平台就要实现 `FileStorage` 这个接口,并进行实例化,注意返回的 bean 是个 list 这里拿 LocalFileStorage 举例 ```java /** * 实现 FileStorage 接口,这里使用了 Lombok 和 Hutool 工具类 */ @Getter @Setter public class LocalFileStorage implements FileStorage { /* 本地存储路径*/ private String basePath; /* 存储平台 */ private String platform; /* 访问域名 */ private String domain; /** * 保存文件 */ @Override public 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); } } /** * 删除文件 */ @Override public 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())); } /** * 文件是否存在 */ @Override public boolean exists(FileInfo fileInfo) { return new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getFilename()).exists(); } /** * 下载文件 */ @Override public void download(FileInfo fileInfo,Consumer consumer) { try (InputStream in = FileUtil.getInputStream(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename())) { consumer.accept(in); } catch (IOException e) { throw new FileStorageRuntimeException("文件下载失败!platform:" + fileInfo,e); } } /** * 下载缩略图文件 */ @Override public void downloadTh(FileInfo fileInfo,Consumer 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 */ @Bean public List localFileStorageList() { ArrayList list = new ArrayList<>(); LocalFileStorage localFileStorage = new LocalFileStorage(); localFileStorage.setPlatform("my-local-1");//平台名称 localFileStorage.setBasePath(""); localFileStorage.setDomain(""); list.add(localFileStorage); return list; } } ```
#### 自定义上传和删除等切面
点击查看详情 只需要实现`FileStorageAspect`接口即可对文件上传和删除等进行干预。 不需要的方法可以不用实现,此接口里的方法全部都有默认实现 ```java /** * 使用切面打印文件上传和删除的日志 */ @Slf4j @Component public class LogFileStorageAspect implements FileStorageAspect { /** * 上传,成功返回文件信息,失败返回 null */ @Override public 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 */ @Override public 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; } /** * 文件是否存在 */ @Override public boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) { log.info("文件是否存在 before -> {}",fileInfo); boolean res = chain.next(fileInfo,fileStorage); log.info("文件是否存在 after -> {}",res); return res; } /** * 下载文件 */ @Override public void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { log.info("下载文件 before -> {}",fileInfo); chain.next(fileInfo,fileStorage,consumer); log.info("下载文件 after -> {}",fileInfo); } /** * 下载缩略图文件 */ @Override public void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { log.info("下载缩略图文件 before -> {}",fileInfo); chain.next(fileInfo,fileStorage,consumer); log.info("下载缩略图文件 after -> {}",fileInfo); } } ```