diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java index 509b6d36..b14b828f 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java @@ -105,9 +105,22 @@ public class PluginController extends BaseCurdController return Result.ok(pluginService.preparePluginsForCurrentUser(plugins, true, false)); } + /** + * 按分类分页查询插件,并支持按名称模糊查询。 + * + * @param request 当前请求 + * @param sortKey 排序字段 + * @param sortType 排序方向 + * @param pageNumber 页码 + * @param pageSize 每页数量 + * @param category 分类 ID,0 表示全部分类 + * @param name 插件名称关键字 + * @return 插件分页结果 + */ @GetMapping("/pageByCategory") @SaCheckPermission("/api/v1/plugin/query") - public Result> pageByCategory(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize, int category) { + public Result> pageByCategory(HttpServletRequest request, String sortKey, String sortType, + Long pageNumber, Long pageSize, int category, String name) { if (pageNumber == null || pageNumber < 1) { pageNumber = 1L; } @@ -120,7 +133,7 @@ public class PluginController extends BaseCurdController queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper)); } else { - Result> result = pluginService.pageByCategory(pageNumber, pageSize, category); + Result> result = pluginService.pageByCategory(pageNumber, pageSize, category, name); if (result != null && result.getData() != null) { aiResourceCreatorNameSupport.fillPluginCreatorNames(result.getData().getRecords()); } diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java index 8551e592..d38a7064 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginService.java @@ -1,5 +1,6 @@ package tech.easyflow.ai.service; +import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.service.IService; import tech.easyflow.ai.entity.Plugin; import tech.easyflow.common.domain.Result; @@ -20,7 +21,16 @@ public interface PluginService extends IService { List getList(); - Result pageByCategory(Long pageNumber, Long pageSize, int category); + /** + * 按分类分页查询插件。 + * + * @param pageNumber 页码 + * @param pageSize 每页数量 + * @param category 分类 ID + * @param name 插件名称关键字 + * @return 插件分页结果 + */ + Result> pageByCategory(Long pageNumber, Long pageSize, int category, String name); boolean updatePlugin(Plugin plugin); diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java index e14eb77e..b66d969c 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java @@ -132,7 +132,7 @@ public class PluginServiceImpl extends ServiceImpl impleme } @Override - public Result> pageByCategory(Long pageNumber, Long pageSize, int category) { + public Result> pageByCategory(Long pageNumber, Long pageSize, int category, String name) { RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("PLUGIN"); QueryWrapper queryWrapper = QueryWrapper.create().select(PluginCategoryMapping::getPluginId) .eq(PluginCategoryMapping::getCategoryId, category); @@ -158,7 +158,8 @@ public class PluginServiceImpl extends ServiceImpl impleme return Result.ok(new Page<>(Collections.emptyList(), pageNumber, pageSize, 0L)); } - List totalPlugins = preparePluginsForCurrentUser(queryPluginsByIds(visiblePluginIds), true, false); + List totalPlugins = preparePluginsForCurrentUser( + queryPluginsByIds(visiblePluginIds, name), true, false); int fromIndex = Math.max(0, Math.toIntExact((pageNumber - 1) * pageSize)); if (fromIndex >= totalPlugins.size()) { return Result.ok(new Page<>(Collections.emptyList(), pageNumber, pageSize, totalPlugins.size())); @@ -251,11 +252,21 @@ public class PluginServiceImpl extends ServiceImpl impleme return pluginMapper.selectListByQueryAs(creatorPluginWrapper, BigInteger.class); } - private List queryPluginsByIds(List pluginIds) { + /** + * 按给定顺序查询插件,并按名称关键字过滤。 + * + * @param pluginIds 插件 ID 列表 + * @param name 插件名称关键字 + * @return 保持输入 ID 顺序的插件列表 + */ + private List queryPluginsByIds(List pluginIds, String name) { if (CollectionUtil.isEmpty(pluginIds)) { return Collections.emptyList(); } QueryWrapper queryPluginWrapper = QueryWrapper.create().select().in(Plugin::getId, pluginIds); + if (name != null && !name.isBlank()) { + queryPluginWrapper.like(Plugin::getName, name.trim()); + } List plugins = pluginMapper.selectListWithRelationsByQuery(queryPluginWrapper); Map pluginMap = plugins.stream().collect(Collectors.toMap( Plugin::getId, diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/service/impl/PluginServicePageQueryTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/service/impl/PluginServicePageQueryTest.java new file mode 100644 index 00000000..6f851b49 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/service/impl/PluginServicePageQueryTest.java @@ -0,0 +1,83 @@ +package tech.easyflow.ai.service.impl; + +import com.mybatisflex.core.query.QueryWrapper; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.ai.mapper.PluginCategoryMappingMapper; +import tech.easyflow.ai.mapper.PluginMapper; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * 插件分类分页查询测试。 + */ +public class PluginServicePageQueryTest { + + /** + * 验证具体分类下仍会按插件名称过滤。 + */ + @Test + public void shouldFilterCategorizedPluginsByName() { + PluginMapper pluginMapper = mock(PluginMapper.class); + PluginCategoryMappingMapper mappingMapper = mock(PluginCategoryMappingMapper.class); + CategoryPermissionService categoryPermissionService = mock(CategoryPermissionService.class); + RoleCategoryAccessSnapshot access = mock(RoleCategoryAccessSnapshot.class); + PluginServiceImpl service = new PluginServiceImpl(); + service.pluginMapper = pluginMapper; + service.pluginCategoryMappingMapper = mappingMapper; + setField(service, "categoryPermissionService", categoryPermissionService); + + when(categoryPermissionService.getCurrentAccess("PLUGIN")).thenReturn(access); + when(access.isRestricted()).thenReturn(false); + when(mappingMapper.selectListByQueryAs(any(QueryWrapper.class), eq(BigInteger.class))) + .thenReturn(List.of(BigInteger.ONE)); + when(pluginMapper.selectListWithRelationsByQuery(any(QueryWrapper.class))) + .thenReturn(Collections.emptyList()); + + service.pageByCategory(1L, 12L, 7, " 1213 "); + + ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(QueryWrapper.class); + verify(pluginMapper).selectListWithRelationsByQuery(queryCaptor.capture()); + String sql = queryCaptor.getValue().toSQL().toLowerCase(Locale.ROOT); + Assert.assertTrue(sql.contains("name")); + Assert.assertTrue(sql.contains("like")); + Assert.assertTrue(sql.contains("1213")); + } + + /** + * 通过反射设置私有依赖。 + * + * @param target 目标对象 + * @param fieldName 字段名称 + * @param value 字段值 + */ + private static void setField(Object target, String fieldName, Object value) { + Class current = target.getClass(); + while (current != null) { + try { + java.lang.reflect.Field field = current.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + return; + } catch (NoSuchFieldException ignored) { + current = current.getSuperclass(); + } catch (IllegalAccessException e) { + throw new IllegalStateException("设置测试字段失败: " + fieldName, e); + } + } + throw new IllegalArgumentException("未找到字段: " + fieldName); + } +} diff --git a/easyflow-ui-admin/app/src/router/__tests__/detail-return-navigation.test.ts b/easyflow-ui-admin/app/src/router/__tests__/detail-return-navigation.test.ts index 27725c1a..8131f70b 100644 --- a/easyflow-ui-admin/app/src/router/__tests__/detail-return-navigation.test.ts +++ b/easyflow-ui-admin/app/src/router/__tests__/detail-return-navigation.test.ts @@ -47,12 +47,19 @@ describe('detail return navigation', () => { it('returns plugin tool editing to its plugin tool list with a safe fallback', () => { const editSource = readViewSource('ai/plugin/PluginToolEdit.vue'); + const toolsSource = readViewSource('ai/plugin/PluginTools.vue'); + const pluginListSource = readViewSource('ai/plugin/Plugin.vue'); const listSource = readViewSource('ai/plugin/PluginToolTable.vue'); expect(editSource).not.toMatch(/router\.(?:back|go)\s*\(/); expect(editSource).toContain("path: '/ai/plugin/tools'"); expect(editSource).toContain("path: '/ai/plugin'"); + expect(editSource).toContain('buildPluginToolsRouteQueryFromEdit'); + expect(toolsSource).toContain('parsePluginToolsReturnState'); + expect(toolsSource).toContain('buildPluginListRouteQuery'); + expect(pluginListSource).toContain('buildPluginToolsReturnQuery'); expect(listSource).toContain('pluginId: props.pluginId'); + expect(listSource).toContain('...route.query'); }); it('returns execution steps to the filtered execution record list', () => { diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue b/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue index ca2be18a..36265ec8 100644 --- a/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue +++ b/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue @@ -1,11 +1,13 @@ @@ -239,6 +320,7 @@ const handleClickCategory = (item: PluginCategory) => {
{ :menus="categoryList" :control-btns="controlBtns" :footer-button="footerButton" - default-selected="0" + :default-selected="selectedCategoryId" @change="handleClickCategory" />
@@ -261,9 +343,11 @@ const handleClickCategory = (item: PluginCategory) => {