feat: 完善 Agent 标准交互与安全运行时
- 接入 AG-UI 运行投影、Turn 时间线和审批隔离 - 增加 Agent Skill 冻结绑定与运行时消费闭环 - 增加受控工作区、内置工具和私有 Artifact 生命周期
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Agent 五类产品级内置工具的类型化配置。
|
||||
*/
|
||||
public final class AgentBuiltinToolsConfig {
|
||||
|
||||
/** 当前配置结构版本。 */
|
||||
public static final int SCHEMA_VERSION = 1;
|
||||
|
||||
private final ToolSwitch read;
|
||||
private final ToolSwitch write;
|
||||
private final ToolSwitch patch;
|
||||
private final ToolSwitch shell;
|
||||
private final ToolSwitch artifactPublish;
|
||||
|
||||
/**
|
||||
* 创建内置工具配置。
|
||||
*
|
||||
* @param read 读取工具配置
|
||||
* @param write 写入工具配置
|
||||
* @param patch 补丁工具配置
|
||||
* @param shell Shell 工具配置
|
||||
* @param artifactPublish 产物发布工具配置
|
||||
*/
|
||||
public AgentBuiltinToolsConfig(ToolSwitch read,
|
||||
ToolSwitch write,
|
||||
ToolSwitch patch,
|
||||
ToolSwitch shell,
|
||||
ToolSwitch artifactPublish) {
|
||||
this.read = read;
|
||||
this.write = write;
|
||||
this.patch = patch;
|
||||
this.shell = shell;
|
||||
this.artifactPublish = artifactPublish;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回新 Agent 的安全默认配置。
|
||||
*
|
||||
* @return 五项启用且仅 Shell 要求审批的配置
|
||||
*/
|
||||
public static AgentBuiltinToolsConfig newAgentDefaults() {
|
||||
return new AgentBuiltinToolsConfig(
|
||||
new ToolSwitch(true, false),
|
||||
new ToolSwitch(true, false),
|
||||
new ToolSwitch(true, false),
|
||||
new ToolSwitch(true, true),
|
||||
new ToolSwitch(true, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回旧发布快照的无扩权兼容配置。
|
||||
*
|
||||
* @return 五项全部禁用的配置
|
||||
*/
|
||||
public static AgentBuiltinToolsConfig allDisabled() {
|
||||
ToolSwitch disabled = new ToolSwitch(false, false);
|
||||
return new AgentBuiltinToolsConfig(disabled, disabled, disabled, disabled, disabled);
|
||||
}
|
||||
|
||||
/** @return 读取工具配置 */
|
||||
public ToolSwitch read() { return read; }
|
||||
/** @return 写入工具配置 */
|
||||
public ToolSwitch write() { return write; }
|
||||
/** @return 补丁工具配置 */
|
||||
public ToolSwitch patch() { return patch; }
|
||||
/** @return Shell 工具配置 */
|
||||
public ToolSwitch shell() { return shell; }
|
||||
/** @return 产物发布工具配置 */
|
||||
public ToolSwitch artifactPublish() { return artifactPublish; }
|
||||
|
||||
/**
|
||||
* 转换为可写入 executionConfigJson 的稳定结构。
|
||||
*
|
||||
* @return 不含权限确认临时字段的安全 Map
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("schemaVersion", SCHEMA_VERSION);
|
||||
result.put("read", read.toMap());
|
||||
result.put("write", write.toMap());
|
||||
result.put("patch", patch.toMap());
|
||||
result.put("shell", shell.toMap());
|
||||
result.put("artifactPublish", artifactPublish.toMap());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个内置工具的启用与审批配置。
|
||||
*
|
||||
* @param enabled 是否启用
|
||||
* @param approvalRequired 是否要求调用前审批
|
||||
*/
|
||||
public record ToolSwitch(boolean enabled, boolean approvalRequired) {
|
||||
|
||||
/**
|
||||
* 转换为持久化结构。
|
||||
*
|
||||
* @return 工具开关 Map
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("enabled", enabled);
|
||||
result.put("approvalRequired", approvalRequired);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Agent 内置工具配置的默认值、兼容和权限统一解析器。
|
||||
*/
|
||||
@Component
|
||||
public class AgentBuiltinToolsConfigResolver {
|
||||
|
||||
/** executionConfigJson 中的内置工具字段。 */
|
||||
public static final String BUILTIN_TOOLS_KEY = "builtinTools";
|
||||
/** 超级管理员关闭 Shell 审批时提交的一次性确认字段。 */
|
||||
public static final String SHELL_RISK_CONFIRMATION_KEY = "shellApprovalRiskConfirmed";
|
||||
|
||||
private final CategoryPermissionService categoryPermissionService;
|
||||
|
||||
/**
|
||||
* 创建解析器。
|
||||
*
|
||||
* @param categoryPermissionService 平台超级管理员判定服务
|
||||
*/
|
||||
public AgentBuiltinToolsConfigResolver(CategoryPermissionService categoryPermissionService) {
|
||||
this.categoryPermissionService = categoryPermissionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为草稿详情补齐展示默认值,但不直接写回数据库。
|
||||
*
|
||||
* @param source 原执行配置
|
||||
* @return 带完整五项配置的副本
|
||||
*/
|
||||
public Map<String, Object> normalizeForDraftRead(Map<String, Object> source) {
|
||||
return replaceBuiltinTools(source, parse(source, AgentBuiltinToolsConfig.newAgentDefaults()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化一次显式草稿保存,并校验关闭 Shell 审批的权限与风险确认。
|
||||
*
|
||||
* @param source 客户端提交的执行配置
|
||||
* @param existingSource 更新前执行配置;新建时为 null
|
||||
* @param account 当前账号
|
||||
* @return 可持久化且已移除一次性确认字段的配置
|
||||
*/
|
||||
public Map<String, Object> normalizeForDraftSave(Map<String, Object> source,
|
||||
Map<String, Object> existingSource,
|
||||
LoginAccount account) {
|
||||
AgentBuiltinToolsConfig incoming = parse(source, AgentBuiltinToolsConfig.newAgentDefaults());
|
||||
AgentBuiltinToolsConfig existing = existingSource == null
|
||||
? AgentBuiltinToolsConfig.newAgentDefaults()
|
||||
: parse(existingSource, AgentBuiltinToolsConfig.newAgentDefaults());
|
||||
boolean disablesShellApproval = disablesShellApproval(incoming, existing);
|
||||
if (disablesShellApproval) {
|
||||
if (!categoryPermissionService.isSuperAdmin(account)) {
|
||||
throw new BusinessException(403, 403, "仅平台超级管理员可以关闭 Shell 调用前确认");
|
||||
}
|
||||
if (!riskConfirmed(source)) {
|
||||
throw new BusinessException(400, 400, "关闭 Shell 调用前确认前必须完成高风险确认");
|
||||
}
|
||||
}
|
||||
return replaceBuiltinTools(source, incoming);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断保存前后是否真实发生了 Shell 审批关闭变更。
|
||||
*
|
||||
* @param source 已规范化或待保存的执行配置
|
||||
* @param existingSource 保存前执行配置;新建时为 null
|
||||
* @return 从需审批或禁用状态切换到启用且免审批时为 true
|
||||
*/
|
||||
public boolean isShellApprovalDisableTransition(Map<String, Object> source,
|
||||
Map<String, Object> existingSource) {
|
||||
AgentBuiltinToolsConfig incoming = parse(source, AgentBuiltinToolsConfig.newAgentDefaults());
|
||||
AgentBuiltinToolsConfig existing = existingSource == null
|
||||
? AgentBuiltinToolsConfig.newAgentDefaults()
|
||||
: parse(existingSource, AgentBuiltinToolsConfig.newAgentDefaults());
|
||||
return disablesShellApproval(incoming, existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析草稿运行配置;缺失时使用新 Agent 默认值。
|
||||
*
|
||||
* @param source 执行配置
|
||||
* @return 类型化配置
|
||||
*/
|
||||
public AgentBuiltinToolsConfig resolveDraftRuntime(Map<String, Object> source) {
|
||||
return parse(source, AgentBuiltinToolsConfig.newAgentDefaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析发布快照;旧快照缺失内置工具字段时全部禁用。
|
||||
*
|
||||
* @param source 发布快照中的执行配置
|
||||
* @return 类型化配置
|
||||
*/
|
||||
public AgentBuiltinToolsConfig resolvePublishedRuntime(Map<String, Object> source) {
|
||||
return parse(source, AgentBuiltinToolsConfig.allDisabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化发布运行配置;旧快照缺失字段时显式写入五项禁用结果。
|
||||
*
|
||||
* @param source 发布快照执行配置
|
||||
* @return 无静默扩权的完整配置副本
|
||||
*/
|
||||
public Map<String, Object> normalizeForPublishedRuntime(Map<String, Object> source) {
|
||||
return replaceBuiltinTools(source, resolvePublishedRuntime(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断执行配置是否显式包含内置工具结构。
|
||||
*
|
||||
* @param source 执行配置
|
||||
* @return 包含时为 true
|
||||
*/
|
||||
public boolean hasBuiltinTools(Map<String, Object> source) {
|
||||
return source != null && source.containsKey(BUILTIN_TOOLS_KEY);
|
||||
}
|
||||
|
||||
private Map<String, Object> replaceBuiltinTools(Map<String, Object> source,
|
||||
AgentBuiltinToolsConfig config) {
|
||||
Map<String, Object> result = source == null ? new LinkedHashMap<>() : new LinkedHashMap<>(source);
|
||||
result.put(BUILTIN_TOOLS_KEY, config.toMap());
|
||||
return result;
|
||||
}
|
||||
|
||||
private AgentBuiltinToolsConfig parse(Map<String, Object> source, AgentBuiltinToolsConfig fallback) {
|
||||
if (source == null || !source.containsKey(BUILTIN_TOOLS_KEY)) {
|
||||
return fallback;
|
||||
}
|
||||
Map<String, Object> raw = requireMap(source.get(BUILTIN_TOOLS_KEY), "builtinTools 必须为对象");
|
||||
validateSchemaVersion(raw.get("schemaVersion"));
|
||||
return new AgentBuiltinToolsConfig(
|
||||
tool(raw, "read", fallback.read()),
|
||||
tool(raw, "write", fallback.write()),
|
||||
tool(raw, "patch", fallback.patch()),
|
||||
tool(raw, "shell", fallback.shell()),
|
||||
tool(raw, "artifactPublish", fallback.artifactPublish()));
|
||||
}
|
||||
|
||||
private boolean disablesShellApproval(AgentBuiltinToolsConfig incoming,
|
||||
AgentBuiltinToolsConfig existing) {
|
||||
return incoming.shell().enabled()
|
||||
&& !incoming.shell().approvalRequired()
|
||||
&& (existing.shell().approvalRequired() || !existing.shell().enabled());
|
||||
}
|
||||
|
||||
private void validateSchemaVersion(Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (!(value instanceof Number number)
|
||||
|| number.doubleValue() != number.intValue()
|
||||
|| number.intValue() != AgentBuiltinToolsConfig.SCHEMA_VERSION) {
|
||||
throw new BusinessException("不支持的 Agent 内置工具配置版本");
|
||||
}
|
||||
}
|
||||
|
||||
private AgentBuiltinToolsConfig.ToolSwitch tool(Map<String, Object> source,
|
||||
String key,
|
||||
AgentBuiltinToolsConfig.ToolSwitch fallback) {
|
||||
if (!source.containsKey(key)) {
|
||||
return fallback;
|
||||
}
|
||||
Map<String, Object> raw = requireMap(source.get(key), "Agent 内置工具项必须为对象: " + key);
|
||||
return new AgentBuiltinToolsConfig.ToolSwitch(
|
||||
booleanValue(raw, "enabled", fallback.enabled()),
|
||||
booleanValue(raw, "approvalRequired", fallback.approvalRequired()));
|
||||
}
|
||||
|
||||
private boolean riskConfirmed(Map<String, Object> source) {
|
||||
Map<String, Object> raw = mapValue(source == null ? null : source.get(BUILTIN_TOOLS_KEY));
|
||||
return raw != null && Boolean.TRUE.equals(raw.get(SHELL_RISK_CONFIRMATION_KEY));
|
||||
}
|
||||
|
||||
private boolean booleanValue(Map<String, Object> source, String key, boolean fallback) {
|
||||
if (!source.containsKey(key)) {
|
||||
return fallback;
|
||||
}
|
||||
Object value = source.get(key);
|
||||
if (value instanceof Boolean bool) {
|
||||
return bool;
|
||||
}
|
||||
throw new BusinessException("Agent 内置工具开关必须为布尔值");
|
||||
}
|
||||
|
||||
private Map<String, Object> requireMap(Object value, String message) {
|
||||
Map<String, Object> mapped = mapValue(value);
|
||||
if (mapped == null) {
|
||||
throw new BusinessException(message);
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private Map<String, Object> mapValue(Object value) {
|
||||
if (!(value instanceof Map<?, ?> raw)) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
raw.forEach((key, item) -> result.put(String.valueOf(key), item));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@EnableConfigurationProperties({
|
||||
AgentRuntimeProperties.class,
|
||||
AgentMediaProperties.class,
|
||||
AgentDocumentProperties.class
|
||||
AgentDocumentProperties.class,
|
||||
AgentWorkspaceProperties.class,
|
||||
AgentShellProperties.class
|
||||
})
|
||||
public class AgentModuleConfig {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import com.easyagents.agent.runtime.tool.operate.ControlledShellTool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 在应用启动后报告受控 Shell 固定白名单命令的实际可用性。
|
||||
*/
|
||||
@Component
|
||||
public class AgentShellCommandAvailabilityReporter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AgentShellCommandAvailabilityReporter.class);
|
||||
|
||||
/**
|
||||
* 检查当前进程 PATH,并报告已安装与缺失的白名单命令。
|
||||
*
|
||||
* @param event 应用就绪事件
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void report(ApplicationReadyEvent event) {
|
||||
List<String> available = new ArrayList<>();
|
||||
List<String> missing = new ArrayList<>();
|
||||
for (String command : ControlledShellTool.DEFAULT_ALLOWED_COMMANDS.stream().sorted().toList()) {
|
||||
(isAvailable(command) ? available : missing).add(command);
|
||||
}
|
||||
LOG.info("Agent controlled Shell allowlist check completed, available={}", available);
|
||||
if (!missing.isEmpty()) {
|
||||
LOG.warn("Agent controlled Shell commands are unavailable in this runtime: {}", missing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个不含路径分隔符的固定命令是否存在于当前 PATH。
|
||||
*
|
||||
* @param command 固定白名单命令
|
||||
* @return 存在可执行普通文件时为 true
|
||||
*/
|
||||
private boolean isAvailable(String command) {
|
||||
String pathValue = System.getenv("PATH");
|
||||
if (pathValue == null || pathValue.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (String directory : pathValue.split(java.io.File.pathSeparator)) {
|
||||
if (directory == null || directory.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Path executable = Path.of(directory).resolve(command);
|
||||
if (Files.isRegularFile(executable) && Files.isExecutable(executable)) {
|
||||
return true;
|
||||
}
|
||||
} catch (InvalidPathException ignored) {
|
||||
// PATH 中的无效目录仅视为不可用,避免把宿主路径写入普通日志。
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Agent 受控 Shell 的统一平台限制。
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "easyflow.agent.shell")
|
||||
public class AgentShellProperties implements InitializingBean {
|
||||
|
||||
private Duration defaultTimeout = Duration.ofSeconds(60);
|
||||
private Duration maxTimeout = Duration.ofSeconds(300);
|
||||
private int maxCommandLength = 4_096;
|
||||
private DataSize maxOutputSize = DataSize.ofMegabytes(1);
|
||||
private int maxConcurrentPerInstance = 2;
|
||||
|
||||
/** @return 默认超时 */
|
||||
public Duration getDefaultTimeout() { return defaultTimeout; }
|
||||
/** @param defaultTimeout 默认超时 */
|
||||
public void setDefaultTimeout(Duration defaultTimeout) { this.defaultTimeout = defaultTimeout; }
|
||||
/** @return 最大超时 */
|
||||
public Duration getMaxTimeout() { return maxTimeout; }
|
||||
/** @param maxTimeout 最大超时 */
|
||||
public void setMaxTimeout(Duration maxTimeout) { this.maxTimeout = maxTimeout; }
|
||||
/** @return 命令最大字符数 */
|
||||
public int getMaxCommandLength() { return maxCommandLength; }
|
||||
/** @param maxCommandLength 命令最大字符数 */
|
||||
public void setMaxCommandLength(int maxCommandLength) { this.maxCommandLength = maxCommandLength; }
|
||||
/** @return 输出最大字节数 */
|
||||
public DataSize getMaxOutputSize() { return maxOutputSize; }
|
||||
/** @param maxOutputSize 输出最大字节数 */
|
||||
public void setMaxOutputSize(DataSize maxOutputSize) { this.maxOutputSize = maxOutputSize; }
|
||||
/** @return 单实例最大并发数 */
|
||||
public int getMaxConcurrentPerInstance() { return maxConcurrentPerInstance; }
|
||||
/** @param maxConcurrentPerInstance 单实例最大并发数 */
|
||||
public void setMaxConcurrentPerInstance(int maxConcurrentPerInstance) {
|
||||
this.maxConcurrentPerInstance = maxConcurrentPerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动期校验 Shell 限制。
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (!positive(defaultTimeout) || !positive(maxTimeout)
|
||||
|| maxTimeout.compareTo(defaultTimeout) < 0) {
|
||||
throw new IllegalStateException("Shell 最大超时必须大于等于正值默认超时");
|
||||
}
|
||||
if (maxCommandLength <= 0 || maxOutputSize == null || maxOutputSize.toBytes() <= 0
|
||||
|| maxConcurrentPerInstance <= 0) {
|
||||
throw new IllegalStateException("Shell 命令长度、输出大小和并发数必须为正值");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean positive(Duration value) {
|
||||
return value != null && !value.isZero() && !value.isNegative();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Agent 单会话工作区的路径、配额与保留配置。
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "easyflow.agent.workspace")
|
||||
public class AgentWorkspaceProperties implements InitializingBean {
|
||||
|
||||
private String root = "./agent-workspaces";
|
||||
private DataSize maxTotalSize = DataSize.ofMegabytes(512);
|
||||
private DataSize maxSingleFileSize = DataSize.ofMegabytes(100);
|
||||
private int maxFileCount = 2_000;
|
||||
private DataSize maxReadSize = DataSize.ofMegabytes(2);
|
||||
private Duration retention = Duration.ofHours(24);
|
||||
private Duration cleanupInterval = Duration.ofMinutes(30);
|
||||
|
||||
/** @return 工作区根目录 */
|
||||
public String getRoot() { return root; }
|
||||
/** @param root 工作区根目录 */
|
||||
public void setRoot(String root) { this.root = root; }
|
||||
/** @return 单会话工作区总量上限 */
|
||||
public DataSize getMaxTotalSize() { return maxTotalSize; }
|
||||
/** @param maxTotalSize 单会话工作区总量上限 */
|
||||
public void setMaxTotalSize(DataSize maxTotalSize) { this.maxTotalSize = maxTotalSize; }
|
||||
/** @return 单文件大小上限 */
|
||||
public DataSize getMaxSingleFileSize() { return maxSingleFileSize; }
|
||||
/** @param maxSingleFileSize 单文件大小上限 */
|
||||
public void setMaxSingleFileSize(DataSize maxSingleFileSize) { this.maxSingleFileSize = maxSingleFileSize; }
|
||||
/** @return 文件数量上限 */
|
||||
public int getMaxFileCount() { return maxFileCount; }
|
||||
/** @param maxFileCount 文件数量上限 */
|
||||
public void setMaxFileCount(int maxFileCount) { this.maxFileCount = maxFileCount; }
|
||||
/** @return 单次读取大小上限 */
|
||||
public DataSize getMaxReadSize() { return maxReadSize; }
|
||||
/** @param maxReadSize 单次读取大小上限 */
|
||||
public void setMaxReadSize(DataSize maxReadSize) { this.maxReadSize = maxReadSize; }
|
||||
/** @return 工作区保留期 */
|
||||
public Duration getRetention() { return retention; }
|
||||
/** @param retention 工作区保留期 */
|
||||
public void setRetention(Duration retention) { this.retention = retention; }
|
||||
/** @return 清理周期 */
|
||||
public Duration getCleanupInterval() { return cleanupInterval; }
|
||||
/** @param cleanupInterval 清理周期 */
|
||||
public void setCleanupInterval(Duration cleanupInterval) { this.cleanupInterval = cleanupInterval; }
|
||||
|
||||
/**
|
||||
* 启动期校验工作区配置,避免以无界或互相矛盾的限制启动。
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (!StringUtils.hasText(root)) {
|
||||
throw new IllegalStateException("easyflow.agent.workspace.root 不能为空");
|
||||
}
|
||||
requirePositive(maxTotalSize, "max-total-size");
|
||||
requirePositive(maxSingleFileSize, "max-single-file-size");
|
||||
requirePositive(maxReadSize, "max-read-size");
|
||||
if (maxTotalSize.toBytes() < maxSingleFileSize.toBytes()) {
|
||||
throw new IllegalStateException("工作区总量上限不能小于单文件大小上限");
|
||||
}
|
||||
if (maxReadSize.toBytes() > maxSingleFileSize.toBytes()) {
|
||||
throw new IllegalStateException("工作区读取上限不能大于单文件大小上限");
|
||||
}
|
||||
if (maxFileCount <= 0 || !positive(retention) || !positive(cleanupInterval)) {
|
||||
throw new IllegalStateException("工作区文件数量、保留期和清理周期必须为正值");
|
||||
}
|
||||
}
|
||||
|
||||
private void requirePositive(DataSize value, String name) {
|
||||
if (value == null || value.toBytes() <= 0) {
|
||||
throw new IllegalStateException("easyflow.agent.workspace." + name + " 必须为正值");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean positive(Duration value) {
|
||||
return value != null && !value.isZero() && !value.isNegative();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user