feat: 增加智能体对话体验配置
- 支持欢迎语、猜你想问和输入提示的编辑、草稿预览与发布态展示 - 补充配置校验、发布快照持久化和发布后回显修复
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Agent 对话体验配置的规范化与校验工具。
|
||||
*/
|
||||
public final class AgentInteractionConfigSupport {
|
||||
|
||||
/** 欢迎语最大字符数。 */
|
||||
public static final int MAX_WELCOME_MESSAGE_LENGTH = 300;
|
||||
/** 猜你想问最大数量。 */
|
||||
public static final int MAX_SUGGESTED_QUESTION_COUNT = 6;
|
||||
/** 单条猜你想问最大字符数。 */
|
||||
public static final int MAX_SUGGESTED_QUESTION_LENGTH = 80;
|
||||
/** 输入提示最大字符数。 */
|
||||
public static final int MAX_INPUT_PLACEHOLDER_LENGTH = 40;
|
||||
|
||||
private AgentInteractionConfigSupport() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化并校验对话体验配置。
|
||||
*
|
||||
* @param source 原始配置
|
||||
* @return 仅包含受支持字段的稳定配置
|
||||
* @throws BusinessException 配置字段类型或内容不合法时抛出
|
||||
*/
|
||||
public static Map<String, Object> normalize(Map<String, Object> source) {
|
||||
Map<String, Object> config = source == null ? Map.of() : source;
|
||||
String welcomeMessage = optionalString(config.get("welcomeMessage"), "欢迎语");
|
||||
if (welcomeMessage.length() > MAX_WELCOME_MESSAGE_LENGTH) {
|
||||
throw new BusinessException("欢迎语不能超过 300 个字符");
|
||||
}
|
||||
|
||||
String inputPlaceholder = optionalString(config.get("inputPlaceholder"), "输入提示");
|
||||
if (inputPlaceholder.contains("\n") || inputPlaceholder.contains("\r")) {
|
||||
throw new BusinessException("输入提示仅支持单行文本");
|
||||
}
|
||||
if (inputPlaceholder.length() > MAX_INPUT_PLACEHOLDER_LENGTH) {
|
||||
throw new BusinessException("输入提示不能超过 40 个字符");
|
||||
}
|
||||
|
||||
List<String> suggestedQuestions = normalizeSuggestedQuestions(config.get("suggestedQuestions"));
|
||||
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||
normalized.put("welcomeMessage", welcomeMessage);
|
||||
normalized.put("suggestedQuestions", suggestedQuestions);
|
||||
normalized.put("inputPlaceholder", inputPlaceholder);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化猜你想问列表,过滤空项并保留原有顺序。
|
||||
*
|
||||
* @param value 原始列表值
|
||||
* @return 规范化后的问题列表
|
||||
* @throws BusinessException 列表类型、数量、长度或重复性不合法时抛出
|
||||
*/
|
||||
private static List<String> normalizeSuggestedQuestions(Object value) {
|
||||
if (value == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (!(value instanceof Collection<?> values)) {
|
||||
throw new BusinessException("猜你想问格式不正确");
|
||||
}
|
||||
List<String> normalized = new ArrayList<>();
|
||||
Set<String> uniqueQuestions = new LinkedHashSet<>();
|
||||
for (Object item : values) {
|
||||
if (!(item instanceof String question)) {
|
||||
throw new BusinessException("猜你想问仅支持文本内容");
|
||||
}
|
||||
String trimmedQuestion = question.trim();
|
||||
if (trimmedQuestion.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (trimmedQuestion.length() > MAX_SUGGESTED_QUESTION_LENGTH) {
|
||||
throw new BusinessException("单条猜你想问不能超过 80 个字符");
|
||||
}
|
||||
if (!uniqueQuestions.add(trimmedQuestion)) {
|
||||
throw new BusinessException("猜你想问不能重复");
|
||||
}
|
||||
normalized.add(trimmedQuestion);
|
||||
}
|
||||
if (normalized.size() > MAX_SUGGESTED_QUESTION_COUNT) {
|
||||
throw new BusinessException("猜你想问最多配置 6 条");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将可选字段转换为去除首尾空白的文本。
|
||||
*
|
||||
* @param value 原始字段值
|
||||
* @param fieldName 字段名称
|
||||
* @return 规范化后的文本
|
||||
* @throws BusinessException 字段不是文本时抛出
|
||||
*/
|
||||
private static String optionalString(Object value, String fieldName) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
if (!(value instanceof String text)) {
|
||||
throw new BusinessException(fieldName + "格式不正确");
|
||||
}
|
||||
return text.trim();
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,9 @@ public class Agent extends DateEntity implements VisibilityResource, Serializabl
|
||||
private Map<String, Object> memoryConfigJson = new LinkedHashMap<>();
|
||||
@Column(typeHandler = FastjsonTypeHandler.class)
|
||||
private Map<String, Object> executionConfigJson = new LinkedHashMap<>();
|
||||
/** 对话欢迎语、猜你想问和输入提示配置。 */
|
||||
@Column(typeHandler = FastjsonTypeHandler.class)
|
||||
private Map<String, Object> interactionConfigJson = new LinkedHashMap<>();
|
||||
private Integer status;
|
||||
private String visibilityScope;
|
||||
private String publishStatus;
|
||||
@@ -95,6 +98,18 @@ public class Agent extends DateEntity implements VisibilityResource, Serializabl
|
||||
public void setMemoryConfigJson(Map<String, Object> memoryConfigJson) { this.memoryConfigJson = memoryConfigJson == null ? new LinkedHashMap<>() : memoryConfigJson; }
|
||||
public Map<String, Object> getExecutionConfigJson() { return executionConfigJson; }
|
||||
public void setExecutionConfigJson(Map<String, Object> executionConfigJson) { this.executionConfigJson = executionConfigJson == null ? new LinkedHashMap<>() : executionConfigJson; }
|
||||
/**
|
||||
* 获取对话体验配置。
|
||||
*
|
||||
* @return 对话体验配置
|
||||
*/
|
||||
public Map<String, Object> getInteractionConfigJson() { return interactionConfigJson; }
|
||||
/**
|
||||
* 设置对话体验配置。
|
||||
*
|
||||
* @param interactionConfigJson 对话体验配置
|
||||
*/
|
||||
public void setInteractionConfigJson(Map<String, Object> interactionConfigJson) { this.interactionConfigJson = interactionConfigJson == null ? new LinkedHashMap<>() : interactionConfigJson; }
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
public String getVisibilityScope() { return visibilityScope; }
|
||||
|
||||
@@ -2,6 +2,7 @@ package tech.easyflow.agent.publish;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.update.UpdateChain;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
|
||||
@@ -122,32 +123,33 @@ public class AgentApprovalSubjectHandler extends AbstractAiResourceLifecycleHand
|
||||
|
||||
@Override
|
||||
protected void persistResourceState(BigInteger resourceId, PublishStatus publishStatus, BigInteger currentApprovalInstanceId) {
|
||||
Agent agent = new Agent();
|
||||
agent.setId(resourceId);
|
||||
agent.setPublishStatus(publishStatus.getCode());
|
||||
agent.setCurrentApprovalInstanceId(currentApprovalInstanceId);
|
||||
agentService.updateById(agent);
|
||||
// 生命周期操作仅更新状态字段,避免实体默认空配置覆盖 Agent 草稿配置。
|
||||
UpdateChain<Agent> updateChain = agentService.updateChain();
|
||||
updateChain.set(Agent::getPublishStatus, publishStatus.getCode());
|
||||
updateChain.set(Agent::getCurrentApprovalInstanceId, currentApprovalInstanceId);
|
||||
updateChain.eq(Agent::getId, resourceId);
|
||||
updateChain.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResource(BigInteger resourceId, Map<String, Object> resourceSnapshot, BigInteger operatorId) {
|
||||
Agent agent = new Agent();
|
||||
agent.setId(resourceId);
|
||||
agent.setPublishStatus(PublishStatus.PUBLISHED.getCode());
|
||||
agent.setPublishedSnapshotJson(resourceSnapshot);
|
||||
agent.setPublishedAt(new Date());
|
||||
agent.setPublishedBy(operatorId);
|
||||
agent.setCurrentApprovalInstanceId(null);
|
||||
agentService.updateById(agent);
|
||||
UpdateChain<Agent> updateChain = agentService.updateChain();
|
||||
updateChain.set(Agent::getPublishStatus, PublishStatus.PUBLISHED.getCode());
|
||||
updateChain.set(Agent::getPublishedSnapshotJson, resourceSnapshot);
|
||||
updateChain.set(Agent::getPublishedAt, new Date());
|
||||
updateChain.set(Agent::getPublishedBy, operatorId);
|
||||
updateChain.set(Agent::getCurrentApprovalInstanceId, null);
|
||||
updateChain.eq(Agent::getId, resourceId);
|
||||
updateChain.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void markResourceOffline(BigInteger resourceId) {
|
||||
Agent agent = new Agent();
|
||||
agent.setId(resourceId);
|
||||
agent.setPublishStatus(PublishStatus.OFFLINE.getCode());
|
||||
agent.setCurrentApprovalInstanceId(null);
|
||||
agentService.updateById(agent);
|
||||
UpdateChain<Agent> updateChain = agentService.updateChain();
|
||||
updateChain.set(Agent::getPublishStatus, PublishStatus.OFFLINE.getCode());
|
||||
updateChain.set(Agent::getCurrentApprovalInstanceId, null);
|
||||
updateChain.eq(Agent::getId, resourceId);
|
||||
updateChain.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tech.easyflow.agent.config.AgentInteractionConfigSupport;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
|
||||
import tech.easyflow.agent.entity.AgentToolBinding;
|
||||
@@ -133,6 +134,7 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
|
||||
snapshot.put("promptConfigJson", detail.getPromptConfigJson());
|
||||
snapshot.put("memoryConfigJson", detail.getMemoryConfigJson());
|
||||
snapshot.put("executionConfigJson", detail.getExecutionConfigJson());
|
||||
snapshot.put("interactionConfigJson", detail.getInteractionConfigJson());
|
||||
snapshot.put("visibilityScope", detail.getVisibilityScope());
|
||||
snapshot.put("toolBindings", snapshotToolBindings(detail.getToolBindings()));
|
||||
snapshot.put("knowledgeBindings", snapshotKnowledgeBindings(detail.getKnowledgeBindings()));
|
||||
@@ -162,6 +164,7 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
|
||||
agent.setModelId(toBigInteger(snapshot.get("modelId")));
|
||||
agent.setCategoryId(toBigInteger(snapshot.get("categoryId")));
|
||||
agent.setPublishStatus(PublishStatus.PUBLISHED.getCode());
|
||||
agent.setInteractionConfigJson(AgentInteractionConfigSupport.normalize(agent.getInteractionConfigJson()));
|
||||
agent.setPublishedSnapshotJson(snapshot);
|
||||
agent.setToolBindings(objectMapper.convertValue(snapshot.get("toolBindings"), TOOL_BINDING_LIST_TYPE));
|
||||
agent.setKnowledgeBindings(objectMapper.convertValue(snapshot.get("knowledgeBindings"), KNOWLEDGE_BINDING_LIST_TYPE));
|
||||
@@ -191,6 +194,7 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
|
||||
throw new BusinessException("Agent 模型不存在");
|
||||
}
|
||||
agent.setVisibilityScope(VisibilityScope.fromOrDefault(agent.getVisibilityScope(), VisibilityScope.PRIVATE).name());
|
||||
agent.setInteractionConfigJson(AgentInteractionConfigSupport.normalize(agent.getInteractionConfigJson()));
|
||||
}
|
||||
|
||||
private void applyDraftDefaults(Agent agent) {
|
||||
@@ -231,6 +235,7 @@ public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements
|
||||
existing.setPromptConfigJson(incoming.getPromptConfigJson());
|
||||
existing.setMemoryConfigJson(incoming.getMemoryConfigJson());
|
||||
existing.setExecutionConfigJson(incoming.getExecutionConfigJson());
|
||||
existing.setInteractionConfigJson(incoming.getInteractionConfigJson());
|
||||
existing.setStatus(incoming.getStatus() == null ? 1 : incoming.getStatus());
|
||||
existing.setVisibilityScope(incoming.getVisibilityScope());
|
||||
existing.setModified(new Date());
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package tech.easyflow.agent.config;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link AgentInteractionConfigSupport} 单元测试。
|
||||
*/
|
||||
public class AgentInteractionConfigSupportTest {
|
||||
|
||||
/**
|
||||
* 验证空配置可以转换为稳定结构。
|
||||
*/
|
||||
@Test
|
||||
public void shouldNormalizeEmptyConfig() {
|
||||
Map<String, Object> normalized = AgentInteractionConfigSupport.normalize(null);
|
||||
|
||||
Assert.assertEquals("", normalized.get("welcomeMessage"));
|
||||
Assert.assertEquals(List.of(), normalized.get("suggestedQuestions"));
|
||||
Assert.assertEquals("", normalized.get("inputPlaceholder"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证空问题会被过滤,合法问题会去除首尾空白并保持顺序。
|
||||
*/
|
||||
@Test
|
||||
public void shouldNormalizeSuggestedQuestions() {
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("welcomeMessage", " 欢迎使用\n智能助手 ");
|
||||
source.put("suggestedQuestions", List.of(" 第一个问题 ", "", " 第二个问题"));
|
||||
source.put("inputPlaceholder", " 请输入问题 ");
|
||||
|
||||
Map<String, Object> normalized = AgentInteractionConfigSupport.normalize(source);
|
||||
|
||||
Assert.assertEquals("欢迎使用\n智能助手", normalized.get("welcomeMessage"));
|
||||
Assert.assertEquals(List.of("第一个问题", "第二个问题"), normalized.get("suggestedQuestions"));
|
||||
Assert.assertEquals("请输入问题", normalized.get("inputPlaceholder"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证重复问题会被拒绝。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectDuplicateQuestionsAfterTrim() {
|
||||
Map<String, Object> source = Map.of(
|
||||
"suggestedQuestions", List.of("如何使用?", " 如何使用? ")
|
||||
);
|
||||
|
||||
assertBusinessException(() -> AgentInteractionConfigSupport.normalize(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证多行输入提示会被拒绝。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectMultilinePlaceholder() {
|
||||
Map<String, Object> source = Map.of("inputPlaceholder", "第一行\n第二行");
|
||||
|
||||
assertBusinessException(() -> AgentInteractionConfigSupport.normalize(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证问题数量不能超过上限。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectTooManyQuestions() {
|
||||
Map<String, Object> source = Map.of(
|
||||
"suggestedQuestions",
|
||||
List.of("问题1", "问题2", "问题3", "问题4", "问题5", "问题6", "问题7")
|
||||
);
|
||||
|
||||
assertBusinessException(() -> AgentInteractionConfigSupport.normalize(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言操作会抛出业务异常。
|
||||
*
|
||||
* @param action 待执行操作
|
||||
*/
|
||||
private void assertBusinessException(Runnable action) {
|
||||
try {
|
||||
action.run();
|
||||
Assert.fail("应抛出 BusinessException");
|
||||
} catch (BusinessException expected) {
|
||||
Assert.assertNotNull(expected.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,86 @@
|
||||
package tech.easyflow.agent.publish;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mybatisflex.core.update.UpdateChain;
|
||||
import com.mybatisflex.core.util.LambdaGetter;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
|
||||
import tech.easyflow.agent.service.AgentService;
|
||||
import tech.easyflow.agent.service.AgentToolBindingService;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.math.BigInteger;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link AgentApprovalSubjectHandler} 单元测试。
|
||||
*/
|
||||
public class AgentApprovalSubjectHandlerTest {
|
||||
|
||||
/**
|
||||
* 审批状态变更必须使用字段级更新,避免覆盖 Agent 草稿配置。
|
||||
*/
|
||||
@Test
|
||||
public void persistResourceStateShouldUseSelectiveUpdate() {
|
||||
AgentService agentService = mock(AgentService.class);
|
||||
UpdateChain<Agent> updateChain = prepareUpdateChain(agentService);
|
||||
AgentApprovalSubjectHandler handler = handler(agentService);
|
||||
|
||||
handler.persistResourceState(
|
||||
BigInteger.valueOf(1001),
|
||||
PublishStatus.PUBLISH_PENDING,
|
||||
BigInteger.valueOf(2001)
|
||||
);
|
||||
|
||||
verifySelectiveUpdate(updateChain, BigInteger.valueOf(1001), 2);
|
||||
verify(agentService, never()).updateById(any(Agent.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布 Agent 必须使用字段级更新,确保发布后草稿配置仍可回显。
|
||||
*/
|
||||
@Test
|
||||
public void publishResourceShouldUseSelectiveUpdate() {
|
||||
AgentService agentService = mock(AgentService.class);
|
||||
UpdateChain<Agent> updateChain = prepareUpdateChain(agentService);
|
||||
AgentApprovalSubjectHandler handler = handler(agentService);
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
snapshot.put("interactionConfigJson", Map.of("welcomeMessage", "你好"));
|
||||
|
||||
handler.publishResource(BigInteger.valueOf(1001), snapshot, BigInteger.valueOf(3001));
|
||||
|
||||
verifySelectiveUpdate(updateChain, BigInteger.valueOf(1001), 5);
|
||||
verify(agentService, never()).updateById(any(Agent.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent 下线必须使用字段级更新,避免覆盖未发布配置。
|
||||
*/
|
||||
@Test
|
||||
public void markResourceOfflineShouldUseSelectiveUpdate() {
|
||||
AgentService agentService = mock(AgentService.class);
|
||||
UpdateChain<Agent> updateChain = prepareUpdateChain(agentService);
|
||||
AgentApprovalSubjectHandler handler = handler(agentService);
|
||||
|
||||
handler.markResourceOffline(BigInteger.valueOf(1001));
|
||||
|
||||
verifySelectiveUpdate(updateChain, BigInteger.valueOf(1001), 2);
|
||||
verify(agentService, never()).updateById(any(Agent.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批删除 Agent 前必须同步清理工具绑定和知识库绑定,避免留下孤儿数据。
|
||||
*/
|
||||
@@ -39,6 +105,60 @@ public class AgentApprovalSubjectHandlerTest {
|
||||
Assert.assertEquals(1, knowledgeRemoveCalls.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用于生命周期测试的处理器。
|
||||
*
|
||||
* @param agentService Agent 服务
|
||||
* @return Agent 审批资源处理器
|
||||
*/
|
||||
private static AgentApprovalSubjectHandler handler(AgentService agentService) {
|
||||
return new AgentApprovalSubjectHandler(
|
||||
null,
|
||||
new ObjectMapper(),
|
||||
agentService,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备字段更新对象。
|
||||
*
|
||||
* @param agentService Agent 服务
|
||||
* @return 字段更新对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static UpdateChain<Agent> prepareUpdateChain(AgentService agentService) {
|
||||
UpdateChain<Agent> updateChain = mock(UpdateChain.class);
|
||||
when(agentService.updateChain()).thenReturn(updateChain);
|
||||
return updateChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验生命周期操作仅通过指定主键执行字段更新。
|
||||
*
|
||||
* @param updateChain 字段更新对象
|
||||
* @param resourceId Agent 主键
|
||||
* @param fieldCount 更新字段数量
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void verifySelectiveUpdate(UpdateChain<Agent> updateChain,
|
||||
BigInteger resourceId,
|
||||
int fieldCount) {
|
||||
verify(updateChain, times(fieldCount)).set(any(LambdaGetter.class), any());
|
||||
verify(updateChain).eq(any(LambdaGetter.class), eq(resourceId));
|
||||
verify(updateChain).update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建只统计删除调用的服务代理。
|
||||
*
|
||||
* @param type 服务接口类型
|
||||
* @param removeCalls 删除调用计数器
|
||||
* @param <T> 服务接口类型
|
||||
* @return 服务代理
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T proxy(Class<T> type, AtomicInteger removeCalls) {
|
||||
return (T) Proxy.newProxyInstance(
|
||||
|
||||
Reference in New Issue
Block a user