zhou zhou
17 小时以前 e1a6b6a5507c101d58c1ae6183361df18daef1da
rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java
@@ -18,37 +18,103 @@
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);
@@ -56,40 +122,68 @@
        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) {
@@ -112,9 +206,10 @@
        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;