From 377145cafe7607e71dc3afe7ad4ca4d4ace34a25 Mon Sep 17 00:00:00 2001 From: my_local_kimi_dev2 Date: Mon, 12 Oct 2026 14:09:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(district):=20position.detail=20=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E9=80=8F=E5=87=BA=E5=AF=B9=E9=BD=90=20ToolCallExcepti?= =?UTF-8?q?on=20(REQ-20261012-0003)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 入参校验/超 31 天/超 StatisticsDays 抛 INVALID_ARGUMENT,站点无权抛 NOT_FOUND_OR_FORBIDDEN,具体文案透出给调用方(对齐 traffic/device_average), 不再被统一掩码成 DATA_INTEGRITY_ERROR。Redis StatisticsDays 缺失维持 IllegalStateException 兜底掩码(服务端配置问题)。 --- .../DistrictPaperPositionDetailTools.java | 30 +++++++++------- .../DistrictPaperPositionDetailToolsTest.java | 36 ++++++++++--------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailTools.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailTools.java index be25fa7..eeb984c 100644 --- a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailTools.java +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailTools.java @@ -10,6 +10,8 @@ import me.iiv.agent.mapper.m0.DistrictDistrictStrategyMapper; import me.iiv.agent.mapper.m0.MonthlyStatisticsMapper; import me.iiv.agent.mapper.m0.PositionsMapper; import me.iiv.agent.mapper.m0.ToiletMapper; +import me.iiv.agent.mcp.McpErrorCode; +import me.iiv.agent.mcp.ToolCallException; import me.iiv.agent.mcp.ToolObject; import me.iiv.agent.mcp.XqTool; import me.iiv.agent.permission.PermissionService; @@ -93,7 +95,7 @@ public class DistrictPaperPositionDetailTools implements ToolObject { @ToolParam(required = false, description = "厕所 id,正整数,精确匹配") Long toiletId) { if (districtId == null) { - throw new IllegalArgumentException("districtId 必填"); + throw invalid("districtId 必填"); } validatePositive(districtId, "districtId"); validatePositive(buildingId, "buildingId"); @@ -102,16 +104,16 @@ public class DistrictPaperPositionDetailTools implements ToolObject { LocalDate end = parseDay(endDate, "endDate"); LocalDate today = LocalDate.now(SHANGHAI); if (start.isAfter(end)) { - throw new IllegalArgumentException("startDate 不能晚于 endDate"); + throw invalid("startDate 不能晚于 endDate"); } if (end.isAfter(today)) { - throw new IllegalArgumentException("endDate 不得晚于今天"); + throw invalid("endDate 不得晚于今天"); } if (StringUtils.hasText(positionMode) && !"position".equals(positionMode) && !"wall".equals(positionMode)) { - throw new IllegalArgumentException("positionMode 仅支持 position、wall"); + throw invalid("positionMode 仅支持 position、wall"); } if (deviceType != null && deviceType != 0 && deviceType != 1) { - throw new IllegalArgumentException("deviceType 仅支持 0、1"); + throw invalid("deviceType 仅支持 0、1"); } User user = AuthContextHolder.get().getUser(); @@ -121,17 +123,16 @@ public class DistrictPaperPositionDetailTools implements ToolObject { permissionService.applyDistrictRowFilter(districtWrapper, user); DistrictDistrictStrategy district = districtMapper.selectOne(districtWrapper); if (district == null) { - throw new IllegalStateException("站点不存在或无权访问"); + throw new ToolCallException(McpErrorCode.NOT_FOUND_OR_FORBIDDEN, "站点不存在或无权访问"); } int queryDays = (int) ChronoUnit.DAYS.between(start, end) + 1; if (queryDays > MAX_QUERY_DAYS) { - throw new IllegalArgumentException( - "查询时间段最长 " + MAX_QUERY_DAYS + " 天(一个月),请修改日期范围"); + throw invalid("查询时间段最长 " + MAX_QUERY_DAYS + " 天(一个月),请修改日期范围"); } int maxDays = statisticsDays(); if (queryDays > maxDays) { - throw new IllegalArgumentException("查询时间段最大" + maxDays + "天"); + throw invalid("查询时间段最大" + maxDays + "天"); } List sourceRows = queryRows(districtId, start, end, @@ -313,21 +314,26 @@ public class DistrictPaperPositionDetailTools implements ToolObject { private static LocalDate parseDay(String value, String name) { if (!StringUtils.hasText(value)) { - throw new IllegalArgumentException(name + " 必填,格式 yyyy-MM-dd"); + throw invalid(name + " 必填,格式 yyyy-MM-dd"); } try { return LocalDate.parse(value.trim()); } catch (java.time.format.DateTimeParseException e) { - throw new IllegalArgumentException(name + " 格式非法,要求 yyyy-MM-dd: " + value); + throw invalid(name + " 格式非法,要求 yyyy-MM-dd: " + value); } } private static void validatePositive(Long value, String name) { if (value != null && value < 1) { - throw new IllegalArgumentException(name + " 必须为正整数"); + throw invalid(name + " 必须为正整数"); } } + /** 入参类错误透出具体文案给调用方(对齐 traffic/device_average 风格),便于 LLM 自愈 */ + private static ToolCallException invalid(String message) { + return new ToolCallException(McpErrorCode.INVALID_ARGUMENT, message); + } + private static double round2(double value) { return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).doubleValue(); } diff --git a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailToolsTest.java b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailToolsTest.java index 740c526..d0398c2 100644 --- a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailToolsTest.java +++ b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DistrictPaperPositionDetailToolsTest.java @@ -11,6 +11,8 @@ import me.iiv.agent.mapper.m0.DistrictDistrictStrategyMapper; import me.iiv.agent.mapper.m0.MonthlyStatisticsMapper; import me.iiv.agent.mapper.m0.PositionsMapper; import me.iiv.agent.mapper.m0.ToiletMapper; +import me.iiv.agent.mcp.McpErrorCode; +import me.iiv.agent.mcp.ToolCallException; import me.iiv.agent.permission.PermissionService; import me.iiv.agent.security.AuthContext; import me.iiv.agent.security.AuthContextHolder; @@ -144,24 +146,25 @@ class DistrictPaperPositionDetailToolsTest { @Test void rejectsInvalidArgumentsBeforeAnyQuery() { - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( null, start, end, null, null, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 0L, start, end, null, null, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 9L, "2026/09/01", end, null, null, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 9L, start, end, "bad", null, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 9L, start, end, null, 2, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 9L, end, start, null, null, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 9L, start, today.plusDays(1).toString(), null, null, null, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( + assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery( 9L, start, end, null, null, -1L, null, null)); - assertThrows(IllegalArgumentException.class, () -> tools.districtPaperPositionDetailQuery( - 9L, start, end, null, null, null, null, 0L)); + ToolCallException exception = assertThrows(ToolCallException.class, + () -> tools.districtPaperPositionDetailQuery(9L, start, end, null, null, null, null, 0L)); + assertEquals(McpErrorCode.INVALID_ARGUMENT, exception.getCode()); verifyNoInteractions(districtMapper, monthlyStatisticsMapper, positionsMapper, toiletMapper, redisTemplate); } @@ -170,9 +173,10 @@ class DistrictPaperPositionDetailToolsTest { void rejectsInvisibleDistrict() { when(districtMapper.selectOne(any())).thenReturn(null); - IllegalStateException exception = assertThrows(IllegalStateException.class, + ToolCallException exception = assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery(9L, start, end, null, null, null, null, null)); - assertEquals("站点不存在或无权访问", exception.getMessage()); + assertEquals(McpErrorCode.NOT_FOUND_OR_FORBIDDEN, exception.getCode()); + assertEquals("站点不存在或无权访问", exception.getRawMessage()); verify(permissionService).applyDistrictRowFilter(any(), any()); verifyNoInteractions(monthlyStatisticsMapper, positionsMapper, toiletMapper); } @@ -195,9 +199,9 @@ class DistrictPaperPositionDetailToolsTest { mockDistrictAndDictionary(); String wideStart = today.minusDays(40).toString(); - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + ToolCallException exception = assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery(9L, wideStart, end, null, null, null, null, null)); - assertTrue(exception.getMessage().contains("最长 31 天")); + assertTrue(exception.getRawMessage().contains("最长 31 天")); verifyNoInteractions(monthlyStatisticsMapper, positionsMapper, toiletMapper); } @@ -206,9 +210,9 @@ class DistrictPaperPositionDetailToolsTest { mockDistrictAndDictionary(); when(hashOperations.get("Dictionary", "StatisticsDays")).thenReturn("5"); - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + ToolCallException exception = assertThrows(ToolCallException.class, () -> tools.districtPaperPositionDetailQuery(9L, start, end, null, null, null, null, null)); - assertEquals("查询时间段最大5天", exception.getMessage()); + assertEquals("查询时间段最大5天", exception.getRawMessage()); verifyNoInteractions(monthlyStatisticsMapper, positionsMapper, toiletMapper); } -- 2.54.0