Commit 46d7e9a98c1fa8115f94da691de847a950a25699
1 parent
f8ebdbdd
🎈 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 | 53 | AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( |
| 54 | 54 | AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret())); |
| 55 | 55 | URI endpoint = URI.create(buildEndpoint()); |
| 56 | + URI presignerEndpoint = URI.create(buildPresignerEndpoint()); | |
| 56 | 57 | S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问 |
| 57 | 58 | .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess())) |
| 58 | 59 | .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57 |
| ... | ... | @@ -66,7 +67,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { |
| 66 | 67 | presigner = S3Presigner.builder() |
| 67 | 68 | .credentialsProvider(credentialsProvider) |
| 68 | 69 | .region(region) |
| 69 | - .endpointOverride(endpoint) | |
| 70 | + .endpointOverride(presignerEndpoint) | |
| 70 | 71 | .serviceConfiguration(serviceConfiguration) |
| 71 | 72 | .build(); |
| 72 | 73 | } |
| ... | ... | @@ -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 | 183 | * 解析 AWS 区域 |
| 166 | 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 | 2 | |
| 3 | 3 | import cn.hutool.core.io.resource.ResourceUtil; |
| 4 | 4 | import cn.hutool.core.util.IdUtil; |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| ... | ... | @@ -93,7 +94,7 @@ public class FileConfigServiceImpl implements FileConfigService { |
| 93 | 94 | fileConfigMapper.updateById(updateObj); |
| 94 | 95 | |
| 95 | 96 | // 清空缓存 |
| 96 | - clearCache(config.getId(), null); | |
| 97 | + clearCache(config.getId(), config.getMaster()); | |
| 97 | 98 | } |
| 98 | 99 | |
| 99 | 100 | @Override |
| ... | ... | @@ -132,7 +133,7 @@ public class FileConfigServiceImpl implements FileConfigService { |
| 132 | 133 | fileConfigMapper.deleteById(id); |
| 133 | 134 | |
| 134 | 135 | // 清空缓存 |
| 135 | - clearCache(id, null); | |
| 136 | + clearCache(id, config.getMaster()); | |
| 136 | 137 | } |
| 137 | 138 | |
| 138 | 139 | @Override |
| ... | ... | @@ -149,7 +150,7 @@ public class FileConfigServiceImpl implements FileConfigService { |
| 149 | 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 | 192 | validateFileConfigExists(id); |
| 192 | 193 | // 上传文件 |
| 193 | 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 | 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 | 3 | import cn.hutool.core.date.LocalDateTimeUtil; |
| 4 | 4 | import cn.hutool.core.io.FileUtil; |
| 5 | 5 | import cn.hutool.core.lang.Assert; |
| 6 | +import cn.hutool.core.util.RandomUtil; | |
| 6 | 7 | import cn.hutool.core.util.StrUtil; |
| 7 | 8 | import cn.hutool.crypto.digest.DigestUtil; |
| 8 | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| ... | ... | @@ -101,17 +102,12 @@ public class FileServiceImpl implements FileService { |
| 101 | 102 | } |
| 102 | 103 | String suffix = null; |
| 103 | 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 | 108 | // 2.1 先拼接 suffix 后缀 |
| 108 | 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 | 112 | // 2.2 再拼接 prefix 前缀 |
| 117 | 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 | 93 | String url = randomString(); |
| 94 | 94 | AtomicReference<String> pathRef = new AtomicReference<>(); |
| 95 | 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 | 97 | pathRef.set(path); |
| 98 | 98 | return true; |
| 99 | 99 | }), eq(type))).thenReturn(url); |
| ... | ... | @@ -125,7 +125,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 125 | 125 | String url = randomString(); |
| 126 | 126 | AtomicReference<String> pathRef = new AtomicReference<>(); |
| 127 | 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 | 129 | pathRef.set(path); |
| 130 | 130 | return true; |
| 131 | 131 | }), eq(type))).thenReturn(url); |
| ... | ... | @@ -200,10 +200,10 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 200 | 200 | String path = fileService.generateUploadPath(name, directory); |
| 201 | 201 | |
| 202 | 202 | // 断言 |
| 203 | - // 格式为:avatar/yyyyMMdd/test_timestamp.jpg | |
| 203 | + // 格式为:avatar/yyyyMMdd/timestamp/test.jpg | |
| 204 | 204 | assertTrue(path.startsWith(directory + "/")); |
| 205 | 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 | 209 | @Test |
| ... | ... | @@ -236,9 +236,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 236 | 236 | String path = fileService.generateUploadPath(name, directory); |
| 237 | 237 | |
| 238 | 238 | // 断言 |
| 239 | - // 格式为:avatar/test_timestamp.jpg | |
| 239 | + // 格式为:avatar/timestamp/test.jpg | |
| 240 | 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 | 244 | @Test |
| ... | ... | @@ -269,9 +269,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 269 | 269 | String path = fileService.generateUploadPath(name, directory); |
| 270 | 270 | |
| 271 | 271 | // 断言 |
| 272 | - // 格式为:avatar/yyyyMMdd/test_timestamp | |
| 272 | + // 格式为:avatar/yyyyMMdd/timestamp/test | |
| 273 | 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 | 277 | @Test |
| ... | ... | @@ -286,8 +286,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 286 | 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 | 293 | @Test |
| ... | ... | @@ -302,8 +302,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 302 | 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 | } | ... | ... |