Commit 60f13815eeb1d4dbdde5c1ed49435689120be9c6
Committed by
Gitee
!1514 perf:优化 S3 对象存储操作
Merge pull request !1514 from 半栈幼儿员/perf/s3
Showing
4 changed files
with
39 additions
and
24 deletions
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
| @@ -53,6 +53,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | @@ -53,6 +53,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 53 | AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( | 53 | AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( |
| 54 | AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret())); | 54 | AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret())); |
| 55 | URI endpoint = URI.create(buildEndpoint()); | 55 | URI endpoint = URI.create(buildEndpoint()); |
| 56 | + URI presignerEndpoint = URI.create(buildPresignerEndpoint()); | ||
| 56 | S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问 | 57 | S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问 |
| 57 | .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess())) | 58 | .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess())) |
| 58 | .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57 | 59 | .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57 |
| @@ -66,7 +67,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | @@ -66,7 +67,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 66 | presigner = S3Presigner.builder() | 67 | presigner = S3Presigner.builder() |
| 67 | .credentialsProvider(credentialsProvider) | 68 | .credentialsProvider(credentialsProvider) |
| 68 | .region(region) | 69 | .region(region) |
| 69 | - .endpointOverride(endpoint) | 70 | + .endpointOverride(presignerEndpoint) |
| 70 | .serviceConfiguration(serviceConfiguration) | 71 | .serviceConfiguration(serviceConfiguration) |
| 71 | .build(); | 72 | .build(); |
| 72 | } | 73 | } |
| @@ -162,6 +163,23 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | @@ -162,6 +163,23 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 162 | } | 163 | } |
| 163 | 164 | ||
| 164 | /** | 165 | /** |
| 166 | + * presigner 节点地址 | ||
| 167 | + * | ||
| 168 | + * @return 节点地址 | ||
| 169 | + */ | ||
| 170 | + private String buildPresignerEndpoint() { | ||
| 171 | + // 补全 domain | ||
| 172 | + if (StrUtil.isEmpty(config.getDomain())) { | ||
| 173 | + config.setDomain(buildDomain()); | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + if (Boolean.TRUE.equals(config.getEnablePathStyleAccess())) { | ||
| 177 | + return StrUtil.removeSuffix(config.getDomain(), StrUtil.format("/{}", config.getBucket())); | ||
| 178 | + } | ||
| 179 | + return StrUtil.replace(config.getDomain(), StrUtil.format("://{}.", config.getBucket()), "://"); | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + /** | ||
| 165 | * 解析 AWS 区域 | 183 | * 解析 AWS 区域 |
| 166 | * 优先级:配置的 region > 从 endpoint 解析的 region > 默认值 us-east-1 | 184 | * 优先级:配置的 region > 从 endpoint 解析的 region > 默认值 us-east-1 |
| 167 | * | 185 | * |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.infra.service.file; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.infra.service.file; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.io.resource.ResourceUtil; | 3 | import cn.hutool.core.io.resource.ResourceUtil; |
| 4 | import cn.hutool.core.util.IdUtil; | 4 | import cn.hutool.core.util.IdUtil; |
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | 8 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| @@ -93,7 +94,7 @@ public class FileConfigServiceImpl implements FileConfigService { | @@ -93,7 +94,7 @@ public class FileConfigServiceImpl implements FileConfigService { | ||
| 93 | fileConfigMapper.updateById(updateObj); | 94 | fileConfigMapper.updateById(updateObj); |
| 94 | 95 | ||
| 95 | // 清空缓存 | 96 | // 清空缓存 |
| 96 | - clearCache(config.getId(), null); | 97 | + clearCache(config.getId(), config.getMaster()); |
| 97 | } | 98 | } |
| 98 | 99 | ||
| 99 | @Override | 100 | @Override |
| @@ -132,7 +133,7 @@ public class FileConfigServiceImpl implements FileConfigService { | @@ -132,7 +133,7 @@ public class FileConfigServiceImpl implements FileConfigService { | ||
| 132 | fileConfigMapper.deleteById(id); | 133 | fileConfigMapper.deleteById(id); |
| 133 | 134 | ||
| 134 | // 清空缓存 | 135 | // 清空缓存 |
| 135 | - clearCache(id, null); | 136 | + clearCache(id, config.getMaster()); |
| 136 | } | 137 | } |
| 137 | 138 | ||
| 138 | @Override | 139 | @Override |
| @@ -149,7 +150,7 @@ public class FileConfigServiceImpl implements FileConfigService { | @@ -149,7 +150,7 @@ public class FileConfigServiceImpl implements FileConfigService { | ||
| 149 | fileConfigMapper.deleteByIds(ids); | 150 | fileConfigMapper.deleteByIds(ids); |
| 150 | 151 | ||
| 151 | // 清空缓存 | 152 | // 清空缓存 |
| 152 | - ids.forEach(id -> clearCache(id, null)); | 153 | + ids.forEach(id -> clearCache(id, false)); |
| 153 | } | 154 | } |
| 154 | 155 | ||
| 155 | /** | 156 | /** |
| @@ -191,7 +192,7 @@ public class FileConfigServiceImpl implements FileConfigService { | @@ -191,7 +192,7 @@ public class FileConfigServiceImpl implements FileConfigService { | ||
| 191 | validateFileConfigExists(id); | 192 | validateFileConfigExists(id); |
| 192 | // 上传文件 | 193 | // 上传文件 |
| 193 | byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); | 194 | byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); |
| 194 | - return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg"); | 195 | + return getFileClient(id).upload(content, "public" + StrUtil.SLASH + IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg"); |
| 195 | } | 196 | } |
| 196 | 197 | ||
| 197 | @Override | 198 | @Override |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
| @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.service.file; | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.service.file; | ||
| 3 | import cn.hutool.core.date.LocalDateTimeUtil; | 3 | import cn.hutool.core.date.LocalDateTimeUtil; |
| 4 | import cn.hutool.core.io.FileUtil; | 4 | import cn.hutool.core.io.FileUtil; |
| 5 | import cn.hutool.core.lang.Assert; | 5 | import cn.hutool.core.lang.Assert; |
| 6 | +import cn.hutool.core.util.RandomUtil; | ||
| 6 | import cn.hutool.core.util.StrUtil; | 7 | import cn.hutool.core.util.StrUtil; |
| 7 | import cn.hutool.crypto.digest.DigestUtil; | 8 | import cn.hutool.crypto.digest.DigestUtil; |
| 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| @@ -101,17 +102,12 @@ public class FileServiceImpl implements FileService { | @@ -101,17 +102,12 @@ public class FileServiceImpl implements FileService { | ||
| 101 | } | 102 | } |
| 102 | String suffix = null; | 103 | String suffix = null; |
| 103 | if (PATH_SUFFIX_TIMESTAMP_ENABLE) { | 104 | if (PATH_SUFFIX_TIMESTAMP_ENABLE) { |
| 104 | - suffix = String.valueOf(System.currentTimeMillis()); | 105 | + suffix = String.valueOf(System.currentTimeMillis()) + RandomUtil.randomInt(10000, 100000); |
| 105 | } | 106 | } |
| 106 | 107 | ||
| 107 | // 2.1 先拼接 suffix 后缀 | 108 | // 2.1 先拼接 suffix 后缀 |
| 108 | if (StrUtil.isNotEmpty(suffix)) { | 109 | if (StrUtil.isNotEmpty(suffix)) { |
| 109 | - String ext = FileUtil.extName(name); | ||
| 110 | - if (StrUtil.isNotEmpty(ext)) { | ||
| 111 | - name = FileUtil.mainName(name) + StrUtil.C_UNDERLINE + suffix + StrUtil.DOT + ext; | ||
| 112 | - } else { | ||
| 113 | - name = name + StrUtil.C_UNDERLINE + suffix; | ||
| 114 | - } | 110 | + name = suffix + StrUtil.SLASH + name; |
| 115 | } | 111 | } |
| 116 | // 2.2 再拼接 prefix 前缀 | 112 | // 2.2 再拼接 prefix 前缀 |
| 117 | if (StrUtil.isNotEmpty(prefix)) { | 113 | if (StrUtil.isNotEmpty(prefix)) { |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
| @@ -93,7 +93,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -93,7 +93,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 93 | String url = randomString(); | 93 | String url = randomString(); |
| 94 | AtomicReference<String> pathRef = new AtomicReference<>(); | 94 | AtomicReference<String> pathRef = new AtomicReference<>(); |
| 95 | when(client.upload(same(content), argThat(path -> { | 95 | when(client.upload(same(content), argThat(path -> { |
| 96 | - assertTrue(path.matches(directory + "/\\d{8}/" + name + "_\\d+.jpg")); | 96 | + assertTrue(path.matches(directory + "/\\d{8}/\\d+/" + name + ".jpg")); |
| 97 | pathRef.set(path); | 97 | pathRef.set(path); |
| 98 | return true; | 98 | return true; |
| 99 | }), eq(type))).thenReturn(url); | 99 | }), eq(type))).thenReturn(url); |
| @@ -125,7 +125,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -125,7 +125,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 125 | String url = randomString(); | 125 | String url = randomString(); |
| 126 | AtomicReference<String> pathRef = new AtomicReference<>(); | 126 | AtomicReference<String> pathRef = new AtomicReference<>(); |
| 127 | when(client.upload(same(content), argThat(path -> { | 127 | when(client.upload(same(content), argThat(path -> { |
| 128 | - assertTrue(path.matches("\\d{8}/6318848e882d8a7e7e82789d87608f684ee52d41966bfc8cad3ce15aad2b970e_\\d+\.jpg")); | 128 | + assertTrue(path.matches("\\d{8}/\\d+/6318848e882d8a7e7e82789d87608f684ee52d41966bfc8cad3ce15aad2b970e\.jpg")); |
| 129 | pathRef.set(path); | 129 | pathRef.set(path); |
| 130 | return true; | 130 | return true; |
| 131 | }), eq(type))).thenReturn(url); | 131 | }), eq(type))).thenReturn(url); |
| @@ -200,10 +200,10 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -200,10 +200,10 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 200 | String path = fileService.generateUploadPath(name, directory); | 200 | String path = fileService.generateUploadPath(name, directory); |
| 201 | 201 | ||
| 202 | // 断言 | 202 | // 断言 |
| 203 | - // 格式为:avatar/yyyyMMdd/test_timestamp.jpg | 203 | + // 格式为:avatar/yyyyMMdd/timestamp/test.jpg |
| 204 | assertTrue(path.startsWith(directory + "/")); | 204 | assertTrue(path.startsWith(directory + "/")); |
| 205 | // 包含日期格式:8 位数字,如 20240517 | 205 | // 包含日期格式:8 位数字,如 20240517 |
| 206 | - assertTrue(path.matches(directory + "/\\d{8}/test_\\d+\.jpg")); | 206 | + assertTrue(path.matches(directory + "/\\d{8}/\\d+/test\.jpg")); |
| 207 | } | 207 | } |
| 208 | 208 | ||
| 209 | @Test | 209 | @Test |
| @@ -236,9 +236,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -236,9 +236,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 236 | String path = fileService.generateUploadPath(name, directory); | 236 | String path = fileService.generateUploadPath(name, directory); |
| 237 | 237 | ||
| 238 | // 断言 | 238 | // 断言 |
| 239 | - // 格式为:avatar/test_timestamp.jpg | 239 | + // 格式为:avatar/timestamp/test.jpg |
| 240 | assertTrue(path.startsWith(directory + "/")); | 240 | assertTrue(path.startsWith(directory + "/")); |
| 241 | - assertTrue(path.matches(directory + "/test_\\d+\.jpg")); | 241 | + assertTrue(path.matches(directory + "/\\d+/test\.jpg")); |
| 242 | } | 242 | } |
| 243 | 243 | ||
| 244 | @Test | 244 | @Test |
| @@ -269,9 +269,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -269,9 +269,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 269 | String path = fileService.generateUploadPath(name, directory); | 269 | String path = fileService.generateUploadPath(name, directory); |
| 270 | 270 | ||
| 271 | // 断言 | 271 | // 断言 |
| 272 | - // 格式为:avatar/yyyyMMdd/test_timestamp | 272 | + // 格式为:avatar/yyyyMMdd/timestamp/test |
| 273 | assertTrue(path.startsWith(directory + "/")); | 273 | assertTrue(path.startsWith(directory + "/")); |
| 274 | - assertTrue(path.matches(directory + "/\\d{8}/test_\\d+")); | 274 | + assertTrue(path.matches(directory + "/\\d{8}/\\d+/test")); |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | @Test | 277 | @Test |
| @@ -286,8 +286,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -286,8 +286,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 286 | String path = fileService.generateUploadPath(name, directory); | 286 | String path = fileService.generateUploadPath(name, directory); |
| 287 | 287 | ||
| 288 | // 断言 | 288 | // 断言 |
| 289 | - // 格式为:yyyyMMdd/test_timestamp.jpg | ||
| 290 | - assertTrue(path.matches("\\d{8}/test_\\d+\\.jpg")); | 289 | + // 格式为:yyyyMMdd/timestamp/test.jpg |
| 290 | + assertTrue(path.matches("\\d{8}/\\d+/test\\.jpg")); | ||
| 291 | } | 291 | } |
| 292 | 292 | ||
| 293 | @Test | 293 | @Test |
| @@ -302,8 +302,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { | @@ -302,8 +302,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { | ||
| 302 | String path = fileService.generateUploadPath(name, directory); | 302 | String path = fileService.generateUploadPath(name, directory); |
| 303 | 303 | ||
| 304 | // 断言 | 304 | // 断言 |
| 305 | - // 格式为:yyyyMMdd/test_timestamp.jpg | ||
| 306 | - assertTrue(path.matches("\\d{8}/test_\\d+\\.jpg")); | 305 | + // 格式为:yyyyMMdd/timestamp/test.jpg |
| 306 | + assertTrue(path.matches("\\d{8}/\\d+/test\\.jpg")); | ||
| 307 | } | 307 | } |
| 308 | 308 | ||
| 309 | } | 309 | } |