package com.dl.oss.core; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; import com.amazonaws.ClientConfiguration; import com.amazonaws.HttpMethod; import com.amazonaws.Protocol; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.client.builder.AwsClientBuilder; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.*; import com.dl.common.utils.DateUtils; import com.dl.common.utils.StringUtils; import com.dl.oss.constant.OssConstant; import com.dl.oss.entity.UploadResult; import com.dl.oss.enumd.AccessPolicyType; import com.dl.oss.enumd.PolicyType; import com.dl.oss.exception.OssException; import com.dl.oss.properties.OssProperties; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; import java.util.Date; /** * S3 存储协议 所有兼容S3协议的云厂商均支持 * 阿里云 腾讯云 七牛云 minio * * @author Lion Li */ public class OssClient { private final String configKey; private final OssProperties properties; private final AmazonS3 client; public OssClient(String configKey, OssProperties ossProperties) { this.configKey = configKey; this.properties = ossProperties; try { AwsClientBuilder.EndpointConfiguration endpointConfig = new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); ClientConfiguration clientConfig = new ClientConfiguration(); if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) { clientConfig.setProtocol(Protocol.HTTPS); } else { clientConfig.setProtocol(Protocol.HTTP); } AmazonS3ClientBuilder build = AmazonS3Client.builder() .withEndpointConfiguration(endpointConfig) .withClientConfiguration(clientConfig) .withCredentials(credentialsProvider) .disableChunkedEncoding(); if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) { // minio 使用https限制使用域名访问 需要此配置 站点填域名 build.enablePathStyleAccess(); } this.client = build.build(); createBucket(); } catch (Exception e) { if (e instanceof OssException) { throw e; } throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); } } public void createBucket() { try { String bucketName = properties.getBucketName(); if (client.doesBucketExistV2(bucketName)) { return; } CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); AccessPolicyType accessPolicy = getAccessPolicy(); createBucketRequest.setCannedAcl(accessPolicy.getAcl()); client.createBucket(createBucketRequest); client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); } catch (Exception e) { throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); } } public UploadResult upload(byte[] data, String path, String contentType) { return upload(new ByteArrayInputStream(data), path, contentType); } public UploadResult upload(InputStream inputStream, String path, String contentType) { if (!(inputStream instanceof ByteArrayInputStream)) { inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); } try { ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType(contentType); metadata.setContentLength(inputStream.available()); PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); // 设置上传对象的 Acl 为公共读 putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); client.putObject(putObjectRequest); } catch (Exception e) { throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); } return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); } public void delete(String path) { path = path.replace(getUrl() + "/", ""); try { client.deleteObject(properties.getBucketName(), path); } catch (Exception e) { throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); } } public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { return upload(data, getPath(properties.getPrefix(), suffix), contentType); } public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) { return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType); } /** * 获取文件元数据 * * @param path 完整文件路径 */ public ObjectMetadata getObjectMetadata(String path) { path = path.replace(getUrl() + "/", ""); S3Object object = client.getObject(properties.getBucketName(), path); return object.getObjectMetadata(); } public InputStream getObjectContent(String path) { path = path.replace(getUrl() + "/", ""); S3Object object = client.getObject(properties.getBucketName(), path); return object.getObjectContent(); } public String getUrl() { String domain = properties.getDomain(); String endpoint = properties.getEndpoint(); String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://"; // 云服务商直接返回 if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { if (StringUtils.isNotBlank(domain)) { return header + domain; } return header + properties.getBucketName() + "." + endpoint; } // minio 单独处理 if (StringUtils.isNotBlank(domain)) { return header + domain + "/" + properties.getBucketName(); } return header + endpoint + "/" + properties.getBucketName(); } public String getPath(String prefix, String suffix) { // 生成uuid String uuid = IdUtil.fastSimpleUUID(); // 文件路径 String path = DateUtils.datePath() + "/" + uuid; if (StringUtils.isNotBlank(prefix)) { path = prefix + "/" + path; } return path + suffix; } public String getConfigKey() { return configKey; } /** * 获取私有URL链接 * * @param objectKey 对象KEY * @param second 授权时间 */ public String getPrivateUrl(String objectKey, Integer second) { GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) .withMethod(HttpMethod.GET) .withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); URL url = client.generatePresignedUrl(generatePresignedUrlRequest); return url.toString(); } /** * 检查配置是否相同 */ public boolean checkPropertiesSame(OssProperties properties) { return this.properties.equals(properties); } /** * 获取当前桶权限类型 * * @return 当前桶权限类型code */ public AccessPolicyType getAccessPolicy() { return AccessPolicyType.getByType(properties.getAccessPolicy()); } private static String getPolicy(String bucketName, PolicyType policyType) { StringBuilder builder = new StringBuilder(); builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); if (policyType == PolicyType.WRITE) { builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"); } else if (policyType == PolicyType.READ_WRITE) { builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"); } else { builder.append("\"s3:GetBucketLocation\"\n"); } builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); builder.append(bucketName); builder.append("\"\n},\n"); if (policyType == PolicyType.READ) { builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); builder.append(bucketName); builder.append("\"\n},\n"); } builder.append("{\n\"Action\": "); switch (policyType) { case WRITE: builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"); break; case READ_WRITE: builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"); break; default: builder.append("\"s3:GetObject\",\n"); break; } builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); builder.append(bucketName); builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); return builder.toString(); } }