| | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | | @Component |
| | | @RequiredArgsConstructor |
| | | public class RsfWmsTaskTools { |
| | | |
| | | private static final Pattern TASK_CODE_PATTERN = Pattern.compile("[A-Za-z]{1,8}\\d{6,}"); |
| | | private static final Pattern TASK_CODE_DIGIT_PATTERN = Pattern.compile("\\d{8,}"); |
| | | |
| | | private final TaskService taskService; |
| | | private final TaskItemService taskItemService; |
| | | |
| | | /** |
| | | * 查询任务列表。 |
| | | * 方法要求至少带一个过滤条件,避免模型把任务表当作可直接遍历的数据源。 |
| | | * 统一任务查询。 |
| | | * 传精确 taskCode 时返回任务汇总和明细;否则按条件返回任务列表。 |
| | | */ |
| | | @Tool(name = "rsf_query_task_list", description = "只读查询工具。按任务号、状态、任务类型、源站点、目标站点等条件查询任务列表。") |
| | | public List<Map<String, Object>> queryTaskList( |
| | | @ToolParam(description = "任务号,可模糊查询") String taskCode, |
| | | @ToolParam(description = "任务状态,可选") Integer taskStatus, |
| | | @ToolParam(description = "任务类型,可选") Integer taskType, |
| | | @Tool(name = "rsf_query_task", description = "只读查询工具。支持按任务号、状态、类型、源站点、目标站点查询任务;传精确任务号时返回任务明细,不传过滤条件时返回最近任务。") |
| | | public Map<String, Object> queryTask( |
| | | @ToolParam(description = "任务号,可模糊查询;支持直接传入自然语言,工具会自动提取任务号") String taskCode, |
| | | @ToolParam(description = "任务状态,可选;空字符串或 0 视为未传") String taskStatus, |
| | | @ToolParam(description = "任务类型,可选;空字符串或 0 视为未传") String taskType, |
| | | @ToolParam(description = "源站点,可选") String orgSite, |
| | | @ToolParam(description = "目标站点,可选") String targSite, |
| | | @ToolParam(description = "返回条数,默认 10,最大 50") Integer limit) { |
| | | String normalizedTaskCode = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "任务号", 64); |
| | | @ToolParam(description = "返回条数,默认 10,最大 50;空字符串或 0 视为默认值") String limit) { |
| | | String normalizedTaskCode = normalizeTaskCode(taskCode); |
| | | Integer normalizedTaskStatus = normalizeOptionalFilterNumber(taskStatus, "任务状态"); |
| | | Integer normalizedTaskType = normalizeOptionalFilterNumber(taskType, "任务类型"); |
| | | String normalizedOrgSite = BuiltinToolGovernanceSupport.sanitizeQueryText(orgSite, "源站点", 64); |
| | | String normalizedTargSite = BuiltinToolGovernanceSupport.sanitizeQueryText(targSite, "目标站点", 64); |
| | | BuiltinToolGovernanceSupport.requireAnyFilter("任务列表查询至少需要提供一个过滤条件", |
| | | normalizedTaskCode, normalizedOrgSite, normalizedTargSite, |
| | | taskStatus == null ? null : String.valueOf(taskStatus), |
| | | taskType == null ? null : String.valueOf(taskType)); |
| | | int finalLimit = normalizeLimit(limit, 10, 50); |
| | | |
| | | Task exactMatchedTask = findExactMatchedTask(normalizedTaskCode, normalizedTaskStatus, normalizedTaskType, |
| | | normalizedOrgSite, normalizedTargSite); |
| | | if (exactMatchedTask != null) { |
| | | Map<String, Object> response = new LinkedHashMap<>(); |
| | | response.put("queryMode", "detail"); |
| | | response.put("taskCount", 1); |
| | | response.put("limit", finalLimit); |
| | | response.put("resolvedTaskCode", exactMatchedTask.getTaskCode()); |
| | | response.put("tasks", List.of(buildTaskDetail(exactMatchedTask))); |
| | | return response; |
| | | } |
| | | |
| | | LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>(); |
| | | int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 10, 50); |
| | | if (StringUtils.hasText(normalizedTaskCode)) { |
| | | queryWrapper.like(Task::getTaskCode, normalizedTaskCode); |
| | | applyCommonFilters(queryWrapper, normalizedTaskCode, normalizedTaskStatus, normalizedTaskType, |
| | | normalizedOrgSite, normalizedTargSite, true); |
| | | queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit); |
| | | List<Task> tasks = taskService.list(queryWrapper); |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (Task task : tasks) { |
| | | result.add(buildTaskSummary(task)); |
| | | } |
| | | Map<String, Object> response = new LinkedHashMap<>(); |
| | | response.put("queryMode", "list"); |
| | | response.put("taskCount", result.size()); |
| | | response.put("limit", finalLimit); |
| | | response.put("resolvedTaskCode", normalizedTaskCode); |
| | | response.put("tasks", result); |
| | | return response; |
| | | } |
| | | |
| | | private Task findExactMatchedTask(String taskCode, Integer taskStatus, Integer taskType, String orgSite, String targSite) { |
| | | if (!StringUtils.hasText(taskCode)) { |
| | | return null; |
| | | } |
| | | Task exactTask = findUniqueTask(taskCode, taskStatus, taskType, orgSite, targSite, false); |
| | | if (exactTask != null) { |
| | | return exactTask; |
| | | } |
| | | String digitCandidate = extractDigitCandidate(taskCode); |
| | | if (!StringUtils.hasText(digitCandidate) || digitCandidate.equals(taskCode)) { |
| | | return null; |
| | | } |
| | | return findUniqueTask(digitCandidate, taskStatus, taskType, orgSite, targSite, true); |
| | | } |
| | | |
| | | private Task findUniqueTask(String taskCode, Integer taskStatus, Integer taskType, String orgSite, |
| | | String targSite, boolean fuzzyTaskCode) { |
| | | LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>(); |
| | | applyCommonFilters(queryWrapper, taskCode, taskStatus, taskType, orgSite, targSite, fuzzyTaskCode); |
| | | queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT 2"); |
| | | List<Task> tasks = taskService.list(queryWrapper); |
| | | if (tasks.size() == 1) { |
| | | return tasks.get(0); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void applyCommonFilters(LambdaQueryWrapper<Task> queryWrapper, String taskCode, Integer taskStatus, |
| | | Integer taskType, String orgSite, String targSite, boolean fuzzyTaskCode) { |
| | | if (StringUtils.hasText(taskCode)) { |
| | | if (fuzzyTaskCode) { |
| | | queryWrapper.like(Task::getTaskCode, taskCode); |
| | | } else { |
| | | queryWrapper.eq(Task::getTaskCode, taskCode); |
| | | } |
| | | } |
| | | if (taskStatus != null) { |
| | | queryWrapper.eq(Task::getTaskStatus, taskStatus); |
| | |
| | | if (taskType != null) { |
| | | queryWrapper.eq(Task::getTaskType, taskType); |
| | | } |
| | | if (StringUtils.hasText(normalizedOrgSite)) { |
| | | queryWrapper.eq(Task::getOrgSite, normalizedOrgSite); |
| | | if (StringUtils.hasText(orgSite)) { |
| | | queryWrapper.eq(Task::getOrgSite, orgSite); |
| | | } |
| | | if (StringUtils.hasText(normalizedTargSite)) { |
| | | queryWrapper.eq(Task::getTargSite, normalizedTargSite); |
| | | if (StringUtils.hasText(targSite)) { |
| | | queryWrapper.eq(Task::getTargSite, targSite); |
| | | } |
| | | queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit); |
| | | List<Task> tasks = taskService.list(queryWrapper); |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (Task task : tasks) { |
| | | result.add(buildTaskSummary(task)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 查询单个任务详情。 |
| | | * 与列表查询不同,这里允许返回更丰富的字段,但仍然要求调用方通过任务 ID 或任务号做精确定位。 |
| | | */ |
| | | @Tool(name = "rsf_query_task_detail", description = "只读查询工具。查询任务列表有正常返回值可以根据任务ID查询任务详情。") |
| | | public Map<String, Object> queryTaskDetail( |
| | | @ToolParam(description = "任务 ID") Long taskId |
| | | ) { |
| | | |
| | | if (taskId == null) { |
| | | throw new CoolException("任务 ID 需要提供"); |
| | | private String normalizeTaskCode(String taskCode) { |
| | | String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "任务号", 128); |
| | | if (!StringUtils.hasText(normalized)) { |
| | | return null; |
| | | } |
| | | List<TaskItem> taskItems = new ArrayList<>(); |
| | | taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, taskId)); |
| | | |
| | | if (taskItems.isEmpty()) { |
| | | throw new CoolException("未查询到任务"); |
| | | Matcher taskCodeMatcher = TASK_CODE_PATTERN.matcher(normalized); |
| | | if (taskCodeMatcher.find()) { |
| | | return taskCodeMatcher.group().toUpperCase(); |
| | | } |
| | | return buildTaskItemDetail(taskItems); |
| | | Matcher digitMatcher = TASK_CODE_DIGIT_PATTERN.matcher(normalized); |
| | | if (digitMatcher.find()) { |
| | | return digitMatcher.group(); |
| | | } |
| | | return normalized.replace("*", "").trim(); |
| | | } |
| | | |
| | | private String extractDigitCandidate(String taskCode) { |
| | | if (!StringUtils.hasText(taskCode)) { |
| | | return null; |
| | | } |
| | | Matcher digitMatcher = TASK_CODE_DIGIT_PATTERN.matcher(taskCode); |
| | | if (digitMatcher.find()) { |
| | | return digitMatcher.group(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Integer normalizeOptionalFilterNumber(String value, String fieldLabel) { |
| | | String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(value, fieldLabel, 32); |
| | | if (!StringUtils.hasText(normalized)) { |
| | | return null; |
| | | } |
| | | try { |
| | | int parsed = Integer.parseInt(normalized); |
| | | return parsed <= 0 ? null : parsed; |
| | | } catch (NumberFormatException e) { |
| | | throw new CoolException(fieldLabel + "必须是整数"); |
| | | } |
| | | } |
| | | |
| | | private int normalizeLimit(String value, int defaultValue, int maxValue) { |
| | | String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(value, "limit", 16); |
| | | if (!StringUtils.hasText(normalized)) { |
| | | return defaultValue; |
| | | } |
| | | try { |
| | | int parsed = Integer.parseInt(normalized); |
| | | if (parsed <= 0) { |
| | | return defaultValue; |
| | | } |
| | | return BuiltinToolGovernanceSupport.normalizeLimit(parsed, defaultValue, maxValue); |
| | | } catch (NumberFormatException e) { |
| | | throw new CoolException("limit 必须是整数"); |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> buildTaskSummary(Task task) { |
| | |
| | | return item; |
| | | } |
| | | |
| | | private Map<String, Object> buildTaskItemDetail(List<TaskItem> taskItems) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("taskId", taskItems.get(0).getTaskId()); |
| | | private Map<String, Object> buildTaskDetail(Task task) { |
| | | Map<String, Object> result = buildTaskSummary(task); |
| | | List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>() |
| | | .eq(TaskItem::getTaskId, task.getId())); |
| | | result.put("itemCount", taskItems.size()); |
| | | |
| | | double totalAnfme = 0D; |