Commit 97a981c2949027127934082bfb497e889c66438f
1 parent
4c5134ae
feat:【infra 基础设施】支持私有桶
Showing
12 changed files
with
141 additions
and
63 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java
| ... | ... | @@ -47,8 +47,15 @@ public class HttpUtils { |
| 47 | 47 | return builder.build(); |
| 48 | 48 | } |
| 49 | 49 | |
| 50 | - private String append(String base, Map<String, ?> query, boolean fragment) { | |
| 51 | - return append(base, query, null, fragment); | |
| 50 | + public static String removeUrlQuery(String url) { | |
| 51 | + if (!StrUtil.contains(url, '?')) { | |
| 52 | + return url; | |
| 53 | + } | |
| 54 | + UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); | |
| 55 | + // 移除 query、fragment | |
| 56 | + builder.setQuery(null); | |
| 57 | + builder.setFragment(null); | |
| 58 | + return builder.build(); | |
| 52 | 59 | } |
| 53 | 60 | |
| 54 | 61 | /** | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java
| ... | ... | @@ -42,4 +42,14 @@ public interface FileApi { |
| 42 | 42 | String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content, |
| 43 | 43 | String name, String directory, String type); |
| 44 | 44 | |
| 45 | + /** | |
| 46 | + * 生成文件预签名地址,用于读取 | |
| 47 | + * | |
| 48 | + * @param url 完整的文件访问地址 | |
| 49 | + * @param expirationSeconds 访问有效期,单位秒 | |
| 50 | + * @return 文件预签名地址 | |
| 51 | + */ | |
| 52 | + String presignGetUrl(@NotEmpty(message = "URL 不能为空") String url, | |
| 53 | + Integer expirationSeconds); | |
| 54 | + | |
| 45 | 55 | } | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java
| ... | ... | @@ -22,4 +22,9 @@ public class FileApiImpl implements FileApi { |
| 22 | 22 | return fileService.createFile(content, name, directory, type); |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | + @Override | |
| 26 | + public String presignGetUrl(String url, Integer expirationSeconds) { | |
| 27 | + return fileService.presignGetUrl(url, expirationSeconds); | |
| 28 | + } | |
| 29 | + | |
| 25 | 30 | } | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
| ... | ... | @@ -51,7 +51,7 @@ public class FileController { |
| 51 | 51 | } |
| 52 | 52 | |
| 53 | 53 | @GetMapping("/presigned-url") |
| 54 | - @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") | |
| 54 | + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") | |
| 55 | 55 | @Parameters({ |
| 56 | 56 | @Parameter(name = "name", description = "文件名称", required = true), |
| 57 | 57 | @Parameter(name = "directory", description = "文件目录") |
| ... | ... | @@ -59,7 +59,7 @@ public class FileController { |
| 59 | 59 | public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( |
| 60 | 60 | @RequestParam("name") String name, |
| 61 | 61 | @RequestParam(value = "directory", required = false) String directory) { |
| 62 | - return success(fileService.getFilePresignedUrl(name, directory)); | |
| 62 | + return success(fileService.presignPutUrl(name, directory)); | |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | @PostMapping("/create") | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java
| ... | ... | @@ -41,7 +41,7 @@ public class AppFileController { |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | @GetMapping("/presigned-url") |
| 44 | - @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") | |
| 44 | + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") | |
| 45 | 45 | @Parameters({ |
| 46 | 46 | @Parameter(name = "name", description = "文件名称", required = true), |
| 47 | 47 | @Parameter(name = "directory", description = "文件目录") |
| ... | ... | @@ -49,7 +49,7 @@ public class AppFileController { |
| 49 | 49 | public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( |
| 50 | 50 | @RequestParam("name") String name, |
| 51 | 51 | @RequestParam(value = "directory", required = false) String directory) { |
| 52 | - return success(fileService.getFilePresignedUrl(name, directory)); | |
| 52 | + return success(fileService.presignPutUrl(name, directory)); | |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | 55 | @PostMapping("/create") | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java
| 1 | 1 | package cn.iocoder.yudao.module.infra.framework.file.core.client; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; | |
| 4 | - | |
| 5 | 3 | /** |
| 6 | 4 | * 文件客户端 |
| 7 | 5 | * |
| ... | ... | @@ -42,13 +40,26 @@ public interface FileClient { |
| 42 | 40 | */ |
| 43 | 41 | byte[] getContent(String path) throws Exception; |
| 44 | 42 | |
| 43 | + // ========== 文件签名,目前仅 S3 支持 ========== | |
| 44 | + | |
| 45 | 45 | /** |
| 46 | - * 获得文件预签名地址 | |
| 46 | + * 获得文件预签名地址,用于上传 | |
| 47 | 47 | * |
| 48 | 48 | * @param path 相对路径 |
| 49 | 49 | * @return 文件预签名地址 |
| 50 | 50 | */ |
| 51 | - default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception { | |
| 51 | + default String presignPutUrl(String path) { | |
| 52 | + throw new UnsupportedOperationException("不支持的操作"); | |
| 53 | + } | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * 生成文件预签名地址,用于读取 | |
| 57 | + * | |
| 58 | + * @param url 完整的文件访问地址 | |
| 59 | + * @param expirationSeconds 访问有效期,单位秒 | |
| 60 | + * @return 文件预签名地址 | |
| 61 | + */ | |
| 62 | + default String presignGetUrl(String url, Integer expirationSeconds) { | |
| 52 | 63 | throw new UnsupportedOperationException("不支持的操作"); |
| 53 | 64 | } |
| 54 | 65 | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.infra.framework.file.core.client.s3; | |
| 2 | - | |
| 3 | -import lombok.AllArgsConstructor; | |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.NoArgsConstructor; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * 文件预签名地址 Response DTO | |
| 9 | - * | |
| 10 | - * @author owen | |
| 11 | - */ | |
| 12 | -@Data | |
| 13 | -@AllArgsConstructor | |
| 14 | -@NoArgsConstructor | |
| 15 | -public class FilePresignedUrlRespDTO { | |
| 16 | - | |
| 17 | - /** | |
| 18 | - * 文件上传 URL(用于上传) | |
| 19 | - * | |
| 20 | - * 例如说: | |
| 21 | - */ | |
| 22 | - private String uploadUrl; | |
| 23 | - | |
| 24 | - /** | |
| 25 | - * 文件 URL(用于读取、下载等) | |
| 26 | - */ | |
| 27 | - private String url; | |
| 28 | - | |
| 29 | -} |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
| 1 | 1 | package cn.iocoder.yudao.module.infra.framework.file.core.client.s3; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.io.IoUtil; |
| 4 | +import cn.hutool.core.util.BooleanUtil; | |
| 4 | 5 | import cn.hutool.core.util.StrUtil; |
| 5 | 6 | import cn.hutool.http.HttpUtil; |
| 7 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | |
| 6 | 8 | import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; |
| 7 | 9 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; |
| 8 | 10 | import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; |
| ... | ... | @@ -15,9 +17,11 @@ import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; |
| 15 | 17 | import software.amazon.awssdk.services.s3.model.GetObjectRequest; |
| 16 | 18 | import software.amazon.awssdk.services.s3.model.PutObjectRequest; |
| 17 | 19 | import software.amazon.awssdk.services.s3.presigner.S3Presigner; |
| 20 | +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; | |
| 18 | 21 | import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; |
| 19 | 22 | |
| 20 | 23 | import java.net.URI; |
| 24 | +import java.net.URL; | |
| 21 | 25 | import java.time.Duration; |
| 22 | 26 | |
| 23 | 27 | /** |
| ... | ... | @@ -27,6 +31,8 @@ import java.time.Duration; |
| 27 | 31 | */ |
| 28 | 32 | public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { |
| 29 | 33 | |
| 34 | + private static final Duration EXPIRATION_DEFAULT = Duration.ofHours(24); | |
| 35 | + | |
| 30 | 36 | private S3Client client; |
| 31 | 37 | private S3Presigner presigner; |
| 32 | 38 | |
| ... | ... | @@ -75,7 +81,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { |
| 75 | 81 | // 上传文件 |
| 76 | 82 | client.putObject(putRequest, RequestBody.fromBytes(content)); |
| 77 | 83 | // 拼接返回路径 |
| 78 | - return config.getDomain() + "/" + path; | |
| 84 | + return presignGetUrl(path, null); | |
| 79 | 85 | } |
| 80 | 86 | |
| 81 | 87 | @Override |
| ... | ... | @@ -97,23 +103,38 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { |
| 97 | 103 | } |
| 98 | 104 | |
| 99 | 105 | @Override |
| 100 | - public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) { | |
| 101 | - Duration expiration = Duration.ofHours(24); | |
| 102 | - return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path); | |
| 106 | + public String presignPutUrl(String path) { | |
| 107 | + return presigner.presignPutObject(PutObjectPresignRequest.builder() | |
| 108 | + .signatureDuration(EXPIRATION_DEFAULT) | |
| 109 | + .putObjectRequest(b -> b.bucket(config.getBucket()).key(path)).build()) | |
| 110 | + .url().toString(); | |
| 103 | 111 | } |
| 104 | 112 | |
| 105 | - /** | |
| 106 | - * 生成动态的预签名上传 URL | |
| 107 | - * | |
| 108 | - * @param path 相对路径 | |
| 109 | - * @param expiration 过期时间 | |
| 110 | - * @return 生成的上传 URL | |
| 111 | - */ | |
| 112 | - private String getPresignedUrl(String path, Duration expiration) { | |
| 113 | - return presigner.presignPutObject(PutObjectPresignRequest.builder() | |
| 113 | + @Override | |
| 114 | + public String presignGetUrl(String url, Integer expirationSeconds) { | |
| 115 | + // 1. 将 url 转换为 path | |
| 116 | + String path = StrUtil.removePrefix(url, config.getDomain() + "/"); | |
| 117 | + path = HttpUtils.removeUrlQuery(path); | |
| 118 | + | |
| 119 | + // 2.1 情况一:公开访问:无需签名 | |
| 120 | + // 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名 | |
| 121 | + if (!BooleanUtil.isFalse(config.getEnablePublicAccess())) { | |
| 122 | + return config.getDomain() + "/" + path; | |
| 123 | + } | |
| 124 | + | |
| 125 | + // 2.2 情况二:私有访问:生成 GET 预签名 URL | |
| 126 | + String finalPath = path; | |
| 127 | + Duration expiration = expirationSeconds != null ? Duration.ofSeconds(expirationSeconds) : EXPIRATION_DEFAULT; | |
| 128 | + URL signedUrl = presigner.presignGetObject(GetObjectPresignRequest.builder() | |
| 114 | 129 | .signatureDuration(expiration) |
| 115 | - .putObjectRequest(b -> b.bucket(config.getBucket()).key(path)) | |
| 116 | - .build()).url().toString(); | |
| 130 | + .getObjectRequest(b -> b.bucket(config.getBucket()).key(finalPath)).build()) | |
| 131 | + .url(); | |
| 132 | + // 特殊:适配未使用 domain 返回的情况!!! | |
| 133 | + String signedUrlStr = signedUrl.toString(); | |
| 134 | + if (!signedUrlStr.startsWith(config.getDomain())) { | |
| 135 | + signedUrlStr = signedUrlStr.replaceFirst(signedUrl.getProtocol() + "://" + signedUrl.getHost(), config.getDomain()); | |
| 136 | + } | |
| 137 | + return signedUrlStr; | |
| 117 | 138 | } |
| 118 | 139 | |
| 119 | 140 | /** | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java
| ... | ... | @@ -73,6 +73,15 @@ public class S3FileClientConfig implements FileClientConfig { |
| 73 | 73 | @NotNull(message = "enablePathStyleAccess 不能为空") |
| 74 | 74 | private Boolean enablePathStyleAccess; |
| 75 | 75 | |
| 76 | + /** | |
| 77 | + * 是否公开访问 | |
| 78 | + * | |
| 79 | + * true:公开访问,所有人都可以访问 | |
| 80 | + * false:私有访问,只有配置的 accessKey 才可以访问 | |
| 81 | + */ | |
| 82 | + @NotNull(message = "是否公开访问不能为空") | |
| 83 | + private Boolean enablePublicAccess; | |
| 84 | + | |
| 76 | 85 | @SuppressWarnings("RedundantIfStatement") |
| 77 | 86 | @AssertTrue(message = "domain 不能为空") |
| 78 | 87 | @JsonIgnore | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
| ... | ... | @@ -37,14 +37,22 @@ public interface FileService { |
| 37 | 37 | String name, String directory, String type); |
| 38 | 38 | |
| 39 | 39 | /** |
| 40 | - * 生成文件预签名地址信息 | |
| 40 | + * 生成文件预签名地址信息,用于上传 | |
| 41 | 41 | * |
| 42 | 42 | * @param name 文件名 |
| 43 | 43 | * @param directory 目录 |
| 44 | 44 | * @return 预签名地址信息 |
| 45 | 45 | */ |
| 46 | - FilePresignedUrlRespVO getFilePresignedUrl(@NotEmpty(message = "文件名不能为空") String name, | |
| 47 | - String directory); | |
| 46 | + FilePresignedUrlRespVO presignPutUrl(@NotEmpty(message = "文件名不能为空") String name, | |
| 47 | + String directory); | |
| 48 | + /** | |
| 49 | + * 生成文件预签名地址信息,用于读取 | |
| 50 | + * | |
| 51 | + * @param url 完整的文件访问地址 | |
| 52 | + * @param expirationSeconds 访问有效期,单位秒 | |
| 53 | + * @return 文件预签名地址 | |
| 54 | + */ | |
| 55 | + String presignGetUrl(String url, Integer expirationSeconds); | |
| 48 | 56 | |
| 49 | 57 | /** |
| 50 | 58 | * 创建文件 | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.hutool.core.lang.Assert; |
| 6 | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | 7 | import cn.hutool.crypto.digest.DigestUtil; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | |
| 9 | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | 11 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; |
| 11 | 12 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; |
| ... | ... | @@ -13,7 +14,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned |
| 13 | 14 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 14 | 15 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; |
| 15 | 16 | import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; |
| 16 | -import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; | |
| 17 | 17 | import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; |
| 18 | 18 | import com.google.common.annotations.VisibleForTesting; |
| 19 | 19 | import jakarta.annotation.Resource; |
| ... | ... | @@ -126,19 +126,27 @@ public class FileServiceImpl implements FileService { |
| 126 | 126 | |
| 127 | 127 | @Override |
| 128 | 128 | @SneakyThrows |
| 129 | - public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) { | |
| 129 | + public FilePresignedUrlRespVO presignPutUrl(String name, String directory) { | |
| 130 | 130 | // 1. 生成上传的 path,需要保证唯一 |
| 131 | 131 | String path = generateUploadPath(name, directory); |
| 132 | 132 | |
| 133 | 133 | // 2. 获取文件预签名地址 |
| 134 | 134 | FileClient fileClient = fileConfigService.getMasterFileClient(); |
| 135 | - FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path); | |
| 136 | - return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class, | |
| 137 | - object -> object.setConfigId(fileClient.getId()).setPath(path)); | |
| 135 | + String uploadUrl = fileClient.presignPutUrl(path); | |
| 136 | + String visitUrl = fileClient.presignGetUrl(path, null); | |
| 137 | + return new FilePresignedUrlRespVO().setConfigId(fileClient.getId()) | |
| 138 | + .setPath(path).setUploadUrl(uploadUrl).setUrl(visitUrl); | |
| 139 | + } | |
| 140 | + | |
| 141 | + @Override | |
| 142 | + public String presignGetUrl(String url, Integer expirationSeconds) { | |
| 143 | + FileClient fileClient = fileConfigService.getMasterFileClient(); | |
| 144 | + return fileClient.presignGetUrl(url, expirationSeconds); | |
| 138 | 145 | } |
| 139 | 146 | |
| 140 | 147 | @Override |
| 141 | 148 | public Long createFile(FileCreateReqVO createReqVO) { |
| 149 | + createReqVO.setUrl(HttpUtils.removeUrlQuery(createReqVO.getUrl())); // 目的:移除私有桶情况下,URL 的签名参数 | |
| 142 | 150 | FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); |
| 143 | 151 | fileMapper.insert(file); |
| 144 | 152 | return file.getId(); | ... | ... |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/s3/S3FileClientTest.java
| ... | ... | @@ -9,6 +9,7 @@ import jakarta.validation.Validation; |
| 9 | 9 | import org.junit.jupiter.api.Disabled; |
| 10 | 10 | import org.junit.jupiter.api.Test; |
| 11 | 11 | |
| 12 | +@SuppressWarnings("resource") | |
| 12 | 13 | public class S3FileClientTest { |
| 13 | 14 | |
| 14 | 15 | @Test |
| ... | ... | @@ -70,6 +71,7 @@ public class S3FileClientTest { |
| 70 | 71 | config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); |
| 71 | 72 | config.setBucket("ruoyi-vue-pro"); |
| 72 | 73 | config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn |
| 74 | + config.setEnablePathStyleAccess(false); | |
| 73 | 75 | // 默认上海的 endpoint |
| 74 | 76 | config.setEndpoint("s3-cn-south-1.qiniucs.com"); |
| 75 | 77 | |
| ... | ... | @@ -78,6 +80,32 @@ public class S3FileClientTest { |
| 78 | 80 | } |
| 79 | 81 | |
| 80 | 82 | @Test |
| 83 | + @Disabled // 七牛云存储(读私有桶),如果要集成测试,可以注释本行 | |
| 84 | + public void testQiniu_privateGet() { | |
| 85 | + S3FileClientConfig config = new S3FileClientConfig(); | |
| 86 | + // 配置成你自己的 | |
| 87 | +// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY")); | |
| 88 | +// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY")); | |
| 89 | + config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8"); | |
| 90 | + config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); | |
| 91 | + config.setBucket("ruoyi-vue-pro-private"); | |
| 92 | + config.setDomain("http://t151glocd.hn-bkt.clouddn.com"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn | |
| 93 | + config.setEnablePathStyleAccess(false); | |
| 94 | + // 默认上海的 endpoint | |
| 95 | + config.setEndpoint("s3-cn-south-1.qiniucs.com"); | |
| 96 | + | |
| 97 | + // 校验配置 | |
| 98 | + ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); | |
| 99 | + // 创建 Client | |
| 100 | + S3FileClient client = new S3FileClient(0L, config); | |
| 101 | + client.init(); | |
| 102 | + // 执行生成 URL 签名 | |
| 103 | + String path = "output.png"; | |
| 104 | + String presignedUrl = client.presignGetUrl(path, 300); | |
| 105 | + System.out.println(presignedUrl); | |
| 106 | + } | |
| 107 | + | |
| 108 | + @Test | |
| 81 | 109 | @Disabled // 华为云存储,如果要集成测试,可以注释本行 |
| 82 | 110 | public void testHuaweiCloud() throws Exception { |
| 83 | 111 | S3FileClientConfig config = new S3FileClientConfig(); |
| ... | ... | @@ -93,7 +121,7 @@ public class S3FileClientTest { |
| 93 | 121 | testExecuteUpload(config); |
| 94 | 122 | } |
| 95 | 123 | |
| 96 | - private void testExecuteUpload(S3FileClientConfig config) throws Exception { | |
| 124 | + private void testExecuteUpload(S3FileClientConfig config) { | |
| 97 | 125 | // 校验配置 |
| 98 | 126 | ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); |
| 99 | 127 | // 创建 Client | ... | ... |