代码有点长,可能会有点卡
功能说明
- S3、阿里云OSS、腾讯云COS、MinIO使用的是S3兼容协议,所以大部分代码可以通用
- 支持的文件常用操作有:
- 创建桶、判断桶是否存在、删除桶
- 创建文件夹
- 上传文件、下载文件、判断文件是否存在、重命名、复制文件、获取文件外链(部分支持)、删除文件、批量删除文件
- 获取文件详情、条件查询文件列表(文件名模糊查询、多个文件后缀查询、查询某个目录下面的直接文件和目录)
文件说明
具体代码
POM文件导入依赖
<!--S3兼容协议API-->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-sdk-java</artifactId>
<version>2.17.201</version>
</dependency>
<!-- FTP -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
<!-- SFTP -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
<!-- yaml工具 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
实体类
import cn.hutool.core.text.CharSequenceUtil;
import com.smartadmin.common.model.StorageConfig;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 存储配置属性类
*
* @Author: hanYong
* @CreateTime: 2021-07-18
*/
@Getter
public class StorageProperties {
private List<StorageConfig> client = new ArrayList<>();
public void setClient(List<StorageConfig> client) {
this.client = client;
}
/**
* 根据存储平台获取存储平台配置
*
* @param platform 存储平台
* @return 存储平台配置
*/
public StorageConfig getStorageConfig(String platform) {
for (StorageConfig storageConfig : client) {
if (CharSequenceUtil.equals(storageConfig.getPlatform(), platform)) {
return storageConfig;
}
}
return null;
}
/**
* 判断是否开启了某个存储平台
*
* @param platform 存储平台
* @return 结果
*/
public Boolean isEnableStorage(String platform){
StorageConfig storageConfig = getStorageConfig(platform);
return Objects.nonNull(storageConfig) && storageConfig.getEnable();
}
}
import com.smartadmin.common.api.AliyunOssFileOperatorApi;
import com.smartadmin.common.api.AmazonS3FileOperatorApi;
import com.smartadmin.common.api.FileOperatorApi;
import com.smartadmin.common.api.FtpFileOperatorApi;
import com.smartadmin.common.api.LocalFileOperatorApi;
import com.smartadmin.common.api.MinIoFileOperatorApi;
import com.smartadmin.common.api.SftpFileOperatorApi;
import com.smartadmin.common.api.TencentCosFileOperatorApi;
import lombok.Getter;
/**
* 存储平台
*
* @Author: hanYong
* @CreateTime: 2021-07-18
*/
@Getter
public enum StoragePlatformEnum {
/**
* minio
*/
FTP("ftp", FtpFileOperatorApi.class),
/**
* minio
*/
SFTP("sftp", SftpFileOperatorApi.class),
/**
* minio
*/
MINIO("minio", MinIoFileOperatorApi.class),
/**
* 阿里云oss
*/
ALIYUN_OSS("aliyun-oss", AliyunOssFileOperatorApi.class),
/**
* 腾讯云cos
*/
TENCENT_COS("tencent-cos", TencentCosFileOperatorApi.class),
/**
* 腾讯云cos
*/
AMAZON_S3("amazon-s3", AmazonS3FileOperatorApi.class),
/**
* 本地存储
*/
LOCAL("local", LocalFileOperatorApi.class);
private final String value;
private final Class<? extends FileOperatorApi> aClass;
StoragePlatformEnum(String value, Class<? extends FileOperatorApi> aClass) {
this.value = value;
this.aClass = aClass;
}
}
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class FileInfo {
/**
* 文件唯一标识
*/
private String key;
/**
* 存储平台标识
*/
private String platform;
/**
* 存储空间
*/
private String bucket;
/**
* 文件名
*/
private String name;
/**
* 是否为目录
*/
private Boolean isDirectory;
/**
* 原文件名
*/
private String oldFilename;
/**
* 文件外链过期时间
*/
private Long timeoutMillis;
/**
* 文件路径
*/
private String path;
/**
* 文件类别
*/
private String fileCategory;
/**
* 文件类型
*/
private String type;
/**
* 文件大小(单位:字节)
*/
private Long size;
/**
* 友好的文件大小信息,例如 KB、MB、GB 等
*/
private String friendlySize;
/**
* 修改日期
*/
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date lastModified;
/**
* 备注
*/
private String remark;
/**
* 文件唯一标识集合
*/
private List<String> keys;
}
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ListFileInfo {
/**
* 文件唯一标识
*/
private String key;
/**
* 文件名
*/
private String name;
/**
* 文件路径
*/
private String path;
/**
* 文件类型
*/
private List<String> types = new ArrayList<>();
/**
* 文件唯一标识集合
*/
private List<String> keys = new ArrayList<>();
/**
* 排序字段
*/
private String orderBy = "lastModified asc";
}
import com.smartadmin.common.utils.base.ApplicationUtil;
import lombok.Data;
/**
* 存储配置实体类
*
* @Author: hanYong
* @CreateTime: 2021-07-18
*/
@Data
public class StorageConfig {
/**
* 存储平台标识
*/
private String platform = "local";
/**
* 开启存储
*/
private Boolean enable = true;
/**
* 请求域名
*/
private String endpoint;
/**
* 访问密钥
*/
private String accessKey;
/**
* 访问密码
*/
private String accessSecret;
/**
* 存储空间
*/
private String bucket = ApplicationUtil.getProjectName();
/**
* 基础路径(只有本地才用到)
*/
private String basePath;
}
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.FileInfo;
import com.smartadmin.common.model.ListFileInfo;
import com.smartadmin.common.model.StorageConfig;
import com.smartadmin.common.properties.StorageProperties;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 文件操作API
* <p>
* 如果存在未包含的操作,可以调用getClient()自行获取client进行操作
*
* @author: hanYong
* @createTime: 2023-08-29
*/
public interface FileOperatorApi {
/**
* 关闭客户端
*/
void closeClient();
/**
* 获取客户端
*/
Object getClient();
/**
* 判断存储空间是否存在
*
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
Boolean existBucket();
/**
* 判断存储空间是否存在
*
* @param bucket 存储空间
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
Boolean existBucket(String bucket);
/**
* 创建存储空间
*
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
Boolean createBucket();
/**
* 创建存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
Boolean createBucket(String bucket);
/**
* 创建存储空间
*
* @param bucket 存储空间
* @param expirationDays 存储空间的生命周期,单位天
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
Boolean createBucket(String bucket, Integer expirationDays);
/**
* 删除存储空间
*
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
Boolean deleteBucket();
/**
* 删除存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
Boolean deleteBucket(String bucket);
/**
* 判断文件是否存在
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
Boolean existFile(String key);
/**
* 判断文件是否存在
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
Boolean existFile(String bucket, String key);
/**
* 创建文件夹
*
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
Boolean createFolder(String path);
/**
* 创建文件夹
*
* @param bucket 存储空间
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
Boolean createFolder(String bucket, String path);
/**
* 上传文件
*
* @param bytes 文件字节数组
*/
String uploadFile(byte[] bytes);
/**
* 上传文件
*
* @param inputStream 文件流
*/
String uploadFile(InputStream inputStream);
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
String uploadFile(String key, byte[] bytes);
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
String uploadFile(String key, InputStream inputStream);
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
String uploadFile(String bucket, String key, byte[] bytes);
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
String uploadFile(String bucket, String key, InputStream inputStream);
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
* @param objectMetadata 元数据信息
*/
String uploadFile(String bucket, String key, byte[] bytes, Map<String, String> objectMetadata);
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
* @param objectMetadata 元数据信息
*/
String uploadFile(String bucket, String key, InputStream inputStream, Map<String, String> objectMetadata);
/**
* 下载文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
byte[] downloadFile(String key);
/**
* 下载文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
byte[] downloadFile(String bucket, String key);
/**
* 复制文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
Boolean copyFile(String originFileKey, String newFileKey);
/**
* 复制文件
*
* @param originBucketName 源存储空间
* @param originFileKey 源文件名称
* @param newBucketName 新存储空间
* @param newFileKey 新文件名称
*/
Boolean copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey);
/**
* 重命名文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
Boolean renameFile(String originFileKey, String newFileKey);
/**
* 重命名文件
*
* @param bucket 存储空间
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
Boolean renameFile(String bucket, String originFileKey, String newFileKey);
/**
* 获取文件的下载地址,生成外网地址
*
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
String getFileLink(String key, Long timeoutMillis);
/**
* 获取文件的下载地址,生成外网地址
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
String getFileLink(String bucket, String key, Long timeoutMillis);
/**
* 删除文件
*
* @param key 文件唯一标识
*/
Boolean deleteFile(String key);
/**
* 删除文件
*
* @param bucket 存储空间
* @param key 文件唯一标识
*/
Boolean deleteFile(String bucket, String key);
/**
* 批量删除文件
*
* @param keys 文件唯一标识集合
*/
Boolean deleteFiles(List<String> keys);
/**
* 批量删除文件
*
* @param bucket 存储空间
* @param keys 文件唯一标识集合
*/
Boolean deleteFiles(String bucket, List<String> keys);
/**
* 获取文件信息
*
* @param key 文件唯一标识
* @return 文件信息
*/
FileInfo getFileInfo(String key);
/**
* 获取文件信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 文件信息
*/
FileInfo getFileInfo(String bucket, String key);
/**
* 获取文件列表
*
* @param listFileInfo 查询条件
* @return 文件列表
*/
List<FileInfo> listFile(ListFileInfo listFileInfo);
/**
* 获取文件列表
*
* @param bucket 存储空间
* @param listFileInfo 查询条件
* @return 文件列表
*/
List<FileInfo> listFile(String bucket, ListFileInfo listFileInfo);
/**
* 获取文件元数据信息
*
* @param key 文件唯一标识
* @return 元数据信息
*/
Map<String, String> getObjectMetadata(String key);
/**
* 获取文件元数据信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 元数据信息
*/
Map<String, String> getObjectMetadata(String bucket, String key);
/**
* 获取当前api的文件存储类型
*/
StoragePlatformEnum getStoragePlatformEnum();
/**
* 获取存储空间,如果没传存储空间,就返回默认的
*
* @param bucket 存储空间
* @return 存储空间
*/
default String getBucketName(String bucket) {
if (StrUtil.isNotBlank(bucket)) {
return bucket;
}
StorageProperties storageProperties = SpringUtil.getBean(StorageProperties.class);
bucket = Optional.ofNullable(storageProperties)
.map(storageProperties1 -> storageProperties1.getStorageConfig(getStoragePlatformEnum().getValue()))
.map(StorageConfig::getBucket)
.orElse(null);
return bucket;
}
}
S3兼容协议文件API
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.FileInfo;
import com.smartadmin.common.model.ListFileInfo;
import com.smartadmin.common.model.StorageConfig;
import lombok.extern.slf4j.Slf4j;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.BucketLifecycleConfiguration;
import software.amazon.awssdk.services.s3.model.CommonPrefix;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.services.s3.model.CopyObjectResponse;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.s3.model.CreateBucketResponse;
import software.amazon.awssdk.services.s3.model.Delete;
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
import software.amazon.awssdk.services.s3.model.DeleteBucketResponse;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse;
import software.amazon.awssdk.services.s3.model.ExpirationStatus;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
import software.amazon.awssdk.services.s3.model.HeadBucketResponse;
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
import software.amazon.awssdk.services.s3.model.LifecycleExpiration;
import software.amazon.awssdk.services.s3.model.LifecycleRule;
import software.amazon.awssdk.services.s3.model.LifecycleRuleFilter;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationRequest;
import software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Error;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.S3Object;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
public class AmazonS3FileOperatorApi implements FileOperatorApi {
/**
* 存储配置
*/
protected final StorageConfig storageConfig;
/**
* S3兼容协议文件操作客户端
*/
protected S3Client s3Client;
public AmazonS3FileOperatorApi(StorageConfig storageConfig) {
this.storageConfig = storageConfig;
this.initClient();
}
/**
* 初始化操作的客户端
*/
public void initClient() {
// S3兼容协议文件操作客户端实例。
s3Client = S3Client.builder()
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(storageConfig.getAccessKey(), storageConfig.getAccessSecret())))
.region(Region.AWS_GLOBAL)
.endpointOverride(URI.create(storageConfig.getEndpoint()))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true)
.chunkedEncodingEnabled(false)
.build())
.build();
}
/**
* 销毁操作的客户端
*/
@Override
public void closeClient() {
if (Objects.nonNull(s3Client)) {
s3Client.close();
}
}
/**
* 获取操作的客户端
* <p>
* 例如,获取阿里云的客户端com.aliyun.oss.OSS
*/
@Override
public Object getClient() {
return s3Client;
}
/**
* 判断存储空间是否存在
* <p>
* 例如:传入参数exampleBucket-1250000000,返回true代表存在此桶BucketAuthEnum.java
*
* @return boolean true-存在,false-不存在
*/
@Override
public Boolean existBucket() {
return existBucket(storageConfig.getBucket());
}
/**
* 判断存储空间是否存在
* <p>
* 例如:传入参数exampleBucket-1250000000,返回true代表存在此桶BucketAuthEnum.java
*
* @param bucket 存储空间
* @return boolean true-存在,false-不存在
*/
@Override
public Boolean existBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
HeadBucketResponse response = s3Client.headBucket(HeadBucketRequest.builder()
.bucket(bucket)
.build());
return response.sdkHttpResponse().isSuccessful();
} catch (Exception e) {
log.error("存储桶不存在或访问受限!error:{}", e.getMessage());
return false;
}
}
/**
* 创建存储空间
*
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket() {
return createBucket(storageConfig.getBucket(), null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket) {
return createBucket(bucket, null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @param expirationDays 存储空间的生命周期,单位天
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket, Integer expirationDays) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
if(ObjectUtil.isNotEmpty(expirationDays) && expirationDays > 0){
// 创建生命周期规则
List<LifecycleRule> rules = new ArrayList<>();
LifecycleRule rule = LifecycleRule.builder()
.id("ExampleRule")
.expiration(LifecycleExpiration.builder().days(expirationDays).build())
.filter(LifecycleRuleFilter.builder().prefix("prefix/").build())
.status(ExpirationStatus.ENABLED)
.build();
rules.add(rule);
// 创建生命周期配置
BucketLifecycleConfiguration configuration = BucketLifecycleConfiguration.builder()
.rules(rules)
.build();
// 应用生命周期配置到存储桶
PutBucketLifecycleConfigurationRequest request = PutBucketLifecycleConfigurationRequest.builder()
.bucket(bucket)
.lifecycleConfiguration(configuration)
.build();
PutBucketLifecycleConfigurationResponse response = s3Client.putBucketLifecycleConfiguration(request);
return response.sdkHttpResponse().isSuccessful();
} else {
CreateBucketRequest bucketRequest = CreateBucketRequest.builder()
.bucket(bucket)
.build();
CreateBucketResponse response = s3Client.createBucket(bucketRequest);
return response.sdkHttpResponse().isSuccessful();
}
} catch (Exception e) {
log.error("创建存储空间失败!error:{}", e.getMessage());
return false;
}
}
/**
* 删除存储空间
*
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket() {
return deleteBucket(storageConfig.getBucket());
}
/**
* 删除存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
// 创建 DeleteBucketRequest 请求
DeleteBucketRequest request = DeleteBucketRequest.builder()
.bucket(bucket)
.build();
// 删除存储桶
DeleteBucketResponse deleteBucketResponse = s3Client.deleteBucket(request);
return deleteBucketResponse.sdkHttpResponse().isSuccessful();
} catch (Exception e){
log.error("删除存储空间失败!error:{}", e.getMessage());
return false;
}
}
/**
* 判断文件是否存在
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String key) {
return existFile(storageConfig.getBucket(), key);
}
/**
* 判断文件是否存在
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String bucket, String key) {
if (CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return false;
}
try {
// 创建 HeadObjectRequest 请求
HeadObjectRequest request = HeadObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
// 发送 HeadObject 请求,检查文件是否存在
HeadObjectResponse response = s3Client.headObject(request);
return response.sdkHttpResponse().isSuccessful();
} catch (Exception e){
log.error("判断文件是否存在失败!error:{}", e.getMessage());
return false;
}
}
/**
* 创建文件夹
*
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String path) {
return createFolder(storageConfig.getBucket(), path);
}
/**
* 创建文件夹
*
* @param bucket 存储空间
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String bucket, String path) {
if (CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(path)) {
return false;
}
try {
// 创建 PutObjectRequest 请求
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucket)
.key(path)
.build();
PutObjectResponse putObjectResponse = s3Client.putObject(putObjectRequest, RequestBody.empty());
return putObjectResponse.sdkHttpResponse().isSuccessful();
} catch (Exception e){
log.error("判断创建文件夹是否失败!error:{}", e.getMessage());
return false;
}
}
/**
* 上传文件
*
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(byte[] bytes) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), bytes, null);
}
/**
* 上传文件
*
* @param inputStream 文件流
*/
@Override
public String uploadFile(InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), inputStream, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String key, byte[] bytes) {
return uploadFile(storageConfig.getBucket(), key, bytes, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String key, InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes) {
return uploadFile(bucket, key, bytes, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream) {
return uploadFile(bucket, key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes, Map<String, String> objectMetadata) {
return uploadFile(bucket, key, IoUtil.toStream(bytes), objectMetadata);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream, Map<String, String> objectMetadata) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
IoUtil.close(inputStream);
return null;
}
try {
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(bucket)
.metadata(objectMetadata)
.key(key)
.build();
s3Client.putObject(objectRequest, RequestBody.fromInputStream(inputStream, inputStream.available()));
return key;
} catch (Exception e) {
log.error("上传文件失败!error:{}", e.getMessage());
return null;
} finally {
IoUtil.close(inputStream);
}
}
/**
* 下载文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String key) {
return downloadFile(storageConfig.getBucket(), key);
}
/**
* 下载文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return new byte[0];
}
try {
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
ResponseBytes<GetObjectResponse> s3ClientObject = s3Client.getObject(getObjectRequest,
ResponseTransformer.toBytes());
return s3ClientObject.asByteArray();
} catch (Exception e) {
log.error("下载文件失败!error:{}", e.getMessage());
return new byte[0];
}
}
/**
* 复制文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originFileKey, String newFileKey) {
return copyFile(storageConfig.getBucket(), originFileKey, storageConfig.getBucket(), newFileKey);
}
/**
* 复制文件
*
* @param originBucketName 源存储空间
* @param originFileKey 源文件名称
* @param newBucketName 新存储空间
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(originBucketName, originFileKey, newBucketName, newFileKey)) {
return false;
}
try {
CopyObjectRequest copyObjectRequest = CopyObjectRequest.builder()
.sourceBucket(originBucketName)
.sourceKey(originFileKey)
.destinationBucket(newBucketName)
.destinationKey(newFileKey)
.build();
CopyObjectResponse copyObjectResponse = s3Client.copyObject(copyObjectRequest);
return copyObjectResponse.sdkHttpResponse().isSuccessful();
} catch (S3Exception e) {
log.error("复制文件失败!error:{}", e.getMessage());
return false;
}
}
/**
* 重命名文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String originFileKey, String newFileKey) {
return renameFile(storageConfig.getBucket(), originFileKey, newFileKey);
}
/**
* 重命名文件
*
* @param bucket 存储空间
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String bucket, String originFileKey, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(bucket, originFileKey, newFileKey)) {
return false;
}
copyFile(bucket, originFileKey, bucket, newFileKey);
Boolean existFile = existFile(bucket, newFileKey);
if(Boolean.TRUE.equals(existFile)){
return deleteFile(bucket, newFileKey);
}
return false;
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String key, Long timeoutMillis) {
return getFileLink(storageConfig.getBucket(), key, timeoutMillis);
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String bucket, String key, Long timeoutMillis) {
String preSignatureUrl = null;
try {
int defaultTimeoutMillis = 7 * 24 * 60 * 60 * 1000;
timeoutMillis = (Long) ObjectUtil.defaultIfNull(timeoutMillis, defaultTimeoutMillis);
if(timeoutMillis > defaultTimeoutMillis){
timeoutMillis = (long) defaultTimeoutMillis;
}
PresignedGetObjectRequest presignedGetObjectRequest;
try (S3Presigner s3PreSigner = S3Presigner.builder()
.region(Region.AWS_GLOBAL)
.credentialsProvider(ProfileCredentialsProvider.create())
.build()) {
GetObjectRequest getObjectRequest =
GetObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
//设置预签名URL可访问时间
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMillis(timeoutMillis))
.getObjectRequest(getObjectRequest)
.build();
presignedGetObjectRequest = s3PreSigner.presignGetObject(getObjectPresignRequest);
}
preSignatureUrl = String.valueOf(presignedGetObjectRequest.url());
} catch (Exception e) {
log.error("获取文件的下载地址失败!error:{}", e.getMessage());
return null;
}
return preSignatureUrl;
}
/**
* 删除文件
*
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String key) {
return deleteFile(storageConfig.getBucket(), key);
}
/**
* 删除文件
*
* @param bucket 存储空间
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String bucket, String key) {
try {
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
return s3Client.deleteObject(deleteObjectRequest).sdkHttpResponse().isSuccessful();
} catch (Exception e) {
log.error("删除文件失败!error:{}", e.getMessage());
return false;
}
}
/**
* 批量删除文件
*
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(List<String> keys) {
return deleteFiles(storageConfig.getBucket(), keys);
}
/**
* 批量删除文件
*
* @param bucket 存储空间
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(String bucket, List<String> keys) {
if(CharSequenceUtil.isBlank(bucket) || ObjectUtil.isEmpty(keys)){
return false;
}
try {
List<ObjectIdentifier> identifierList = keys.stream().map(key ->
ObjectIdentifier.builder().key(key).build()).toList();
DeleteObjectsRequest multiObjectDeleteRequest = DeleteObjectsRequest.builder()
.bucket(bucket)
.delete(Delete.builder().objects(identifierList).build())
.build();
DeleteObjectsResponse deleteObjectsResponse = s3Client.deleteObjects(multiObjectDeleteRequest);
if(deleteObjectsResponse.errors().size() < keys.size()){
String s = deleteObjectsResponse.errors().stream().map(S3Error::key).collect(Collectors.joining(","));
// 部分文件删除成功
log.error("部分文件删除成功!未删除成功的文件有:【{}】", s);
}
return deleteObjectsResponse.sdkHttpResponse().isSuccessful();
} catch (Exception e) {
log.error("批量删除文件失败!error:{}", e.getMessage());
return false;
}
}
/**
* 获取文件信息
*
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String key) {
return getFileInfo(storageConfig.getBucket(), key);
}
/**
* 获取文件信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return null;
}
Map<String, String> objectMetadata = getObjectMetadata(bucket, key);
if(ObjectUtil.isEmpty(objectMetadata)){
return null;
}
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(MapUtil.getStr(objectMetadata, "key"));
fileInfo.setPath(MapUtil.getStr(objectMetadata, "path"));
fileInfo.setName(MapUtil.getStr(objectMetadata, "name"));
fileInfo.setBucket(bucket);
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(MapUtil.getLong(objectMetadata, "Content-Length"));
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(MapUtil.getStr(objectMetadata, "Content-Type"));
fileInfo.setLastModified(new Date(MapUtil.getLong(objectMetadata, "lastModified")));
fileInfo.setTimeoutMillis(MapUtil.getLong(objectMetadata, "timeoutMillis"));
return fileInfo;
}
/**
* 获取文件列表
*
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(ListFileInfo listFileInfo) {
return listFile(storageConfig.getBucket(), listFileInfo);
}
/**
* 获取文件列表
*
* @param bucket 存储空间
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(String bucket, ListFileInfo listFileInfo) {
if (CharSequenceUtil.isBlank(bucket)) {
return new ArrayList<>();
}
List<FileInfo> fileInfoList = new ArrayList<>();
try {
if(ObjectUtil.isNotEmpty(listFileInfo.getPath())){
String path = listFileInfo.getPath();
if(CharSequenceUtil.equals(StrPool.SLASH, listFileInfo.getPath())){
path = CharSequenceUtil.EMPTY;
} else {
path = FileUtil.normalize(path) + StrPool.SLASH;
}
ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(bucket).maxKeys(5000)
.prefix(path).delimiter(StrPool.SLASH).build();
ListObjectsV2Response listObjectsV2Response = s3Client.listObjectsV2(listRequest);
Optional.ofNullable(listObjectsV2Response.commonPrefixes()).orElse(ListUtil.empty())
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
Optional.ofNullable(listObjectsV2Response.contents()).orElse(ListUtil.empty())
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
} else if(ObjectUtil.isNotEmpty(listFileInfo.getTypes())) {
List<String> typeList = listFileInfo.getTypes();
List<String> pathList = ListUtil.of(CharSequenceUtil.EMPTY);
List<String> pathList2 = new ArrayList<>();
ListObjectsV2Request listRequest;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
pathList2 = new ArrayList<>();
}
if(fileInfoList.size() >= 5000){
break;
}
listRequest = ListObjectsV2Request.builder().bucket(bucket).maxKeys(5000)
.prefix(pathList.get(i)).delimiter(StrPool.SLASH).build();
ListObjectsV2Response listObjectsV2Response = s3Client.listObjectsV2(listRequest);
// 文件名称筛选
Optional.ofNullable(listObjectsV2Response.contents()).orElse(ListUtil.empty())
.stream().filter(file -> typeList.contains(FileNameUtil.getSuffix(file.key())))
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
List<CommonPrefix> fileList = Optional.ofNullable(listObjectsV2Response.commonPrefixes()).orElse(ListUtil.empty());
for (CommonPrefix file : fileList) {
// 记录目录,方便一层一层的查询
pathList2.add(file.prefix());
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
} else if(ObjectUtil.isNotEmpty(listFileInfo.getKey()) || ObjectUtil.isNotEmpty(listFileInfo.getKeys())){
List<String> keys = new ArrayList<>();
if(ObjectUtil.isNotEmpty(listFileInfo.getKeys())){
keys.addAll(listFileInfo.getKeys());
}
if(ObjectUtil.isNotEmpty(listFileInfo.getKey())){
keys.add(listFileInfo.getKey());
}
// 获取某几个文件
for (String key : keys) {
if(CharSequenceUtil.equals(StrPool.SLASH, key)){
continue;
}
String parentPath = FileUtil.normalize(Paths.get(key).getParent().toString()) + StrPool.SLASH;
ListObjectsV2Request listRequest = ListObjectsV2Request.builder().bucket(bucket).maxKeys(5000)
.prefix(parentPath).delimiter(StrPool.SLASH).build();
ListObjectsV2Response listObjectsV2Response = s3Client.listObjectsV2(listRequest);
Optional.ofNullable(listObjectsV2Response.commonPrefixes()).orElse(ListUtil.empty())
.stream().filter(file -> CharSequenceUtil.equals(FileNameUtil.getName(file.prefix()), FileNameUtil.getName(key)))
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
Optional.ofNullable(listObjectsV2Response.contents()).orElse(ListUtil.empty())
.stream().filter(file -> CharSequenceUtil.equals(file.key(), key))
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
}
} else if(ObjectUtil.isNotEmpty(listFileInfo.getName())){
String name = listFileInfo.getName();
List<String> pathList = ListUtil.of(CharSequenceUtil.EMPTY);
List<String> pathList2 = new ArrayList<>();
ListObjectsV2Request listRequest;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
pathList2 = new ArrayList<>();
}
listRequest = ListObjectsV2Request.builder().bucket(bucket).maxKeys(5000)
.prefix(pathList.get(i)).delimiter(StrPool.SLASH).build();
ListObjectsV2Response listObjectsV2Response = s3Client.listObjectsV2(listRequest);
// 文件名称筛选
Optional.ofNullable(listObjectsV2Response.contents()).orElse(ListUtil.empty())
.stream().filter(file -> FileNameUtil.getName(file.key()).contains(name))
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
List<CommonPrefix> fileList = Optional.ofNullable(listObjectsV2Response.commonPrefixes()).orElse(ListUtil.empty());
for (CommonPrefix file : fileList) {
// 记录目录,方便一层一层的查询
pathList2.add(file.prefix());
// 判断目录名称是否匹配
if (FileNameUtil.getName(file.prefix()).contains(name)) {
fileInfoList.add(toFileInfo(file, bucket));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
} else {
int k = 0;
List<String> pathList = ListUtil.of(CharSequenceUtil.EMPTY);
List<String> pathList2 = new ArrayList<>();
ListObjectsV2Request listRequest;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
++k;
if(k == 10){
break;
}
pathList2 = new ArrayList<>();
}
if(fileInfoList.size() == 5000){
break;
}
listRequest = ListObjectsV2Request.builder().bucket(bucket).maxKeys(5000)
.prefix(pathList.get(i)).delimiter(StrPool.SLASH).build();
ListObjectsV2Response listObjectsV2Response = s3Client.listObjectsV2(listRequest);
List<CommonPrefix> fileList = Optional.ofNullable(listObjectsV2Response.commonPrefixes()).orElse(ListUtil.empty());
for (CommonPrefix file : fileList) {
// 记录目录,方便一层一层的查询
pathList2.add(file.prefix());
fileInfoList.add(toFileInfo(file, bucket));
}
Optional.ofNullable(listObjectsV2Response.contents()).orElse(ListUtil.empty())
.forEach(file -> fileInfoList.add(toFileInfo(file, bucket)));
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
}
} catch (S3Exception e) {
log.error("复制文件失败!error:{}", e.getMessage());
}
return fileInfoList;
}
public FileInfo toFileInfo(CommonPrefix commonPrefix, String bucket){
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(commonPrefix.prefix());
fileInfo.setPath(FileUtil.normalize(Paths.get(fileInfo.getKey()).getParent().toString()));
fileInfo.setName(FileNameUtil.getName(fileInfo.getKey()));
fileInfo.setBucket(bucket);
fileInfo.setIsDirectory(true);
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(0L);
fileInfo.setFriendlySize("");
fileInfo.setType(null);
fileInfo.setLastModified(null);
return fileInfo;
}
public FileInfo toFileInfo(S3Object file, String bucket){
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(file.key());
fileInfo.setPath(FileUtil.normalize(Paths.get(fileInfo.getKey()).getParent().toString()));
fileInfo.setName(FileNameUtil.getName(fileInfo.getKey()));
fileInfo.setBucket(bucket);
fileInfo.setIsDirectory(false);
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(file.size());
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(null);
fileInfo.setLastModified(new Date(file.lastModified().getEpochSecond() * 1000));
return fileInfo;
}
/**
* 获取文件元数据信息
*
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String key) {
return getObjectMetadata(storageConfig.getBucket(), key);
}
/**
* 获取文件元数据信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return new HashMap<>(0);
}
try {
HeadObjectRequest headObjectRequest = HeadObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
HeadObjectResponse headObjectResponse = s3Client.headObject(headObjectRequest);
if(!headObjectResponse.sdkHttpResponse().isSuccessful()){
return new HashMap<>(0);
}
Map<String, String> map = new LinkedHashMap<>(headObjectResponse.metadata());
if(key.contains(StrPool.SLASH)){
map.put("path", key.substring(0, key.lastIndexOf(StrPool.SLASH)));
} else {
map.put("path", StrPool.SLASH);
}
map.put("key", key);
map.put("name", FileUtil.getName(key));
map.put("Content-Type", headObjectResponse.contentType());
map.put("Content-Length", headObjectResponse.contentLength() + "");
map.put("lastModified", String.valueOf(headObjectResponse.lastModified().getEpochSecond() * 1000));
return map;
} catch (Exception e){
log.error("获取文件元数据失败!error:{}", e.getMessage());
return new HashMap<>(0);
}
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.AMAZON_S3;
}
}
阿里云OSS文件API
import java.net.URI;
public class AliyunOssFileOperatorApi extends AmazonS3FileOperatorApi{
public AliyunOssFileOperatorApi(StorageConfig storageConfig) {
super(storageConfig);
}
/**
* 初始化操作的客户端
*/
@Override
public void initClient() {
// S3兼容协议文件操作客户端实例。
s3Client = S3Client.builder()
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(storageConfig.getAccessKey(), storageConfig.getAccessSecret())))
.region(Region.AWS_GLOBAL)
.endpointOverride(URI.create(storageConfig.getEndpoint()))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(false)
.chunkedEncodingEnabled(false)
.build())
.build();
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.ALIYUN_OSS;
}
}
腾讯COS文件API
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.StorageConfig;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import java.net.URI;
public class TencentCosFileOperatorApi extends AmazonS3FileOperatorApi{
public TencentCosFileOperatorApi(StorageConfig storageConfig) {
super(storageConfig);
}
/**
* 初始化操作的客户端
*/
@Override
public void initClient() {
// S3兼容协议文件操作客户端实例。
s3Client = S3Client.builder()
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(storageConfig.getAccessKey(), storageConfig.getAccessSecret())))
.region(Region.AWS_GLOBAL)
.endpointOverride(URI.create(storageConfig.getEndpoint()))
.build();
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.TENCENT_COS;
}
}
MinIO文件API
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.StorageConfig;
public class MinIoFileOperatorApi extends AmazonS3FileOperatorApi{
public MinIoFileOperatorApi(StorageConfig storageConfig) {
super(storageConfig);
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.MINIO;
}
}
FTP文件API
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.ftp.Ftp;
import cn.hutool.extra.ftp.FtpMode;
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.FileInfo;
import com.smartadmin.common.model.ListFileInfo;
import com.smartadmin.common.model.StorageConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPFile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Slf4j
public class FtpFileOperatorApi implements FileOperatorApi{
/**
* 路径前缀
*/
private String pathPrefix = "";
/**
* 存储配置
*/
protected final StorageConfig storageConfig;
private Ftp ftp;
public FtpFileOperatorApi(StorageConfig storageConfig) {
this.storageConfig = storageConfig;
this.initClient();
}
/**
* 初始化客户端
*/
private void initClient() {
try {
URI uri = URLUtil.toURI(storageConfig.getEndpoint());
if(CharSequenceUtil.isNotBlank(storageConfig.getAccessKey())){
ftp = new Ftp(uri.getHost(),
uri.getPort(),
storageConfig.getAccessKey(),
storageConfig.getAccessSecret());
} else {
ftp = new Ftp(uri.getHost(),
uri.getPort());
}
ftp.setMode(FtpMode.Passive);
// 判断存储空间是否存在,不存在则创建
if(CharSequenceUtil.isNotBlank(storageConfig.getBasePath())){
pathPrefix = FileUtil.normalize(storageConfig.getBasePath());
if (CharSequenceUtil.isNotBlank(pathPrefix) && !ftp.exist(pathPrefix)) {
ftp.mkDirs(pathPrefix);
}
}
} finally {
closeClient();
}
}
/**
* 关闭客户端
*/
@Override
public void closeClient() {
try {
ftp.close();
} catch (Exception ignored){
}
}
/**
* 获取客户端
*/
@Override
public Object getClient() {
ftp.init();
return ftp;
}
private String getBucketPath(String bucket){
return FileUtil.normalize(pathPrefix + File.separator + bucket);
}
/**
* 判断存储空间是否存在
*
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existBucket() {
return existBucket(storageConfig.getBucket());
}
/**
* 判断存储空间是否存在
*
* @param bucket 存储空间
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
ftp = (Ftp) getClient();
return ftp.exist(getBucketPath(bucket));
} finally {
closeClient();
}
}
/**
* 创建存储空间
*
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket() {
return createBucket(storageConfig.getBucket(), null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket) {
return createBucket(bucket, null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @param expirationDays 存储空间的生命周期,单位天
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket, Integer expirationDays) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
ftp = (Ftp) getClient();
ftp.mkDirs(getBucketPath(bucket));
return true;
} finally {
closeClient();
}
}
/**
* 删除存储空间
*
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket() {
return deleteBucket(storageConfig.getBucket());
}
/**
* 删除存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
ftp = (Ftp) getClient();
return ftp.delDir(getBucketPath(bucket));
} finally {
closeClient();
}
}
/**
* 判断文件是否存在
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String key) {
return existFile(storageConfig.getBucket(), key);
}
/**
* 判断文件是否存在
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String bucket, String key) {
if (CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return false;
}
try {
ftp = (Ftp) getClient();
return ftp.exist(FileUtil.normalize(getBucketPath(bucket) + File.separator + key));
} finally {
closeClient();
}
}
/**
* 创建文件夹
*
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String path) {
return createFolder(storageConfig.getBucket(), path);
}
/**
* 创建文件夹
*
* @param bucket 存储空间
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String bucket, String path) {
if (CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(path)) {
return false;
}
try {
ftp = (Ftp) getClient();
String dirPath = FileUtil.normalize(getBucketPath(bucket) + File.separator + path);
ftp.mkDirs(dirPath);
return ftp.exist(dirPath);
} finally {
closeClient();
}
}
/**
* 上传文件
*
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(byte[] bytes) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), bytes, null);
}
/**
* 上传文件
*
* @param inputStream 文件流
*/
@Override
public String uploadFile(InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), inputStream, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String key, byte[] bytes) {
return uploadFile(storageConfig.getBucket(), key, bytes, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String key, InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes) {
return uploadFile(bucket, key, bytes, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream) {
return uploadFile(bucket, key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes, Map<String, String> objectMetadata) {
return uploadFile(bucket, key, IoUtil.toStream(bytes), objectMetadata);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream, Map<String, String> objectMetadata) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
IoUtil.close(inputStream);
return null;
}
try {
ftp = (Ftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
Path directoryPath = Paths.get(path).getParent();
String directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
String filename = FileUtil.getName(path);
if(CharSequenceUtil.isNotBlank(directory) && !ftp.exist(directory)){
ftp.mkDirs(directory);
}
ftp.upload(directory, filename, inputStream);
return key;
} finally {
IoUtil.close(inputStream);
closeClient();
}
}
/**
* 下载文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String key) {
return downloadFile(storageConfig.getBucket(), key);
}
/**
* 下载文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return new byte[0];
}
try {
ftp = (Ftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!ftp.exist(path)){
return new byte[0];
}
Path directoryPath = Paths.get(path).getParent();
String directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
String filename = FileUtil.getName(path);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
ftp.download(directory, filename, outputStream);
return outputStream.toByteArray();
} catch (Exception e) {
log.error("下载文件失败!error:{}", e.getMessage());
return new byte[0];
}
} finally {
closeClient();
}
}
/**
* 复制文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originFileKey, String newFileKey) {
return copyFile(storageConfig.getBucket(), originFileKey, storageConfig.getBucket(), newFileKey);
}
/**
* 复制文件
*
* @param originBucketName 源存储空间
* @param originFileKey 源文件名称
* @param newBucketName 新存储空间
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(originBucketName, originFileKey, newBucketName, newFileKey)) {
return false;
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
ftp = (Ftp) getClient();
String path = FileUtil.normalize(getBucketPath(originBucketName) + File.separator + originFileKey);
if(!ftp.exist(path)){
return false;
}
// 先获取文件数据
Path directoryPath = Paths.get(path).getParent();
String directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
String filename = FileUtil.getName(path);
ftp.download(directory, filename, outputStream);
byte[] bytes = outputStream.toByteArray();
// 在上传到对应的位置
path = FileUtil.normalize(getBucketPath(newBucketName) + File.separator + newFileKey);
directoryPath = Paths.get(path).getParent();
directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
filename = FileUtil.getName(path);
if(CharSequenceUtil.isNotBlank(directory) && !ftp.exist(directory)){
ftp.mkDirs(directory);
}
try (ByteArrayInputStream inputStream = IoUtil.toStream(bytes)){
return ftp.upload(directory, filename, inputStream);
}
} catch (Exception e){
return false;
} finally {
closeClient();
}
}
/**
* 重命名文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String originFileKey, String newFileKey) {
return renameFile(storageConfig.getBucket(), originFileKey, newFileKey);
}
/**
* 重命名文件
*
* @param bucket 存储空间
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String bucket, String originFileKey, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(bucket, originFileKey, newFileKey)) {
return false;
}
copyFile(bucket, originFileKey, bucket, newFileKey);
Boolean existFile = existFile(bucket, newFileKey);
if(Boolean.TRUE.equals(existFile)){
return deleteFile(bucket, newFileKey);
}
return false;
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String key, Long timeoutMillis) {
return getFileLink(storageConfig.getBucket(), key, timeoutMillis);
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String bucket, String key, Long timeoutMillis) {
// empty
return null;
}
/**
* 删除文件
*
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String key) {
return deleteFile(storageConfig.getBucket(), key);
}
/**
* 删除文件
*
* @param bucket 存储空间
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return false;
}
try {
ftp = (Ftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!ftp.exist(path)){
return false;
}
return ftp.delFile(FileUtil.normalize(path));
} finally {
closeClient();
}
}
/**
* 批量删除文件
*
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(List<String> keys) {
return deleteFiles(storageConfig.getBucket(), keys);
}
/**
* 批量删除文件
*
* @param bucket 存储空间
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(String bucket, List<String> keys) {
if(CharSequenceUtil.isBlank(bucket) || ObjectUtil.isEmpty(keys)){
return false;
}
try {
ftp = (Ftp) getClient();
for (String key : keys) {
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!ftp.exist(path)){
continue;
}
ftp.delFile(FileUtil.normalize(path));
}
} finally {
closeClient();
}
return true;
}
/**
* 获取文件信息
*
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String key) {
return getFileInfo(storageConfig.getBucket(), key);
}
/**
* 获取文件信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return null;
}
Map<String, String> objectMetadata = getObjectMetadata(bucket, key);
if(ObjectUtil.isEmpty(objectMetadata)){
return null;
}
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(MapUtil.getStr(objectMetadata, "key"));
fileInfo.setPath(MapUtil.getStr(objectMetadata, "path"));
fileInfo.setName(MapUtil.getStr(objectMetadata, "name"));
fileInfo.setBucket(bucket);
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(MapUtil.getLong(objectMetadata, "Content-Length"));
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(MapUtil.getStr(objectMetadata, "Content-Type"));
fileInfo.setLastModified(new Date(MapUtil.getLong(objectMetadata, "lastModified")));
fileInfo.setTimeoutMillis(MapUtil.getLong(objectMetadata, "timeoutMillis"));
return fileInfo;
}
/**
* 获取文件列表
*
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(ListFileInfo listFileInfo) {
return listFile(storageConfig.getBucket(), listFileInfo);
}
/**
* 获取文件列表
*
* @param bucket 存储空间
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(String bucket, ListFileInfo listFileInfo) {
if(CharSequenceUtil.isBlank(bucket)){
return new ArrayList<>();
}
List<FileInfo> fileInfoList = new ArrayList<>();
try {
ftp = (Ftp) getClient();
if(ObjectUtil.isNotEmpty(listFileInfo.getPath())) {
// 查询某个路径下的所有文件及目录(层级1)
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + listFileInfo.getPath());
List<FTPFile> fileList = ftp.lsFiles(path, Objects::nonNull);
for (FTPFile ftpFile : fileList) {
fileInfoList.add(toFileInfo(listFileInfo.getPath(), ftpFile, bucket));
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getTypes())) {
// 获取指定类型的文件
// 获取指定类型的文件
String basePath = FileUtil.normalize(getBucketPath(bucket));
List<String> pathList = ListUtil.of(basePath);
List<String> pathList2 = new ArrayList<>();
String path;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
pathList2 = new ArrayList<>();
}
if(fileInfoList.size() >= 5000){
break;
}
path = pathList.get(i);
List<FTPFile> ftpFileList = ftp.lsFiles(path, Objects::nonNull);
for (FTPFile ftpFile : ftpFileList) {
if (ftpFile.isDirectory()) {
pathList2.add(FileUtil.normalize(path + File.separator + ftpFile.getName()));
continue;
}
if(listFileInfo.getTypes().contains(FileUtil.getSuffix(ftpFile.getName()))){
fileInfoList.add(toFileInfo(path.replace(basePath, StrPool.SLASH), ftpFile, bucket));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getKeys()) || ObjectUtil.isNotEmpty(listFileInfo.getKey())) {
List<String> keys = new ArrayList<>();
if(ObjectUtil.isNotEmpty(listFileInfo.getKeys())){
keys.addAll(listFileInfo.getKeys());
}
if(ObjectUtil.isNotEmpty(listFileInfo.getKey())){
keys.add(listFileInfo.getKey());
}
// 获取某几个文件
for (String key : keys) {
if(CharSequenceUtil.equals(StrPool.SLASH, key)){
continue;
}
String parentPath = FileUtil.normalize(Paths.get(key).getParent().toString());
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + parentPath);
List<FTPFile> ftpFileList = ftp.lsFiles(path, Objects::nonNull);
for (FTPFile ftpFile : ftpFileList) {
if (CharSequenceUtil.equals(FileNameUtil.getName(ftpFile.getName()), FileNameUtil.getName(key))) {
fileInfoList.add(toFileInfo(parentPath, ftpFile, bucket));
}
}
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getName())) {
// 按文件名称模糊搜索
String basePath = FileUtil.normalize(getBucketPath(bucket));
List<String> pathList = ListUtil.of(basePath);
List<String> pathList2 = new ArrayList<>();
String path;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
pathList2 = new ArrayList<>();
}
path = pathList.get(i);
List<FTPFile> ftpFileList = ftp.lsFiles(path, Objects::nonNull);
for (FTPFile ftpFile : ftpFileList) {
if(ftpFile.getName().contains(listFileInfo.getName())){
fileInfoList.add(toFileInfo(path.replace(basePath, StrPool.SLASH), ftpFile, bucket));
}
if (ftpFile.isDirectory()) {
pathList2.add(FileUtil.normalize(path + File.separator + ftpFile.getName()));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
} else {
int k = 0;
String basePath = FileUtil.normalize(getBucketPath(bucket));
List<String> pathList = ListUtil.of(basePath);
List<String> pathList2 = new ArrayList<>();
String path;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
++k;
if(k == 10){
break;
}
pathList2 = new ArrayList<>();
}
if(fileInfoList.size() >= 5000){
break;
}
path = pathList.get(i);
List<FTPFile> ftpFileList = ftp.lsFiles(path, Objects::nonNull);
for (FTPFile ftpFile : ftpFileList) {
fileInfoList.add(toFileInfo(path.replace(basePath, StrUtil.EMPTY), ftpFile, bucket));
if (ftpFile.isDirectory()) {
pathList2.add(FileUtil.normalize(path + File.separator + ftpFile.getName()));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
}
return fileInfoList;
} finally {
closeClient();
}
}
private FileInfo toFileInfo(String path, FTPFile file, String bucket){
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(FileUtil.normalize(path + File.separator + file.getName()));
if(CharSequenceUtil.isBlank(path) || CharSequenceUtil.equals(StrPool.SLASH, path)){
fileInfo.setPath(StrPool.SLASH);
} else {
fileInfo.setPath(FileUtil.normalize(path));
}
fileInfo.setName(FileUtil.getName(file.getName()));
fileInfo.setBucket(bucket);
fileInfo.setIsDirectory(file.isDirectory());
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(file.getSize());
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(FileUtil.getMimeType(fileInfo.getKey()));
fileInfo.setLastModified(file.getTimestamp().getTime());
return fileInfo;
}
/**
* 获取文件元数据信息
*
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String key) {
return getObjectMetadata(storageConfig.getBucket(), key);
}
public static void main(String[] args) {
System.out.println(Paths.get(null).getParent());
}
/**
* 获取文件元数据信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return Collections.emptyMap();
}
try {
ftp = (Ftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if (!ftp.exist(path)) {
return Collections.emptyMap();
}
Path directoryPath = Paths.get(path).getParent();
String directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
FTPFile ftpFile =ftp.lsFiles(directory, file -> file.getName().equals(FileUtil.getName(path)))
.stream().findFirst().orElse(null);
Map<String, String> map = new HashMap<>();
if(key.contains(StrPool.SLASH)){
map.put("path", key.substring(0, key.lastIndexOf(StrPool.SLASH)));
} else {
map.put("path", StrPool.SLASH);
}
map.put("key", key);
map.put("name", FileUtil.getName(key));
map.put("Content-Type", null);
map.put("Content-Length", ftpFile.getSize() + "");
map.put("lastModified", ftpFile.getTimestamp().getTime().getTime() + "");
return map;
} catch (Exception e) {
return Collections.emptyMap();
} finally {
closeClient();
}
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.FTP;
}
}
SFTP文件API
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.extra.ssh.Sftp;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.FileInfo;
import com.smartadmin.common.model.ListFileInfo;
import com.smartadmin.common.model.StorageConfig;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Slf4j
public class SftpFileOperatorApi implements FileOperatorApi{
/**
* 路径前缀
*/
private String pathPrefix = "";
/**
* 存储配置
*/
protected final StorageConfig storageConfig;
private Sftp sftp;
public SftpFileOperatorApi(StorageConfig storageConfig) {
this.storageConfig = storageConfig;
this.initClient();
}
/**
* 初始化客户端
*/
private void initClient() {
try {
// 创建sftp客户端
URI uri = URLUtil.toURI(storageConfig.getEndpoint());
sftp = JschUtil.createSftp(uri.getHost(),
uri.getPort(),
storageConfig.getAccessKey(),
storageConfig.getAccessSecret());
// 判断存储空间是否存在,不存在则创建
if(CharSequenceUtil.isNotBlank(storageConfig.getBasePath())){
pathPrefix = FileUtil.normalize(storageConfig.getBasePath());
if (CharSequenceUtil.isNotBlank(pathPrefix) && !sftp.exist(pathPrefix)) {
sftp.mkDirs(pathPrefix);
}
}
} finally {
// 关闭客户端
closeClient();
}
}
/**
* 关闭客户端
*/
@Override
public void closeClient() {
try {
sftp.close();
} catch (Exception ignored){
}
}
/**
* 获取客户端
*/
@Override
public Object getClient() {
URI uri = URLUtil.toURI(storageConfig.getEndpoint());
sftp.init(
uri.getHost(),
uri.getPort(),
storageConfig.getAccessKey(),
storageConfig.getAccessSecret(),
CharsetUtil.CHARSET_UTF_8
);
return sftp;
}
private String getBucketPath(String bucket){
return FileUtil.normalize(pathPrefix + File.separator + bucket);
}
/**
* 判断存储空间是否存在
*
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existBucket() {
return existBucket(storageConfig.getBucket());
}
/**
* 判断存储空间是否存在
*
* @param bucket 存储空间
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
sftp = (Sftp) getClient();
return sftp.exist(getBucketPath(bucket));
} finally {
closeClient();
}
}
/**
* 创建存储空间
*
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket() {
return createBucket(storageConfig.getBucket(), null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket) {
return createBucket(bucket, null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @param expirationDays 存储空间的生命周期,单位天
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket, Integer expirationDays) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
sftp = (Sftp) getClient();
sftp.mkDirs(getBucketPath(bucket));
return true;
} finally {
closeClient();
}
}
/**
* 删除存储空间
*
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket() {
return deleteBucket(storageConfig.getBucket());
}
/**
* 删除存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
try {
sftp = (Sftp) getClient();
return sftp.delDir(getBucketPath(bucket));
} finally {
closeClient();
}
}
/**
* 判断文件是否存在
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String key) {
return existFile(storageConfig.getBucket(), key);
}
/**
* 判断文件是否存在
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String bucket, String key) {
if (CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return false;
}
try {
sftp = (Sftp) getClient();
return sftp.exist(FileUtil.normalize(getBucketPath(bucket) + File.separator + key));
} finally {
closeClient();
}
}
/**
* 创建文件夹
*
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String path) {
return existFile(storageConfig.getBucket(), path);
}
/**
* 创建文件夹
*
* @param bucket 存储空间
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String bucket, String path) {
if (CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(path)) {
return false;
}
try {
sftp = (Sftp) getClient();
String dirPath = FileUtil.normalize(getBucketPath(bucket) + File.separator + path);
sftp.mkDirs(dirPath);
return sftp.exist(dirPath);
} finally {
closeClient();
}
}
/**
* 上传文件
*
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(byte[] bytes) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), bytes, null);
}
/**
* 上传文件
*
* @param inputStream 文件流
*/
@Override
public String uploadFile(InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), inputStream, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String key, byte[] bytes) {
return uploadFile(storageConfig.getBucket(), key, bytes, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String key, InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes) {
return uploadFile(bucket, key, bytes, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream) {
return uploadFile(bucket, key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes, Map<String, String> objectMetadata) {
return uploadFile(bucket, key, IoUtil.toStream(bytes), objectMetadata);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream, Map<String, String> objectMetadata) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
IoUtil.close(inputStream);
return null;
}
try {
sftp = (Sftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
Path directoryPath = Paths.get(path).getParent();
String directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
String filename = FileUtil.getName(path);
if(CharSequenceUtil.isNotBlank(directory) && !sftp.exist(directory)){
sftp.mkDirs(directory);
}
sftp.upload(directory, filename, inputStream);
return key;
} finally {
IoUtil.close(inputStream);
closeClient();
}
}
/**
* 下载文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String key) {
return downloadFile(storageConfig.getBucket(), key);
}
/**
* 下载文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return new byte[0];
}
try {
sftp = (Sftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!sftp.exist(path)){
return new byte[0];
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
sftp.download(path, outputStream);
return outputStream.toByteArray();
} catch (Exception e) {
log.error("下载文件失败!error:{}", e.getMessage());
return new byte[0];
}
} finally {
closeClient();
}
}
/**
* 复制文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originFileKey, String newFileKey) {
return copyFile(storageConfig.getBucket(), originFileKey, storageConfig.getBucket(), newFileKey);
}
/**
* 复制文件
*
* @param originBucketName 源存储空间
* @param originFileKey 源文件名称
* @param newBucketName 新存储空间
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(originBucketName, originFileKey, newBucketName, newFileKey)) {
return false;
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
sftp = (Sftp) getClient();
String path = FileUtil.normalize(getBucketPath(originBucketName) + File.separator + originFileKey);
if(!sftp.exist(path)){
return false;
}
// 先获取文件数据
sftp.download(path, outputStream);
byte[] bytes = outputStream.toByteArray();
// 在上传到对应的位置
path = FileUtil.normalize(getBucketPath(newBucketName) + File.separator + newFileKey);
Path directoryPath = Paths.get(path).getParent();
String directory = directoryPath != null ? FileUtil.normalize(directoryPath.toString()) : "";
String filename = FileUtil.getName(path);
if(CharSequenceUtil.isNotBlank(directory) && !sftp.exist(directory)){
sftp.mkDirs(directory);
}
try (ByteArrayInputStream inputStream = IoUtil.toStream(bytes)){
return sftp.upload(directory, filename, inputStream);
}
} catch (Exception e){
return false;
} finally {
closeClient();
}
}
/**
* 重命名文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String originFileKey, String newFileKey) {
return renameFile(storageConfig.getBucket(), originFileKey, newFileKey);
}
/**
* 重命名文件
*
* @param bucket 存储空间
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String bucket, String originFileKey, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(bucket, originFileKey, newFileKey)) {
return false;
}
copyFile(bucket, originFileKey, bucket, newFileKey);
Boolean existFile = existFile(bucket, newFileKey);
if(Boolean.TRUE.equals(existFile)){
return deleteFile(bucket, newFileKey);
}
return false;
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String key, Long timeoutMillis) {
return getFileLink(storageConfig.getBucket(), key, timeoutMillis);
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String bucket, String key, Long timeoutMillis) {
// empty
return null;
}
/**
* 删除文件
*
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String key) {
return deleteFile(storageConfig.getBucket(), key);
}
/**
* 删除文件
*
* @param bucket 存储空间
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)) {
return false;
}
try {
sftp = (Sftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!sftp.exist(path)){
return false;
}
return sftp.delFile(FileUtil.normalize(path));
} finally {
closeClient();
}
}
/**
* 批量删除文件
*
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(List<String> keys) {
return deleteFiles(storageConfig.getBucket(), keys);
}
/**
* 批量删除文件
*
* @param bucket 存储空间
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(String bucket, List<String> keys) {
if(CharSequenceUtil.isBlank(bucket) || ObjectUtil.isEmpty(keys)){
return false;
}
try {
sftp = (Sftp) getClient();
for (String key : keys) {
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!sftp.exist(path)){
continue;
}
sftp.delFile(FileUtil.normalize(path));
}
} finally {
closeClient();
}
return true;
}
/**
* 获取文件信息
*
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String key) {
return getFileInfo(storageConfig.getBucket(), key);
}
/**
* 获取文件信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return null;
}
Map<String, String> objectMetadata = getObjectMetadata(bucket, key);
if(ObjectUtil.isEmpty(objectMetadata)){
return null;
}
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(MapUtil.getStr(objectMetadata, "key"));
fileInfo.setPath(MapUtil.getStr(objectMetadata, "path"));
fileInfo.setName(MapUtil.getStr(objectMetadata, "name"));
fileInfo.setBucket(bucket);
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(MapUtil.getLong(objectMetadata, "Content-Length"));
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(MapUtil.getStr(objectMetadata, "Content-Type"));
fileInfo.setLastModified(new Date(MapUtil.getLong(objectMetadata, "lastModified")));
fileInfo.setTimeoutMillis(MapUtil.getLong(objectMetadata, "timeoutMillis"));
return fileInfo;
}
/**
* 获取文件列表
*
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(ListFileInfo listFileInfo) {
return listFile(storageConfig.getBucket(), listFileInfo);
}
/**
* 获取文件列表
*
* @param bucket 存储空间
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(String bucket, ListFileInfo listFileInfo) {
if(CharSequenceUtil.isBlank(bucket)){
return new ArrayList<>();
}
List<FileInfo> fileInfoList = new ArrayList<>();
try {
sftp = (Sftp) getClient();
if(ObjectUtil.isNotEmpty(listFileInfo.getPath())) {
// 查询某个路径下的所有文件及目录(层级1)
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + listFileInfo.getPath());
List<ChannelSftp.LsEntry> fileList = sftp.lsEntries(path, Objects::nonNull);
for (ChannelSftp.LsEntry lsEntry : fileList) {
fileInfoList.add(toFileInfo(listFileInfo.getPath(), lsEntry, bucket));
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getTypes())) {
// 获取指定类型的文件
String basePath = FileUtil.normalize(getBucketPath(bucket));
List<String> pathList = ListUtil.of(basePath);
List<String> pathList2 = new ArrayList<>();
String path;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
pathList2 = new ArrayList<>();
}
if(fileInfoList.size() >= 5000){
break;
}
path = pathList.get(i);
List<ChannelSftp.LsEntry> lsEntries = sftp.lsEntries(path, Objects::nonNull);
for (ChannelSftp.LsEntry lsEntry : lsEntries) {
if (lsEntry.getAttrs().isDir()) {
pathList2.add(FileUtil.normalize(path + File.separator + lsEntry.getFilename()));
continue;
}
if(listFileInfo.getTypes().contains(FileUtil.getSuffix(lsEntry.getFilename()))){
fileInfoList.add(toFileInfo(path.replace(basePath, StrPool.SLASH), lsEntry, bucket));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getKeys()) || ObjectUtil.isNotEmpty(listFileInfo.getKey())) {
List<String> keys = new ArrayList<>();
if(ObjectUtil.isNotEmpty(listFileInfo.getKeys())){
keys.addAll(listFileInfo.getKeys());
}
if(ObjectUtil.isNotEmpty(listFileInfo.getKey())){
keys.add(listFileInfo.getKey());
}
// 获取某几个文件
for (String key : keys) {
if(CharSequenceUtil.equals(StrPool.SLASH, key)){
continue;
}
String parentPath = FileUtil.normalize(Paths.get(key).getParent().toString());
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + parentPath);
List<ChannelSftp.LsEntry> ftpFiles = sftp.lsEntries(path, Objects::nonNull);
for (ChannelSftp.LsEntry ftpFile : ftpFiles) {
if (CharSequenceUtil.equals(FileNameUtil.getName(ftpFile.getFilename()), FileNameUtil.getName(key))) {
fileInfoList.add(toFileInfo(parentPath, ftpFile, bucket));
}
}
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getName())) {
// 按文件名称模糊搜索
String basePath = FileUtil.normalize(getBucketPath(bucket));
List<String> pathList = ListUtil.of(basePath);
List<String> pathList2 = new ArrayList<>();
String path;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
pathList2 = new ArrayList<>();
}
path = pathList.get(i);
List<ChannelSftp.LsEntry> lsEntries = sftp.lsEntries(path, Objects::nonNull);
for (ChannelSftp.LsEntry lsEntry : lsEntries) {
if(lsEntry.getFilename().contains(listFileInfo.getName())){
fileInfoList.add(toFileInfo(path.replace(basePath, StrPool.SLASH), lsEntry, bucket));
}
if (lsEntry.getAttrs().isDir()) {
pathList2.add(FileUtil.normalize(path + File.separator + lsEntry.getFilename()));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
} else {
int k = 0;
String basePath = FileUtil.normalize(getBucketPath(bucket));
List<String> pathList = ListUtil.of(basePath);
List<String> pathList2 = new ArrayList<>();
String path;
for (int i = 0; i < pathList.size(); i++) {
if(i == 0){
++k;
if(k == 10){
break;
}
pathList2 = new ArrayList<>();
}
if(fileInfoList.size() >= 5000){
break;
}
path = pathList.get(i);
List<ChannelSftp.LsEntry> lsEntries = sftp.lsEntries(path, Objects::nonNull);
for (ChannelSftp.LsEntry lsEntry : lsEntries) {
fileInfoList.add(toFileInfo(path.replace(basePath, StrUtil.EMPTY), lsEntry, bucket));
if (lsEntry.getAttrs().isDir()) {
pathList2.add(FileUtil.normalize(path + File.separator + lsEntry.getFilename()));
}
}
if(i + 1 == pathList.size() && ObjectUtil.isNotEmpty(pathList2)){
pathList = pathList2;
i = -1;
}
}
}
return fileInfoList;
} finally {
closeClient();
}
}
private FileInfo toFileInfo(String path, ChannelSftp.LsEntry file, String bucket){
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(FileUtil.normalize(path + File.separator + file.getFilename()));
if(CharSequenceUtil.isBlank(path) || CharSequenceUtil.equals(StrPool.SLASH, path)){
fileInfo.setPath(StrPool.SLASH);
} else {
fileInfo.setPath(FileUtil.normalize(path));
}
fileInfo.setName(FileUtil.getName(file.getFilename()));
fileInfo.setBucket(bucket);
fileInfo.setIsDirectory(file.getAttrs().isDir());
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(file.getAttrs().getSize());
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(FileUtil.getMimeType(fileInfo.getKey()));
fileInfo.setLastModified(new Date(file.getAttrs().getMTime() * 1000L));
return fileInfo;
}
/**
* 获取文件元数据信息
*
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String key) {
return getObjectMetadata(storageConfig.getBucket(), key);
}
/**
* 获取文件元数据信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return Collections.emptyMap();
}
try {
sftp = (Sftp) getClient();
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if(!sftp.exist(path)){
return Collections.emptyMap();
}
SftpATTRS sftpATTRS = sftp.getClient().lstat(path);
Map<String, String> map = new HashMap<>();
if(key.contains(StrPool.SLASH)){
map.put("path", key.substring(0, key.lastIndexOf(StrPool.SLASH)));
} else {
map.put("path", StrPool.SLASH);
}
map.put("key", key);
map.put("name", FileUtil.getName(key));
map.put("Content-Type", null);
map.put("Content-Length", sftpATTRS.getSize() + "");
map.put("lastModified", sftpATTRS.getMTime() * 1000L + "");
return map;
} catch (SftpException e) {
return Collections.emptyMap();
} finally {
closeClient();
}
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.SFTP;
}
}
本地存储文件API
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.FileInfo;
import com.smartadmin.common.model.ListFileInfo;
import com.smartadmin.common.model.StorageConfig;
import com.smartadmin.common.utils.FileAttributeUtil;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class LocalFileOperatorApi implements FileOperatorApi{
/**
* 路径前缀
*/
private String pathPrefix = "";
/**
* 存储配置
*/
protected final StorageConfig storageConfig;
public LocalFileOperatorApi(StorageConfig storageConfig) {
this.storageConfig = storageConfig;
initClient();
}
/**
* 初始化客户端
*/
private void initClient() {
// 格式化路径为相对路径
if(StrUtil.isNotBlank(storageConfig.getBasePath())){
pathPrefix = FileUtil.normalize(storageConfig.getBasePath());
} else {
pathPrefix = SystemUtil.getOsInfo().isWindows() ? "D:/apps/storage" : "/apps/storage";
}
if (!FileUtil.exist(pathPrefix)) {
FileUtil.mkdir(pathPrefix);
}
}
/**
* 关闭客户端
*/
@Override
public void closeClient() {
// empty
}
/**
* 获取客户端
*/
@Override
public Object getClient() {
// empty
return null;
}
private String getBucketPath(String bucket){
return FileUtil.normalize(pathPrefix + File.separator + bucket);
}
/**
* 判断存储空间是否存在
*
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existBucket() {
return existBucket(storageConfig.getBucket());
}
/**
* 判断存储空间是否存在
*
* @param bucket 存储空间
* @return @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existBucket(String bucket) {
if (CharSequenceUtil.isBlank(bucket)) {
return false;
}
return FileUtil.exist(getBucketPath(bucket));
}
/**
* 创建存储空间
*
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket() {
return createBucket(storageConfig.getBucket(), null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket) {
return createBucket(bucket, null);
}
/**
* 创建存储空间
*
* @param bucket 存储空间
* @param expirationDays 存储空间的生命周期,单位天
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createBucket(String bucket, Integer expirationDays) {
if(CharSequenceUtil.isBlank(bucket)){
return false;
}
return FileUtil.mkdir(getBucketPath(bucket)).exists();
}
/**
* 删除存储空间
*
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket() {
return deleteBucket(storageConfig.getBucket());
}
/**
* 删除存储空间
*
* @param bucket 存储空间
* @return Boolean {@code true} 删除成功 {@code false} 删除失败
*/
@Override
public Boolean deleteBucket(String bucket) {
if(CharSequenceUtil.isBlank(bucket)){
return false;
}
return FileUtil.del(getBucketPath(bucket));
}
/**
* 判断文件是否存在
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String key) {
return existFile(storageConfig.getBucket(), key);
}
/**
* 判断文件是否存在
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return Boolean {@code true} 存在 {@code false} 不存在
*/
@Override
public Boolean existFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return false;
}
return FileUtil.exist(FileUtil.normalize(getBucketPath(bucket) + File.separator + key));
}
/**
* 创建文件夹
*
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String path) {
return createFolder(storageConfig.getBucket(), path);
}
/**
* 创建文件夹
*
* @param bucket 存储空间
* @param path 文件夹路径
* @return Boolean {@code true} 创建成功 {@code false} 创建失败
*/
@Override
public Boolean createFolder(String bucket, String path) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(path)){
return false;
}
// 拼接路径并格式化路径为相对路径
String dirPath = FileUtil.normalize(getBucketPath(bucket) + File.separator + path);
File file = FileUtil.mkdir(dirPath);
return file.exists();
}
/**
* 上传文件
*
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(byte[] bytes) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), bytes, null);
}
/**
* 上传文件
*
* @param inputStream 文件流
*/
@Override
public String uploadFile(InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), IdUtil.simpleUUID(), inputStream, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String key, byte[] bytes) {
return uploadFile(storageConfig.getBucket(), key, bytes, null);
}
/**
* 上传文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String key, InputStream inputStream) {
return uploadFile(storageConfig.getBucket(), key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes) {
return uploadFile(bucket, key, bytes, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream) {
return uploadFile(bucket, key, inputStream, null);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param bytes 文件字节数组
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, byte[] bytes, Map<String, String> objectMetadata) {
return uploadFile(bucket, key, IoUtil.toStream(bytes), objectMetadata);
}
/**
* 上传文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @param inputStream 文件流
* @param objectMetadata 元数据信息
*/
@Override
public String uploadFile(String bucket, String key, InputStream inputStream, Map<String, String> objectMetadata) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
IoUtil.close(inputStream);
return null;
}
try {
// 拼接路径并格式化路径为相对路径
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
FileUtil.writeFromStream(inputStream, path);
// 设置元数据
if(ObjectUtil.isNotEmpty(objectMetadata)){
for (Map.Entry<String, String> entry : objectMetadata.entrySet()) {
FileAttributeUtil.setAttribute(path, entry.getKey(), entry.getValue());
}
}
if(!FileUtil.exist(path)){
return null;
}
return key;
} finally {
IoUtil.close(inputStream);
}
}
/**
* 下载文件
*
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String key) {
return downloadFile(storageConfig.getBucket(), key);
}
/**
* 下载文件
*
* @param bucket 存储空间
* @param key 唯一标示id,例如a.txt, doc/a.txt
* @return byte[] 字节数组为文件的字节数组
*/
@Override
public byte[] downloadFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return null;
}
// 拼接路径并格式化路径为相对路径
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
// 判断文件是否存在
if (!FileUtil.exist(path)) {
return null;
}
return FileUtil.readBytes(path);
}
/**
* 复制文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originFileKey, String newFileKey) {
return copyFile(storageConfig.getBucket(), originFileKey, storageConfig.getBucket(), newFileKey);
}
/**
* 复制文件
*
* @param originBucketName 源存储空间
* @param originFileKey 源文件名称
* @param newBucketName 新存储空间
* @param newFileKey 新文件名称
*/
@Override
public Boolean copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(originBucketName, originFileKey, newBucketName, newFileKey)) {
return false;
}
String originFile = FileUtil.normalize(getBucketPath(originBucketName) + File.separator + originFileKey);
// 判断文件存在不存在
if (!FileUtil.exist(originFile)) {
return false;
}
// 拷贝文件
String destFile = FileUtil.normalize(getBucketPath(originBucketName) + File.separator + newFileKey);
File file = FileUtil.copy(originFile, destFile, true);
return file.exists();
}
/**
* 重命名文件
*
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String originFileKey, String newFileKey) {
return renameFile(storageConfig.getBucket(), originFileKey, newFileKey);
}
/**
* 重命名文件
*
* @param bucket 存储空间
* @param originFileKey 源文件名称
* @param newFileKey 新文件名称
*/
@Override
public Boolean renameFile(String bucket, String originFileKey, String newFileKey) {
if (!CharSequenceUtil.isAllNotBlank(bucket, originFileKey, newFileKey)) {
return false;
}
File targetFile = new File(FileUtil.normalize(getBucketPath(bucket) + File.separator + originFileKey));
File destFile = new File(FileUtil.normalize(getBucketPath(bucket) + File.separator + newFileKey));
FileUtil.move(targetFile, destFile, false);
return destFile.exists();
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String key, Long timeoutMillis) {
return getFileLink(storageConfig.getBucket(), key, timeoutMillis);
}
/**
* 获取文件的下载地址,生成外网地址
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @param timeoutMillis url失效时间,单位毫秒(最高7天)
* @return 外部系统可以直接访问的url
*/
@Override
public String getFileLink(String bucket, String key, Long timeoutMillis) {
// empty
return null;
}
/**
* 删除文件
*
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String key) {
return deleteFile(storageConfig.getBucket(), key);
}
/**
* 删除文件
*
* @param bucket 存储空间
* @param key 文件唯一标识
*/
@Override
public Boolean deleteFile(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return false;
}
// 拼接路径并格式化路径为相对路径
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
// 判断文件是否存在
if (!FileUtil.exist(path)) {
return false;
}
return FileUtil.del(path);
}
/**
* 批量删除文件
*
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(List<String> keys) {
return deleteFiles(storageConfig.getBucket(), keys);
}
/**
* 批量删除文件
*
* @param bucket 存储空间
* @param keys 文件唯一标识集合
*/
@Override
public Boolean deleteFiles(String bucket, List<String> keys) {
if(CharSequenceUtil.isBlank(bucket) || ObjectUtil.isEmpty(keys)){
return false;
}
keys.stream().map(s -> getBucketPath(bucket) + File.separator + s).forEach(FileUtil::del);
return true;
}
/**
* 获取文件信息
*
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String key) {
return getFileInfo(storageConfig.getBucket(), key);
}
/**
* 获取文件信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 文件信息
*/
@Override
public FileInfo getFileInfo(String bucket, String key) {
if(CharSequenceUtil.isBlank(bucket) || CharSequenceUtil.isBlank(key)){
return null;
}
Map<String, String> objectMetadata = getObjectMetadata(bucket, key);
if(ObjectUtil.isEmpty(objectMetadata)){
return null;
}
FileInfo fileInfo = new FileInfo();
fileInfo.setKey(MapUtil.getStr(objectMetadata, "key"));
fileInfo.setPath(MapUtil.getStr(objectMetadata, "path"));
fileInfo.setName(MapUtil.getStr(objectMetadata, "name"));
fileInfo.setBucket(bucket);
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(MapUtil.getLong(objectMetadata, "Content-Length"));
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(MapUtil.getStr(objectMetadata, "Content-Type"));
fileInfo.setLastModified(new Date(MapUtil.getLong(objectMetadata, "lastModified")));
fileInfo.setTimeoutMillis(MapUtil.getLong(objectMetadata, "timeoutMillis"));
return fileInfo;
}
/**
* 获取文件列表
*
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(ListFileInfo listFileInfo) {
return listFile(storageConfig.getBucket(), listFileInfo);
}
/**
* 获取文件列表
*
* @param bucket 存储空间
* @param listFileInfo 查询条件
* @return 文件列表
*/
@Override
public List<FileInfo> listFile(String bucket, ListFileInfo listFileInfo) {
if(CharSequenceUtil.isBlank(bucket)){
return new ArrayList<>();
}
List<File> fileList = new ArrayList<>();
if(ObjectUtil.isNotEmpty(listFileInfo.getPath())) {
// 查询某个路径下的所有文件及目录(层级1)
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + listFileInfo.getPath());
fileList = FileUtil.loopFiles(new File(path), 1, pathname -> !pathname.isHidden());
} else if (ObjectUtil.isNotEmpty(listFileInfo.getTypes())) {
// 获取指定类型的文件
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator);
AtomicInteger k = new AtomicInteger();
fileList = FileUtil.loopFiles(new File(path), -1, pathname -> {
boolean isNotHidden = !pathname.isHidden()
&& listFileInfo.getTypes().contains(FileUtil.getSuffix(pathname)) && k.get() <= 5000;
if(isNotHidden){
k.incrementAndGet();
}
return isNotHidden;
});
} else if (ObjectUtil.isNotEmpty(listFileInfo.getKeys()) || ObjectUtil.isNotEmpty(listFileInfo.getKey())) {
List<String> keys = new ArrayList<>();
if(ObjectUtil.isNotEmpty(listFileInfo.getKeys())){
keys.addAll(listFileInfo.getKeys());
}
if(ObjectUtil.isNotEmpty(listFileInfo.getKey())){
keys.add(listFileInfo.getKey());
}
// 获取某几个文件
for (String key : keys) {
if(CharSequenceUtil.equals(StrPool.SLASH, key)){
continue;
}
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
File file = new File(path);
if (!file.exists() || file.isHidden()) {
// 文件不存在或者是隐藏文件
continue;
}
fileList.add(file);
}
} else if (ObjectUtil.isNotEmpty(listFileInfo.getName())) {
// 按文件名称模糊搜索
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator);
fileList = FileUtil.loopFiles(new File(path), -1, pathname -> !pathname.isHidden()
&& FileNameUtil.getName(pathname).contains(listFileInfo.getName()));
} else {
// 查询某个路径下的所有文件及目录
String path = FileUtil.normalize(getBucketPath(bucket) + File.separator);
AtomicInteger k = new AtomicInteger();
fileList = FileUtil.loopFiles(new File(path), 10, pathname -> {
boolean isNotHidden = !pathname.isHidden() && k.get() <= 5000;
if(isNotHidden){
k.incrementAndGet();
}
return isNotHidden;
});
}
if(ObjectUtil.isEmpty(fileList)) {
return new ArrayList<>();
}
List<FileInfo> fileInfoList = new ArrayList<>();
FileInfo fileInfo;
for (File file : fileList) {
fileInfo = new FileInfo();
fileInfo.setKey(FileUtil.normalize(file.getAbsolutePath()).replace(getBucketPath(bucket), ""));
fileInfo.setPath(FileUtil.normalize(Paths.get(fileInfo.getKey()).getParent().toString()));
fileInfo.setName(FileUtil.getName(fileInfo.getKey()));
fileInfo.setBucket(bucket);
fileInfo.setIsDirectory(file.isDirectory());
fileInfo.setPlatform(getStoragePlatformEnum().getValue());
fileInfo.setSize(FileUtil.size(file));
fileInfo.setFriendlySize(DataSizeUtil.format(fileInfo.getSize()));
fileInfo.setType(FileUtil.getMimeType(fileInfo.getKey()));
fileInfo.setLastModified(new Date(file.lastModified()));
fileInfoList.add(fileInfo);
}
return fileInfoList;
}
public static void main(String[] args) {
String name = FileUtil.getName("C:/Users/hanyo/Desktop/日期.txt");
System.out.println(name);
String prefix = FileUtil.getPrefix("C:/Users/hanyo/Desktop/日期.txt");
System.out.println(prefix);
String suffix = FileUtil.getSuffix("C:/Users/hanyo/Desktop/日期.txt");
System.out.println(suffix);
String parent = FileUtil.getParent("C:/Users/hanyo/Desktop/日期.txt", 1);
System.out.println(parent);
String parent2 = FileUtil.getParent("C:/Users/hanyo/Desktop/日期", 1);
System.out.println(parent2);
String parent3 = FileUtil.getParent("/Users", 1);
System.out.println(parent3);
}
/**
* 获取文件元数据信息
*
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String key) {
return getObjectMetadata(storageConfig.getBucket(), key);
}
/**
* 获取文件元数据信息
*
* @param bucket 存储空间
* @param key 文件唯一标识
* @return 元数据信息
*/
@Override
public Map<String, String> getObjectMetadata(String bucket, String key) {
// 拼接路径并格式化路径为相对路径
String filePath = FileUtil.normalize(getBucketPath(bucket) + File.separator + key);
if (!FileUtil.exist(filePath)) {
return new HashMap<>(0);
}
File file = FileUtil.file(filePath);
if(Boolean.FALSE.equals(existFile(bucket, key))){
return new HashMap<>(0);
}
Map<String, String> map = FileAttributeUtil.getAttributes(filePath);
if(key.contains(StrPool.SLASH)){
map.put("path", key.substring(0, key.lastIndexOf(StrPool.SLASH)));
} else {
map.put("path", StrPool.SLASH);
}
map.put("key", key);
map.put("name", FileUtil.getName(key));
map.put("Content-Type", null);
map.put("Content-Length", FileUtil.size(file) + "");
map.put("lastModified", String.valueOf(file.lastModified()));
return map;
}
/**
* 获取当前api的文件存储类型
*/
@Override
public StoragePlatformEnum getStoragePlatformEnum() {
return StoragePlatformEnum.LOCAL;
}
}
代码测试
创建配置文件storage.yml
# 配置示例文件
sm:
storage:
client:
######################### minio配置 #########################
- platform: minio
endpoint: http://101.32.27.52:1900
accessKey: AKIAIOSFODNN3EXAMPLE
accessSecret: wJalrXUtnFE1I/K7MDENG/bPxRfiCYEXAMPLEKEY
bucket: test666
enable: false
######################### 阿里云oss配置 #########################
- platform: aliyun-oss
endpoint: http://s3.oss-cn-beijing.aliyuncs.com
accessKey: LTAI5tDTr***tkNrQhHELptbG
accessSecret: k2gK4vZkY***TXURQk3SKR83Fjze5h
bucket: bucketdemo666
enable: true
######################### 腾讯云cos配置 #########################
- platform: tencent-cos
endpoint: http://cos.ap-beijing.myqcloud.com
accessKey: AKID3xeuxlLgnf***f7LK5cu1uupX00nM
accessSecret: OXTP7cv1tXx****t52kFZheR1OXc
bucket: test-1257373000
enable: false
######################### ftp配置 #########################
- platform: ftp
endpoint: http://192.168.18.173:21
accessKey: ftp
accessSecret: ftp
bucket: demo
enable: true
######################### sftp配置 #########################
- platform: sftp
endpoint: http://101.35.26.52:22
accessKey: root
accessSecret: 12313#$%^&234YHsN
basePath: /apps/storage/dev
bucket: demo
enable: true
######################### 本地存储配置 #########################
- platform: local
bucket: demo
basePath: D:/apps/storage/dev
enable: true
创建读取配置文件的工具类
注意修改里面的路径
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.setting.yaml.YamlUtil;
import com.smartadmin.common.properties.StorageProperties;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
public class StoragePropertiesUtil {
public static StorageProperties getStorageProperties() {
return getStorageProperties("配置示例/storage.yml");
}
public static StorageProperties getStorageProperties(String ymlPath) {
Dict dict = YamlUtil.loadByPath(ymlPath);
Map sm = dict.getBean("sm");
Map storage = Optional.ofNullable(sm).map(map -> (Map) map.get("storage")).orElse(new LinkedHashMap<>());
StorageProperties storageProperties = BeanUtil.mapToBean(storage, StorageProperties.class, true);
return storageProperties;
}
}
文件API测试
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.smartadmin.common.enums.StoragePlatformEnum;
import com.smartadmin.common.model.FileInfo;
import com.smartadmin.common.model.ListFileInfo;
import com.smartadmin.common.model.StorageConfig;
import com.smartadmin.common.properties.StorageProperties;
import com.smartadmin.common.utils.StoragePropertiesUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件操作API工厂
*
* @Author: hanYong
* @CreateTime: 2021-07-18
*/
public class OperatorApiFactory {
/**
* 根据存储平台获取文件操作API
*
* @param storagePlatformEnum 存储平台
* @param storageProperties 存储配置
* @return 文件操作API
*/
public static FileOperatorApi getFileOperatorApi(StoragePlatformEnum storagePlatformEnum,
StorageProperties storageProperties) {
if(ObjectUtil.isNotEmpty(storagePlatformEnum) && ObjectUtil.isNotEmpty(storageProperties)){
StorageConfig storageConfig = storageProperties.getStorageConfig(storagePlatformEnum.getValue());
if(ObjectUtil.isNotEmpty(storageConfig) && Boolean.TRUE.equals(storageConfig.getEnable())){
return ReflectUtil.newInstance(storagePlatformEnum.getAClass(), storageConfig);
}
}
return null;
}
public static void main(String[] args) {
FileOperatorApi fileOperatorApi = getFileOperatorApi(
StoragePlatformEnum.ALIYUN_OSS,
StoragePropertiesUtil.getStorageProperties()
);
// fileOperatorApi.createBucket();
ListFileInfo listFileInfo = new ListFileInfo();
// listFileInfo.setKeys(List.of("/d.txt", "/fff", "/fff/ttt/新建文本文档.txt"));
//listFileInfo.setKeys(List.of("demo/新建文件夹/", "demo/fff/新建文本文档.txt"));
//listFileInfo.setName("新建");
// listFileInfo.setPath("demo");
listFileInfo.setTypes(ListUtil.of("txt"));
List<FileInfo> fileInfos = fileOperatorApi.listFile(listFileInfo);
System.out.println(JSONUtil.toJsonPrettyStr(fileInfos));
//System.out.println("判断存储空间是否存在:" + fileOperatorApi.existBucket("demo"));
//System.out.println("创建存储空间:" + fileOperatorApi.createBucket("demo"));
//System.out.println("判断存储空间是否存在:" + fileOperatorApi.existBucket("demo"));
//System.out.println("删除存储空间:" + fileOperatorApi.deleteBucket("demo"));
//System.out.println("判断存储空间是否存在:" + fileOperatorApi.existBucket());
//System.out.println("-----------------------------------------------");
//System.out.println("判断文件是否存在:" + fileOperatorApi.existFile("a.txt"));
//System.out.println("创建文件:" + fileOperatorApi.uploadFile("a.txt", StrUtil.bytes("123")));
//System.out.println("判断文件是否存在:" + fileOperatorApi.existFile("a.txt"));
//System.out.println("复制文件:" + fileOperatorApi.copyFile("a.txt", "b.txt"));
//System.out.println("复制文件:" + fileOperatorApi.copyFile("a.txt", "fff/ttt/c.txt"));
//System.out.println("复制文件:" + fileOperatorApi.copyFile("a.txt", "d.txt"));
//System.out.println("删除文件:" + fileOperatorApi.deleteFile("a.txt"));
//System.out.println("批量删除文件:" + fileOperatorApi.deleteFiles(ListUtil.of("a.txt", "b.txt")));
//System.out.println("获取文件元数据:" + fileOperatorApi.getObjectMetadata("d.txt"));
//System.out.println("获取文件数据:" + StrUtil.str(fileOperatorApi.downloadFile("d.txt"), CharsetUtil.CHARSET_UTF_8));
//System.out.println("获取文件信息:" + fileOperatorApi.getFileInfo("fff/ttt/c.txt"));
//System.out.println("获取文件链接:" + fileOperatorApi.getFileLink("fff/ttt/c.txt", 7L));
//System.out.println("-----------------------------------------------");
}
}