暂存提交
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package com.baiyue.file.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
@@ -11,12 +15,15 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class LocalQualificationStorage {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocalQualificationStorage.class);
|
||||
private static final long MAX_SIZE = 5L * 1024 * 1024;
|
||||
private static final Set<String> TYPES = Set.of("image/jpeg", "image/png");
|
||||
private final Path root;
|
||||
@@ -25,6 +32,20 @@ public class LocalQualificationStorage {
|
||||
this.root = Path.of(root).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
/** 服务启动时校验存储根目录,避免健康检查正常但首次上传才暴露挂载或权限问题。 */
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
try {
|
||||
Files.createDirectories(root);
|
||||
if (!Files.isDirectory(root) || !Files.isWritable(root)) {
|
||||
throw new IOException("存储根目录不可写");
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
LOGGER.error("企业资质存储根目录不可用:{}", root, exception);
|
||||
throw new IllegalStateException("企业资质存储根目录不可用", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public StoredFile store(long tenantId, MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) throw new BizException("One business-license image is required");
|
||||
if (file.getSize() > MAX_SIZE) throw new BizException("Business-license image must not exceed 5 MB");
|
||||
@@ -40,11 +61,19 @@ public class LocalQualificationStorage {
|
||||
if (!target.startsWith(directory)) throw new BizException("Invalid upload path");
|
||||
try {
|
||||
Files.createDirectories(directory);
|
||||
file.transferTo(target);
|
||||
try (InputStream input = file.getInputStream();
|
||||
OutputStream output = Files.newOutputStream(target,
|
||||
StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||
input.transferTo(output);
|
||||
}
|
||||
return new StoredFile(target, fileName,
|
||||
"/api/merchant/v1/files/qualifications/" + fileName);
|
||||
} catch (IOException exception) {
|
||||
throw new BizException("Unable to store enterprise qualification");
|
||||
try { Files.deleteIfExists(target); } catch (IOException cleanupException) {
|
||||
exception.addSuppressed(cleanupException);
|
||||
}
|
||||
LOGGER.error("企业资质文件写入失败,tenantId={},目录={}", tenantId, directory, exception);
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "企业资质文件存储暂不可用");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,5 +32,5 @@ baiyue:
|
||||
iam:
|
||||
url: ${IAM_SERVICE_URL:}
|
||||
file:
|
||||
storage-root: ${FILE_STORAGE_ROOT:D:/baiyue-firstleg-runtime/uploads}
|
||||
storage-root: ${FILE_STORAGE_ROOT:${user.home}/baiyue-firstleg/uploads}
|
||||
management.endpoints.web.exposure.include: health,info,prometheus
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -9,6 +10,7 @@ import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@DisplayName("企业资质文件接口契约功能")
|
||||
class QualificationFileContractTest {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.baiyue.file.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
@@ -12,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@DisplayName("本地企业资质文件存储功能")
|
||||
class LocalQualificationStorageTest {
|
||||
|
||||
@TempDir
|
||||
@@ -20,6 +23,7 @@ class LocalQualificationStorageTest {
|
||||
@Test
|
||||
void storeReturnsFileServiceUrlBackedByARealTenantFile() throws Exception {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
storage.initialize();
|
||||
byte[] png = {(byte) 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x01};
|
||||
var upload = new MockMultipartFile("file", "license.png", "image/png", png);
|
||||
|
||||
@@ -39,4 +43,29 @@ class LocalQualificationStorageTest {
|
||||
assertThrows(BizException.class,
|
||||
() -> storage.load(10001L, "demo-business-license.jpg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidStorageFailsFastDuringStartup() throws Exception {
|
||||
Path invalidRoot = storageRoot.resolve("root-is-file");
|
||||
Files.writeString(invalidRoot, "not-a-directory");
|
||||
var storage = new LocalQualificationStorage(invalidRoot.toString());
|
||||
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, storage::initialize);
|
||||
|
||||
assertTrue(exception.getMessage().contains("存储根目录不可用"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeFailureReturnsServiceUnavailableInsteadOfBusinessError() throws Exception {
|
||||
var storage = new LocalQualificationStorage(storageRoot.toString());
|
||||
storage.initialize();
|
||||
Files.writeString(storageRoot.resolve("qualifications"), "阻止创建租户目录");
|
||||
byte[] png = {(byte) 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x01};
|
||||
var upload = new MockMultipartFile("file", "license.png", "image/png", png);
|
||||
|
||||
BizException exception = assertThrows(BizException.class,
|
||||
() -> storage.store(10001L, upload));
|
||||
|
||||
assertEquals(ResultCode.SERVICE_UNAVAILABLE, exception.getResultCode());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user