diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java index d56682e0cd..14e376905e 100644 --- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java +++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/scheduler/SchedulerManager.java @@ -6,6 +6,7 @@ import org.quartz.*; import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; +import static org.quartz.Scheduler.DEFAULT_GROUP; /** * {@link org.quartz.Scheduler} 的管理器,负责创建任务 @@ -41,13 +42,15 @@ public class SchedulerManager { Integer retryCount, Integer retryInterval) throws SchedulerException { validateScheduler(); + String jobKeyName = jobHandlerName+ "_"+ jobId; + JobKey jobKey = JobKey.jobKey(jobKeyName, DEFAULT_GROUP); // 创建 JobDetail 对象 JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class) .usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId) .usingJobData(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName) - .withIdentity(jobHandlerName).build(); + .withIdentity(jobKey).build(); // 创建 Trigger 对象 - Trigger trigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval); + Trigger trigger = this.buildTrigger(jobKey, jobHandlerParam, cronExpression, retryCount, retryInterval); // 新增 Job 调度 scheduler.scheduleJob(jobDetail, trigger); } @@ -140,6 +143,18 @@ public class SchedulerManager { .build(); } + private Trigger buildTrigger(JobKey jobKey, String jobHandlerParam, String cronExpression, + Integer retryCount, Integer retryInterval) { + return TriggerBuilder.newTrigger() + .withIdentity(jobKey.getName()) + .forJob(jobKey) + .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)) + .usingJobData(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam) + .usingJobData(JobDataKeyEnum.JOB_RETRY_COUNT.name(), retryCount) + .usingJobData(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), retryInterval) + .build(); + } + private void validateScheduler() { if (scheduler == null) { throw exception0(NOT_IMPLEMENTED.getCode(), diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java index c0490dc111..87a2158466 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java @@ -61,7 +61,7 @@ public class JobServiceImpl implements JobService { jobMapper.insert(job); // 3.1 添加 Job 到 Quartz 中 - schedulerManager.addJob(job.getId(), job.getName(), job.getHandlerParam(), job.getCronExpression(), + schedulerManager.addJob(job.getId(), job.getHandlerName(), job.getHandlerParam(), job.getCronExpression(), createReqVO.getRetryCount(), createReqVO.getRetryInterval()); // 3.2 更新 JobDO JobDO updateObj = JobDO.builder().id(job.getId()).status(JobStatusEnum.STOP.getStatus()).build(); @@ -135,7 +135,7 @@ public class JobServiceImpl implements JobService { JobDO job = validateJobExists(id); // 触发 Quartz 中的 Job - schedulerManager.triggerJob(job.getId(), job.getName(), job.getHandlerParam()); + schedulerManager.triggerJob(job.getId(), job.getHandlerName(), job.getHandlerParam()); } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java index d02f465134..e91aab8955 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java @@ -176,13 +176,22 @@ public class DeviceController { @GetMapping("/singleDevice") @Operation(summary = "单设备查看") // @PreAuthorize("@ss.hasPermission('iot:device:query')") - public CommonResult> > singleDevice(@RequestParam("deviceId") Long deviceId) throws JsonProcessingException { - Map> deviceContactModelDO=deviceService.singleDevice(deviceId); + public CommonResult>>> singleDevice(@RequestParam("deviceId") Long deviceId) throws JsonProcessingException { + Map>> deviceContactModelDO=deviceService.singleDevice(deviceId); return success(deviceContactModelDO); } - + @GetMapping("/historyRecord") + @Operation(summary = "历史记录查询") +// @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult>> historyRecord(@RequestParam("deviceId") Long deviceId, + @RequestParam(name = "collectionStartTime", required = false) String collectionStartTime, + @RequestParam(name = "collectionEndTime", required = false) String collectionEndTime + ) throws JsonProcessingException { + List> deviceContactModelDO=deviceService.historyRecord(deviceId,collectionStartTime,collectionEndTime); + return success(deviceContactModelDO); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/LineDeviceRequestVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/LineDeviceRequestVO.java index e2967298ed..54771ab05d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/LineDeviceRequestVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/LineDeviceRequestVO.java @@ -31,4 +31,10 @@ public class LineDeviceRequestVO extends PageParam { @Schema(description = "采集时间") private LocalDateTime collectionTime; + @Schema(description = "开始采集时间") + private String collectionStartTime; + + @Schema(description = "结束采集时间") + private String collectionEndTime; + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/SingleDeviceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/SingleDeviceRespVO.java new file mode 100644 index 0000000000..92c36f8051 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/SingleDeviceRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 单设备监控 Resp VO") +@Data +public class SingleDeviceRespVO { + + @Schema(description = "点位名称", example = "26404") + private String pointName; + + @Schema(description = "点位值", example = "26404") + private String pointValue; + + @Schema(description = "采集时间") + private String collectTime; +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java index 2b4fec8108..6769e217cc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java @@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO; import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO; import cn.iocoder.yudao.module.iot.dal.devicecontactmodel.DeviceContactModelDO; import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; import java.util.Collection; @@ -124,5 +125,7 @@ public interface DeviceService { PageResult lineDevicePage(LineDeviceRequestVO pageReqVO); - Map> singleDevice(Long deviceId) throws JsonProcessingException; + Map>> singleDevice(Long deviceId) throws JsonProcessingException; + + List> historyRecord(Long deviceId,String collectionStartTime, String collectionEndTime); } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java index 81a61da753..c7194e7df3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java @@ -393,11 +393,6 @@ public class DeviceServiceImpl implements DeviceService { } - - - - - }else { throw exception(OPC_CONNECT_FAILURE_DOES_NOT_EXIST); } @@ -502,93 +497,139 @@ public class DeviceServiceImpl implements DeviceService { return lineDeviceRespVOPageResult; - } @Override - public Map> singleDevice(Long deviceId) throws JsonProcessingException { + public Map>> singleDevice(Long deviceId) throws JsonProcessingException { - List resultList = new ArrayList<>(); + Map>> resultMap = new LinkedHashMap<>(); try { - // 获取设备数据列表 - List> deviceDataList = tdengineService.getAllDeviceDataOrderByTimeDesc(deviceId); - + // 1. 获取设备数据列表 + List> deviceDataList = tdengineService.getNewestDeviceDataOrderByTimeDesc(deviceId); + + // 2. 获取属性类型映射 + Map idToNameMap = deviceAttributeTypeMapper.selectList() + .stream() + .collect(Collectors.toMap( + DeviceAttributeTypeDO::getId, + DeviceAttributeTypeDO::getName + )); + + // 3. 遍历并处理 for (Map deviceData : deviceDataList) { String queryDataJson = (String) deviceData.get("queryData"); - Timestamp timestamp = (Timestamp) deviceData.get("timestamp"); - - - if (queryDataJson != null && !queryDataJson.isEmpty()) { - ObjectMapper objectMapper = new ObjectMapper(); - - // 简化配置,只注册基础模块 - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - // 忽略未知属性,避免因缺少字段而报错 - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - // 解析JSON数组为对象列表 - List models = objectMapper.readValue( + if (StringUtils.isNotBlank(queryDataJson)) { + // 使用TypeReference解析为List而不是具体的DO对象 + List> dataList = new ObjectMapper().readValue( queryDataJson, - new TypeReference>() {} + new TypeReference>>() {} ); - // 可以为每个对象设置时间戳(如果需要) - for (DeviceContactModelDO model : models) { - // 设置查询时间戳 - model.setLatestCollectionTime(String.valueOf(timestamp)); - resultList.add(model); + for (Map data : dataList) { + // 获取属性类型名称 + String attributeTypeName = "其他"; + String typeStr = (String) data.get("attributeType"); + if (typeStr != null) { + try { + attributeTypeName = typeStr; + } catch (Exception e) { + attributeTypeName = "未知"; + } + } + + // 提取需要的字段 + Map simplifiedData = new HashMap<>(); + simplifiedData.put("addressValue", data.get("addressValue")); + simplifiedData.put("attributeName", data.get("attributeName")); + + resultMap + .computeIfAbsent(attributeTypeName, k -> new ArrayList<>()) + .add(simplifiedData); } } } + } catch (Exception e) { System.out.println("处理设备数据时发生异常: " + e.getMessage()); } + return resultMap; + } + @Override + public List> historyRecord(Long deviceId,String collectionStartTime, String collectionEndTime) { - List deviceAttributeTypeDOS = deviceAttributeTypeMapper.selectList(); - // 最基本的转换方式 - Map idToNameMap = deviceAttributeTypeDOS.stream() - .collect(Collectors.toMap(DeviceAttributeTypeDO::getId, DeviceAttributeTypeDO::getName)); + List> resultList = new ArrayList<>(); + try { + // 1. 获取设备数据列表 + List> deviceDataList = tdengineService.getstDeviceDataOrderByTimeDesc(deviceId,collectionStartTime,collectionEndTime); + + // 2. 获取属性类型映射 + Map idToNameMap = deviceAttributeTypeMapper.selectList() + .stream() + .collect(Collectors.toMap( + DeviceAttributeTypeDO::getId, + DeviceAttributeTypeDO::getName + )); + + // 3. 遍历每个时间点的数据 + for (Map deviceData : deviceDataList) { + String queryDataJson = (String) deviceData.get("queryData"); + Timestamp timestamp = (Timestamp) deviceData.get("timestamp"); - // 分组并排序 - Map> groupedAndSorted = resultList.stream() - .collect(Collectors.groupingBy( - // 处理attributeType为null的情况,设为"其他" - item -> { - String typeStr = item.getAttributeType(); - if (typeStr == null) { - return "其他"; - } + if (StringUtils.isNotBlank(queryDataJson) && timestamp != null) { + List> dataList = new ObjectMapper().readValue( + queryDataJson, + new TypeReference>>() {} + ); + + // 按属性类型分组 + Map>> groupedData = new LinkedHashMap<>(); + + for (Map data : dataList) { + String attributeTypeName = "其他"; + String typeStr = (String) data.get("attributeType"); + if (typeStr != null) { try { - // 关键步骤:将 String 转换为 Long - Long typeLong = Long.valueOf(typeStr); - String name = idToNameMap.get(typeLong); - return (name == null) ? "未知" : name; - } catch (NumberFormatException e) { - // 如果字符串不能转换为Long,则归类为"未知" - return "未知"; + attributeTypeName = typeStr; + } catch (Exception e) { + attributeTypeName = "未知"; } - }, // 使用LinkedHashMap保持分组顺序(可选) - LinkedHashMap::new, - // 对每个分组内的元素按latestCollectionTime倒序排序 - Collectors.collectingAndThen( - Collectors.toList(), - list -> list.stream() - .sorted(Comparator.comparing( - DeviceContactModelDO::getLatestCollectionTime, - Comparator.nullsLast(Comparator.reverseOrder()) // 处理latestCollectionTime为null的情况 - )) - .collect(Collectors.toList()) - ) - )); - - - return groupedAndSorted; + } + + Map simplifiedData = new HashMap<>(); + simplifiedData.put("addressValue", data.get("addressValue")); + simplifiedData.put("attributeName", data.get("attributeName")); + + groupedData + .computeIfAbsent(attributeTypeName, k -> new ArrayList<>()) + .add(simplifiedData); + } + + // 创建当前时间点的Map + Map timePointData = new LinkedHashMap<>(); + + // 添加属性分组 + for (Map.Entry>> entry : groupedData.entrySet()) { + timePointData.put(entry.getKey(), entry.getValue()); + } + + // 添加收集时间 + timePointData.put("collectTime", timestamp.toString()); + + resultList.add(timePointData); + } + } + + } catch (Exception e) { + System.out.println("处理设备数据时发生异常: " + e.getMessage()); + } + + return resultList; + } private void validateDeviceAttributeExists(Long id) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index 6ef8a3551b..7b3cbc7387 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -1,19 +1,25 @@ package cn.iocoder.yudao.module.iot.service.device; import com.baomidou.dynamic.datasource.annotation.DS; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.taosdata.jdbc.utils.BlobUtil; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Service public class TDengineService { @@ -40,7 +46,7 @@ public class TDengineService { // 3. 创建超级表 String createSuperTableSQL = "CREATE STABLE IF NOT EXISTS device_data (" + "ts TIMESTAMP, " + - "query_data NCHAR(2048)" + + "query_data BLOB" + ") TAGS (device_id BIGINT)"; jdbcTemplate.execute(createSuperTableSQL); @@ -82,8 +88,44 @@ public class TDengineService { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String timestampStr = sdf.format(ts); result.put("timestamp", timestampStr); - String queryData = rs.getString("query_data"); - result.put("queryData", queryData); + + byte[] blob = rs.getBytes("query_data"); + if (blob != null) { + String jsonStr = new String(blob, StandardCharsets.UTF_8); + + try { + // 1. 先去除外层的双引号(如果存在) + String trimmed = jsonStr.trim(); + if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { + trimmed = trimmed.substring(1, trimmed.length() - 1); + } + + // 2. 检查是否是十六进制字符串 + if (isHexString(trimmed)) { + // 3. 十六进制解码 + String decodedJson = hexToString(trimmed); + result.put("queryData", decodedJson); + } else { + // 如果不是十六进制,尝试直接解析 + ObjectMapper objectMapper = new ObjectMapper(); + List> queryData = objectMapper.readValue( + trimmed, + new TypeReference>>() {} + ); + result.put("queryData", queryData); + } + + } catch (Exception e) { + System.err.println("解析JSON失败: " + e.getMessage()); + result.put("queryData", new ArrayList<>()); + result.put("rawData", jsonStr); + } + + } else { + result.put("queryData", new ArrayList<>()); + } + + result.put("deviceId", id); result.put("tableName", tableName); return result; @@ -112,12 +154,11 @@ public class TDengineService { /** * 向指定设备插入带时间戳的数据 * @param id 设备ID - * @param queryData 查询数据 * @param timestamp 时间戳 * @return 插入是否成功 */ @DS("tdengine") - public boolean insertDeviceData(Long id, String queryData, Timestamp timestamp) { + public boolean insertDeviceData(Long id, String jsonString, Timestamp timestamp) { try { // 确保使用正确的数据库 jdbcTemplate.execute("USE besure"); @@ -125,9 +166,12 @@ public class TDengineService { String tableName = "d_" + id; String sql = "INSERT INTO " + tableName + " (ts, query_data) VALUES (?, ?)"; - int affectedRows = jdbcTemplate.update(sql, timestamp, queryData); - System.out.println("向设备" + id + "插入数据成功,时间戳: " + timestamp); - return affectedRows > 0; + return jdbcTemplate.update(sql, ps -> { + ps.setTimestamp(1, timestamp); + // JSON字符串转byte数组 + byte[] blobData = jsonString.getBytes(StandardCharsets.UTF_8); + ps.setBytes(2, blobData); + }) > 0; } catch (Exception e) { System.out.println("向设备" + id + "插入数据时发生异常: " + e.getMessage()); return false; @@ -140,24 +184,202 @@ public class TDengineService { * @return 设备数据列表,按时间戳倒序排列 */ @DS("tdengine") - public List> getAllDeviceDataOrderByTimeDesc(Long id) { + public List> getNewestDeviceDataOrderByTimeDesc(Long id) { String tableName = "d_" + id; - String sql = "SELECT ts, query_data FROM besure." + tableName + " ORDER BY ts DESC"; + String sql = "SELECT ts, query_data FROM besure." + tableName + " ORDER BY ts DESC LIMIT 1"; try { - return jdbcTemplate.query(sql, new RowMapper>() { + return Collections.singletonList(jdbcTemplate.queryForObject(sql, new RowMapper>() { @Override public Map mapRow(ResultSet rs, int rowNum) throws SQLException { Map result = new HashMap<>(); result.put("timestamp", rs.getTimestamp("ts")); - result.put("queryData", rs.getString("query_data")); result.put("deviceId", id); + + byte[] blob = rs.getBytes("query_data"); + if (blob != null) { + String jsonStr = new String(blob, StandardCharsets.UTF_8); + + try { + // 1. 先去除外层的双引号(如果存在) + String trimmed = jsonStr.trim(); + if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { + trimmed = trimmed.substring(1, trimmed.length() - 1); + } + + // 2. 检查是否是十六进制字符串 + if (isHexString(trimmed)) { + // 3. 十六进制解码 + String decodedJson = hexToString(trimmed); + + result.put("queryData", decodedJson); + } else { + // 如果不是十六进制,尝试直接解析 + ObjectMapper objectMapper = new ObjectMapper(); + List> queryData = objectMapper.readValue( + trimmed, + new TypeReference>>() {} + ); + result.put("queryData", queryData); + } + + } catch (Exception e) { + System.err.println("解析JSON失败: " + e.getMessage()); + result.put("queryData", new ArrayList<>()); + result.put("rawData", jsonStr); + } + + } else { + result.put("queryData", new ArrayList<>()); + } + return result; } - }); + })); + } catch (EmptyResultDataAccessException e) { + return Collections.singletonList(createEmptyResult(id)); } catch (Exception e) { - System.out.println("查询设备" + id + "的全部数据时发生异常: " + e.getMessage()); + System.err.println("查询设备" + id + "的最新数据时发生异常: " + e.getMessage()); + e.printStackTrace(); + return Collections.singletonList(createEmptyResult(id)); + } + } + + + // 检查是否是十六进制字符串 + private boolean isHexString(String str) { + if (str == null || str.trim().isEmpty()) { + return false; + } + String s = str.trim(); + // 检查是否只包含十六进制字符(0-9, a-f, A-F) + return s.matches("^[0-9a-fA-F]+$"); + } + + // 十六进制解码 + private String hexToString(String hex) { + try { + // 使用 Apache Commons Codec + byte[] bytes = Hex.decodeHex(hex); + return new String(bytes, StandardCharsets.UTF_8); + } catch (DecoderException e) { + throw new RuntimeException("十六进制解码失败: " + e.getMessage(), e); + } + } + private List> parseJsonData(byte[] blob) { + if (blob == null || blob.length == 0) { return new ArrayList<>(); } + + try { + String jsonStr = new String(blob, StandardCharsets.UTF_8); + + // 检查是否是有效的JSON + if (jsonStr.trim().startsWith("[") && jsonStr.trim().endsWith("]")) { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(jsonStr, + new TypeReference>>() {}); + } else { + // 可能是字符串化的JSON,尝试去除引号 + jsonStr = jsonStr.trim(); + if (jsonStr.startsWith("\"") && jsonStr.endsWith("\"")) { + jsonStr = jsonStr.substring(1, jsonStr.length() - 1); + return new ObjectMapper().readValue(jsonStr, + new TypeReference>>() {}); + } + } + } catch (Exception e) { + System.err.println("解析JSON数据失败: " + e.getMessage()); + } + + return new ArrayList<>(); + } + + private Map createEmptyResult(Long deviceId) { + Map result = new HashMap<>(); + result.put("deviceId", deviceId); + result.put("queryData", new ArrayList<>()); + result.put("timestamp", null); + return result; + } + /** + * 查询指定设备表的全部数据并按时间倒序排序 + * @param id 设备ID + * @return 设备数据列表,按时间戳倒序排列 + */ + @DS("tdengine") + public List> getstDeviceDataOrderByTimeDesc(Long id,String collectionStartTime, String collectionEndTime) { + String tableName = "d_" + id; + StringBuilder sqlBuilder = new StringBuilder(); + List params = new ArrayList<>(); + + sqlBuilder.append("SELECT ts, query_data FROM besure.").append(tableName).append(" WHERE 1=1"); + + if (collectionStartTime != null) { + // 直接将时间字符串拼接到SQL中 + sqlBuilder.append(" AND ts >= '").append(collectionStartTime).append("'"); + } + + if (collectionEndTime != null) { + sqlBuilder.append(" AND ts <= '").append(collectionEndTime).append("'"); + } + + sqlBuilder.append(" ORDER BY ts DESC"); + + try { + return jdbcTemplate.query(sqlBuilder.toString(), new RowMapper>() { + @Override + public Map mapRow(ResultSet rs, int rowNum) throws SQLException { + Map result = new HashMap<>(); + result.put("timestamp", rs.getTimestamp("ts")); + result.put("deviceId", id); + + byte[] blob = rs.getBytes("query_data"); + if (blob != null) { + String jsonStr = new String(blob, StandardCharsets.UTF_8); + + try { + // 1. 先去除外层的双引号(如果存在) + String trimmed = jsonStr.trim(); + if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { + trimmed = trimmed.substring(1, trimmed.length() - 1); + } + + // 2. 检查是否是十六进制字符串 + if (isHexString(trimmed)) { + // 3. 十六进制解码 + String decodedJson = hexToString(trimmed); + + result.put("queryData", decodedJson); + } else { + // 如果不是十六进制,尝试直接解析 + ObjectMapper objectMapper = new ObjectMapper(); + List> queryData = objectMapper.readValue( + trimmed, + new TypeReference>>() {} + ); + result.put("queryData", queryData); + } + + } catch (Exception e) { + System.err.println("解析JSON失败: " + e.getMessage()); + result.put("queryData", new ArrayList<>()); + result.put("rawData", jsonStr); + } + + } else { + result.put("queryData", new ArrayList<>()); + } + + return result; + } + }); + } catch (EmptyResultDataAccessException e) { + return Collections.singletonList(createEmptyResult(id)); + } catch (Exception e) { + System.err.println("查询设备" + id + "的最新数据时发生异常: " + e.getMessage()); + e.printStackTrace(); + return Collections.singletonList(createEmptyResult(id)); + } } } \ No newline at end of file