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,8 +47,15 @@ public class HttpUtils { | ||
| 47 | return builder.build(); | 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,4 +42,14 @@ public interface FileApi { | ||
| 42 | String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content, | 42 | String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content, |
| 43 | String name, String directory, String type); | 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,4 +22,9 @@ public class FileApiImpl implements FileApi { | ||
| 22 | return fileService.createFile(content, name, directory, type); | 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,7 +51,7 @@ public class FileController { | ||
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | @GetMapping("/presigned-url") | 53 | @GetMapping("/presigned-url") |
| 54 | - @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") | 54 | + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") |
| 55 | @Parameters({ | 55 | @Parameters({ |
| 56 | @Parameter(name = "name", description = "文件名称", required = true), | 56 | @Parameter(name = "name", description = "文件名称", required = true), |
| 57 | @Parameter(name = "directory", description = "文件目录") | 57 | @Parameter(name = "directory", description = "文件目录") |
| @@ -59,7 +59,7 @@ public class FileController { | @@ -59,7 +59,7 @@ public class FileController { | ||
| 59 | public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( | 59 | public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( |
| 60 | @RequestParam("name") String name, | 60 | @RequestParam("name") String name, |
| 61 | @RequestParam(value = "directory", required = false) String directory) { | 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 | @PostMapping("/create") | 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,7 +41,7 @@ public class AppFileController { | ||
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | @GetMapping("/presigned-url") | 43 | @GetMapping("/presigned-url") |
| 44 | - @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") | 44 | + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") |
| 45 | @Parameters({ | 45 | @Parameters({ |
| 46 | @Parameter(name = "name", description = "文件名称", required = true), | 46 | @Parameter(name = "name", description = "文件名称", required = true), |
| 47 | @Parameter(name = "directory", description = "文件目录") | 47 | @Parameter(name = "directory", description = "文件目录") |
| @@ -49,7 +49,7 @@ public class AppFileController { | @@ -49,7 +49,7 @@ public class AppFileController { | ||
| 49 | public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( | 49 | public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( |
| 50 | @RequestParam("name") String name, | 50 | @RequestParam("name") String name, |
| 51 | @RequestParam(value = "directory", required = false) String directory) { | 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 | @PostMapping("/create") | 55 | @PostMapping("/create") |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/FileClient.java
| 1 | package cn.iocoder.yudao.module.infra.framework.file.core.client; | 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,13 +40,26 @@ public interface FileClient { | ||
| 42 | */ | 40 | */ |
| 43 | byte[] getContent(String path) throws Exception; | 41 | byte[] getContent(String path) throws Exception; |
| 44 | 42 | ||
| 43 | + // ========== 文件签名,目前仅 S3 支持 ========== | ||
| 44 | + | ||
| 45 | /** | 45 | /** |
| 46 | - * 获得文件预签名地址 | 46 | + * 获得文件预签名地址,用于上传 |
| 47 | * | 47 | * |
| 48 | * @param path 相对路径 | 48 | * @param path 相对路径 |
| 49 | * @return 文件预签名地址 | 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 | throw new UnsupportedOperationException("不支持的操作"); | 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 | package cn.iocoder.yudao.module.infra.framework.file.core.client.s3; | 1 | package cn.iocoder.yudao.module.infra.framework.file.core.client.s3; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.io.IoUtil; | 3 | import cn.hutool.core.io.IoUtil; |
| 4 | +import cn.hutool.core.util.BooleanUtil; | ||
| 4 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 5 | import cn.hutool.http.HttpUtil; | 6 | import cn.hutool.http.HttpUtil; |
| 7 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | ||
| 6 | import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; | 8 | import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; |
| 7 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; | 9 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; |
| 8 | import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; | 10 | import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; |
| @@ -15,9 +17,11 @@ import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; | @@ -15,9 +17,11 @@ import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; | ||
| 15 | import software.amazon.awssdk.services.s3.model.GetObjectRequest; | 17 | import software.amazon.awssdk.services.s3.model.GetObjectRequest; |
| 16 | import software.amazon.awssdk.services.s3.model.PutObjectRequest; | 18 | import software.amazon.awssdk.services.s3.model.PutObjectRequest; |
| 17 | import software.amazon.awssdk.services.s3.presigner.S3Presigner; | 19 | import software.amazon.awssdk.services.s3.presigner.S3Presigner; |
| 20 | +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; | ||
| 18 | import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; | 21 | import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; |
| 19 | 22 | ||
| 20 | import java.net.URI; | 23 | import java.net.URI; |
| 24 | +import java.net.URL; | ||
| 21 | import java.time.Duration; | 25 | import java.time.Duration; |
| 22 | 26 | ||
| 23 | /** | 27 | /** |
| @@ -27,6 +31,8 @@ import java.time.Duration; | @@ -27,6 +31,8 @@ import java.time.Duration; | ||
| 27 | */ | 31 | */ |
| 28 | public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | 32 | public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { |
| 29 | 33 | ||
| 34 | + private static final Duration EXPIRATION_DEFAULT = Duration.ofHours(24); | ||
| 35 | + | ||
| 30 | private S3Client client; | 36 | private S3Client client; |
| 31 | private S3Presigner presigner; | 37 | private S3Presigner presigner; |
| 32 | 38 | ||
| @@ -75,7 +81,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | @@ -75,7 +81,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 75 | // 上传文件 | 81 | // 上传文件 |
| 76 | client.putObject(putRequest, RequestBody.fromBytes(content)); | 82 | client.putObject(putRequest, RequestBody.fromBytes(content)); |
| 77 | // 拼接返回路径 | 83 | // 拼接返回路径 |
| 78 | - return config.getDomain() + "/" + path; | 84 | + return presignGetUrl(path, null); |
| 79 | } | 85 | } |
| 80 | 86 | ||
| 81 | @Override | 87 | @Override |
| @@ -97,23 +103,38 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | @@ -97,23 +103,38 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 97 | } | 103 | } |
| 98 | 104 | ||
| 99 | @Override | 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 | .signatureDuration(expiration) | 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,6 +73,15 @@ public class S3FileClientConfig implements FileClientConfig { | ||
| 73 | @NotNull(message = "enablePathStyleAccess 不能为空") | 73 | @NotNull(message = "enablePathStyleAccess 不能为空") |
| 74 | private Boolean enablePathStyleAccess; | 74 | private Boolean enablePathStyleAccess; |
| 75 | 75 | ||
| 76 | + /** | ||
| 77 | + * 是否公开访问 | ||
| 78 | + * | ||
| 79 | + * true:公开访问,所有人都可以访问 | ||
| 80 | + * false:私有访问,只有配置的 accessKey 才可以访问 | ||
| 81 | + */ | ||
| 82 | + @NotNull(message = "是否公开访问不能为空") | ||
| 83 | + private Boolean enablePublicAccess; | ||
| 84 | + | ||
| 76 | @SuppressWarnings("RedundantIfStatement") | 85 | @SuppressWarnings("RedundantIfStatement") |
| 77 | @AssertTrue(message = "domain 不能为空") | 86 | @AssertTrue(message = "domain 不能为空") |
| 78 | @JsonIgnore | 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,14 +37,22 @@ public interface FileService { | ||
| 37 | String name, String directory, String type); | 37 | String name, String directory, String type); |
| 38 | 38 | ||
| 39 | /** | 39 | /** |
| 40 | - * 生成文件预签名地址信息 | 40 | + * 生成文件预签名地址信息,用于上传 |
| 41 | * | 41 | * |
| 42 | * @param name 文件名 | 42 | * @param name 文件名 |
| 43 | * @param directory 目录 | 43 | * @param directory 目录 |
| 44 | * @return 预签名地址信息 | 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 +6,7 @@ import cn.hutool.core.lang.Assert; | ||
| 6 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | import cn.hutool.crypto.digest.DigestUtil; | 7 | import cn.hutool.crypto.digest.DigestUtil; |
| 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; | ||
| 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; | 11 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; |
| 11 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; | 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,7 +14,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned | ||
| 13 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 14 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 14 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; | 15 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; |
| 15 | import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; | 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 | import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; | 17 | import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; |
| 18 | import com.google.common.annotations.VisibleForTesting; | 18 | import com.google.common.annotations.VisibleForTesting; |
| 19 | import jakarta.annotation.Resource; | 19 | import jakarta.annotation.Resource; |
| @@ -126,19 +126,27 @@ public class FileServiceImpl implements FileService { | @@ -126,19 +126,27 @@ public class FileServiceImpl implements FileService { | ||
| 126 | 126 | ||
| 127 | @Override | 127 | @Override |
| 128 | @SneakyThrows | 128 | @SneakyThrows |
| 129 | - public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) { | 129 | + public FilePresignedUrlRespVO presignPutUrl(String name, String directory) { |
| 130 | // 1. 生成上传的 path,需要保证唯一 | 130 | // 1. 生成上传的 path,需要保证唯一 |
| 131 | String path = generateUploadPath(name, directory); | 131 | String path = generateUploadPath(name, directory); |
| 132 | 132 | ||
| 133 | // 2. 获取文件预签名地址 | 133 | // 2. 获取文件预签名地址 |
| 134 | FileClient fileClient = fileConfigService.getMasterFileClient(); | 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 | @Override | 147 | @Override |
| 141 | public Long createFile(FileCreateReqVO createReqVO) { | 148 | public Long createFile(FileCreateReqVO createReqVO) { |
| 149 | + createReqVO.setUrl(HttpUtils.removeUrlQuery(createReqVO.getUrl())); // 目的:移除私有桶情况下,URL 的签名参数 | ||
| 142 | FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); | 150 | FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); |
| 143 | fileMapper.insert(file); | 151 | fileMapper.insert(file); |
| 144 | return file.getId(); | 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,6 +9,7 @@ import jakarta.validation.Validation; | ||
| 9 | import org.junit.jupiter.api.Disabled; | 9 | import org.junit.jupiter.api.Disabled; |
| 10 | import org.junit.jupiter.api.Test; | 10 | import org.junit.jupiter.api.Test; |
| 11 | 11 | ||
| 12 | +@SuppressWarnings("resource") | ||
| 12 | public class S3FileClientTest { | 13 | public class S3FileClientTest { |
| 13 | 14 | ||
| 14 | @Test | 15 | @Test |
| @@ -70,6 +71,7 @@ public class S3FileClientTest { | @@ -70,6 +71,7 @@ public class S3FileClientTest { | ||
| 70 | config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); | 71 | config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP"); |
| 71 | config.setBucket("ruoyi-vue-pro"); | 72 | config.setBucket("ruoyi-vue-pro"); |
| 72 | config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn | 73 | config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名,则可以设置。http://static.yudao.iocoder.cn |
| 74 | + config.setEnablePathStyleAccess(false); | ||
| 73 | // 默认上海的 endpoint | 75 | // 默认上海的 endpoint |
| 74 | config.setEndpoint("s3-cn-south-1.qiniucs.com"); | 76 | config.setEndpoint("s3-cn-south-1.qiniucs.com"); |
| 75 | 77 | ||
| @@ -78,6 +80,32 @@ public class S3FileClientTest { | @@ -78,6 +80,32 @@ public class S3FileClientTest { | ||
| 78 | } | 80 | } |
| 79 | 81 | ||
| 80 | @Test | 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 | @Disabled // 华为云存储,如果要集成测试,可以注释本行 | 109 | @Disabled // 华为云存储,如果要集成测试,可以注释本行 |
| 82 | public void testHuaweiCloud() throws Exception { | 110 | public void testHuaweiCloud() throws Exception { |
| 83 | S3FileClientConfig config = new S3FileClientConfig(); | 111 | S3FileClientConfig config = new S3FileClientConfig(); |
| @@ -93,7 +121,7 @@ public class S3FileClientTest { | @@ -93,7 +121,7 @@ public class S3FileClientTest { | ||
| 93 | testExecuteUpload(config); | 121 | testExecuteUpload(config); |
| 94 | } | 122 | } |
| 95 | 123 | ||
| 96 | - private void testExecuteUpload(S3FileClientConfig config) throws Exception { | 124 | + private void testExecuteUpload(S3FileClientConfig config) { |
| 97 | // 校验配置 | 125 | // 校验配置 |
| 98 | ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); | 126 | ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config); |
| 99 | // 创建 Client | 127 | // 创建 Client |