feat(wm-production): #62 在线监测列表与多维筛选

This commit is contained in:
2026-06-14 16:12:59 +08:00
parent 4a82e90fcb
commit 21cf0e97af
11 changed files with 1404 additions and 0 deletions
@@ -0,0 +1,130 @@
package com.water.production.controller;
import com.water.common.core.result.R;
import com.water.production.dto.MonitorExportRequest;
import com.water.production.dto.MonitorQueryRequest;
import com.water.production.service.MonitorExportService;
import com.water.production.service.MonitorListService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* 在线监测列表 Controller
* 提供设备列表、实时数据、统计、导出等接口
*/
@Tag(name = "在线监测管理")
@RestController
@RequestMapping("/api/production/monitor")
@RequiredArgsConstructor
public class MonitorController {
private final MonitorListService monitorListService;
private final MonitorExportService monitorExportService;
// ========== 1. 设备列表(分页 + 多维筛选) ==========
@Operation(summary = "在线监测设备列表(分页+多维筛选)")
@GetMapping("/list")
public R<Map<String, Object>> list(MonitorQueryRequest request) {
return R.ok(monitorListService.queryDeviceList(request));
}
// ========== 2. 设备详情 ==========
@Operation(summary = "获取设备详情")
@GetMapping("/device/{deviceId}")
public R<Map<String, Object>> deviceDetail(@PathVariable Long deviceId) {
Map<String, Object> detail = monitorListService.getDeviceDetail(deviceId);
if (detail.isEmpty()) {
return R.fail(404, "设备不存在");
}
return R.ok(detail);
}
// ========== 3. 设备实时数据 ==========
@Operation(summary = "获取设备实时监测数据")
@GetMapping("/device/{deviceId}/realtime")
public R<List<Map<String, Object>>> deviceRealtime(@PathVariable Long deviceId) {
return R.ok(monitorListService.getDeviceRealtimeData(deviceId));
}
// ========== 4. 设备统计概览 ==========
@Operation(summary = "监测设备统计概览(按状态/类型/区域分组)")
@GetMapping("/statistics")
public R<Map<String, Object>> statistics() {
return R.ok(monitorListService.getDeviceStatistics());
}
// ========== 5. 更新设备状态 ==========
@Operation(summary = "更新设备状态(online/offline/fault/abnormal)")
@PutMapping("/device/{deviceId}/status")
public R<String> updateStatus(@PathVariable Long deviceId, @RequestParam String status) {
monitorListService.updateDeviceStatus(deviceId, status);
return R.ok("状态已更新");
}
// ========== 6. 获取区域列表 ==========
@Operation(summary = "获取所有监测区域列表")
@GetMapping("/areas")
public R<List<String>> areas() {
return R.ok(monitorListService.getAreaList());
}
// ========== 7. 获取设备类型列表 ==========
@Operation(summary = "获取所有设备类型列表")
@GetMapping("/device-types")
public R<List<String>> deviceTypes() {
return R.ok(monitorListService.getDeviceTypeList());
}
// ========== 8. 导出 Excel ==========
@Operation(summary = "导出在线监测数据(Excel)")
@PostMapping("/export/excel")
public ResponseEntity<byte[]> exportExcel(@RequestBody MonitorExportRequest request) {
request.setFormat("excel");
byte[] data = monitorExportService.exportExcel(request);
String filename = URLEncoder.encode("在线监测数据.xlsx", StandardCharsets.UTF_8);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(data);
}
// ========== 9. 导出 CSV ==========
@Operation(summary = "导出在线监测数据(CSV)")
@PostMapping("/export/csv")
public ResponseEntity<byte[]> exportCsv(@RequestBody MonitorExportRequest request) {
request.setFormat("csv");
byte[] data = monitorExportService.exportCsv(request);
String filename = URLEncoder.encode("在线监测数据.csv", StandardCharsets.UTF_8);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
.body(data);
}
// ========== 10. 通用导出(按 format 参数自动选择) ==========
@Operation(summary = "通用导出(format=excel|csv)")
@PostMapping("/export")
public ResponseEntity<byte[]> export(@RequestBody MonitorExportRequest request) {
if ("csv".equalsIgnoreCase(request.getFormat())) {
return exportCsv(request);
}
return exportExcel(request);
}
// ========== 11. 批量获取实时数据 ==========
@Operation(summary = "批量获取多设备实时数据")
@PostMapping("/realtime/batch")
public R<List<Map<String, Object>>> batchRealtime(@RequestBody List<Long> deviceIds) {
return R.ok(monitorListService.getBatchRealtimeData(deviceIds));
}
}
@@ -0,0 +1,39 @@
package com.water.production.dto;
import lombok.Data;
import java.util.List;
/**
* 在线监测数据导出请求
*/
@Data
public class MonitorExportRequest {
/** 导出格式: excel/csv */
private String format = "excel";
/** 区域 */
private String area;
/** 设备类型 */
private String deviceType;
/** 设备状态 */
private String status;
/** 关键词 */
private String keyword;
/** 开始时间 */
private String startTime;
/** 结束时间 */
private String endTime;
/** 指定导出的设备ID列表(可选,为空则按筛选条件导出) */
private List<Long> deviceIds;
/** 是否包含实时数据 */
private Boolean includeRealtime = true;
}
@@ -0,0 +1,40 @@
package com.water.production.dto;
import lombok.Data;
/**
* 在线监测列表查询请求
*/
@Data
public class MonitorQueryRequest {
/** 区域 */
private String area;
/** 设备类型: flow/pressure/level/quality */
private String deviceType;
/** 设备状态: online/offline/fault/abnormal */
private String status;
/** 关键词(设备编号/名称模糊搜索) */
private String keyword;
/** 开始时间(最后上报时间范围) */
private String startTime;
/** 结束时间 */
private String endTime;
/** 排序字段: deviceCode/deviceName/status/lastReportTime */
private String sortField;
/** 排序方向: asc/desc */
private String sortOrder;
/** 页码(默认1) */
private Integer pageNum = 1;
/** 每页条数(默认20) */
private Integer pageSize = 20;
}
@@ -0,0 +1,60 @@
package com.water.production.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 在线监测设备实体
*/
@Data
@TableName("prod_monitor_device")
public class MonitorDevice {
@TableId(type = IdType.AUTO)
private Long id;
/** 设备编号 */
private String deviceCode;
/** 设备名称 */
private String deviceName;
/** 设备类型: flow/pressure/level/quality */
private String deviceType;
/** 所属区域 */
private String area;
/** 安装位置 */
private String location;
/** 经度 */
private BigDecimal lng;
/** 纬度 */
private BigDecimal lat;
/** 设备状态: online/offline/fault/abnormal */
private String status;
/** 最后上报时间 */
private LocalDateTime lastReportTime;
/** 设备品牌/型号 */
private String brand;
/** 安装日期 */
private LocalDateTime installTime;
/** 备注 */
private String remark;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
@@ -0,0 +1,48 @@
package com.water.production.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 监测实时数据实体
*/
@Data
@TableName("prod_monitor_realtime_data")
public class MonitorRealtimeData {
@TableId(type = IdType.AUTO)
private Long id;
/** 关联设备ID */
private Long deviceId;
/** 设备编号 */
private String deviceCode;
/** 监测参数类型: flow/pressure/level/turbidity/ph/residual_chlorine/temperature */
private String metricKey;
/** 实时值 */
private BigDecimal metricValue;
/** 单位 */
private String unit;
/** 阈值上限 */
private BigDecimal thresholdHigh;
/** 阈值下限 */
private BigDecimal thresholdLow;
/** 是否异常: 0正常 1异常 */
private Integer isAbnormal;
/** 采集时间 */
private LocalDateTime collectTime;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
}
@@ -0,0 +1,47 @@
package com.water.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.production.entity.MonitorDevice;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 在线监测设备 Mapper
*/
@Mapper
public interface MonitorDeviceMapper extends BaseMapper<MonitorDevice> {
/**
* 查询设备列表(含最新实时数据)
*/
@Select("<script>" +
"SELECT d.*, " +
" (SELECT COUNT(*) FROM prod_monitor_realtime_data r WHERE r.device_id = d.id) AS metric_count " +
"FROM prod_monitor_device d " +
"WHERE 1=1 " +
"<if test='area != null and area != \"\"'> AND d.area = #{area}</if> " +
"<if test='deviceType != null and deviceType != \"\"'> AND d.device_type = #{deviceType}</if> " +
"<if test='status != null and status != \"\"'> AND d.status = #{status}</if> " +
"<if test='keyword != null and keyword != \"\"'> AND (d.device_code ILIKE CONCAT('%',#{keyword},'%') OR d.device_name ILIKE CONCAT('%',#{keyword},'%'))</if> " +
"<if test='startTime != null and startTime != \"\"'> AND d.last_report_time &gt;= #{startTime}::timestamp</if> " +
"<if test='endTime != null and endTime != \"\"'> AND d.last_report_time &lt;= #{endTime}::timestamp</if> " +
"<if test='sortField == \"deviceCode\"'> ORDER BY d.device_code ${sortOrder}</if> " +
"<if test='sortField == \"deviceName\"'> ORDER BY d.device_name ${sortOrder}</if> " +
"<if test='sortField == \"status\"'> ORDER BY d.status ${sortOrder}</if> " +
"<if test='sortField == \"lastReportTime\"'> ORDER BY d.last_report_time ${sortOrder}</if> " +
"<if test='sortField == null or sortField == \"\"'> ORDER BY d.last_report_time DESC</if> " +
"</script>")
List<Map<String, Object>> selectDeviceListWithMetrics(
@Param("area") String area,
@Param("deviceType") String deviceType,
@Param("status") String status,
@Param("keyword") String keyword,
@Param("startTime") String startTime,
@Param("endTime") String endTime,
@Param("sortField") String sortField,
@Param("sortOrder") String sortOrder);
}
@@ -0,0 +1,50 @@
package com.water.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.production.entity.MonitorRealtimeData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 监测实时数据 Mapper
*/
@Mapper
public interface MonitorRealtimeDataMapper extends BaseMapper<MonitorRealtimeData> {
/**
* 获取指定设备的最新实时数据(所有指标)
*/
@Select("SELECT r.* FROM prod_monitor_realtime_data r " +
"INNER JOIN (SELECT device_id, metric_key, MAX(collect_time) AS max_time " +
" FROM prod_monitor_realtime_data WHERE device_id = #{deviceId} GROUP BY device_id, metric_key) latest " +
"ON r.device_id = latest.device_id AND r.metric_key = latest.metric_key AND r.collect_time = latest.max_time")
List<MonitorRealtimeData> selectLatestByDeviceId(@Param("deviceId") Long deviceId);
/**
* 批量获取多个设备的最新实时数据
*/
@Select("<script>" +
"SELECT r.* FROM prod_monitor_realtime_data r " +
"INNER JOIN (SELECT device_id, metric_key, MAX(collect_time) AS max_time " +
" FROM prod_monitor_realtime_data " +
" WHERE device_id IN " +
" <foreach item='id' collection='deviceIds' open='(' separator=',' close=')'>#{id}</foreach> " +
" GROUP BY device_id, metric_key) latest " +
"ON r.device_id = latest.device_id AND r.metric_key = latest.metric_key AND r.collect_time = latest.max_time " +
"ORDER BY r.device_id, r.metric_key" +
"</script>")
List<Map<String, Object>> selectLatestBatch(@Param("deviceIds") List<Long> deviceIds);
/**
* 按设备ID统计异常数据数量
*/
@Select("SELECT device_id, COUNT(*) AS cnt FROM prod_monitor_realtime_data " +
"WHERE is_abnormal = 1 AND device_id IN " +
"<script><foreach item='id' collection='deviceIds' open='(' separator=',' close=')'>#{id}</foreach></script> " +
"GROUP BY device_id")
List<Map<String, Object>> countAbnormalByDevices(@Param("deviceIds") List<Long> deviceIds);
}
@@ -0,0 +1,206 @@
package com.water.production.service;
import com.alibaba.excel.EasyExcel;
import com.water.production.dto.MonitorExportRequest;
import com.water.production.dto.MonitorQueryRequest;
import com.water.production.entity.MonitorDevice;
import com.water.production.entity.MonitorRealtimeData;
import com.water.production.mapper.MonitorRealtimeDataMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/**
* 在线监测数据导出服务
* 支持 Excel / CSV 格式导出
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MonitorExportService {
private final MonitorListService monitorListService;
private final MonitorRealtimeDataMapper realtimeDataMapper;
/**
* 导出 Excel 格式
*/
public byte[] exportExcel(MonitorExportRequest req) {
List<MonitorDevice> devices = getDevicesForExport(req);
if (devices.isEmpty()) {
return new byte[0];
}
// 构建导出数据
List<List<String>> head = buildExcelHead();
List<List<Object>> data = buildExcelData(devices, req.getIncludeRealtime());
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
EasyExcel.write(bos)
.sheet("在线监测数据")
.head(head.stream()
.map(row -> row.stream()
.map(c -> (String) c)
.collect(Collectors.toList()))
.collect(Collectors.toList()))
.doWrite(data);
return bos.toByteArray();
} catch (IOException e) {
log.error("Excel 导出失败", e);
throw new RuntimeException("Excel 导出失败: " + e.getMessage());
}
}
/**
* 导出 CSV 格式
*/
public byte[] exportCsv(MonitorExportRequest req) {
List<MonitorDevice> devices = getDevicesForExport(req);
if (devices.isEmpty()) {
return new byte[0];
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(bos, StandardCharsets.UTF_8))) {
// BOM for Excel UTF-8
bos.write(0xEF);
bos.write(0xBB);
bos.write(0xBF);
// CSV header
writer.println("设备编号,设备名称,设备类型,区域,安装位置,状态,最后上报时间,流量(m³/h),压力(MPa),液位(m),浊度(NTU),pH,余氯(mg/L)");
for (MonitorDevice device : devices) {
StringBuilder line = new StringBuilder();
line.append(csvEsc(device.getDeviceCode())).append(",");
line.append(csvEsc(device.getDeviceName())).append(",");
line.append(csvEsc(device.getDeviceType())).append(",");
line.append(csvEsc(device.getArea())).append(",");
line.append(csvEsc(device.getLocation())).append(",");
line.append(csvEsc(device.getStatus())).append(",");
line.append(device.getLastReportTime() != null ? device.getLastReportTime().toString() : "");
if (Boolean.TRUE.equals(req.getIncludeRealtime())) {
List<MonitorRealtimeData> realtimeList = realtimeDataMapper.selectLatestByDeviceId(device.getId());
Map<String, String> metricMap = new LinkedHashMap<>();
for (MonitorRealtimeData data : realtimeList) {
metricMap.put(data.getMetricKey(),
data.getMetricValue() != null ? data.getMetricValue().toPlainString() : "");
}
line.append(",").append(metricMap.getOrDefault("flow", ""));
line.append(",").append(metricMap.getOrDefault("pressure", ""));
line.append(",").append(metricMap.getOrDefault("level", ""));
line.append(",").append(metricMap.getOrDefault("turbidity", ""));
line.append(",").append(metricMap.getOrDefault("ph", ""));
line.append(",").append(metricMap.getOrDefault("residual_chlorine", ""));
}
writer.println(line);
}
writer.flush();
return bos.toByteArray();
} catch (IOException e) {
log.error("CSV 导出失败", e);
throw new RuntimeException("CSV 导出失败: " + e.getMessage());
}
}
private List<MonitorDevice> getDevicesForExport(MonitorExportRequest req) {
// 如果指定了设备ID列表,直接按ID查询
if (req.getDeviceIds() != null && !req.getDeviceIds().isEmpty()) {
MonitorQueryRequest queryReq = new MonitorQueryRequest();
queryReq.setPageSize(10000);
return monitorListService.queryDevicesForExport(queryReq).stream()
.filter(d -> req.getDeviceIds().contains(d.getId()))
.collect(Collectors.toList());
}
// 按筛选条件查询
MonitorQueryRequest queryReq = new MonitorQueryRequest();
queryReq.setArea(req.getArea());
queryReq.setDeviceType(req.getDeviceType());
queryReq.setStatus(req.getStatus());
queryReq.setKeyword(req.getKeyword());
queryReq.setStartTime(req.getStartTime());
queryReq.setEndTime(req.getEndTime());
queryReq.setPageSize(10000);
return monitorListService.queryDevicesForExport(queryReq);
}
private List<List<String>> buildExcelHead() {
List<List<String>> head = new ArrayList<>();
head.add(List.of("设备编号"));
head.add(List.of("设备名称"));
head.add(List.of("设备类型"));
head.add(List.of("区域"));
head.add(List.of("安装位置"));
head.add(List.of("状态"));
head.add(List.of("最后上报时间"));
head.add(List.of("流量(m³/h)"));
head.add(List.of("压力(MPa)"));
head.add(List.of("液位(m)"));
head.add(List.of("浊度(NTU)"));
head.add(List.of("pH"));
head.add(List.of("余氯(mg/L)"));
return head;
}
private List<List<Object>> buildExcelData(List<MonitorDevice> devices, Boolean includeRealtime) {
List<List<Object>> data = new ArrayList<>();
for (MonitorDevice device : devices) {
List<Object> row = new ArrayList<>();
row.add(device.getDeviceCode());
row.add(device.getDeviceName());
row.add(device.getDeviceType());
row.add(device.getArea());
row.add(device.getLocation());
row.add(formatStatus(device.getStatus()));
row.add(device.getLastReportTime() != null ? device.getLastReportTime().toString() : "");
if (Boolean.TRUE.equals(includeRealtime)) {
List<MonitorRealtimeData> realtimeList = realtimeDataMapper.selectLatestByDeviceId(device.getId());
Map<String, Object> metricMap = new LinkedHashMap<>();
for (MonitorRealtimeData d : realtimeList) {
metricMap.put(d.getMetricKey(), d.getMetricValue());
}
row.add(metricMap.getOrDefault("flow", ""));
row.add(metricMap.getOrDefault("pressure", ""));
row.add(metricMap.getOrDefault("level", ""));
row.add(metricMap.getOrDefault("turbidity", ""));
row.add(metricMap.getOrDefault("ph", ""));
row.add(metricMap.getOrDefault("residual_chlorine", ""));
}
data.add(row);
}
return data;
}
private String formatStatus(String status) {
if (status == null) return "未知";
return switch (status) {
case "online" -> "在线";
case "offline" -> "离线";
case "fault" -> "故障";
case "abnormal" -> "数据异常";
default -> status;
};
}
private String csvEsc(String value) {
if (value == null) return "";
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
return "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
}
@@ -0,0 +1,271 @@
package com.water.production.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.water.production.dto.MonitorQueryRequest;
import com.water.production.entity.MonitorDevice;
import com.water.production.entity.MonitorRealtimeData;
import com.water.production.mapper.MonitorDeviceMapper;
import com.water.production.mapper.MonitorRealtimeDataMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* 在线监测列表服务
* 提供设备列表查询、实时数据获取、设备详情、统计等功能
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MonitorListService {
private final MonitorDeviceMapper deviceMapper;
private final MonitorRealtimeDataMapper realtimeDataMapper;
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 分页查询在线监测设备列表(含最新实时数据)
*/
public Map<String, Object> queryDeviceList(MonitorQueryRequest req) {
// 使用 MyBatis-Plus 分页
Page<MonitorDevice> page = new Page<>(req.getPageNum(), req.getPageSize());
LambdaQueryWrapper<MonitorDevice> wrapper = new LambdaQueryWrapper<>();
if (req.getArea() != null && !req.getArea().isEmpty()) {
wrapper.eq(MonitorDevice::getArea, req.getArea());
}
if (req.getDeviceType() != null && !req.getDeviceType().isEmpty()) {
wrapper.eq(MonitorDevice::getDeviceType, req.getDeviceType());
}
if (req.getStatus() != null && !req.getStatus().isEmpty()) {
wrapper.eq(MonitorDevice::getStatus, req.getStatus());
}
if (req.getKeyword() != null && !req.getKeyword().isEmpty()) {
wrapper.and(w -> w.like(MonitorDevice::getDeviceCode, req.getKeyword())
.or().like(MonitorDevice::getDeviceName, req.getKeyword()));
}
if (req.getStartTime() != null && !req.getStartTime().isEmpty()) {
wrapper.ge(MonitorDevice::getLastReportTime, LocalDateTime.parse(req.getStartTime(), DTF));
}
if (req.getEndTime() != null && !req.getEndTime().isEmpty()) {
wrapper.le(MonitorDevice::getLastReportTime, LocalDateTime.parse(req.getEndTime(), DTF));
}
// 排序
String sortField = req.getSortField();
boolean isAsc = "asc".equalsIgnoreCase(req.getSortOrder());
if ("deviceCode".equals(sortField)) {
wrapper.orderBy(true, isAsc, MonitorDevice::getDeviceCode);
} else if ("deviceName".equals(sortField)) {
wrapper.orderBy(true, isAsc, MonitorDevice::getDeviceName);
} else if ("status".equals(sortField)) {
wrapper.orderBy(true, isAsc, MonitorDevice::getStatus);
} else {
wrapper.orderByDesc(MonitorDevice::getLastReportTime);
}
Page<MonitorDevice> result = deviceMapper.selectPage(page, wrapper);
// 填充实时数据
List<Map<String, Object>> deviceList = new ArrayList<>();
for (MonitorDevice device : result.getRecords()) {
Map<String, Object> deviceMap = new LinkedHashMap<>();
deviceMap.put("id", device.getId());
deviceMap.put("deviceCode", device.getDeviceCode());
deviceMap.put("deviceName", device.getDeviceName());
deviceMap.put("deviceType", device.getDeviceType());
deviceMap.put("area", device.getArea());
deviceMap.put("location", device.getLocation());
deviceMap.put("lng", device.getLng());
deviceMap.put("lat", device.getLat());
deviceMap.put("status", device.getStatus());
deviceMap.put("lastReportTime", device.getLastReportTime());
deviceMap.put("brand", device.getBrand());
// 获取最新实时数据
List<MonitorRealtimeData> realtimeList = realtimeDataMapper.selectLatestByDeviceId(device.getId());
Map<String, Object> realtimeMap = new LinkedHashMap<>();
for (MonitorRealtimeData data : realtimeList) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("metricKey", data.getMetricKey());
item.put("value", data.getMetricValue());
item.put("unit", data.getUnit());
item.put("thresholdHigh", data.getThresholdHigh());
item.put("thresholdLow", data.getThresholdLow());
item.put("isAbnormal", data.getIsAbnormal());
item.put("collectTime", data.getCollectTime());
realtimeMap.put(data.getMetricKey(), item);
}
deviceMap.put("realtimeData", realtimeMap);
deviceList.add(deviceMap);
}
Map<String, Object> pageResult = new LinkedHashMap<>();
pageResult.put("records", deviceList);
pageResult.put("total", result.getTotal());
pageResult.put("pageNum", result.getCurrent());
pageResult.put("pageSize", result.getSize());
pageResult.put("pages", result.getPages());
return pageResult;
}
/**
* 获取单个设备详情(含实时数据)
*/
public Map<String, Object> getDeviceDetail(Long deviceId) {
MonitorDevice device = deviceMapper.selectById(deviceId);
if (device == null) {
return Collections.emptyMap();
}
Map<String, Object> detail = new LinkedHashMap<>();
detail.put("id", device.getId());
detail.put("deviceCode", device.getDeviceCode());
detail.put("deviceName", device.getDeviceName());
detail.put("deviceType", device.getDeviceType());
detail.put("area", device.getArea());
detail.put("location", device.getLocation());
detail.put("lng", device.getLng());
detail.put("lat", device.getLat());
detail.put("status", device.getStatus());
detail.put("lastReportTime", device.getLastReportTime());
detail.put("brand", device.getBrand());
detail.put("installTime", device.getInstallTime());
detail.put("remark", device.getRemark());
List<MonitorRealtimeData> realtimeList = realtimeDataMapper.selectLatestByDeviceId(deviceId);
detail.put("realtimeData", realtimeList);
return detail;
}
/**
* 获取设备实时数据(多参数)
*/
public List<Map<String, Object>> getDeviceRealtimeData(Long deviceId) {
List<MonitorRealtimeData> list = realtimeDataMapper.selectLatestByDeviceId(deviceId);
List<Map<String, Object>> result = new ArrayList<>();
for (MonitorRealtimeData data : list) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("metricKey", data.getMetricKey());
map.put("value", data.getMetricValue());
map.put("unit", data.getUnit());
map.put("thresholdHigh", data.getThresholdHigh());
map.put("thresholdLow", data.getThresholdLow());
map.put("isAbnormal", data.getIsAbnormal());
map.put("collectTime", data.getCollectTime());
result.add(map);
}
return result;
}
/**
* 获取设备统计概览(按状态、类型、区域分组)
*/
public Map<String, Object> getDeviceStatistics() {
Map<String, Object> stats = new LinkedHashMap<>();
// 按状态统计
List<MonitorDevice> allDevices = deviceMapper.selectList(null);
Map<String, Long> statusCount = allDevices.stream()
.collect(Collectors.groupingBy(d -> d.getStatus() != null ? d.getStatus() : "unknown", Collectors.counting()));
stats.put("statusDistribution", statusCount);
stats.put("totalDevices", allDevices.size());
stats.put("onlineCount", statusCount.getOrDefault("online", 0L));
stats.put("offlineCount", statusCount.getOrDefault("offline", 0L));
stats.put("faultCount", statusCount.getOrDefault("fault", 0L));
stats.put("abnormalCount", statusCount.getOrDefault("abnormal", 0L));
// 按类型统计
Map<String, Long> typeCount = allDevices.stream()
.collect(Collectors.groupingBy(d -> d.getDeviceType() != null ? d.getDeviceType() : "unknown", Collectors.counting()));
stats.put("typeDistribution", typeCount);
// 按区域统计
Map<String, Long> areaCount = allDevices.stream()
.collect(Collectors.groupingBy(d -> d.getArea() != null ? d.getArea() : "unknown", Collectors.counting()));
stats.put("areaDistribution", areaCount);
return stats;
}
/**
* 更新设备状态
*/
public void updateDeviceStatus(Long deviceId, String status) {
MonitorDevice device = new MonitorDevice();
device.setId(deviceId);
device.setStatus(status);
device.setUpdatedTime(LocalDateTime.now());
deviceMapper.updateById(device);
log.info("设备 {} 状态更新为 {}", deviceId, status);
}
/**
* 获取所有支持的区域列表
*/
public List<String> getAreaList() {
LambdaQueryWrapper<MonitorDevice> wrapper = new LambdaQueryWrapper<>();
wrapper.select(MonitorDevice::getArea).groupBy(MonitorDevice::getArea);
return deviceMapper.selectList(wrapper).stream()
.map(MonitorDevice::getArea)
.filter(Objects::nonNull)
.distinct()
.sorted()
.collect(Collectors.toList());
}
/**
* 获取所有支持的设备类型列表
*/
public List<String> getDeviceTypeList() {
LambdaQueryWrapper<MonitorDevice> wrapper = new LambdaQueryWrapper<>();
wrapper.select(MonitorDevice::getDeviceType).groupBy(MonitorDevice::getDeviceType);
return deviceMapper.selectList(wrapper).stream()
.map(MonitorDevice::getDeviceType)
.filter(Objects::nonNull)
.distinct()
.sorted()
.collect(Collectors.toList());
}
/**
* 批量获取设备实时数据(用于导出)
*/
public List<Map<String, Object>> getBatchRealtimeData(List<Long> deviceIds) {
if (deviceIds == null || deviceIds.isEmpty()) {
return Collections.emptyList();
}
return realtimeDataMapper.selectLatestBatch(deviceIds);
}
/**
* 根据筛选条件查询设备列表(不分页,用于导出)
*/
public List<MonitorDevice> queryDevicesForExport(MonitorQueryRequest req) {
LambdaQueryWrapper<MonitorDevice> wrapper = new LambdaQueryWrapper<>();
if (req.getArea() != null && !req.getArea().isEmpty()) {
wrapper.eq(MonitorDevice::getArea, req.getArea());
}
if (req.getDeviceType() != null && !req.getDeviceType().isEmpty()) {
wrapper.eq(MonitorDevice::getDeviceType, req.getDeviceType());
}
if (req.getStatus() != null && !req.getStatus().isEmpty()) {
wrapper.eq(MonitorDevice::getStatus, req.getStatus());
}
if (req.getKeyword() != null && !req.getKeyword().isEmpty()) {
wrapper.and(w -> w.like(MonitorDevice::getDeviceCode, req.getKeyword())
.or().like(MonitorDevice::getDeviceName, req.getKeyword()));
}
wrapper.orderByDesc(MonitorDevice::getLastReportTime);
return deviceMapper.selectList(wrapper);
}
}