From 8527b0233d8ef824c51b306e12df932ce0a14945 Mon Sep 17 00:00:00 2001 From: duandazhi Date: Thu, 29 Jul 2021 15:20:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=85=BE=E8=AE=AF=E4=BA=91?= =?UTF-8?q?=20tencent=20cos=20=E5=AF=B9=E8=B1=A1=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=EF=BC=8C=E5=B7=B2=E7=BB=8F=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-modules/ruoyi-file/pom.xml | 8 + .../ruoyi/file/config/TencentCosConfig.java | 161 ++++++++++++++ .../file/service/FastDfsServiceImpl.java | 2 +- .../file/service/TencentCosServiceImpl.java | 198 ++++++++++++++++++ .../src/main/resources/bootstrap.yml | 9 + 5 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/TencentCosConfig.java create mode 100644 ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/TencentCosServiceImpl.java diff --git a/ruoyi-modules/ruoyi-file/pom.xml b/ruoyi-modules/ruoyi-file/pom.xml index 3cd5dbc6..40f8062f 100644 --- a/ruoyi-modules/ruoyi-file/pom.xml +++ b/ruoyi-modules/ruoyi-file/pom.xml @@ -139,6 +139,14 @@ [7.7.0, 7.7.99] + + + + com.qcloud + cos_api + 5.6.45 + + diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/TencentCosConfig.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/TencentCosConfig.java new file mode 100644 index 00000000..0fe996fb --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/TencentCosConfig.java @@ -0,0 +1,161 @@ +package com.ruoyi.file.config; + +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.region.Region; +import io.minio.MinioClient; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 腾讯云文件存储 cos 配置 + * 对象存储文档api地址:https://cloud.tencent.com/document/product/436/6474 + * 控制台地址:https://console.cloud.tencent.com/cos5 + * API密钥获取地址:https://console.cloud.tencent.com/cam/capi + * Region | endpoint, bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + * 历史版本地域列表 https://cloud.tencent.com/document/product/436/7777 + * + * @author yabo + */ +@RefreshScope +@Configuration +@ConfigurationProperties(prefix = TencentCosConfig.PREFIX) +public class TencentCosConfig { + public static final String PREFIX = "tencent-cos"; + + @Bean + public COSClient getCosClient() { + //初始化用户身份信息(secretId, secretKey)。 + // SECRETID和SECRETKEY请登录访问管理控制台进行查看和管理 + String secretId = this.getAccessKey(); + String secretKey = this.getSecretKey(); + String endpoint = this.getEndpoint(); + COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); + // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。 + Region region = new Region(endpoint); + ClientConfig clientConfig = new ClientConfig(region); + // 这里建议设置使用 https 协议 + clientConfig.setHttpProtocol(HttpProtocol.https); + // 设置最大重试次数为 1 次 + clientConfig.setMaxErrorRetry(1); + // 3 生成 cos 客户端。 + return new COSClient(cred, clientConfig); + } + + /** + * ak,同其他 平台 + * + * @see AliyunOssConfig#getAccessKey() + * @see QiniuKodoConfig#getAccessKey() + * @see MinioConfig#getAccessKey() + * eg: AKIDX9hNAzpdUI0XyRpASj098xa7uYzOekmh + * 别名:SecretId + */ + private String accessKey = ""; + /** + * sk, 同其他 平台 + * + * @see AliyunOssConfig#getSecretKey() + * @see QiniuKodoConfig#getSecretKey() + * @see MinioConfig#getSecretKey() + * eg: sW5VgkdHlDYqy01xiGbkjV5TghUEvYEw + * 别名:SecretKey + */ + private String secretKey = ""; + /** + * bucket,同其他平台 桶概念 + * + * @see AliyunOssConfig#getBucketName() + * @see QiniuKodoConfig#getBucketName() + * @see MinioConfig#getBucketName() + */ + private String bucketName = ""; + /** + * endpoint,同其他平台 概念; + * 别名:Region + * + * @see AliyunOssConfig#getEndpoint() + * @see QiniuKodoConfig 有,Region 目前使用的是自动获取存储区域;暂不启用 + * @see MinioConfig 目前私有部署,不存在endpoint + * eg: ap-chengdu 地域和访问域名 https://cloud.tencent.com/document/product/436/6224 + */ + private String endpoint = ""; + /** + *

+ * 上传地址和访问地址经常是不一样的 + * 1、上传地址是内网;访问地址是外网; + * 2、上传地址是ip; 访问地址域名; + * 3、上传地址是http; 访问地址是https + * 4: eg: https://image.jl-media.cn + */ + private String domain = null; + /** + * 过期时间,单位秒; + * 如:1小时就写:3600L + * 如:9小时就写:32400L + * 如:12小时就写:43200L, 【不支持】,最大是 32400(9小时), 最小 1(1秒钟) + * 如:-1: 就永不过期,原样返回url + * 签名URL的默认过期时间为3600秒,最大值为32400秒 + * 文档:对象存储 授权访问 https://cloud.tencent.com/document/product/436/10199 + *

+ * 注意!!:tencent cos 设置Bucket ACL + * 1、私有 【必须要加签之后才能访问】 + * 2、公共读 + * 3、公共读写 + */ + private Long expiryDuration = 32400L; + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public Long getExpiryDuration() { + return expiryDuration; + } + + public void setExpiryDuration(Long expiryDuration) { + this.expiryDuration = expiryDuration; + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsServiceImpl.java index 23c00f54..cd093e7f 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsServiceImpl.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/FastDfsServiceImpl.java @@ -21,7 +21,7 @@ import com.github.tobato.fastdfs.service.FastFileStorageClient; * @author ruoyi * @see FastDfsConfig */ -@Primary +//@Primary @Service() public class FastDfsServiceImpl implements IDfsService { diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/TencentCosServiceImpl.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/TencentCosServiceImpl.java new file mode 100644 index 00000000..3c761166 --- /dev/null +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/service/TencentCosServiceImpl.java @@ -0,0 +1,198 @@ +package com.ruoyi.file.service; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.qcloud.cos.COSClient; +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.http.HttpMethodName; +import com.qcloud.cos.model.*; +import com.ruoyi.common.core.exception.CustomException; +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.file.config.TencentCosConfig; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.net.URL; +import java.util.Date; +import java.util.List; + +/** + * 腾讯云cos 文件服务器实现 + * + * @author yabo + * @see TencentCosConfig + */ +@Primary +@Service() +public class TencentCosServiceImpl implements IDfsService { + private final COSClient cosClient; + private final TencentCosConfig config; + + public TencentCosServiceImpl(COSClient cosClient, TencentCosConfig config) { + this.cosClient = cosClient; + this.config = config; + } + + @Override + public String uploadFile(MultipartFile file) throws Exception { + return this.uploadFile(file, null); + } + + /** + * 腾讯云文档中心==》对象存储==》JAVA SDK===> 上传对象 + * https://cloud.tencent.com/document/product/436/10199#.E4.B8.8A.E4.BC.A0.E5.AF.B9.E8.B1.A1 + * 上传成功示例:https://cos.ityun.ltd/dev/upload/default/20210729-2be9b217-22ef-45a2-bfda-e793ffe33f55.jpeg + */ + @Override + public String uploadFile(MultipartFile file, String modules) throws Exception { + //key: 这里不能以/开头 + validateModule(file, null); + String newName = extractFileNameSimple(file); + //key: 这里不能以/开头 + String requestKey = "upload/" + StringUtils.defaultString(modules, "default") + "/" + newName; + //这里增加一个前缀区分一下是测试环境还是正式环境 + boolean isProd = "prod".equalsIgnoreCase(SpringUtil.getActiveProfile()); + if (!isProd) { + requestKey = SpringUtil.getActiveProfile() + "/" + requestKey; + } + + if (StringUtils.isBlank(config.getBucketName())) { + throw new CustomException("tencent cos bucket name 不能为空,请检查!"); + } + if (StringUtils.isBlank(config.getEndpoint())) { + throw new CustomException("tencent cos endpoint(Region) 不能为空,请检查!"); + } + ObjectMetadata objectMetadata = new ObjectMetadata(); + + // 指定文件将要存放的存储桶 + String bucketName = config.getBucketName(); + // 指定文件上传到 COS 上的路径,即对象键。例如对象键为folder/picture.jpg,则表示将文件 picture.jpg 上传到 folder 路径下 + String key = requestKey; + PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, file.getInputStream(), objectMetadata); + try { + PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); + return (config.getDomain() + "/" + key); + } catch (CosClientException e) { + e.printStackTrace(); + throw new CustomException("tencent cos upload 失败;错误代码:" + e.getErrorCode() + ";错误原因:" + e.getMessage()); + } + } + + /** + * 腾讯云文档中心==》对象存储==》JAVA SDK===> 删除对象 + * https://cloud.tencent.com/document/product/436/10199#.E5.88.A0.E9.99.A4.E5.AF.B9.E8.B1.A1 + */ + @Override + public boolean deleteFile(String fileUrl) { + if (StringUtils.isBlank(config.getBucketName())) { + throw new CustomException("tencent cos bucket name 不能为空,请检查!"); + } + + // 指定被删除的文件在 COS 上的路径,即对象键。例如对象键为folder/picture.jpg,则表示删除位于 folder 路径下的文件 picture.jpg + String key = this.getStorePath(fileUrl); + try { + cosClient.deleteObject(config.getBucketName(), key); + } catch (CosClientException e) { + e.printStackTrace(); + throw new CustomException("tencent cos 删除重新异常,请排查..."); + } + return true; + } + + /** + * 腾讯云文档中心==》对象存储==》JAVA SDK===> 查询对象列表 + * 无法直接获取所有文件大小 + * https://cloud.tencent.com/document/product/436/10199#.E6.9F.A5.E8.AF.A2.E5.AF.B9.E8.B1.A1.E5.88.97.E8.A1.A8 + */ + @Override + public String objectsCapacityStr() { + if (StringUtils.isBlank(config.getBucketName())) { + throw new CustomException("tencent cos bucket name 不能为空,请检查!"); + } + + // Bucket的命名格式为 BucketName-APPID ,此处填写的存储桶名称必须为此格式 + String bucketName = config.getBucketName(); + ListObjectsRequest listObjectsRequest = new ListObjectsRequest(); + // 设置bucket名称 + listObjectsRequest.setBucketName(bucketName); + /// prefix表示列出的object的key以prefix开始 + ///listObjectsRequest.setPrefix("images/"); + /// deliter表示分隔符, 设置为/表示列出当前目录下的object, 设置为空表示列出所有的object + ///listObjectsRequest.setDelimiter("/"); + // 设置最大遍历出多少个对象, 一次listobject最大支持1000 + listObjectsRequest.setMaxKeys(1000); + ObjectListing objectListing = null; + long size = 0; + do + { + try { + objectListing = cosClient.listObjects(listObjectsRequest); + } catch (CosClientException e) { + e.printStackTrace(); + return ""; + } + // common prefix表示表示被delimiter截断的路径, 如delimter设置为/, common prefix则表示所有子目录的路径 + List commonPrefixs = objectListing.getCommonPrefixes(); + // object summary表示所有列出的object列表 + List cosObjectSummaries = objectListing.getObjectSummaries(); + for (COSObjectSummary cosObjectSummary : cosObjectSummaries) { + // 文件的路径key + String key = cosObjectSummary.getKey(); + // 文件的etag + String etag = cosObjectSummary.getETag(); + // 文件的长度 + long fileSize = cosObjectSummary.getSize(); + // 文件的存储类型 + String storageClasses = cosObjectSummary.getStorageClass(); + size += fileSize; + } + String nextMarker = objectListing.getNextMarker(); + listObjectsRequest.setMarker(nextMarker); + } while (objectListing.isTruncated()); + return FileUtil.readableFileSize(size); + } + + /** + * 获取请求预签名 URL + * 腾讯云文档中心==》对象存储==》JAVA SDK===> 预签名URL ===> 获取请求预签名 URL + * https://cloud.tencent.com/document/product/436/35217#.E8.8E.B7.E5.8F.96.E8.AF.B7.E6.B1.82.E9.A2.84.E7.AD.BE.E5.90.8D-url + * @return 返回示例:https://cos.ityun.ltd/dev/upload/default/20210729-2be9b217-22ef-45a2-bfda-e793ffe33f55.jpeg?4304-bdfc-e2964b8721bb.jpeg?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDX9hNAzpdUI0XyRpASj098xa7uYzOekmh%26q-sign-time%3D1627542548%3B1627574935%26q-key-time%3D1627542548%3B1627574935%26q-header-list%3Dhost%26q-url-param-list%3D%26q-signature%3D872e77dbca69d6b67d7a0027ab41c374703994f8 + * https://tencent-cloud-cos-dazer-1253883700.cos.ap-chengdu.myqcloud.com/dev/upload/default/20210729-2be9b217-22ef-45a2-bfda-e793ffe33f55.jpeg?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDX9hNAzpdUI0XyRpASj098xa7uYzOekmh%26q-sign-time%3D1627542872%3B1627575269%26q-key-time%3D1627542872%3B1627575269%26q-header-list%3Dhost%26q-url-param-list%3D%26q-signature%3D21508af6284564532deb935e03c2226d93bc965a + */ + @Override + public String presignedUrl(String fileUrl) { + // 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式 + String bucketName = config.getBucketName(); + if (StringUtils.isBlank(config.getBucketName())) { + throw new CustomException("tencent cos bucket name 不能为空,请检查!"); + } + + // 此处的key为对象键,对象键是对象在存储桶内的唯一标识 + String key = this.getStorePath(fileUrl); + GeneratePresignedUrlRequest req = + new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET); + // 设置签名过期时间(可选), 若未进行设置, 则默认使用 ClientConfig 中的签名过期时间(1小时) + // 这里设置签名在半个小时后过期 + Date expirationDate = new Date(System.currentTimeMillis() + (config.getExpiryDuration() * 1000)); + req.setExpiration(expirationDate); + URL url = cosClient.generatePresignedUrl(req); + return url.toString(); + } + + /** + * 转换url,为原始的key + * + * @param filePath http://guangdong-oss.ityun.ltd/upload/default/header.jpg + * @return upload/default/header.jpg + *

+ * http://qwc2geifw.hn-bkt.clouddn.com/upload/default/header.jpg + * ==> upload/default/header.jpg + */ + private String getStorePath(String filePath) { + String domain = config.getDomain(); + String publicPath3 = domain + "/"; + filePath = filePath.replace(publicPath3, ""); + return filePath; + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/resources/bootstrap.yml b/ruoyi-modules/ruoyi-file/src/main/resources/bootstrap.yml index 967fa852..4820f729 100644 --- a/ruoyi-modules/ruoyi-file/src/main/resources/bootstrap.yml +++ b/ruoyi-modules/ruoyi-file/src/main/resources/bootstrap.yml @@ -76,3 +76,12 @@ qiniu: bucket-name: guangdong-oss domain: http://guangdong-oss.ityun.ltd expiry-duration: 32400 + +# 文件服务器之7 tencent cos 文件存储 +tencent-cos: + access-key: AKIDX9hNAzpdUI0XyRpASj098xa7uYzOekmh + secret-key: sW5VgkdHlDYqy01xiGbkjV5TghUEvYEw + endpoint: ap-chengdu + bucket-name: tencent-cloud-cos-dazer-1253883700 + domain: https://cos.ityun.ltd + expiry-duration: 32400