feat:qCloud COS support

This commit is contained in:
besscroft
2023-08-13 21:22:30 +08:00
parent fbdb82636f
commit c381a73e5e
11 changed files with 299 additions and 9 deletions

View File

@@ -12,7 +12,8 @@ public enum StorageTypeEnum implements BasicEnum<Integer> {
LOCAL(0, "本地"),
ONE_DRIVE(1, "OneDrive"),
ALIYUN_OSS(2, "阿里云OSS"),
AMAZON_S3(3, "Amazon S3");
AMAZON_S3(3, "Amazon S3"),
QCLOUD_COS(4, "腾讯云COS");
private final int value;
private final String description;

View File

@@ -37,6 +37,12 @@ public class DiyFileException extends RuntimeException {
this.message = message;
}
public DiyFileException(String message, Throwable cause) {
super(message, cause);
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.message = message;
}
public DiyFileException(Integer code, String message) {
this.code = code;
this.message = message;

View File

@@ -0,0 +1,29 @@
package com.besscroft.diyfile.common.param.storage.init;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Description 腾讯云 COS 初始化参数
* @Author Bess Croft
* @Date 2023/8/13 17:11
*/
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class QCloudCosParam extends OssParam {
/** secretId */
private String secretId;
/** secretKey */
private String secretKey;
/** 地域 */
private String region;
/** endpoint 端点 */
private String endpoint;
}

View File

@@ -5,10 +5,7 @@ import com.besscroft.diyfile.common.entity.Storage;
import com.besscroft.diyfile.common.entity.StorageConfig;
import com.besscroft.diyfile.common.enums.StorageTypeEnum;
import com.besscroft.diyfile.common.param.FileInitParam;
import com.besscroft.diyfile.common.param.storage.init.AliYunOssParam;
import com.besscroft.diyfile.common.param.storage.init.AmazonS3Param;
import com.besscroft.diyfile.common.param.storage.init.LocalParam;
import com.besscroft.diyfile.common.param.storage.init.OneDriveParam;
import com.besscroft.diyfile.common.param.storage.init.*;
import java.util.List;
import java.util.Map;
@@ -70,6 +67,16 @@ public class ParamUtils {
param.setMountPath(configMap.get("mount_path"));
param.setBucketName(configMap.get("bucketName"));
return param;
} else if (Objects.equals(storage.getType(), StorageTypeEnum.QCLOUD_COS.getValue())) {
// 腾讯云COS初始化参数
QCloudCosParam param = QCloudCosParam.builder().build();
param.setSecretId(configMap.get("secretId"));
param.setSecretKey(configMap.get("secretKey"));
param.setRegion(configMap.get("region"));
param.setEndpoint(configMap.get("endpoint"));
param.setMountPath(configMap.get("mount_path"));
param.setBucketName(configMap.get("bucketName"));
return param;
}
return null;
}

View File

@@ -76,6 +76,11 @@
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<!-- 腾讯云 COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -162,12 +162,14 @@ public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> impl
try {
storageApplicationContext.init(storage);
} catch (Exception e) {
log.error("启用存储失败:{}", e.getMessage());
throw new DiyFileException("启用存储失败!");
}
} else {
try {
storageApplicationContext.destroy(storage.getId());
} catch (Exception e) {
log.error("停用存储失败:{}", e.getMessage());
throw new DiyFileException("停用存储失败!");
}
}

View File

@@ -143,6 +143,7 @@ public abstract class AbstractS3BaseService<T extends S3Param> extends AbstractF
@Override
public void uploadItem(String folderPath, String fileName) {
// TODO 上传文件
throw new DiyFileException("S3 API 暂不支持上传文件");
}
@@ -164,6 +165,7 @@ public abstract class AbstractS3BaseService<T extends S3Param> extends AbstractF
@Override
public void moveItem(String startPath, String endPath) {
// TODO 移动文件
throw new DiyFileException("S3 API 暂不支持移动文件");
}
/**

View File

@@ -44,7 +44,7 @@ public class AliYunOssServiceImpl extends AbstractOSSBaseService<AliYunOssParam>
*/
@Override
public void init() {
// TODO OSSClient 初始化
// OSSClient 初始化
this.ossClient = new OSSClientBuilder()
.build(initParam.getEndpoint(),
initParam.getAccessKeyId(),
@@ -103,12 +103,12 @@ public class AliYunOssServiceImpl extends AbstractOSSBaseService<AliYunOssParam>
@Override
public void createItem(String folderPath, String fileName) {
throw new DiyFileException("阿里云 OSS 服务暂不支持创建文件夹");
throw new DiyFileException("阿里云 OSS 服务暂不支持创建对象!");
}
@Override
public void updateItem(String filePath, String fileName) {
throw new DiyFileException("阿里云 OSS 服务暂不支持更新文件");
throw new DiyFileException("阿里云 OSS 服务暂不支持更新对象!");
}
@Override
@@ -123,6 +123,7 @@ public class AliYunOssServiceImpl extends AbstractOSSBaseService<AliYunOssParam>
@Override
public void uploadItem(String folderPath, String fileName) {
// TODO 上传文件
throw new DiyFileException("阿里云 OSS 服务暂不支持上传文件");
}

View File

@@ -3,7 +3,6 @@ package com.besscroft.diyfile.storage.service.impl;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.besscroft.diyfile.common.constant.FileConstants;
@@ -117,6 +116,7 @@ public class LocalServiceImpl extends AbstractFileBaseService<LocalParam> {
@Override
public void uploadItem(String folderPath, String fileName) {
// TODO 本地文件上传
throw new DiyFileException("本地文件存储不支持上传文件!");
}

View File

@@ -0,0 +1,230 @@
package com.besscroft.diyfile.storage.service.impl;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.besscroft.diyfile.common.constant.FileConstants;
import com.besscroft.diyfile.common.enums.StorageTypeEnum;
import com.besscroft.diyfile.common.exception.DiyFileException;
import com.besscroft.diyfile.common.param.storage.init.QCloudCosParam;
import com.besscroft.diyfile.common.vo.FileInfoVo;
import com.besscroft.diyfile.storage.service.base.AbstractOSSBaseService;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.COSObjectSummary;
import com.qcloud.cos.model.ListObjectsRequest;
import com.qcloud.cos.model.ObjectListing;
import com.qcloud.cos.region.Region;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @Description 腾讯云 COS 服务实现类
* @Author Bess Croft
* @Date 2023/8/13 17:13
*/
@Slf4j
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class QCloudCosServiceImpl extends AbstractOSSBaseService<QCloudCosParam> {
private COSClient cosClient;
/**
* Q你好麻烦请问下腾讯云 COS 是否支持 AWS Java SDK 2.x 版本?查阅文档只看到 1.x 示例https://cloud.tencent.com/document/product/436/37421#java想确认下是否有支持 2.x
* A您好非常抱歉辛苦您久等了关于您这边反馈的问题这边为您核实了一下呢目前COS仅支持AWS Java SDK 1.x版本暂不支持2.x版本的呢
*/
@Override
public void init() {
COSCredentials cred = new BasicCOSCredentials(initParam.getSecretId(), initParam.getSecretKey());
// 设置 bucket 的地域, COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224
// clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
Region region = new Region(initParam.getRegion());
ClientConfig clientConfig = new ClientConfig(region);
// 这里建议设置使用 https 协议
// 从 5.6.54 版本开始,默认使用了 https
clientConfig.setHttpProtocol(HttpProtocol.https);
// 生成 cos 客户端。
this.cosClient = new COSClient(cred, clientConfig);
initialized = true;
}
@Override
public String getFileDownloadUrl(String fileName, String filePath, String fullPath) {
return null;
}
@Override
public ResponseEntity<Resource> getFileResource(String fileName, String filePath) {
return null;
}
@Override
public List<FileInfoVo> getFileList(String folderPath) {
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
// 设置 bucket 名称
listObjectsRequest.setBucketName(initParam.getBucketName());
// prefix 表示列出的 object 的 key 以 prefix 开始
listObjectsRequest.setPrefix(initParam.getMountPath());
// deliter 表示分隔符, 设置为/表示列出当前目录下的 object, 设置为空表示列出所有的 object
listObjectsRequest.setDelimiter(folderPath);
// 设置最大遍历出多少个对象, 一次 listobject 最大支持1000
listObjectsRequest.setMaxKeys(1000);
ObjectListing objectListing = null;
List<COSObjectSummary> summaryList = new ArrayList<>();
List<String> commonPrefixes = new ArrayList<>();
do {
try {
objectListing = cosClient.listObjects(listObjectsRequest);
} catch (Exception e) {
log.error("获取文件列表失败:{}", e.getMessage());
throw new DiyFileException("获取文件列表失败!");
}
// common prefix 表示被 delimiter 截断的路径, 如 delimter 设置为/, common prefix 则表示所有子目录的路径
List<String> commonPrefixs = objectListing.getCommonPrefixes();
commonPrefixes.addAll(commonPrefixs);
// object summary 表示所有列出的 object 列表
List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
summaryList.addAll(cosObjectSummaries);
String nextMarker = objectListing.getNextMarker();
listObjectsRequest.setMarker(nextMarker);
} while (objectListing.isTruncated());
return handleFileList(summaryList, commonPrefixes, folderPath);
}
@Override
public FileInfoVo getFileInfo(String filePath, String fileName) {
FileInfoVo fileInfoVo = new FileInfoVo();
fileInfoVo.setName(fileName);
fileInfoVo.setSize(0L);
fileInfoVo.setLastModifiedDateTime(null);
fileInfoVo.setType(FileConstants.FILE);
fileInfoVo.setPath(filePath);
fileInfoVo.setFile(null);
// 设置文件下载地址
fileInfoVo.setUrl(getObjectUrl(initParam.getBucketName(), filePath));
return fileInfoVo;
}
@Override
public void createItem(String folderPath, String fileName) {
throw new DiyFileException("腾讯云 COS 不支持创建对象!");
}
@Override
public void updateItem(String filePath, String fileName) {
throw new DiyFileException("腾讯云 COS 不支持修改对象!");
}
@Override
public void deleteItem(String filePath) {
cosClient.deleteObject(initParam.getBucketName(), filePath);
}
@Override
public void uploadItem(String folderPath, String fileName) {
// TODO 上传文件
throw new DiyFileException("腾讯云 COS 不支持上传对象!");
}
@Override
public String getUploadSession(String folderPath) {
return null;
}
@Override
public Integer getStorageType() {
return StorageTypeEnum.QCLOUD_COS.getValue();
}
@Override
public String getObjectUrl(String bucketName, String objectName) {
return cosClient.getObjectUrl(bucketName, objectName).toString();
}
@Override
public void moveItem(String startPath, String endPath) {
}
/**
* 获取文件列表
* @param summaryList 文件列表
* @param commonPrefixes 文件夹列表
* @param folderPath 文件夹路径
* @return 文件列表
*/
private List<FileInfoVo> handleFileList(@NonNull List<COSObjectSummary> summaryList, @NonNull List<String> commonPrefixes, @NonNull String folderPath) {
List<FileInfoVo> infoVoList = new ArrayList<>();
for(COSObjectSummary summary : summaryList) {
FileInfoVo fileInfoVo = new FileInfoVo();
int lastSlashIndex = summary.getKey().lastIndexOf('/');
if (Objects.equals("", summary.getKey().substring(lastSlashIndex + 1))) {
continue;
}
if (summary.getKey().contains("/")) {
fileInfoVo.setName(summary.getKey().substring(lastSlashIndex + 1));
} else {
fileInfoVo.setName(summary.getKey());
}
fileInfoVo.setFile(summary.getETag());
fileInfoVo.setLastModifiedDateTime(LocalDateTimeUtil.of(summary.getLastModified()));
fileInfoVo.setSize(summary.getSize());
fileInfoVo.setPath(folderPath);
fileInfoVo.setType(FileConstants.FILE);
fileInfoVo.setUrl(getObjectUrl(initParam.getBucketName(), summary.getKey()));
infoVoList.add(fileInfoVo);
}
for(String prefix : commonPrefixes) {
FileInfoVo fileInfoVo = new FileInfoVo();
int index = prefix.lastIndexOf("/");
fileInfoVo.setName(prefix.substring(0, index));
fileInfoVo.setFile(null);
fileInfoVo.setLastModifiedDateTime(null);
fileInfoVo.setSize(calculateFolderLength(cosClient, initParam.getBucketName(), prefix));
fileInfoVo.setPath(folderPath);
fileInfoVo.setType(FileConstants.FOLDER);
infoVoList.add(fileInfoVo);
}
return infoVoList;
}
/**
* 获取某个存储空间下指定目录(文件夹)下的文件大小。
* @param ossClient OSSClient实例
* @param bucketName 存储空间名称
* @param folder 指定目录(文件夹)
* @return 文件大小
*/
private static long calculateFolderLength(COSClient ossClient, String bucketName, String folder) {
long size = 0L;
ObjectListing objectListing = null;
do {
// MaxKey 默认值为 100最大值为 1000。
ListObjectsRequest request = new ListObjectsRequest()
.withBucketName(bucketName)
.withPrefix(folder)
.withMaxKeys(1000);
if (objectListing != null) {
request.setMarker(objectListing.getNextMarker());
}
objectListing = ossClient.listObjects(request);
List<COSObjectSummary> sums = objectListing.getObjectSummaries();
for (COSObjectSummary s : sums) {
size += s.getSize();
}
} while (objectListing.isTruncated());
return size;
}
}

View File

@@ -40,6 +40,7 @@
<activation.version>1.1.1</activation.version>
<jaxb-runtime.version>2.3.3</jaxb-runtime.version>
<software.amazon.awssdk.version>2.20.20</software.amazon.awssdk.version>
<cos_api.version>5.6.163</cos_api.version>
</properties>
<modules>
@@ -172,6 +173,12 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 腾讯云 COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${cos_api.version}</version>
</dependency>
</dependencies>
</dependencyManagement>