Commit 46d7e9a98c1fa8115f94da691de847a950a25699

Authored by preschooler
1 parent f8ebdbdd

🎈 perf:

优化 S3 对象存储使支持服务端内网传输客户端外网传输、
修复编辑主配置无法立即生效问题
将唯一值作为文件上级路径以保留原文件名
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 }