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:
+39
-22
@@ -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 + " 必须为正整数");
|
||||
|
||||
+38
-7
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user