refactor(mcp): hardware.statistics.query 改为单次查询 + Java 侧分组

原实现按"每物业 × 每状态"各查一次(20 物业 = 80 次 count),与"统计接口"的定位不符。

改为:
- 一条 LambdaQueryWrapper 只 select(property_id, status),权限过滤照旧
- Java 侧按物业分组累计 total/online/fault/offline
- 测试新增断言:多物业场景下 selectList 只调用 1 次、权限过滤只调用 1 次

理由:PermissionService 未公开"可见物业集合",无法用 QueryWrapper 直接 GROUP BY 再套权限;
用单次两列查询 + 内存分组,既保持行级权限语义,又把查询次数从 4N 降到 1。

测试:DeviceStatisticsToolsTest 6 例全过。
This commit is contained in:
xiaoqu-ai
2026-10-20 17:32:42 +08:00
parent 91d8e6e7a8
commit 06e9321f95
2 changed files with 77 additions and 29 deletions
@@ -82,15 +82,47 @@ public class DeviceStatisticsTools implements ToolObject {
boolean withUnbound = Boolean.TRUE.equals(includeUnbound);
User user = AuthContextHolder.get().getUser();
List<Map<String, Object>> items = new ArrayList<>(targets.size());
// 一条查询取回 (property_id, status) 两列,在 Java 侧分组计数。
// 之前按"每物业 × 每状态"各查一次(20 物业 = 80 次);现在固定 1 次。
LambdaQueryWrapper<HardwareView> wrapper = new LambdaQueryWrapper<HardwareView>()
.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<Long, long[]> 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<Map<String, Object>> items = new ArrayList<>(targets.size());
for (Map.Entry<Long, long[]> entry : counters.entrySet()) {
long[] c = entry.getValue();
Map<String, Object> 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<HardwareView> wrapper = new LambdaQueryWrapper<HardwareView>()
.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 + " 必须为正整数");
@@ -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<HardwareView> 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<String, Object> 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<HardwareView> rows = new ArrayList<>();
rows.addAll(devicesOf(17L, 1, 3));
rows.addAll(devicesOf(18L, 3, 1));
when(hardwareViewMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(rows);
Map<String, Object> result = tools.hardwareStatisticsQuery(null, List.of(17L, 18L), false);
List<Map<String, Object>> items = (List<Map<String, Object>>) 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<HardwareView> devicesOf(Long propertyId, Integer status, int count) {
List<HardwareView> 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<String, Object> result = tools.hardwareStatisticsQuery(null, List.of(17L, 17L), false);