diff --git a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DeviceStatisticsTools.java b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DeviceStatisticsTools.java index 4c79946..e6f816d 100644 --- a/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DeviceStatisticsTools.java +++ b/xiaoqu-agent-service/src/main/java/me/iiv/agent/mcp/tool/DeviceStatisticsTools.java @@ -82,15 +82,47 @@ public class DeviceStatisticsTools implements ToolObject { boolean withUnbound = Boolean.TRUE.equals(includeUnbound); User user = AuthContextHolder.get().getUser(); - List> items = new ArrayList<>(targets.size()); + // 一条查询取回 (property_id, status) 两列,在 Java 侧分组计数。 + // 之前按"每物业 × 每状态"各查一次(20 物业 = 80 次);现在固定 1 次。 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .select(HardwareView::getPropertyId, HardwareView::getStatus) + .eq(HardwareView::getSoftDelete, 0) + .in(HardwareView::getPropertyId, targets); + if (!withUnbound) { + wrapper.gt(HardwareView::getPropertyId, 0); + } + permissionService.applyHardwareRowFilter(wrapper, user); + + Map counters = new LinkedHashMap<>(); for (Long target : targets) { + counters.put(target, new long[4]); // total, online, fault, offline + } + for (HardwareView row : hardwareViewMapper.selectList(wrapper)) { + long[] c = counters.get(row.getPropertyId()); + if (c == null) { + continue; + } + c[0]++; + Integer status = row.getStatus(); + if (status != null) { + switch (status) { + case 1 -> c[1]++; + case 2 -> c[2]++; + case 3 -> c[3]++; + default -> { } + } + } + } + + List> items = new ArrayList<>(targets.size()); + for (Map.Entry entry : counters.entrySet()) { + long[] c = entry.getValue(); Map row = new LinkedHashMap<>(); - row.put("property_id", target); - // 每个计数重建 wrapper:项目内没有 clone() 使用先例,避免依赖未验证的克隆语义。 - row.put("total", countFor(user, target, withUnbound, null)); - row.put("online", countFor(user, target, withUnbound, 1)); - row.put("fault", countFor(user, target, withUnbound, 2)); - row.put("offline", countFor(user, target, withUnbound, 3)); + row.put("property_id", entry.getKey()); + row.put("total", c[0]); + row.put("online", c[1]); + row.put("fault", c[2]); + row.put("offline", c[3]); items.add(row); } @@ -102,21 +134,6 @@ public class DeviceStatisticsTools implements ToolObject { return result; } - /** 统计某物业在指定状态下的设备数;status 为 null 表示不按状态过滤。 */ - private Long countFor(User user, Long target, boolean withUnbound, Integer status) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(HardwareView::getSoftDelete, 0) - .eq(HardwareView::getPropertyId, target); - if (!withUnbound) { - wrapper.gt(HardwareView::getPropertyId, 0); - } - if (status != null) { - wrapper.eq(HardwareView::getStatus, status); - } - permissionService.applyHardwareRowFilter(wrapper, user); - return hardwareViewMapper.selectCount(wrapper); - } - private static void validatePositive(Long value, String name) { if (value != null && value < 1) { throw new IllegalArgumentException(name + " 必须为正整数"); diff --git a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DeviceStatisticsToolsTest.java b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DeviceStatisticsToolsTest.java index 2bb1040..f3b9346 100644 --- a/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DeviceStatisticsToolsTest.java +++ b/xiaoqu-agent-service/src/test/java/me/iiv/agent/mcp/tool/DeviceStatisticsToolsTest.java @@ -81,10 +81,12 @@ class DeviceStatisticsToolsTest { @Test @SuppressWarnings("unchecked") - void returnsFourCountsPerPropertyAndAppliesRowFilter() { - // 依次返回 total/online/fault/offline - when(hardwareViewMapper.selectCount(any(LambdaQueryWrapper.class))) - .thenReturn(10L, 6L, 2L, 2L); + void returnsCountsFromSingleQueryAndAppliesRowFilter() { + List rows = new ArrayList<>(); + rows.addAll(devicesOf(17L, 1, 6)); // 在线 6 + rows.addAll(devicesOf(17L, 2, 2)); // 故障 2 + rows.addAll(devicesOf(17L, 3, 2)); // 离线 2 + when(hardwareViewMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(rows); Map result = tools.hardwareStatisticsQuery(17L, null, false); @@ -97,13 +99,42 @@ class DeviceStatisticsToolsTest { assertEquals(2L, row.get("fault")); assertEquals(2L, row.get("offline")); assertTrue(result.containsKey("data_cutoff_at")); - // 总/在线/故障/离线 各一次查询 = 4 次,且每次都过行级权限 - verify(permissionService, times(4)).applyHardwareRowFilter(any(LambdaQueryWrapper.class), any(User.class)); + // 关键:无论几个物业,只查一次 + verify(hardwareViewMapper, times(1)).selectList(any(LambdaQueryWrapper.class)); + verify(permissionService, times(1)).applyHardwareRowFilter(any(LambdaQueryWrapper.class), any(User.class)); + } + + @Test + @SuppressWarnings("unchecked") + void countsMultiplePropertiesInOneQuery() { + List rows = new ArrayList<>(); + rows.addAll(devicesOf(17L, 1, 3)); + rows.addAll(devicesOf(18L, 3, 1)); + when(hardwareViewMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(rows); + + Map result = tools.hardwareStatisticsQuery(null, List.of(17L, 18L), false); + + List> items = (List>) result.get("items"); + assertEquals(2, items.size()); + assertEquals(3L, items.get(0).get("online")); + assertEquals(1L, items.get(1).get("offline")); + verify(hardwareViewMapper, times(1)).selectList(any(LambdaQueryWrapper.class)); + } + + private static List devicesOf(Long propertyId, Integer status, int count) { + List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + HardwareView view = new HardwareView(); + view.setPropertyId(propertyId); + view.setStatus(status); + list.add(view); + } + return list; } @Test void deduplicatesPropertyIdsAndAcceptsList() { - when(hardwareViewMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(1L); + when(hardwareViewMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of()); Map result = tools.hardwareStatisticsQuery(null, List.of(17L, 17L), false);