fix: 补齐本地回放对象存储部署适配器
Some checks failed
verify / booking-verify (push) Has been cancelled

This commit is contained in:
鲨鱼辣椒
2026-08-20 17:52:48 +08:00
parent cec6828479
commit 29db7da4a6

View File

@@ -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;
}
}