This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package cn.nianxx.thhotel.integrations.storage.aliyunoss.service.impl;
|
||||
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.request.ObjectStorageReadRequest;
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.request.ObjectStoragePutRequest;
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.result.ObjectStorageReadResult;
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.result.ObjectStoragePutResult;
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.service.ObjectStorageService;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Objects;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* local/local-replay 专用文件对象存储。
|
||||
*
|
||||
* <p>对象 key 只能落在配置的单一根目录下;返回 file URL 供同一进程内的 booking worker 读取,
|
||||
* 不提供目录浏览或任意路径读取能力。</p>
|
||||
*/
|
||||
@Service
|
||||
@ConditionalOnProperty(
|
||||
prefix = "debug.agentbus-eml-replay",
|
||||
name = "local-storage-enabled",
|
||||
havingValue = "true")
|
||||
public class LocalReplayObjectStorageService implements ObjectStorageService {
|
||||
|
||||
private final Path root;
|
||||
|
||||
public LocalReplayObjectStorageService(
|
||||
cn.nianxx.thhotel.platform.debug.service.impl.AgentBusEmlReplayProperties properties) {
|
||||
String configured = properties.getLocalStoragePath();
|
||||
if (configured == null || configured.isBlank()) {
|
||||
throw new IllegalStateException("debug.agentbus-eml-replay.local-storage-path 不能为空。");
|
||||
}
|
||||
this.root = Path.of(configured).toAbsolutePath().normalize();
|
||||
try {
|
||||
Files.createDirectories(root);
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("本地回放对象存储目录无法创建。", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectStoragePutResult putObject(ObjectStoragePutRequest request) {
|
||||
Objects.requireNonNull(request, "对象存储请求不能为空");
|
||||
Path target = safeKey(request.objectKey());
|
||||
try {
|
||||
Files.createDirectories(target.getParent());
|
||||
byte[] content = request.content() == null ? new byte[0] : request.content();
|
||||
Files.write(target, content, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING,
|
||||
StandardOpenOption.WRITE);
|
||||
return new ObjectStoragePutResult(
|
||||
request.objectKey(), target.toUri().toString(), request.contentType(), (long) content.length);
|
||||
} catch (IOException exception) {
|
||||
throw new ObjectStorageException("本地回放对象写入失败。", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectStorageReadResult readObject(ObjectStorageReadRequest request) {
|
||||
if (request == null || request.externalUrl() == null || request.externalUrl().isBlank()) {
|
||||
throw new ObjectStorageException("本地回放对象读取地址不能为空。");
|
||||
}
|
||||
Path target;
|
||||
try {
|
||||
target = safeUri(request.externalUrl());
|
||||
} catch (RuntimeException exception) {
|
||||
throw new ObjectStorageException("本地回放对象读取地址无效。", exception);
|
||||
}
|
||||
try {
|
||||
long maxBytes = request.maxBytes() == null || request.maxBytes() <= 0
|
||||
? 10L * 1024L * 1024L
|
||||
: request.maxBytes();
|
||||
if (Files.size(target) > maxBytes) {
|
||||
throw new ObjectStorageException("本地回放对象超过读取大小限制。");
|
||||
}
|
||||
byte[] content = Files.readAllBytes(target);
|
||||
return new ObjectStorageReadResult(content, null, (long) content.length);
|
||||
} catch (IOException exception) {
|
||||
throw new ObjectStorageException("本地回放对象读取失败。", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteObject(String objectKey) {
|
||||
Path target = safeKey(objectKey);
|
||||
if (target.equals(root) || objectKey.endsWith("/") || objectKey.contains("*") || objectKey.contains("?")) {
|
||||
throw new ObjectStorageException("本地回放对象删除目标格式错误。");
|
||||
}
|
||||
try {
|
||||
Files.deleteIfExists(target);
|
||||
} catch (IOException exception) {
|
||||
throw new ObjectStorageException("本地回放对象删除失败。", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private Path safeKey(String objectKey) {
|
||||
if (objectKey == null || objectKey.isBlank() || objectKey.startsWith("/")
|
||||
|| objectKey.contains("\\") || objectKey.contains("\u0000")) {
|
||||
throw new ObjectStorageException("本地回放对象 key 格式错误。");
|
||||
}
|
||||
Path target = root.resolve(objectKey).normalize();
|
||||
if (!target.startsWith(root) || target.equals(root)) {
|
||||
throw new ObjectStorageException("本地回放对象 key 越过存储根目录。");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private Path safeUri(String externalUrl) {
|
||||
URI uri = URI.create(externalUrl);
|
||||
if (!"file".equalsIgnoreCase(uri.getScheme()) || uri.getQuery() != null || uri.getFragment() != null) {
|
||||
throw new ObjectStorageException("本地回放对象只允许 file URL。");
|
||||
}
|
||||
Path target = Path.of(uri).toAbsolutePath().normalize();
|
||||
if (!target.startsWith(root) || target.equals(root)) {
|
||||
throw new ObjectStorageException("本地回放对象读取地址越过存储根目录。");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user