diff --git a/blade-starter-oss/pom.xml b/blade-starter-oss/pom.xml index a0266ed..130d19c 100644 --- a/blade-starter-oss/pom.xml +++ b/blade-starter-oss/pom.xml @@ -41,6 +41,12 @@ qiniu-java-sdk 7.9.4 + + + com.qcloud + cos_api + 5.6.147 + diff --git a/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java b/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java new file mode 100644 index 0000000..5d45b55 --- /dev/null +++ b/blade-starter-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2018-2099, yangkai.shen. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.oss; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.CannedAccessControlList; +import com.qcloud.cos.model.ObjectMetadata; +import com.qcloud.cos.model.PutObjectResult; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springblade.core.oss.model.BladeFile; +import org.springblade.core.oss.model.OssFile; +import org.springblade.core.oss.props.OssProperties; +import org.springblade.core.oss.rule.OssRule; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.core.tool.utils.StringUtil; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.util.List; + +/** + *

+ * 腾讯云 COS 操作 + *

+ * + * @author yangkai.shen + * @date Created in 2020/1/7 17:24 + */ +@AllArgsConstructor +public class TencentCosTemplate implements OssTemplate { + private final COSClient cosClient; + private final OssProperties ossProperties; + private final OssRule ossRule; + + @Override + @SneakyThrows + public void makeBucket(String bucketName) { + if (!bucketExists(bucketName)) { + cosClient.createBucket(getBucketName(bucketName)); + // TODO: 权限是否需要修改为私有,当前为 公有读、私有写 + cosClient.setBucketAcl(getBucketName(bucketName), CannedAccessControlList.PublicRead); + } + } + + @Override + @SneakyThrows + public void removeBucket(String bucketName) { + cosClient.deleteBucket(getBucketName(bucketName)); + } + + @Override + @SneakyThrows + public boolean bucketExists(String bucketName) { + return cosClient.doesBucketExist(getBucketName(bucketName)); + } + + @Override + @SneakyThrows + public void copyFile(String bucketName, String fileName, String destBucketName) { + cosClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName); + } + + @Override + @SneakyThrows + public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) { + cosClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName); + } + + @Override + @SneakyThrows + public OssFile statFile(String fileName) { + return statFile(ossProperties.getBucketName(), fileName); + } + + @Override + @SneakyThrows + public OssFile statFile(String bucketName, String fileName) { + ObjectMetadata stat = cosClient.getObjectMetadata(getBucketName(bucketName), fileName); + OssFile ossFile = new OssFile(); + ossFile.setName(fileName); + ossFile.setLink(fileLink(ossFile.getName())); + ossFile.setHash(stat.getContentMD5()); + ossFile.setLength(stat.getContentLength()); + ossFile.setPutTime(stat.getLastModified()); + ossFile.setContentType(stat.getContentType()); + return ossFile; + } + + @Override + @SneakyThrows + public String filePath(String fileName) { + return getOssHost().concat(StringPool.SLASH).concat(fileName); + } + + @Override + @SneakyThrows + public String filePath(String bucketName, String fileName) { + return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName); + } + + @Override + @SneakyThrows + public String fileLink(String fileName) { + return getOssHost().concat(StringPool.SLASH).concat(fileName); + } + + @Override + @SneakyThrows + public String fileLink(String bucketName, String fileName) { + return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName); + } + + /** + * 文件对象 + * + * @param file 上传文件类 + * @return + */ + @Override + @SneakyThrows + public BladeFile putFile(MultipartFile file) { + return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file); + } + + /** + * @param fileName 上传文件名 + * @param file 上传文件类 + * @return + */ + @Override + @SneakyThrows + public BladeFile putFile(String fileName, MultipartFile file) { + return putFile(ossProperties.getBucketName(), fileName, file); + } + + @Override + @SneakyThrows + public BladeFile putFile(String bucketName, String fileName, MultipartFile file) { + return putFile(bucketName, fileName, file.getInputStream()); + } + + @Override + @SneakyThrows + public BladeFile putFile(String fileName, InputStream stream) { + return putFile(ossProperties.getBucketName(), fileName, stream); + } + + @Override + @SneakyThrows + public BladeFile putFile(String bucketName, String fileName, InputStream stream) { + return put(bucketName, stream, fileName, false); + } + + @SneakyThrows + public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) { + makeBucket(bucketName); + String originalName = key; + key = getFileName(key); + ObjectMetadata objectMetadata = new ObjectMetadata(); + // 覆盖上传 + if (cover) { + cosClient.putObject(getBucketName(bucketName), key, stream, objectMetadata); + } else { + PutObjectResult response = cosClient.putObject(getBucketName(bucketName), key, stream, objectMetadata); + int retry = 0; + int retryCount = 5; + while (!StringUtils.hasText(response.getETag()) && retry < retryCount) { + response = cosClient.putObject(getBucketName(bucketName), key, stream, objectMetadata); + retry++; + } + } + BladeFile file = new BladeFile(); + file.setOriginalName(originalName); + file.setName(key); + file.setDomain(getOssHost(bucketName)); + file.setLink(fileLink(bucketName, key)); + return file; + } + + @Override + @SneakyThrows + public void removeFile(String fileName) { + cosClient.deleteObject(getBucketName(), fileName); + } + + @Override + @SneakyThrows + public void removeFile(String bucketName, String fileName) { + cosClient.deleteObject(getBucketName(bucketName), fileName); + } + + @Override + @SneakyThrows + public void removeFiles(List fileNames) { + fileNames.forEach(this::removeFile); + } + + @Override + @SneakyThrows + public void removeFiles(String bucketName, List fileNames) { + fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName)); + } + + /** + * 根据规则生成存储桶名称规则 + * + * @return String + */ + private String getBucketName() { + return getBucketName(ossProperties.getBucketName()); + } + + /** + * 根据规则生成存储桶名称规则 + * + * @param bucketName 存储桶名称 + * @return String + */ + private String getBucketName(String bucketName) { + return ossRule.bucketName(bucketName).concat(StringPool.DASH).concat(ossProperties.getAppId()); + } + + /** + * 根据规则生成文件名称规则 + * + * @param originalFilename 原始文件名 + * @return string + */ + private String getFileName(String originalFilename) { + return ossRule.fileName(originalFilename); + } + + /** + * 获取域名 + * + * @param bucketName 存储桶名称 + * @return String + */ + public String getOssHost(String bucketName) { + String prefix = getEndpoint().contains("https://") ? "https://" : "http://"; + return prefix + cosClient.getClientConfig().getEndpointBuilder().buildGeneralApiEndpoint(getBucketName(bucketName)); + } + + /** + * 获取域名 + * + * @return String + */ + public String getOssHost() { + return getOssHost(ossProperties.getBucketName()); + } + + /** + * 获取服务地址 + * + * @return String + */ + public String getEndpoint() { + if (StringUtil.isBlank(ossProperties.getTransformEndpoint())) { + return ossProperties.getEndpoint(); + } + return StringUtil.removeSuffix(ossProperties.getTransformEndpoint(), StringPool.SLASH); + } + +} diff --git a/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java b/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java new file mode 100644 index 0000000..e47905f --- /dev/null +++ b/blade-starter-oss/src/main/java/org/springblade/core/oss/config/TencentCosConfiguration.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2018-2099, yangkai.shen. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.oss.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.region.Region; +import lombok.AllArgsConstructor; +import org.springblade.core.oss.TencentCosTemplate; +import org.springblade.core.oss.props.OssProperties; +import org.springblade.core.oss.rule.OssRule; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + *

+ * 腾讯云 COS 自动装配 + *

+ * + * @author yangkai.shen + * @date Created in 2020/1/7 17:24 + */ +@AllArgsConstructor +@AutoConfiguration(after = OssConfiguration.class) +@ConditionalOnClass({COSClient.class}) +@EnableConfigurationProperties(OssProperties.class) +@ConditionalOnProperty(value = "oss.name", havingValue = "tencentcos") +public class TencentCosConfiguration { + + private final OssProperties ossProperties; + private final OssRule ossRule; + + + @Bean + @ConditionalOnMissingBean(COSClient.class) + public COSClient ossClient() { + // 初始化用户身份信息(secretId, secretKey) + COSCredentials credentials = new BasicCOSCredentials(ossProperties.getAccessKey(), ossProperties.getSecretKey()); + // 设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + Region region = new Region(ossProperties.getRegion()); + // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。 + ClientConfig clientConfig = new ClientConfig(region); + // 设置OSSClient允许打开的最大HTTP连接数,默认为1024个。 + clientConfig.setMaxConnectionsCount(1024); + // 设置Socket层传输数据的超时时间,默认为50000毫秒。 + clientConfig.setSocketTimeout(50000); + // 设置建立连接的超时时间,默认为50000毫秒。 + clientConfig.setConnectionTimeout(50000); + // 设置从连接池中获取连接的超时时间(单位:毫秒),默认不超时。 + clientConfig.setConnectionRequestTimeout(1000); + return new COSClient(credentials, clientConfig); + } + + @Bean + @ConditionalOnBean({COSClient.class}) + @ConditionalOnMissingBean(TencentCosTemplate.class) + public TencentCosTemplate tencentCosTemplate(COSClient cosClient) { + return new TencentCosTemplate(cosClient, ossProperties, ossRule); + } + +} diff --git a/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java b/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java index d63c709..041aee9 100644 --- a/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java +++ b/blade-starter-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java @@ -68,6 +68,16 @@ public class OssProperties { */ private String bucketName = "blade"; + /** + * 应用ID TencentCOS需要 + */ + private String appId; + + /** + * 区域简称 TencentCOS/Amazon S3 需要 + */ + private String region; + /** * 自定义属性 */