feat: 统一列表模糊搜索行为

- 统一管理端和用户中心搜索参数及多字段包含匹配

- 修复聊天搜索竞态并优化部门重名路径展示

- 补充部门展开、模型空白词和聊天查询回归测试
This commit is contained in:
2026-08-13 22:29:59 +08:00
parent 765006747a
commit 64a85c6a5b
83 changed files with 1134 additions and 208 deletions

View File

@@ -101,6 +101,16 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
super(service);
}
/**
* 获取 Agent 列表关键字搜索字段。
*
* @return Agent 名称和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"name", "description"};
}
/**
* 获取 Agent 详情。
*

View File

@@ -97,6 +97,16 @@ public class BotController extends BaseCurdController<BotService, Bot> {
this.botMessageService = botMessageService;
}
/**
* 获取智能体列表关键字搜索字段。
*
* @return 标题和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"title", "description"};
}
@Resource
private BotPluginService botPluginService;

View File

@@ -86,6 +86,16 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
this.llmService = llmService;
}
/**
* 获取知识库列表关键字搜索字段。
*
* @return 标题和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"title", "description"};
}
@Override
protected Result<?> onSaveOrUpdateBefore(DocumentCollection entity, boolean isSave) {
normalizeVisibilityScope(entity, isSave);

View File

@@ -99,6 +99,16 @@ public class DocumentController extends BaseCurdController<DocumentService, Docu
super(service);
this.knowledgeService = knowledgeService;
}
/**
* 获取知识库文档关键字搜索字段。
*
* @return 文件标题属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"title"};
}
@PostMapping("removeDoc")
@Transactional
@SaCheckPermission("/api/v1/documentCollection/remove")
@@ -148,13 +158,19 @@ public class DocumentController extends BaseCurdController<DocumentService, Docu
@GetMapping("documentList")
@SaCheckPermission("/api/v1/documentCollection/query")
public Result<Page<Document>> documentList(@RequestParam(name="title", required = false) String fileName, @RequestParam(name="pageSize") int pageSize, @RequestParam(name = "pageNumber") int pageNumber) {
public Result<Page<Document>> documentList(
@RequestParam(name = "keyword", required = false) String keyword,
@RequestParam(name = "title", required = false) String legacyTitle,
@RequestParam(name = "pageSize") int pageSize,
@RequestParam(name = "pageNumber") int pageNumber) {
String kbSlug = RequestUtil.getParamAsString("id");
if (StringUtil.noText(kbSlug)) {
throw new BusinessException("知识库id不能为空");
}
DocumentCollection knowledge = getDocumentCollection(kbSlug, ResourceAction.READ, "无权限访问知识库");
Page<Document> documentList = documentService.getDocumentList(knowledge.getId().toString(), pageSize, pageNumber,fileName);
String effectiveKeyword = StringUtil.hasText(keyword) ? keyword : legacyTitle;
Page<Document> documentList = documentService.getDocumentList(
knowledge.getId().toString(), pageSize, pageNumber, effectiveKeyword);
return Result.ok(documentList);
}

View File

@@ -2,6 +2,7 @@ package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -114,9 +115,14 @@ public class FaqItemController extends BaseCurdController<FaqItemService, FaqIte
QueryWrapper queryWrapper = QueryWrapper.create()
.eq(FaqItem::getCollectionId, collectionId);
String question = request.getParameter("question");
if (question != null && !question.trim().isEmpty()) {
queryWrapper.like(FaqItem::getQuestion, question.trim());
String keyword = normalizeSearchKeyword(request.getParameter("keyword"));
String question = normalizeSearchKeyword(request.getParameter("question"));
if (StringUtils.hasText(keyword)) {
queryWrapper.and(buildLiteralContainsCondition(
keyword, new QueryColumn("question"), new QueryColumn("answer_text")));
} else if (StringUtils.hasText(question)) {
// 兼容旧客户端仅按问题字段搜索。
queryWrapper.and(buildLiteralContainsCondition(question, new QueryColumn("question")));
}
String categoryIdText = request.getParameter("categoryId");

View File

@@ -36,6 +36,16 @@ public class McpController extends BaseCurdController<McpService, Mcp> {
super(service);
}
/**
* 获取 MCP 列表关键字搜索字段。
*
* @return 标题和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"title", "description"};
}
@Resource
private AgentResourceReferenceService agentResourceReferenceService;
@Override

View File

@@ -193,8 +193,10 @@ public class ModelController extends BaseCurdController<ModelService, Model> {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.eq(Model::getProviderId, providerId);
queryWrapper.eq(Model::getModelType, modelType);
if (StringUtils.hasLength(selectText)) {
queryWrapper.and(ModelTableDef.MODEL.TITLE.like(selectText).or(ModelTableDef.MODEL.MODEL_NAME.like(selectText)));
String keyword = normalizeSearchKeyword(selectText);
if (StringUtils.hasText(keyword)) {
queryWrapper.and(buildLiteralContainsCondition(
keyword, ModelTableDef.MODEL.TITLE, ModelTableDef.MODEL.MODEL_NAME));
}
List<Model> totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
Map<String, List<Model>> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName));

View File

@@ -2,6 +2,7 @@ package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
@@ -58,6 +59,16 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
this.pluginService = service;
}
/**
* 获取插件列表关键字搜索字段。
*
* @return 插件名称和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"name", "description"};
}
@Resource
PluginService pluginService;
@Resource
@@ -118,7 +129,7 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
}
/**
* 按分类分页查询插件,并支持按名称模糊查询。
* 按分类分页查询插件,并支持按名称、描述模糊查询。
*
* @param request 当前请求
* @param sortKey 排序字段
@@ -126,13 +137,15 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
* @param pageNumber 页码
* @param pageSize 每页数量
* @param category 分类 ID0 表示全部分类
* @param name 插件名称关键字
* @param keyword 插件名称或描述关键字
* @param name 兼容旧客户端的插件名称关键字
* @return 插件分页结果
*/
@GetMapping("/pageByCategory")
@SaCheckPermission("/api/v1/plugin/query")
public Result<Page<Plugin>> pageByCategory(HttpServletRequest request, String sortKey, String sortType,
Long pageNumber, Long pageSize, int category, String name) {
Long pageNumber, Long pageSize, int category,
String keyword, String name) {
if (pageNumber == null || pageNumber < 1) {
pageNumber = 1L;
}
@@ -145,7 +158,10 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
} else {
Result<Page<Plugin>> result = pluginService.pageByCategory(pageNumber, pageSize, category, name);
String effectiveKeyword = normalizeSearchKeyword(
keyword == null || keyword.isBlank() ? name : keyword);
Result<Page<Plugin>> result = pluginService.pageByCategory(
pageNumber, pageSize, category, effectiveKeyword);
if (result != null && result.getData() != null) {
aiResourceCreatorNameSupport.fillPluginCreatorNames(result.getData().getRecords());
}
@@ -160,7 +176,7 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
queryWrapper.eq("publish_status", tech.easyflow.ai.enums.PublishStatus.PUBLISHED.getCode());
if (keyword != null && !keyword.isBlank()) {
queryWrapper.like("title", keyword.trim());
queryWrapper.and(buildLiteralContainsCondition(keyword, new QueryColumn("title")));
}
queryWrapper.orderBy("modified desc");
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();

View File

@@ -67,6 +67,16 @@ public class PluginItemController extends BaseCurdController<PluginItemService,
super(service);
}
/**
* 获取插件工具列表关键字搜索字段。
*
* @return 工具名称和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"name", "description"};
}
@Resource
private PluginItemService pluginItemService;

View File

@@ -45,6 +45,16 @@ public class ResourceController extends BaseCurdController<ResourceService, Reso
super(service);
}
/**
* 获取资源列表关键字搜索字段。
*
* @return 资源名称属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"resourceName"};
}
@Override
protected Result<?> onSaveOrUpdateBefore(Resource entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();

View File

@@ -6,6 +6,7 @@ import com.easyagents.core.store.DocumentStore;
import com.easyagents.core.store.StoreOptions;
import com.easyagents.core.store.StoreResult;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -47,6 +48,7 @@ import tech.easyflow.ai.vo.FaqImportResultVo;
import tech.easyflow.ai.vo.KnowledgeShareAuthContext;
import tech.easyflow.ai.vo.KnowledgeShareViewDetail;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.util.SearchKeywordUtil;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.vo.UploadResVo;
import tech.easyflow.common.web.exceptions.BusinessException;
@@ -668,9 +670,15 @@ public class ShareKnowledgeController {
faqCategoryService.ensureDefaultCategory(context.getKnowledge().getId());
QueryWrapper queryWrapper = QueryWrapper.create()
.eq(FaqItem::getCollectionId, context.getKnowledge().getId());
String keyword = request.getParameter("keyword");
String question = request.getParameter("question");
if (StringUtils.hasText(question)) {
queryWrapper.like(FaqItem::getQuestion, question.trim());
if (StringUtils.hasText(keyword)) {
String pattern = SearchKeywordUtil.literalContainsPattern(keyword);
queryWrapper.and(new QueryColumn("question").likeRaw(pattern)
.or(new QueryColumn("answer_text").likeRaw(pattern)));
} else if (StringUtils.hasText(question)) {
queryWrapper.and(new QueryColumn("question")
.likeRaw(SearchKeywordUtil.literalContainsPattern(question)));
}
String categoryId = request.getParameter("categoryId");
if (StringUtils.hasText(categoryId)) {

View File

@@ -113,6 +113,16 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
this.modelService = modelService;
}
/**
* 获取工作流列表关键字搜索字段。
*
* @return 标题和描述属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"title", "description"};
}
/**
* 查询工作流设计器初始化所需的安全选项。
*
@@ -130,14 +140,16 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
*
* @param pageNumber 页码
* @param pageSize 每页数量
* @param keyword 插件名称或描述关键字
* @return 插件安全选项分页
*/
@GetMapping("/designer/plugins")
@SaCheckPermission("/api/v1/workflow/query")
public Result<Page<WorkflowDesignerOptionsView.PluginOption>> designerPlugins(
Long pageNumber,
Long pageSize) {
return Result.ok(workflowDesignerOptionService.pagePlugins(pageNumber, pageSize));
Long pageSize,
String keyword) {
return Result.ok(workflowDesignerOptionService.pagePlugins(pageNumber, pageSize, keyword));
}
/**

View File

@@ -37,6 +37,16 @@ public class WorkflowExecResultController extends BaseCurdController<WorkflowExe
super(service);
}
/**
* 获取工作流执行记录关键字搜索字段。
*
* @return 执行 Key 属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"execKey"};
}
@GetMapping("/del")
@Transactional(rollbackFor = Exception.class)
@SaCheckPermission("/api/v1/workflow/remove")

View File

@@ -37,6 +37,16 @@ public class WorkflowExecStepController extends BaseCurdController<WorkflowExecS
super(service);
}
/**
* 获取执行步骤关键字搜索字段。
*
* @return 节点名称和节点 ID 属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"nodeName", "nodeId"};
}
@GetMapping("/getListByRecordId")
public Result<List<WorkflowExecStep>> getListByRecordId(BigInteger recordId) {
if (recordId == null) {
@@ -60,4 +70,4 @@ public class WorkflowExecStepController extends BaseCurdController<WorkflowExecS
}
return Result.ok(list);
}
}
}

View File

@@ -82,6 +82,16 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
this.workflowRunningParameterResolver = workflowRunningParameterResolver;
}
/**
* 获取定时任务关键字搜索字段。
*
* @return 任务名称和备注属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"jobName", "remark"};
}
@GetMapping("/start")
@SaCheckPermission("/api/v1/sysJob/save")
@LogRecord("启动定时任务")

View File

@@ -10,6 +10,7 @@ import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.util.SearchKeywordUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.skill.entity.SkillCategory;
@@ -69,9 +70,11 @@ public class SkillCategoryController {
queryWrapper.eq(SkillCategory::getId, entity.getId(), entity.getId() != null)
.eq(SkillCategory::getParentId, entity.getParentId(), entity.getParentId() != null)
.eq(SkillCategory::getLevelNo, entity.getLevelNo(), entity.getLevelNo() != null)
.eq(SkillCategory::getStatus, entity.getStatus(), entity.getStatus() != null)
.like(SkillCategory::getCategoryName, entity.getCategoryName(),
entity.getCategoryName() != null && !entity.getCategoryName().isBlank());
.eq(SkillCategory::getStatus, entity.getStatus(), entity.getStatus() != null);
if (entity.getCategoryName() != null && !entity.getCategoryName().isBlank()) {
queryWrapper.and("category_name LIKE ?",
SearchKeywordUtil.literalContainsPattern(entity.getCategoryName()));
}
}
RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.SKILL.getCode());
if (access.isRestricted()) {

View File

@@ -24,6 +24,7 @@ import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.util.SearchKeywordUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.skill.capability.SkillCapabilityBindingService;
@@ -159,7 +160,7 @@ public class SkillController {
.eq("source_type", sourceType, hasText(sourceType));
String keyword = hasText(displayName) ? displayName : name;
if (hasText(keyword)) {
String pattern = "%" + keyword + "%";
String pattern = SearchKeywordUtil.literalContainsPattern(keyword);
query.and("(name LIKE ? OR display_name LIKE ? OR description LIKE ?)", pattern, pattern, pattern);
}
if (hasText(capabilityType)) {
@@ -695,4 +696,5 @@ public class SkillController {
private boolean hasText(String value) {
return value != null && !value.isBlank();
}
}

View File

@@ -111,7 +111,7 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
return super.buildQueryWrapper(request);
}
String likePattern = "%" + escapeLikeKeyword(keyword.trim()) + "%";
String likePattern = buildLiteralContainsPattern(normalizeSearchKeyword(keyword));
QueryWrapper roleExistsQuery = QueryMethods.selectOne()
.from(SYS_ACCOUNT_ROLE)
.innerJoin(SYS_ROLE)
@@ -126,20 +126,7 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
.or(SYS_ACCOUNT.EMAIL.likeRaw(likePattern))
.or(QueryMethods.exists(roleExistsQuery));
return QueryWrapper.create().and(keywordCondition);
}
/**
* 转义 MySQL LIKE 模式中的特殊字符,使用户输入按普通文本匹配。
*
* @param keyword 已去除首尾空格的关键字
* @return 可安全放入 LIKE 模式的文本
*/
private String escapeLikeKeyword(String keyword) {
return keyword
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_");
return super.buildQueryWrapper(request).and(keywordCondition);
}
@Override

View File

@@ -58,6 +58,16 @@ public class SysApiKeyController extends BaseCurdController<SysApiKeyService, Sy
super(service);
}
/**
* 获取 API Key 列表关键字搜索字段。
*
* @return 名称和 Key 属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"name", "apiKey"};
}
@Resource
private SysApiKeyResourceMappingService sysApiKeyResourceMappingService;
@Resource

View File

@@ -26,6 +26,16 @@ public class SysApiKeyResourceController extends BaseCurdController<SysApiKeyRes
super(service);
}
/**
* 获取接口授权资源关键字搜索字段。
*
* @return 请求接口和标题属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"requestInterface", "title"};
}
/**
* 查询普通 API Key 接口授权资源。
*

View File

@@ -1,6 +1,7 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
@@ -89,9 +90,49 @@ public class SysDeptController extends BaseCurdController<SysDeptService, SysDep
@GetMapping("list")
public Result<List<SysDept>> list(SysDept entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
String keyword = entity == null ? "" : normalizeSearchKeyword(entity.getKeyword());
if (tech.easyflow.common.util.StringUtil.hasText(keyword)) {
queryWrapper.and(buildLiteralContainsCondition(
keyword, new QueryColumn("dept_name"), new QueryColumn("dept_code")));
}
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<SysDept> sysMenus = service.list(queryWrapper);
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
List<SysDept> matchedDepartments = service.list(queryWrapper);
if (!tech.easyflow.common.util.StringUtil.hasText(keyword)) {
return Result.ok(Tree.tryToTree(matchedDepartments, "id", "parentId"));
}
if (matchedDepartments.isEmpty()) {
return Result.ok(List.of());
}
// 搜索结果保留所有重名命中项,并补齐各自祖先节点以维持可定位的树结构。
Set<BigInteger> visibleIds = new LinkedHashSet<>();
for (SysDept department : matchedDepartments) {
visibleIds.add(department.getId());
addAncestorIds(visibleIds, department.getAncestors());
}
QueryWrapper visibleDepartmentQuery = QueryWrapper.create()
.in(SysDept::getId, visibleIds)
.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<SysDept> visibleDepartments = service.list(visibleDepartmentQuery);
return Result.ok(Tree.tryToTree(visibleDepartments, "id", "parentId"));
}
/**
* 将逗号分隔的祖先 ID 加入可见集合。
*
* @param visibleIds 可见部门 ID 集合
* @param ancestors 祖先路径
*/
private void addAncestorIds(Set<BigInteger> visibleIds, String ancestors) {
if (!tech.easyflow.common.util.StringUtil.hasText(ancestors)) {
return;
}
for (String ancestor : ancestors.split(",")) {
String normalized = ancestor.trim();
if (!normalized.isEmpty() && !"0".equals(normalized)) {
visibleIds.add(new BigInteger(normalized));
}
}
}
/**

View File

@@ -1,12 +1,16 @@
package tech.easyflow.admin.controller.system;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.relation.RelationManager;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.log.annotation.LogRecord;
@@ -20,6 +24,9 @@ import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.Date;
import static tech.easyflow.system.entity.table.SysAccountTableDef.SYS_ACCOUNT;
import static tech.easyflow.system.entity.table.SysLogTableDef.SYS_LOG;
/**
* 操作日志表 控制层。
*
@@ -53,6 +60,22 @@ public class SysLogController extends BaseCurdController<SysLogService, SysLog>
@Override
protected QueryWrapper buildQueryWrapper(HttpServletRequest request) {
QueryWrapper queryWrapper = super.buildQueryWrapper(request);
String keyword = normalizeSearchKeyword(request.getParameter("keyword"));
if (StringUtil.hasText(keyword)) {
LoginAccount account = SaTokenUtil.getLoginAccount();
String pattern = buildLiteralContainsPattern(keyword);
QueryCondition accountKeyword = SYS_ACCOUNT.LOGIN_NAME.likeRaw(pattern)
.or(SYS_ACCOUNT.NICKNAME.likeRaw(pattern));
QueryWrapper accountExists = QueryMethods.selectOne()
.from(SYS_ACCOUNT)
.where(SYS_ACCOUNT.ID.eq(SYS_LOG.ACCOUNT_ID))
.and(SYS_ACCOUNT.TENANT_ID.eq(account.getTenantId()))
.and(accountKeyword);
QueryCondition keywordCondition = SYS_LOG.ACTION_NAME.likeRaw(pattern)
.or(SYS_LOG.ACTION_IP.likeRaw(pattern))
.or(QueryMethods.exists(accountExists));
queryWrapper.and(keywordCondition);
}
Date createdStart = parseQueryTime(request.getParameter("createdStart"));
Date createdEnd = parseQueryTime(request.getParameter("createdEnd"));
if (createdStart != null && createdEnd != null && createdStart.after(createdEnd)) {

View File

@@ -41,7 +41,7 @@ public class SysPositionController extends BaseCurdController<SysPositionService
/**
* 分页查询岗位列表
* <p>
* 支持按岗位名称模糊查询,状态、编码精确查询。
* 支持按岗位名称、岗位编码统一模糊查询,状态保持精确查询。
* </p>
*
* @param request 请求对象
@@ -67,17 +67,22 @@ public class SysPositionController extends BaseCurdController<SysPositionService
.from(SYS_POSITION);
// 获取查询参数
String keyword = normalizeSearchKeyword(request.getParameter("keyword"));
String positionName = request.getParameter("positionName");
String positionCode = request.getParameter("positionCode");
String status = request.getParameter("status");
// 岗位名称 - 模糊查询
if (StringUtil.hasText(positionName)) {
queryWrapper.where(SYS_POSITION.POSITION_NAME.like(positionName));
}
// 岗位编码 - 精确查询
if (StringUtil.hasText(positionCode)) {
queryWrapper.where(SYS_POSITION.POSITION_CODE.eq(positionCode));
if (StringUtil.hasText(keyword)) {
queryWrapper.and(buildLiteralContainsCondition(
keyword, SYS_POSITION.POSITION_NAME, SYS_POSITION.POSITION_CODE));
} else {
// 兼容仍按旧参数调用的客户端。
if (StringUtil.hasText(positionName)) {
queryWrapper.and(buildLiteralContainsCondition(positionName, SYS_POSITION.POSITION_NAME));
}
if (StringUtil.hasText(positionCode)) {
queryWrapper.where(SYS_POSITION.POSITION_CODE.eq(positionCode));
}
}
// 状态 - 精确查询
if (StringUtil.hasText(status)) {
@@ -129,4 +134,4 @@ public class SysPositionController extends BaseCurdController<SysPositionService
}
return null;
}
}
}

View File

@@ -47,6 +47,16 @@ public class SysRoleController extends BaseCurdController<SysRoleService, SysRol
super(service);
}
/**
* 获取角色列表关键字搜索字段。
*
* @return 角色名称和角色标识属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"roleName", "roleKey"};
}
/**
* 查询角色表单所需的菜单和非 Bot 分类选项。
*

View File

@@ -24,6 +24,16 @@ public class SysUserFeedbackController extends BaseCurdController<SysUserFeedbac
super(service);
}
/**
* 获取用户反馈关键字搜索字段。
*
* @return 反馈内容和联系方式属性
*/
@Override
protected String[] getKeywordSearchProperties() {
return new String[]{"feedbackContent", "contactInfo"};
}
@Override
protected Result<?> onSaveOrUpdateBefore(SysUserFeedback entity, boolean isSave) {
if (!isSave) {
@@ -33,4 +43,4 @@ public class SysUserFeedbackController extends BaseCurdController<SysUserFeedbac
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
}
}

View File

@@ -205,15 +205,20 @@ public class WorkflowDesignerOptionService {
*
* @param pageNumber 页码
* @param pageSize 每页数量
* @param keyword 插件名称或描述关键字
* @return 插件安全选项分页
*/
public Page<WorkflowDesignerOptionsView.PluginOption> pagePlugins(Long pageNumber, Long pageSize) {
public Page<WorkflowDesignerOptionsView.PluginOption> pagePlugins(
Long pageNumber, Long pageSize, String keyword) {
LoginAccount account = requireAccount();
QueryWrapper wrapper = QueryWrapper.create()
.eq(Plugin::getTenantId, account.getTenantId().longValue())
.orderBy(Plugin::getCreated, false);
List<Plugin> plugins = pluginService.getMapper().selectListWithRelationsByQuery(wrapper);
List<Plugin> availablePlugins = pluginService.preparePluginsForCurrentUser(plugins, false, true);
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(java.util.Locale.ROOT);
List<Plugin> availablePlugins = pluginService.preparePluginsForCurrentUser(plugins, false, true).stream()
.filter(plugin -> matchesKeyword(normalizedKeyword, plugin.getName(), plugin.getDescription()))
.toList();
List<WorkflowDesignerOptionsView.PluginOption> options = availablePlugins.stream()
.map(this::toPluginOption)
.toList();
@@ -229,6 +234,25 @@ public class WorkflowDesignerOptionService {
);
}
/**
* 判断任一候选文本是否包含关键字。
*
* @param keyword 已归一化的小写关键字
* @param values 候选文本
* @return 空关键字或任一文本命中时返回 {@code true}
*/
private boolean matchesKeyword(String keyword, String... values) {
if (keyword == null || keyword.isEmpty()) {
return true;
}
for (String value : values) {
if (value != null && value.toLowerCase(java.util.Locale.ROOT).contains(keyword)) {
return true;
}
}
return false;
}
/**
* 查询一个插件工具的工作流节点安全配置。
*