编程导航后端万用模板-Oss服务整合

利用S3 存储协议兼容S3协议的云厂商

模板:编程导航:springboot万用模板->springboot-init-master

参考:RuoYiPlus https://gitee.com/dromara/RuoYi-Vue-Plus

为什么要整合多个Oss云厂商?

  1. 有时使用万用模板,不一定要使用模板中的cos,可能有其他选择:Oss,Minio等

达成效果:

  1. 无感替换Oss云厂商。只需要修改配置文件中的Oss配置,皆可完成切换Oss云厂商。

引入依赖

此部分依赖参考RuoYiPlus

依赖

xml
复制代码
<!-- AWS SDK for Java 2.x --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <exclusions> <!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 --> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>netty-nio-client</artifactId> </exclusion> <!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 --> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-crt-client</artifactId> </exclusion> <!-- 将基于 Apache 的 HTTP 客户端从类路径中移除 --> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> </exclusion> <!-- 将配置基于 URL 连接的 HTTP 客户端从类路径中移除 --> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>url-connection-client</artifactId> </exclusion> </exclusions> </dependency> <!-- 使用AWS基于 CRT 的 S3 客户端 --> <dependency> <groupId>software.amazon.awssdk.crt</groupId> <artifactId>aws-crt</artifactId> <version>${aws.crt.version}</version> </dependency> <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3-transfer-manager</artifactId> <version>${aws.sdk.version}</version> </dependency>

依赖版本

xml
复制代码
<!-- OSS 配置 --> <aws.sdk.version>2.28.22</aws.sdk.version> <aws.crt.version>0.31.3</aws.crt.version>

配置OSS客户端

配置文件

在yml的文件中配置
yaml
复制代码
oss: # 对应云厂商的站点域名 endpoint: 127.0.0.1:9000 # 对应云厂商桶的绑定域名 domain: '' accessKey: UBCX82S9TLNq4WxkTBOJ secretKey: j0uPaCTvUKaOCU82tjFW8qdWeZxdsGOOp7mFX0yP # 前缀 prefix: '' # 存储空间名/桶名 bucketName: default # 存储区域 region: '' # 是否https(Y=是,N=否) isHttps: N # 桶权限类型(0private 1public 2custom) accessPolicy: 1

属性类

java
复制代码
/** * OSS对象存储 配置属性 * @author sakura */ @Data @Component @ConfigurationProperties(prefix = "oss") public class OssProperties { /** * 访问站点 */ private String endpoint; /** * 自定义域名 */ private String domain; /** * ACCESS_KEY */ private String accessKey; /** * SECRET_KEY */ private String secretKey; /** * 存储空间名 */ private String bucketName; /** * 存储区域 */ private String region; /** * 是否https(Y=是,N=否) */ private String isHttps; /** * 桶权限类型(0private 1public 2custom) */ private String accessPolicy;

OssClient

java
复制代码
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; import com.sakura.springbootinit.common.ErrorCode; import com.sakura.springbootinit.common.UploadResult; import com.sakura.springbootinit.constant.CommonConstant; import com.sakura.springbootinit.constant.OssConstant; import com.sakura.springbootinit.exception.OssException; import com.sakura.springbootinit.model.enums.AccessPolicyType; import com.sakura.springbootinit.model.enums.PolicyType; import com.sakura.springbootinit.model.properties.OssProperties; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.NoSuchBucketException; import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.model.*; import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.nio.file.Path; import java.time.Duration; import java.util.Date; /** * S3 存储协议 所有兼容S3协议的云厂商均支持 * 阿里云 腾讯云 七牛云 minio * * @author RuoYi-Plus */ public class OssClient { /** * 配置属性 */ private final OssProperties properties; /** * Amazon S3 异步客户端 */ private final S3AsyncClient client; /** * 用于管理 S3 数据传输的高级工具 */ private final S3TransferManager transferManager; /** * AWS S3 预签名 URL 的生成器 */ private final S3Presigner presigner; /** * 构造方法 * * @param ossProperties Oss配置属性 */ public OssClient(OssProperties ossProperties) { this.properties = ossProperties; try { // 创建 AWS 认证信息 StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); // MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问 boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE); // 创建AWS基于 CRT 的 S3 客户端 this.client = S3AsyncClient.crtBuilder() .credentialsProvider(credentialsProvider) .endpointOverride(URI.create(getEndpoint())) .region(of()) .targetThroughputInGbps(20.0) .minimumPartSizeInBytes(10 * 1025 * 1024L) .checksumValidationEnabled(false) .forcePathStyle(isStyle) .httpConfiguration(S3CrtHttpConfiguration.builder() .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时 .build()) .build(); //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端 this.transferManager = S3TransferManager.builder().s3Client(this.client).build(); // 创建 S3 配置对象 S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false) .pathStyleAccessEnabled(isStyle).build(); // 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL this.presigner = S3Presigner.builder() .region(of()) .credentialsProvider(credentialsProvider) .endpointOverride(URI.create(getDomain())) .serviceConfiguration(config) .build(); // 创建存储桶 createBucket(); } catch (Exception e) { if (e instanceof OssException) { throw e; } throw new OssException(ErrorCode.SYSTEM_ERROR, "配置错误! 请检查系统配置:[" + e.getMessage() + "]"); } } /** * 同步创建存储桶 * 如果存储桶不存在,会进行创建;如果存储桶存在,不执行任何操作 * * @throws OssException 当创建存储桶时发生异常时抛出 */ public void createBucket() { String bucketName = properties.getBucketName(); try { // 尝试获取存储桶的信息 client.headBucket( x -> x.bucket(bucketName) .build()) .join(); } catch (Exception ex) { if (ex.getCause() instanceof NoSuchBucketException) { try { // 存储桶不存在,尝试创建存储桶 client.createBucket( x -> x.bucket(bucketName)) .join(); // 设置存储桶的访问策略(Bucket Policy) client.putBucketPolicy( x -> x.bucket(bucketName) .policy(getPolicy(bucketName, getAccessPolicy().getPolicyType()))) .join(); } catch (S3Exception e) { // 存储桶创建或策略设置失败 throw new OssException(ErrorCode.SYSTEM_ERROR, "创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); } } else { throw new OssException(ErrorCode.SYSTEM_ERROR, "判断Bucket是否存在失败,请核对配置信息:[" + ex.getMessage() + "]"); } } } /** * 上传文件到 Amazon S3,并返回上传结果 * * @param filePath 本地文件路径 * @param key 在 Amazon S3 中的对象键 * @param md5Digest 本地文件的 MD5 哈希值(可选) * @param contentType 文件内容类型 * @return UploadResult 包含上传后的文件信息 * @throws OssException 如果上传失败,抛出自定义异常 */ public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) { try { // 构建上传请求对象 FileUpload fileUpload = transferManager.uploadFile( x -> x.putObjectRequest( y -> y.bucket(properties.getBucketName()) .key(key) .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) .contentType(contentType) // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同, // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置) //.acl(getAccessPolicy().getObjectCannedACL()) .build()) .addTransferListener(LoggingTransferListener.create()) .source(filePath).build()); // 等待上传完成并获取上传结果 CompletedFileUpload uploadResult = fileUpload.completionFuture().join(); String eTag = uploadResult.response().eTag(); // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象 return UploadResult.builder().url(getUrl() + CommonConstant.SLASH + key).filename(key).eTag(eTag).build(); } catch (Exception e) { // 捕获异常并抛出自定义异常 throw new OssException(ErrorCode.OPERATION_ERROR, "上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); } finally { // 无论上传是否成功,最终都会删除临时文件 FileUtil.del(filePath); } } /** * 上传 InputStream 到 Amazon S3 * * @param inputStream 要上传的输入流 * @param key 在 Amazon S3 中的对象键 * @param length 输入流的长度 * @param contentType 文件内容类型 * @return UploadResult 包含上传后的文件信息 * @throws OssException 如果上传失败,抛出自定义异常 */ public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) { // 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream if (!(inputStream instanceof ByteArrayInputStream)) { inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); } try { // 创建异步请求体(length如果为空会报错) BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder() .contentLength(length) .subscribeTimeout(Duration.ofSeconds(30)) .build(); // 使用 transferManager 进行上传 Upload upload = transferManager.upload( x -> x.requestBody(body) .putObjectRequest( y -> y.bucket(properties.getBucketName()) .key(key) .contentType(contentType) // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同, // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置) //.acl(getAccessPolicy().getObjectCannedACL()) .build()) .build()); // 将输入流写入请求体 body.writeInputStream(inputStream); // 等待文件上传操作完成 CompletedUpload uploadResult = upload.completionFuture().join(); String eTag = uploadResult.response().eTag(); // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象 return UploadResult.builder().url(getUrl() + CommonConstant.SLASH + key).filename(key).eTag(eTag).build(); } catch (Exception e) { throw new OssException(ErrorCode.OPERATION_ERROR, "上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); } } /** * 下载文件从 Amazon S3 到临时目录 * * @param path 文件在 Amazon S3 中的对象键 * @return 下载后的文件在本地的临时路径 * @throws OssException 如果下载失败,抛出自定义异常 */ public Path fileDownload(String path) { // 构建临时文件 Path tempFilePath = FileUtil.createTempFile().toPath(); // 使用 S3TransferManager 下载文件 FileDownload downloadFile = transferManager.downloadFile( x -> x.getObjectRequest( y -> y.bucket(properties.getBucketName()) .key(removeBaseUrl(path)) .build()) .addTransferListener(LoggingTransferListener.create()) .destination(tempFilePath) .build()); // 等待文件下载操作完成 downloadFile.completionFuture().join(); return tempFilePath; } /** * 下载文件从 Amazon S3 到 输出流 * * @param key 文件在 Amazon S3 中的对象键 * @param out 输出流 * @return 输出流中写入的字节数(长度) * @throws OssException 如果下载失败,抛出自定义异常 */ public long download(String key, OutputStream out) { try { // 构建下载请求 DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder() // 文件对象 .getObjectRequest(y -> y.bucket(properties.getBucketName()) .key(key) .build()) .addTransferListener(LoggingTransferListener.create()) // 使用订阅转换器 .responseTransformer(AsyncResponseTransformer.toBlockingInputStream()) .build(); // 使用 S3TransferManager 下载文件 Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest); // 输出到流中 try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread } } catch (Exception e) { throw new OssException(ErrorCode.OPERATION_ERROR, "文件下载失败,错误信息:[" + e.getMessage() + "]"); } } /** * 获取 S3 客户端的终端点 URL * * @return 终端点 URL */ public String getEndpoint() { // 根据配置文件中的是否使用 HTTPS,设置协议头部 String header = getIsHttps(); // 拼接协议头部和终端点,得到完整的终端点 URL return header + properties.getEndpoint(); } /** * 获取当前桶权限类型 * * @return 当前桶权限类型code */ public AccessPolicyType getAccessPolicy() { return AccessPolicyType.getByType(properties.getAccessPolicy()); } /** * 获取 S3 客户端的终端点 URL(自定义域名) * * @return 终端点 URL */ public String getDomain() { // 从配置中获取域名、终端点、是否使用 HTTPS 等信息 String domain = properties.getDomain(); String endpoint = properties.getEndpoint(); String header = getIsHttps(); // 如果是云服务商,直接返回域名或终端点 if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint; } // 如果是 MinIO,处理域名并返回 if (StringUtils.isNotEmpty(domain)) { return domain.startsWith(CommonConstant.HTTPS) || domain.startsWith(CommonConstant.HTTP) ? domain : header + domain; } // 返回终端点 return header + endpoint; } /** * 获取是否使用 HTTPS 的配置,并返回相应的协议头部。 * * @return 协议头部,根据是否使用 HTTPS 返回 "https://" 或 "http://" */ public String getIsHttps() { return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? CommonConstant.HTTPS : CommonConstant.HTTP; } /** * 获取云存储服务的URL * * @return 文件路径 */ public String getUrl() { String domain = properties.getDomain(); String endpoint = properties.getEndpoint(); String header = getIsHttps(); // 云服务商直接返回 if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint); } // MinIO 单独处理 if (StringUtils.isNotEmpty(domain)) { // 如果 domain 以 "https://" 或 "http://" 开头 return (domain.startsWith(CommonConstant.HTTPS) || domain.startsWith(CommonConstant.HTTP)) ? domain + CommonConstant.SLASH + properties.getBucketName() : header + domain + CommonConstant.SLASH + properties.getBucketName(); } return header + endpoint + CommonConstant.SLASH + properties.getBucketName(); } /** * 移除路径中的基础URL部分,得到相对路径 * * @param path 完整的路径,包括基础URL和相对路径 * @return 去除基础URL后的相对路径 */ public String removeBaseUrl(String path) { return path.replace(getUrl() + CommonConstant.SLASH, ""); } /** * 根据传入的 region 参数返回相应的 AWS 区域 * 如果 region 参数非空,使用 Region.of 方法创建并返回对应的 AWS 区域对象 * 如果 region 参数为空,返回一个默认的 AWS 区域(例如,us-east-1),作为广泛支持的区域 * * @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1) */ public Region of() { //AWS 区域字符串 String region = properties.getRegion(); // 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域 return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1; } /** * 生成 AWS S3 存储桶访问策略 * * @param bucketName 存储桶 * @param policyType 桶策略类型 * @return 符合 AWS S3 存储桶访问策略格式的字符串 */ private static String getPolicy(String bucketName, PolicyType policyType) { String policy = switch (policyType) { case WRITE -> """ { "Version": "2012-10-17", "Statement": [] } """; case READ_WRITE -> """ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": [ "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads" ], "Resource": "arn:aws:s3:::bucketName" }, { "Effect": "Allow", "Principal": "*", "Action": [ "s3:AbortMultipartUpload", "s3:DeleteObject", "s3:GetObject", "s3:ListMultipartUploadParts", "s3:PutObject" ], "Resource": "arn:aws:s3:::bucketName/*" } ] } """; case READ -> """ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": ["s3:GetBucketLocation"], "Resource": "arn:aws:s3:::bucketName" }, { "Effect": "Deny", "Principal": "*", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::bucketName" }, { "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucketName/*" } ] } """; }; return policy.replaceAll("bucketName", bucketName); } /** * 上传 byte[] 数据到 Amazon S3,使用指定的后缀构造对象键。 * * @param data 要上传的 byte[] 数据 * @param suffix 对象键的后缀 * @return UploadResult 包含上传后的文件信息 * @throws OssException 如果上传失败,抛出自定义异常 */ public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), contentType); } /** * 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合,确保了文件路径的独一无二性 * * @param prefix 前缀 * @param suffix 后缀 * @return 文件路径 */ public String getPath(String prefix, String suffix) { // 生成uuid String uuid = IdUtil.fastSimpleUUID(); // 生成日期路径 Date now = new Date(); String datePath = DateFormatUtils.format(now, "yyyy/MM/dd"); // 拼接路径 String path = StringUtils.isNotEmpty(prefix) ? prefix + CommonConstant.SLASH + datePath + CommonConstant.SLASH + uuid : datePath + CommonConstant.SLASH + uuid; return path + suffix; } /** * 获取私有URL链接 * * @param objectKey 对象KEY * @param second 授权时间 */ public String getPrivateUrl(String objectKey, Integer second) { // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL URL url = presigner.presignGetObject( x -> x.signatureDuration(Duration.ofSeconds(second)) .getObjectRequest( y -> y.bucket(properties.getBucketName()) .key(objectKey) .build()) .build()) .url(); return url.toString(); } }

用到的其他类

java
复制代码
/** * 桶访问策略配置 * * @author RuoYi-Plus */ @Getter @AllArgsConstructor public enum AccessPolicyType { /** * private */ PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE), /** * public */ PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE), /** * custom */ CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ); /** * 桶 权限类型(数据库值) */ private final String type; /** * 桶 权限类型 */ private final BucketCannedACL bucketCannedACL; /** * 文件对象 权限类型 */ private final ObjectCannedACL objectCannedACL; /** * 桶策略类型 */ private final PolicyType policyType; public static AccessPolicyType getByType(String type) { for (AccessPolicyType value : values()) { if (value.getType().equals(type)) { return value; } } throw new RuntimeException("'type' not found By " + type); } }
java
复制代码
/** * minio策略配置 * * @author RuoYi-Plus */ @Getter @AllArgsConstructor public enum PolicyType { /** * 只读 */ READ("read-only"), /** * 只写 */ WRITE("write-only"), /** * 读写 */ READ_WRITE("read-write"); /** * 类型 */ private final String type; }
java
复制代码
/** * 处理Oss异常 */ public class OssException extends RuntimeException { /** * 错误码 */ private final int code; public OssException(int code, String message) { super(message); this.code = code; } public OssException(ErrorCode errorCode) { super(errorCode.getMessage()); this.code = errorCode.getCode(); } public OssException(ErrorCode errorCode, String message) { super(message); this.code = errorCode.getCode(); } public int getCode() { return code; } }
java
复制代码
/** * 上传返回体 * * @author RuoYi-Plus */ @Data @Builder public class UploadResult { /** * 文件路径 */ private String url; /** * 文件名 */ private String filename; /** * 已上传对象的实体标记(用来校验文件) */ private String eTag; }
java
复制代码
/** * 对象存储常量 * * @author RuoYi-Plus */ public interface OssConstant { /** * 云服务商 */ String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"}; /** * https 状态 */ String IS_HTTPS = "Y"; }
java
复制代码
/** * 通用常量 * * @author <a href="https://github.com/liyupi">程序员鱼皮</a> * @from <a href="https://yupi.icu">编程导航知识星球</a> */ public interface CommonConstant { // .... /** * http请求 */ String HTTP = "http://"; /** * https请求 */ String HTTPS = "https://"; String SLASH = "/"; }

配置OssClient

java
复制代码
/** * OSS 配置 * * @author sakura */ @Configuration public class OssConfig { /** * OSS 客户端 * @param ossProperties OSS 客户端 * @return OSS 客户端 */ @Bean public OssClient ossClient(OssProperties ossProperties) { return new OssClient(ossProperties); } }

实现Oss服务类

这里就不作数据库的存储管理了,直接返回可分为的url就好

上传方法

java
复制代码
/** * OSS 服务实现类 */ @Slf4j @Service public class OssServiceImpl implements OssService { @Resource private OssClient storage; /** * 上传 MultipartFile 到对象存储服务 * * @param file 要上传的 MultipartFile 对象 * @return 上传成功后的返回可访问的URL信息 */ @Override public String upload(MultipartFile file) { String originalfileName = file.getOriginalFilename(); String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); UploadResult uploadResult; try { uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); } catch (IOException e) { throw new BusinessException(ErrorCode.OPERATION_ERROR, e.getMessage()); } return this.matchingUrl(uploadResult).getUrl(); } /** * 桶类型为 private 的URL 修改为临时URL时长为120s * * @return oss 匹配Url的OSS对象 */ private UploadResult matchingUrl(UploadResult oss) { // 仅修改桶类型为 private 的URL,临时URL时长为120s if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) { oss.setUrl(storage.getPrivateUrl(oss.getFilename(), 120)); } return oss; } }

编写接口

这里选择修改模板中的upload接口
java
复制代码
/** * 文件上传 * * @param multipartFile * @return */ @PostMapping("/upload") public BaseResponse<String> uploadFile(@RequestPart("file") MultipartFile multipartFile) { validFile(multipartFile); String url = ossService.upload(multipartFile); return ResultUtils.success(url); }

测试

这里以Minio为例,不过多介绍minio安装和配置

编写配置文件

模拟请求

响应结果

json
复制代码
{ "code": 0, "data": "http://127.0.0.1:9000/default/2025/01/25/fe1f99c7e88c4354ba7138450223f361.jpg", "message": "ok" }
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP