✨ 接入账单 OSS 存储并区分生产测试目录
This commit is contained in:
@@ -3,7 +3,10 @@
|
||||
# Elasticsearch:172.19.64.13:9200
|
||||
|
||||
# 生产环境文件存储根目录
|
||||
# 上传、导入、头像及账单下载文件都会基于此目录保存
|
||||
# 上传、导入、头像及历史本地账单文件基于此目录保存;新账单文件上传 OSS
|
||||
oss:
|
||||
path-prefix: ${OSS_PATH_PREFIX:bms/prod}
|
||||
|
||||
bms:
|
||||
profile: /www/server/bms/data
|
||||
elasticsearch:
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
# OSS 自定义域名必须已绑定到下方 Bucket,且支持 HTTPS。
|
||||
oss:
|
||||
endpoint: ${OSS_ENDPOINT:https://oss.lionquexpress.com}
|
||||
bucket: ${OSS_BUCKET:lionqueen}
|
||||
# 本地、测试环境默认使用测试目录;prod 配置覆盖为 bms/prod。
|
||||
path-prefix: ${OSS_PATH_PREFIX:bms/test}
|
||||
cname: ${OSS_CNAME:true}
|
||||
access-key-id: ${OSS_ACCESS_KEY_ID:}
|
||||
access-key-secret: ${OSS_ACCESS_KEY_SECRET:}
|
||||
|
||||
# 项目相关配置
|
||||
bms:
|
||||
# 名称
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.bms.ar;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.bms.ar.domain.ArBillExport;
|
||||
import com.bms.ar.mapper.ArBillExportMapper;
|
||||
import com.bms.ar.service.impl.ArBillServiceImpl;
|
||||
import com.bms.common.config.BmsConfig;
|
||||
import com.bms.common.utils.file.OssStorage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class ArBillExportDownloadTest {
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void routesOssDownloadThroughStorage() throws Exception {
|
||||
OssStorage storage = mock(OssStorage.class);
|
||||
ArBillServiceImpl service = service("oss://lionqueen/bms/ar/bill/test.xlsx", storage);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
service.downloadExport(1L, response);
|
||||
verify(storage).download(eq("oss://lionqueen/bms/ar/bill/test.xlsx"), same(response.getOutputStream()));
|
||||
assertTrue(response.getHeader("Content-Disposition").contains("bill.xlsx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void historicalLocalBillStillDownloadsWithoutOss() throws Exception {
|
||||
String previousProfile = BmsConfig.getProfile();
|
||||
try {
|
||||
new BmsConfig().setProfile(tempDir.toString());
|
||||
Path file = tempDir.resolve("download/ar/test.xlsx");
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.write(file, new byte[] {4, 5, 6});
|
||||
OssStorage storage = mock(OssStorage.class);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
service("/profile/download/ar/test.xlsx", storage).downloadExport(1L, response);
|
||||
assertArrayEquals(new byte[] {4, 5, 6}, response.getContentAsByteArray());
|
||||
verifyNoInteractions(storage);
|
||||
} finally {
|
||||
new BmsConfig().setProfile(previousProfile);
|
||||
}
|
||||
}
|
||||
|
||||
private ArBillServiceImpl service(String storageKey, OssStorage storage) {
|
||||
ArBillExport export = new ArBillExport();
|
||||
export.setExportId(1L);
|
||||
export.setExportStatus("SUCCESS");
|
||||
export.setStorageKey(storageKey);
|
||||
export.setFileName("bill.xlsx");
|
||||
ArBillExportMapper mapper = mock(ArBillExportMapper.class);
|
||||
when(mapper.selectById(1L)).thenReturn(export);
|
||||
ArBillServiceImpl service = new ArBillServiceImpl();
|
||||
ReflectionTestUtils.setField(service, "exportMapper", mapper);
|
||||
ReflectionTestUtils.setField(service, "ossStorage", storage);
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.bms.common.utils.file;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.aliyun.oss.ClientBuilderConfiguration;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.OSSObject;
|
||||
import com.aliyun.oss.model.PutObjectRequest;
|
||||
import com.bms.common.config.OssProperties;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedConstruction;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class OssStorageTest {
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
private OssProperties properties() {
|
||||
OssProperties properties = new OssProperties();
|
||||
properties.setEndpoint("https://oss.example.test");
|
||||
properties.setBucket("test-bucket");
|
||||
properties.setAccessKeyId("test-id");
|
||||
properties.setAccessKeySecret("test-secret");
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadsPrivateObjectAndClosesClient() throws Exception {
|
||||
OSS client = mock(OSS.class);
|
||||
Path file = Files.writeString(tempDir.resolve("bill.xlsx"), "bill");
|
||||
try (MockedConstruction<OSSClientBuilder> ignored = builder(client)) {
|
||||
assertEquals("oss://test-bucket/bms/test/ar/bill.xlsx",
|
||||
new OssStorage(properties()).upload(file, "ar/bill.xlsx", "application/test"));
|
||||
ArgumentCaptor<PutObjectRequest> request = ArgumentCaptor.forClass(PutObjectRequest.class);
|
||||
verify(client).putObject(request.capture());
|
||||
assertEquals("test-bucket", request.getValue().getBucketName());
|
||||
assertEquals("bms/test/ar/bill.xlsx", request.getValue().getKey());
|
||||
assertEquals(4, request.getValue().getMetadata().getContentLength());
|
||||
assertEquals("private", request.getValue().getMetadata().getRawMetadata().get("x-oss-object-acl"));
|
||||
verify(client).shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void productionUploadUsesConfiguredPrefix() throws Exception {
|
||||
OSS client = mock(OSS.class);
|
||||
Path file = Files.writeString(tempDir.resolve("bill.xlsx"), "bill");
|
||||
OssProperties properties = properties();
|
||||
properties.setPathPrefix("bms/prod");
|
||||
try (MockedConstruction<OSSClientBuilder> ignored = builder(client)) {
|
||||
assertEquals("oss://test-bucket/bms/prod/ar/bill.xlsx",
|
||||
new OssStorage(properties).upload(file, "ar/bill.xlsx", "application/test"));
|
||||
verify(client).putObject(argThat((PutObjectRequest request) ->
|
||||
"bms/prod/ar/bill.xlsx".equals(request.getKey())));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadFailureIsPropagatedAndClientClosed() throws Exception {
|
||||
OSS client = mock(OSS.class);
|
||||
when(client.putObject(any(PutObjectRequest.class))).thenThrow(new IllegalStateException("upload failed"));
|
||||
Path file = Files.writeString(tempDir.resolve("bill.xlsx"), "bill");
|
||||
try (MockedConstruction<OSSClientBuilder> ignored = builder(client)) {
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> new OssStorage(properties()).upload(file, "ar/bill.xlsx", "application/test"));
|
||||
verify(client).shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadsStoredObjectThroughStream() throws Exception {
|
||||
OSS client = mock(OSS.class);
|
||||
OSSObject object = new OSSObject();
|
||||
object.setObjectContent(new ByteArrayInputStream(new byte[] {1, 2, 3}));
|
||||
when(client.getObject("test-bucket", "ar/bill.xlsx")).thenReturn(object);
|
||||
try (MockedConstruction<OSSClientBuilder> ignored = builder(client)) {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
new OssStorage(properties()).download("oss://test-bucket/ar/bill.xlsx", output);
|
||||
assertArrayEquals(new byte[] {1, 2, 3}, output.toByteArray());
|
||||
verify(client).shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMissingCredentialsAndForeignBucket() {
|
||||
OssStorage storage = new OssStorage(new OssProperties());
|
||||
assertThrows(ServiceException.class, () -> storage.upload(tempDir.resolve("bill.xlsx"), "ar/bill.xlsx", "application/test"));
|
||||
assertThrows(ServiceException.class,
|
||||
() -> new OssStorage(properties()).download("oss://other-bucket/ar/bill.xlsx", new ByteArrayOutputStream()));
|
||||
}
|
||||
|
||||
private MockedConstruction<OSSClientBuilder> builder(OSS client) {
|
||||
return mockConstruction(OSSClientBuilder.class, (mock, context) ->
|
||||
when(mock.build(anyString(), anyString(), anyString(), any(ClientBuilderConfiguration.class)))
|
||||
.thenReturn(client));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user