重构:使用 MyBatis-Flex 迁移应用 ORM

将应用自管表的 JdbcClient 数据访问迁移为实体、Mapper、构造器查询和必要的显式 SQL。

保留 AgentScope 自管表及原有业务语义,并补充事务、查询与数据库集成测试。
This commit is contained in:
Zhu Junhao
2026-08-31 12:13:06 +08:00
parent c13302c0cb
commit e968a8ddc1
54 changed files with 3702 additions and 698 deletions

View File

@@ -0,0 +1,76 @@
package tech.easyflow.manuagent.model;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import tech.easyflow.manuagent.auth.UserService;
import tech.easyflow.manuagent.config.AppProperties;
import tech.easyflow.manuagent.entity.ModelConfigEntity;
import tech.easyflow.manuagent.mapper.AppUserMapper;
import tech.easyflow.manuagent.mapper.ModelAssignmentMapper;
import tech.easyflow.manuagent.mapper.ModelConfigMapper;
/**
* 验证模型管理接口使用最小字段投影,不把加密 API Key 读入普通请求内存。
*/
class ModelServiceQueryTest {
/**
* 模型列表只需要页面展示字段,查询 SQL 不得包含密文和密钥版本列。
*/
@Test
void shouldExcludeEncryptedKeyFromModelListQuery() {
ModelConfigMapper modelMapper = mock(ModelConfigMapper.class);
when(modelMapper.selectListByQuery(any(QueryWrapper.class)))
.thenReturn(List.of(modelViewEntity()));
ModelService service = new ModelService(
modelMapper,
mock(ModelAssignmentMapper.class),
mock(AppUserMapper.class),
mock(UserService.class),
mock(KeyCipher.class),
mock(AppProperties.class),
new ObjectMapper());
List<ModelService.ModelView> models = service.list();
ArgumentCaptor<QueryWrapper> queryCaptor = ArgumentCaptor.forClass(QueryWrapper.class);
verify(modelMapper).selectListByQuery(queryCaptor.capture());
String sql = queryCaptor.getValue().toSQL().toLowerCase(java.util.Locale.ROOT);
assertThat(models).hasSize(1);
assertThat(sql)
.contains("api_key_hint", "capabilities_json", "is_default")
.doesNotContain("api_key_ciphertext", "key_version");
}
/**
* 构造包含全部展示字段的实体,避免测试依赖数据库或模型密钥解密逻辑。
*
* @return 模拟数据库返回的安全投影实体
*/
private ModelConfigEntity modelViewEntity() {
ModelConfigEntity entity = new ModelConfigEntity();
entity.setId(UUID.randomUUID());
entity.setName("测试模型");
entity.setProvider("OPENAI_COMPATIBLE");
entity.setBaseUrl("https://example.test");
entity.setModelId("model");
entity.setApiKeyHint("••••1234");
entity.setConfigJson("{}");
entity.setCapabilitiesJson("{\"contextWindow\":8192}");
entity.setEnabled(true);
entity.setDefaultModel(true);
entity.setUpdatedAt(OffsetDateTime.now());
return entity;
}
}