Compare commits

...

5 Commits

@ -20,8 +20,9 @@ public interface ErpProductCategoryMapper extends BaseMapperX<ErpProductCategory
return selectList(new LambdaQueryWrapperX<ErpProductCategoryDO>()
.likeIfPresent(ErpProductCategoryDO::getName, reqVO.getName())
.eqIfPresent(ErpProductCategoryDO::getStatus, reqVO.getStatus())
.orderByDesc(ErpProductCategoryDO::getSort)
.orderByDesc(ErpProductCategoryDO::getId));
}
}
default ErpProductCategoryDO selectByParentIdAndName(Long parentId, String name) {
return selectOne(ErpProductCategoryDO::getParentId, parentId, ErpProductCategoryDO::getName, name);

@ -231,10 +231,11 @@ public class DeviceController {
public CommonResult<PageResult<Map<String, Object>>> historyRecord(@RequestParam("deviceId") Long deviceId,
@RequestParam(name = "collectionStartTime", required = false) String collectionStartTime,
@RequestParam(name = "collectionEndTime", required = false) String collectionEndTime,
@RequestParam(name = "pageNo", required = false, defaultValue = "1") Integer page,
@RequestParam(name = "attributeCodes", required = false)List<String> attributeCodes,
@RequestParam(name = "pageNo", required = false, defaultValue = "1") Integer page,
@RequestParam(name = "pageSize", required = false, defaultValue = "10") Integer pageSize
) throws JsonProcessingException {
PageResult<Map<String, Object>> deviceContactModelDO=deviceService.historyRecord(deviceId,collectionStartTime,collectionEndTime,page,pageSize);
PageResult<Map<String, Object>> deviceContactModelDO=deviceService.historyRecord(deviceId,collectionStartTime,collectionEndTime,attributeCodes,page,pageSize);
return success(deviceContactModelDO);
}

@ -73,4 +73,9 @@ public class DeviceOperationRecordDO extends BaseDO {
*/
private Double totalWarningTime;
/**
* id
*/
private String tenantId;
}

@ -123,4 +123,8 @@ public interface DeviceMapper extends BaseMapperX<DeviceDO> {
List<LineCodeAndNameRespVO> selectLineBatch(@Param("deviceIds") List<Long> deviceIds);
List<Long> selectDeviceIdsByLine(@Param("lineNode") String lineNode, @Param("lineName") String lineName);
Integer getTotalDeviceCount();
List<Long> getAllDeviceIds();
}

@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.iot.dal.mysql.deviceoperationrecord;
import java.time.LocalDateTime;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.iot.dal.dataobject.deviceoperationrecord.DeviceOperationRecordDO;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
@ -41,4 +43,18 @@ public interface DeviceOperationRecordMapper extends BaseMapperX<DeviceOperation
List<DeviceTotalTimeRecordRespVO> selectLine();
List<DeviceTotalTimeRecordRespVO> deviceOperationList(@Param("deviceTotalTimeRecordReqVO") DeviceTotalTimeRecordReqVO deviceTotalTimeRecordReqVO);
IPage<DeviceTotalTimeRecordRespVO> selectDevicePageFromMySQL(
Page<DeviceTotalTimeRecordRespVO> page,
@Param("pageReqVO") DeviceTotalTimeRecordReqVO pageReqVO
);
@DS("tdengine")
List<Map<String, Object>> selectDeviceStatsFromTD(
@Param("deviceIds") List<Long> deviceIds,
@Param("startTime") String startTime,
@Param("endTime") String endTime
);
List<DeviceTotalTimeRecordRespVO> selectDeviceListFromMySQL(@Param("deviceTotalTimeRecordReqVO") DeviceTotalTimeRecordReqVO reqVO);
}

@ -514,7 +514,8 @@ public class MqttDataHandler extends SuperConsumer<String> {
//TODO 创建人和更新人为内置默认管理员
deviceWarinningRecordDO.setCreator("1");
deviceWarinningRecordDO.setUpdater("1");
deviceWarinningRecordMapper.insert(deviceWarinningRecordDO);
// deviceWarinningRecordMapper.insert(deviceWarinningRecordDO);
tDengineService.insertDeviceWarningRecord(deviceWarinningRecordDO);
}
}

@ -31,8 +31,6 @@ import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@Slf4j
@Component
public class DeviceJob implements JobHandler {
@ -167,7 +165,9 @@ public class DeviceJob implements JobHandler {
record.setRule(DeviceStatusEnum.STANDBY.getCode());
record.setCreator("1");
record.setUpdater("1");
deviceOperationRecordMapper.insert(record);
// deviceOperationRecordMapper.insert(record);
tDengineService.insertDeviceOperationRecord(record);
return;
}
@ -223,7 +223,9 @@ public class DeviceJob implements JobHandler {
record.setCreator("1");
record.setUpdater("1");
deviceOperationRecordMapper.insert(record);
// deviceOperationRecordMapper.insert(record);
tDengineService.insertDeviceOperationRecord(record);
break;
}
}
@ -235,8 +237,8 @@ public class DeviceJob implements JobHandler {
record.setRule(DeviceStatusEnum.OFFLINE.getCode());
record.setCreator("1");
record.setUpdater("1");
deviceOperationRecordMapper.insert(record);
// deviceOperationRecordMapper.insert(record);
tDengineService.insertDeviceOperationRecord(record);
}
/**

@ -124,7 +124,7 @@ public interface DeviceService {
Map<String, List<Map<String, Object>>> singleDevice(Long deviceId) throws JsonProcessingException;
PageResult<Map<String, Object>> historyRecord(Long deviceId,String collectionStartTime, String collectionEndTime,Integer page,Integer size);
PageResult<Map<String, Object>> historyRecord(Long deviceId,String collectionStartTime, String collectionEndTime,List<String> attributeCodes,Integer page,Integer size);
Map<Long, Map<String, Object>> createDeviceDataMap(Long deviceId);

@ -332,7 +332,7 @@ public class DeviceServiceImpl implements DeviceService {
// .orderByDesc(DeviceOperationRecordDO::getCreateTime)
// );
List<DeviceOperationRecordDO> operationRecords = deviceOperationRecordMapper.selectLatestByDeviceAndRule(deviceIds,ruleCodes);
List<DeviceOperationRecordDO> operationRecords = tdengineService.selectLatestByDeviceAndRuleMinimal(deviceIds,ruleCodes);
// 按 deviceId 分组,取最新一条
Map<Long, DeviceOperationRecordDO> latestRecordMap = operationRecords.stream()
@ -949,6 +949,7 @@ public class DeviceServiceImpl implements DeviceService {
Long deviceId,
String collectionStartTime,
String collectionEndTime,
List<String> attributeCodes,
Integer page,
Integer pageSize) {
@ -1030,6 +1031,13 @@ public class DeviceServiceImpl implements DeviceService {
// 核心逻辑:遍历 attributeList保证全部字段返回
for (DeviceContactModelDO attribute : attributeList) {
// 过滤attributeCodes
if (attributeCodes != null && !attributeCodes.isEmpty()
&& !attributeCodes.contains(attribute.getAttributeCode())) {
continue;
}
String column =
attribute.getAttributeCode();
@ -1164,15 +1172,57 @@ public class DeviceServiceImpl implements DeviceService {
@Override
public DeviceOperationStatusRespVO getDeviceOperationalStatus() {
DeviceOperationStatusRespVO deviceOperationalStatus= deviceMapper.getDeviceOperationalStatus();
DeviceOperationStatusRespVO result = new DeviceOperationStatusRespVO();
// 1. 从 MySQL 获取设备总数
Integer totalDevices = deviceMapper.getTotalDeviceCount();
result.setTotalDevices(totalDevices);
// 2. 从 MySQL 获取所有设备ID
List<Long> deviceIds = deviceMapper.getAllDeviceIds();
if (CollectionUtils.isEmpty(deviceIds)) {
// 没有设备所有状态设为0
result.setRunningCount(0);
result.setStandbyCount(0);
result.setFaultCount(0);
result.setWarningCount(0);
return result;
}
// 3. 从 TDengine 批量查询设备最新状态
Map<Long, String> deviceStatusMap = tdengineService.getLatestDeviceStatusAlternative(deviceIds);
// 4. 统计各状态数量
int runningCount = 0;
int standbyCount = 0;
int faultCount = 0;
int warningCount = 0;
for (String status : deviceStatusMap.values()) {
switch (status) {
case "1": runningCount++; break;
case "2": standbyCount++; break;
case "3": faultCount++; break;
case "4": warningCount++; break;
default: break;
}
}
result.setRunningCount(runningCount);
result.setStandbyCount(standbyCount);
result.setFaultCount(faultCount);
result.setWarningCount(warningCount);
// 计算利用率
calculateUtilizationRate(deviceOperationalStatus);
calculateUtilizationRate(result);
// 计算故障率
calculateFaultRate(deviceOperationalStatus);
calculateFaultRate(result);
return deviceOperationalStatus;
return result;
}

@ -1,8 +1,13 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.iocoder.yudao.framework.common.pojo.DeviceEdgeData;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.enums.JavaToTdengineTypeEnum;
import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceTotalTimeRecordRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.DeviceWarinningRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.deviceoperationrecord.DeviceOperationRecordDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicewarinningrecord.DeviceWarinningRecordDO;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
@ -1564,4 +1569,611 @@ public class TDengineService {
}
}
// ========================== 运行记录表 ==========================
/**
* TDengine
* @param record
* @return
*/
@DS("tdengine")
public boolean insertDeviceOperationRecord(DeviceOperationRecordDO record) {
if (record == null || record.getDeviceId() == null) {
log.warn("设备运行记录参数为空");
return false;
}
// 表名 - 使用固定表名或按设备分表
String tableName = "besure_server.iot_device_operation_record";
// 或者按设备分表: "besure_server.operation_record_" + record.getDeviceId();
// 构建 SQL
StringBuilder columnBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
List<Object> params = new ArrayList<>();
// 1. 必须字段: 时间戳 (TDengine 要求)
columnBuilder.append("ts");
valueBuilder.append("?");
// 使用当前时间作为 ts或者使用 record 中的时间
// if (record.getCreateTime() != null) {
// 将 LocalDateTime 转换为 Timestamp
// Timestamp tsTimestamp = Timestamp.valueOf(record.getCreateTime());
params.add(new Timestamp(System.currentTimeMillis()));
// } else {
// params.add(new Timestamp(System.currentTimeMillis()));
// }
// 2. 添加其他字段
// addFieldIfNotNull(record.getId(), "id", columnBuilder, valueBuilder, params);
addFieldIfNotNull(record.getDeviceId(), "device_id", columnBuilder, valueBuilder, params);
addFieldIfNotNull(record.getModelId(), "model_id", columnBuilder, valueBuilder, params);
addFieldIfNotNull(record.getRule(), "rule", columnBuilder, valueBuilder, params);
addFieldIfNotNull(record.getAddressValue(), "address_value", columnBuilder, valueBuilder, params);
addFieldIfNotNull(record.getCreator(), "creator", columnBuilder, valueBuilder, params);
// create_time 单独处理(如果已经作为 ts 使用,可以选择不重复插入)
columnBuilder.append(", create_time");
valueBuilder.append(", ?");
// // 将 LocalDateTime 转换为 Timestamp
// LocalDateTime createTime = record.getCreateTime();
// Timestamp timestamp = Timestamp.valueOf(createTime);
params.add(new Timestamp(System.currentTimeMillis()));
addFieldIfNotNull(record.getUpdater(), "updater", columnBuilder, valueBuilder, params);
// update_time
columnBuilder.append(", update_time");
valueBuilder.append(", ?");
params.add(new Timestamp(System.currentTimeMillis()));
addFieldIfNotNull(0,"deleted", columnBuilder, valueBuilder, params);
//TODO 待优化租户ID
addFieldIfNotNull("1", "tenant_id", columnBuilder, valueBuilder, params);
addFieldIfNotNull(record.getRuleId(), "rule_id", columnBuilder, valueBuilder, params);
// 构建完整 SQL
String sql = String.format("INSERT INTO %s (%s) VALUES (%s)",
tableName, columnBuilder.toString(), valueBuilder.toString());
try {
int rows = jdbcTemplate.update(sql, params.toArray());
log.info("TDengine 设备运行记录插入成功: deviceId={}, table={}, rows={}",
record.getDeviceId(), tableName, rows);
return rows > 0;
} catch (Exception e) {
log.error("TDengine 设备运行记录插入失败: deviceId={}, table={}, sql={}",
record.getDeviceId(), tableName, sql, e);
return false;
}
}
/**
* SQL
*/
private void addFieldIfNotNull(Object value, String columnName,
StringBuilder columnBuilder, StringBuilder valueBuilder,
List<Object> params) {
if (value != null) {
if (columnBuilder.length() > 0 && !columnBuilder.toString().endsWith("ts")) {
columnBuilder.append(", ");
valueBuilder.append(", ");
} else if (columnBuilder.length() > 0) {
columnBuilder.append(", ");
valueBuilder.append(", ");
}
columnBuilder.append(columnName);
valueBuilder.append("?");
params.add(value);
}
}
/**
*
*/
@DS("tdengine")
public List<DeviceOperationRecordDO> selectLatestByDeviceAndRuleMinimal(
List<Long> deviceIds,
List<String> ruleCodes) {
if (CollectionUtils.isEmpty(deviceIds)) {
return Collections.emptyList();
}
List<Object> params = new ArrayList<>();
StringBuilder sql = new StringBuilder(
"SELECT * FROM besure_server.iot_device_operation_record " +
"WHERE deleted = 0 AND device_id IN ("
);
for (int i = 0; i < deviceIds.size(); i++) {
if (i > 0) sql.append(", ");
sql.append("?");
params.add(deviceIds.get(i));
}
sql.append(") ");
if (CollectionUtils.isNotEmpty(ruleCodes)) {
sql.append("AND rule IN (");
for (int i = 0; i < ruleCodes.size(); i++) {
if (i > 0) sql.append(", ");
sql.append("?");
params.add(ruleCodes.get(i));
}
sql.append(") ");
}
sql.append("ORDER BY create_time DESC");
try {
return jdbcTemplate.query(sql.toString(), params.toArray(), (rs, rowNum) -> {
DeviceOperationRecordDO d = new DeviceOperationRecordDO();
// d.setTs(rs.getTimestamp("ts") != null ? rs.getTimestamp("ts").toLocalDateTime() : null);
// d.setId(rs.getLong("id"));
d.setDeviceId(rs.getLong("device_id"));
d.setModelId(rs.getLong("model_id"));
d.setRule(rs.getString("rule"));
d.setAddressValue(rs.getString("address_value"));
d.setCreator(rs.getString("creator"));
d.setCreateTime(rs.getTimestamp("create_time") != null ? rs.getTimestamp("create_time").toLocalDateTime() : null);
d.setUpdater(rs.getString("updater"));
d.setUpdateTime(rs.getTimestamp("update_time") != null ? rs.getTimestamp("update_time").toLocalDateTime() : null);
d.setDeleted(rs.getBoolean("deleted"));
d.setTenantId(String.valueOf(rs.getLong("tenant_id")));
d.setRuleId(rs.getLong("rule_id"));
return d;
});
} catch (Exception e) {
log.error("查询失败", e);
return Collections.emptyList();
}
}
/**
* TDengine
*/
@DS("tdengine")
public Map<Long, String> getLatestDeviceStatusAlternative(List<Long> deviceIds) {
if (CollectionUtils.isEmpty(deviceIds)) {
return Collections.emptyMap();
}
Map<Long, String> statusMap = new HashMap<>();
// 分批查询每批50个设备
int batchSize = 50;
for (int i = 0; i < deviceIds.size(); i += batchSize) {
int end = Math.min(i + batchSize, deviceIds.size());
List<Long> batchIds = deviceIds.subList(i, end);
// 为每批设备构建查询
batchIds.forEach(deviceId -> {
String status = getSingleDeviceStatus(deviceId);
if (status != null) {
statusMap.put(deviceId, status);
}
});
}
return statusMap;
}
/**
*
*/
@DS("tdengine")
private String getSingleDeviceStatus(Long deviceId) {
String sql = "SELECT rule " +
"FROM besure_server.iot_device_operation_record " +
"WHERE device_id = ? " +
" AND deleted = 0 " +
" AND rule IN ('1', '2', '3', '4') " +
"ORDER BY create_time DESC " +
"LIMIT 1";
try {
return jdbcTemplate.queryForObject(sql, String.class, deviceId);
} catch (EmptyResultDataAccessException e) {
// 没有记录
return null;
} catch (Exception e) {
log.error("查询设备 {} 状态失败: {}", deviceId, e.getMessage());
return null;
}
}
@DS("tdengine")
public Map<Long, DeviceTotalTimeRecordRespVO> getDeviceTimeStatsFromTD(List<Long> deviceIds, String startTime, String endTime) {
if (CollectionUtils.isEmpty(deviceIds)) {
return Collections.emptyMap();
}
StringBuilder sql = new StringBuilder();
List<Object> params = new ArrayList<>();
sql.append("SELECT ")
.append(" device_id, ")
.append(" SUM(CASE WHEN rule = '0' THEN 1 ELSE 0 END) * 60 AS totalOfflineTime, ")
.append(" SUM(CASE WHEN rule = '1' THEN 1 ELSE 0 END) * 60 AS totalRunningTime, ")
.append(" SUM(CASE WHEN rule = '2' THEN 1 ELSE 0 END) * 60 AS totalStandbyTime, ")
.append(" SUM(CASE WHEN rule = '3' THEN 1 ELSE 0 END) * 60 AS totalFaultTime ")
.append("FROM besure_server.iot_device_operation_record ")
.append("WHERE deleted = 0 ")
.append(" AND device_id IN (");
for (int i = 0; i < deviceIds.size(); i++) {
if (i > 0) sql.append(", ");
sql.append("?");
params.add(deviceIds.get(i));
}
sql.append(") ");
if (startTime != null) {
sql.append(" AND create_time >= ? ");
params.add(Timestamp.valueOf(startTime));
}
if (endTime != null) {
sql.append(" AND create_time <= ? ");
params.add(Timestamp.valueOf(endTime));
}
sql.append("GROUP BY device_id");
try {
List<Map<String, Object>> result = jdbcTemplate.queryForList(
sql.toString(), params.toArray()
);
return convertStatsToMap(result);
} catch (Exception e) {
log.error("TDengine 查询设备运行时间失败", e);
return Collections.emptyMap();
}
}
/**
*
*/
private Map<Long, DeviceTotalTimeRecordRespVO> convertStatsToMap(
List<Map<String, Object>> result) {
Map<Long, DeviceTotalTimeRecordRespVO> statsMap = new HashMap<>();
for (Map<String, Object> row : result) {
Long deviceId = ((Number) row.get("device_id")).longValue();
DeviceTotalTimeRecordRespVO vo = new DeviceTotalTimeRecordRespVO();
vo.setId(deviceId);
vo.setTotalOfflineTime(getIntValue(row, "totalofflinetime"));
vo.setTotalRunningTime(getIntValue(row, "totalrunningtime"));
vo.setTotalStandbyTime(getIntValue(row, "totalstandbytime"));
vo.setTotalFaultTime(getIntValue(row, "totalfaulttime"));
statsMap.put(deviceId, vo);
}
return statsMap;
}
/**
*
*/
private Integer getIntValue(Map<String, Object> map, String key) {
Object value = map.get(key);
if (value == null) return 0;
if (value instanceof Number) return ((Number) value).intValue();
return 0;
}
//=============================== 告警记录表 ===============================
/**
* TDengine使
*/
@DS("tdengine")
public boolean insertDeviceWarningRecord(DeviceWarinningRecordDO deviceWarningRecordDO) {
if (deviceWarningRecordDO == null) {
log.warn("告警记录参数为空");
return false;
}
String tableName = "besure_server.iot_device_warning_record";
StringBuilder columnBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
List<Object> params = new ArrayList<>();
// 1. 必须字段: 时间戳
columnBuilder.append("ts");
valueBuilder.append("?");
params.add(new Timestamp(System.currentTimeMillis()));
// 2. 设备信息
// addFieldIfNotNull(deviceWarningRecordDO.getId(), "id", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getDeviceId(), "device_id", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getModelId(), "model_id", columnBuilder, valueBuilder, params);
// 3. 规则和告警信息
addFieldIfNotNull(deviceWarningRecordDO.getRule(), "rule", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getAlarmLevel(), "alarm_level", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getAddressValue(), "address_value", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getRuleId(), "rule_id", columnBuilder, valueBuilder, params);
// 4. 名称信息
addFieldIfNotNull(deviceWarningRecordDO.getDeviceName(), "device_name", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getModelName(), "model_name", columnBuilder, valueBuilder, params);
addFieldIfNotNull(deviceWarningRecordDO.getRuleName(), "rule_name", columnBuilder, valueBuilder, params);
// 5. 创建者和更新者
//TODO 待优化
addFieldIfNotNull("1", "creator", columnBuilder, valueBuilder, params);
addFieldIfNotNull("1", "updater", columnBuilder, valueBuilder, params);
// 6. 时间字段
addFieldIfNotNull(new Timestamp(System.currentTimeMillis()), "create_time", columnBuilder, valueBuilder, params);
addFieldIfNotNull(new Timestamp(System.currentTimeMillis()), "update_time", columnBuilder, valueBuilder, params);
// 7. 删除标志
addFieldIfNotNull(0, "deleted", columnBuilder, valueBuilder, params);
// 8. 租户ID
//TODO 待优化
addFieldIfNotNull("1", "tenant_id", columnBuilder, valueBuilder, params);
// 构建完整 SQL
String sql = String.format("INSERT INTO %s (%s) VALUES (%s)",
tableName, columnBuilder.toString(), valueBuilder.toString());
try {
int rows = jdbcTemplate.update(sql, params.toArray());
log.info("告警记录插入成功: deviceId={}, rule={}",
deviceWarningRecordDO.getDeviceId(),
deviceWarningRecordDO.getRule());
return rows > 0;
} catch (Exception e) {
log.error("告警记录插入失败: deviceId={}, sql={}",
deviceWarningRecordDO.getDeviceId(), sql, e);
return false;
}
}
/**
* TDengine
*/
@DS("tdengine")
public PageResult<DeviceWarinningRecordDO> selectRunngingRecordPage(DeviceWarinningRecordPageReqVO reqVO) {
// 构建查询 SQL
StringBuilder sql = new StringBuilder("SELECT * FROM besure_server.iot_device_warning_record ");
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM besure_server.iot_device_warning_record ");
List<Object> params = new ArrayList<>();
List<Object> countParams = new ArrayList<>();
// 添加 WHERE 条件
String whereCondition = buildWhereCondition(reqVO, params);
String countWhereCondition = buildWhereCondition(reqVO, countParams);
sql.append(whereCondition);
countSql.append(countWhereCondition);
// 添加排序
sql.append(" ORDER BY create_time DESC");
// 添加分页
if (reqVO.getPageNo() != null && reqVO.getPageSize() != null) {
int offset = (reqVO.getPageNo() - 1) * reqVO.getPageSize();
sql.append(" LIMIT ").append(reqVO.getPageSize()).append(" OFFSET ").append(offset);
}
try {
// 1. 查询总数
Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class, countParams.toArray());
if (total == null || total == 0) {
return new PageResult<>(Collections.emptyList(), 0L);
}
// 2. 查询数据
List<DeviceWarinningRecordDO> records = jdbcTemplate.query(
sql.toString(),
params.toArray(),
(rs, rowNum) -> mapRowToDO(rs)
);
return new PageResult<>(records, total);
} catch (Exception e) {
log.error("TDengine 分页查询告警记录失败", e);
return new PageResult<>(Collections.emptyList(), 0L);
}
}
/**
* WHERE
*/
private String buildWhereCondition(DeviceWarinningRecordPageReqVO reqVO, List<Object> params) {
StringBuilder where = new StringBuilder(" WHERE deleted = 0 ");
// 设备ID
if (reqVO.getDeviceId() != null) {
where.append(" AND device_id = ? ");
params.add(reqVO.getDeviceId());
}
// 模型ID
if (reqVO.getModelId() != null) {
where.append(" AND model_id = ? ");
params.add(reqVO.getModelId());
}
// 规则
if (StringUtils.isNotBlank(reqVO.getRule())) {
where.append(" AND rule = ? ");
params.add(reqVO.getRule());
}
// 告警级别
if (StringUtils.isNotBlank(reqVO.getAlarmLevel())) {
where.append(" AND alarm_level = ? ");
params.add(reqVO.getAlarmLevel());
}
// 地址值
if (StringUtils.isNotBlank(reqVO.getAddressValue())) {
where.append(" AND address_value = ? ");
params.add(reqVO.getAddressValue());
}
// 创建时间范围
if (reqVO.getCreateTime() != null && reqVO.getCreateTime().length == 2) {
LocalDateTime startTime = reqVO.getCreateTime()[0];
LocalDateTime endTime = reqVO.getCreateTime()[1];
if (startTime != null) {
where.append(" AND create_time >= ? ");
params.add(Timestamp.valueOf(startTime));
}
if (endTime != null) {
where.append(" AND create_time <= ? ");
params.add(Timestamp.valueOf(endTime));
}
}
// 规则ID
if (reqVO.getRuleId() != null) {
where.append(" AND rule_id = ? ");
params.add(reqVO.getRuleId());
}
return where.toString();
}
/**
* DO
*/
private DeviceWarinningRecordDO mapRowToDO(ResultSet rs) throws SQLException {
DeviceWarinningRecordDO record = new DeviceWarinningRecordDO();
// 时间戳
Timestamp ts = rs.getTimestamp("ts");
// if (ts != null) {
// record.setTs(ts.toLocalDateTime());
// }
// 其他字段
// record.setId(rs.getLong("id"));
record.setDeviceId(rs.getLong("device_id"));
record.setModelId(rs.getLong("model_id"));
record.setRule(rs.getString("rule"));
record.setAlarmLevel(rs.getString("alarm_level"));
record.setAddressValue(rs.getString("address_value"));
record.setCreator(rs.getString("creator"));
Timestamp createTime = rs.getTimestamp("create_time");
if (createTime != null) {
record.setCreateTime(createTime.toLocalDateTime());
}
record.setUpdater(rs.getString("updater"));
Timestamp updateTime = rs.getTimestamp("update_time");
if (updateTime != null) {
record.setUpdateTime(updateTime.toLocalDateTime());
}
record.setDeleted(rs.getInt("deleted") == 1);
// record.setTenantId(rs.getLong("tenant_id"));
record.setRuleId(rs.getLong("rule_id"));
record.setDeviceName(rs.getString("device_name"));
record.setModelName(rs.getString("model_name"));
record.setRuleName(rs.getString("rule_name"));
return record;
}
/**
* 100
*/
@DS("tdengine")
public List<DeviceWarinningRecordDO> selectDeviceWarningList(Long deviceId) {
StringBuilder sql = new StringBuilder();
List<Object> params = new ArrayList<>();
sql.append("SELECT * FROM besure_server.iot_device_warning_record ")
.append("WHERE deleted = 0 ");
if (deviceId != null) {
sql.append("AND device_id = ? ");
params.add(deviceId);
}
sql.append("ORDER BY create_time DESC ")
.append("LIMIT 100");
try {
return jdbcTemplate.query(sql.toString(), params.toArray(), (rs, rowNum) -> {
return mapRowToWarningRecordDO(rs);
});
} catch (Exception e) {
log.error("查询设备告警记录失败: deviceId={}", deviceId, e);
return Collections.emptyList();
}
}
/**
*
*/
private DeviceWarinningRecordDO mapRowToWarningRecordDO(ResultSet rs) throws SQLException {
DeviceWarinningRecordDO record = new DeviceWarinningRecordDO();
// 时间戳
Timestamp ts = rs.getTimestamp("ts");
// if (ts != null) {
// record.setTs(ts.toLocalDateTime());
// }
// 其他字段
// record.setId(rs.getLong("id"));
record.setDeviceId(rs.getLong("device_id"));
record.setModelId(rs.getLong("model_id"));
record.setRule(rs.getString("rule"));
record.setAlarmLevel(rs.getString("alarm_level"));
record.setAddressValue(rs.getString("address_value"));
record.setCreator(rs.getString("creator"));
Timestamp createTime = rs.getTimestamp("create_time");
if (createTime != null) {
record.setCreateTime(createTime.toLocalDateTime());
}
record.setUpdater(rs.getString("updater"));
Timestamp updateTime = rs.getTimestamp("update_time");
if (updateTime != null) {
record.setUpdateTime(updateTime.toLocalDateTime());
}
record.setDeleted(rs.getInt("deleted") == 1);
// record.setTenantId(rs.getLong("tenant_id"));
record.setRuleId(rs.getLong("rule_id"));
record.setDeviceName(rs.getString("device_name"));
record.setModelName(rs.getString("model_name"));
record.setRuleName(rs.getString("rule_name"));
return record;
}
}

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.iot.service.deviceoperationrecord;
import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.utils.TimeConverterUtil;
import cn.iocoder.yudao.module.iot.service.device.TDengineService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -40,6 +42,9 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
@Resource
private DeviceOperationRecordMapper deviceOperationRecordMapper;
@Resource
private TDengineService tDengineService;
@Override
public Long createDeviceOperationRecord(DeviceOperationRecordSaveReqVO createReqVO) {
@ -86,15 +91,56 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
@Override
public PageResult<DeviceTotalTimeRecordRespVO> deviceOperationPage(DeviceTotalTimeRecordReqVO pageReqVO) {
// 1. 先从 MySQL 查询设备分页信息
Page<DeviceTotalTimeRecordRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
IPage<DeviceTotalTimeRecordRespVO> deviceOperationRecordRespVOIPage = deviceOperationRecordMapper.deviceOperationPage(page,pageReqVO);
// 计算和转换
if (deviceOperationRecordRespVOIPage != null && !deviceOperationRecordRespVOIPage.getRecords().isEmpty()) {
calculateAndSetConvertedValues(deviceOperationRecordRespVOIPage.getRecords(),pageReqVO);
IPage<DeviceTotalTimeRecordRespVO> mysqlDevicePage =
deviceOperationRecordMapper.selectDevicePageFromMySQL(page, pageReqVO);
if (mysqlDevicePage == null || mysqlDevicePage.getRecords().isEmpty()) {
return new PageResult<>(Collections.emptyList(), 0L);
}
// 2. 提取设备ID列表
List<Long> deviceIds = mysqlDevicePage.getRecords().stream()
.map(DeviceTotalTimeRecordRespVO::getId)
.collect(Collectors.toList());
// 3. 从 TDengine 批量查询统计数据
List<Map<String, Object>> statsList =
deviceOperationRecordMapper.selectDeviceStatsFromTD(
deviceIds,
pageReqVO.getStartTime(),
pageReqVO.getEndTime()
);
// 4. 转换为 Map<设备ID, 统计数据>
Map<Long, DeviceTotalTimeRecordRespVO> tdStatsMap = convertStatsListToMap(statsList);
// 5. 合并数据
List<DeviceTotalTimeRecordRespVO> result = new ArrayList<>();
for (DeviceTotalTimeRecordRespVO device : mysqlDevicePage.getRecords()) {
DeviceTotalTimeRecordRespVO stats = tdStatsMap.get(device.getId());
if (stats != null) {
// 设置统计数据
device.setTotalOfflineTime(stats.getTotalOfflineTime());
device.setTotalRunningTime(stats.getTotalRunningTime());
device.setTotalStandbyTime(stats.getTotalStandbyTime());
device.setTotalFaultTime(stats.getTotalFaultTime());
} else {
// 没有统计数据设为0
device.setTotalOfflineTime(0);
device.setTotalRunningTime(0);
device.setTotalStandbyTime(0);
device.setTotalFaultTime(0);
}
result.add(device);
}
// 5. 计算和转换
calculateAndSetConvertedValues(result, pageReqVO);
return new PageResult<>(deviceOperationRecordRespVOIPage.getRecords(), deviceOperationRecordRespVOIPage.getTotal());
return new PageResult<>(result, mysqlDevicePage.getTotal());
}
@ -118,11 +164,50 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
deviceTotalTimeRecordReqVO.setEndTime(LocalDateTime.now().format(formatter));
deviceTotalTimeRecordReqVO.setIds(ids);
// 4 查询统计数据
List<DeviceTotalTimeRecordRespVO> list =
deviceOperationRecordMapper.deviceOperationList(deviceTotalTimeRecordReqVO);
// // 4 查询统计数据
// List<DeviceTotalTimeRecordRespVO> list =
// deviceOperationRecordMapper.deviceOperationList(deviceTotalTimeRecordReqVO);
if (list != null && !list.isEmpty()) {
// 4.1 先从 MySQL 查询设备列表
List<DeviceTotalTimeRecordRespVO> deviceList =
deviceOperationRecordMapper.selectDeviceListFromMySQL(deviceTotalTimeRecordReqVO);
if (CollectionUtils.isEmpty(deviceList)) {
return Collections.emptyList();
}
// 4.2 提取设备ID列表
List<Long> deviceIds = deviceList.stream()
.map(DeviceTotalTimeRecordRespVO::getId)
.collect(Collectors.toList());
// 4.3 从 TDengine 批量查询设备运行时间统计
Map<Long, DeviceTotalTimeRecordRespVO> statsMap =
tDengineService.getDeviceTimeStatsFromTD(deviceIds, deviceTotalTimeRecordReqVO.getStartTime(), deviceTotalTimeRecordReqVO.getEndTime());
// 4.4 合并数据
List<DeviceTotalTimeRecordRespVO> list = new ArrayList<>();
for (DeviceTotalTimeRecordRespVO device : deviceList) {
DeviceTotalTimeRecordRespVO stats = statsMap.get(device.getId());
if (stats != null) {
// 设置统计数据
device.setTotalOfflineTime(stats.getTotalOfflineTime());
device.setTotalRunningTime(stats.getTotalRunningTime());
device.setTotalStandbyTime(stats.getTotalStandbyTime());
device.setTotalFaultTime(stats.getTotalFaultTime());
} else {
// 没有统计数据设为0
device.setTotalOfflineTime(0);
device.setTotalRunningTime(0);
device.setTotalStandbyTime(0);
device.setTotalFaultTime(0);
}
list.add(device);
}
if (!list.isEmpty()) {
// 5 计算转换
@ -239,4 +324,73 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
record.setUtilizationRate("0%");
}
/**
* Map<ID, >
*/
private Map<Long, DeviceTotalTimeRecordRespVO> convertStatsListToMap(List<Map<String, Object>> statsList) {
if (CollectionUtils.isEmpty(statsList)) {
return Collections.emptyMap();
}
Map<Long, DeviceTotalTimeRecordRespVO> statsMap = new HashMap<>();
for (Map<String, Object> stat : statsList) {
Object deviceIdObj = stat.get("deviceid");
if (deviceIdObj == null) {
continue;
}
Long deviceId = null;
if (deviceIdObj instanceof Number) {
deviceId = ((Number) deviceIdObj).longValue();
} else if (deviceIdObj instanceof String) {
try {
deviceId = Long.parseLong((String) deviceIdObj);
} catch (NumberFormatException e) {
log.warn("设备ID格式错误: {}", deviceIdObj);
continue;
}
} else {
log.warn("设备ID类型错误: {}", deviceIdObj.getClass());
continue;
}
DeviceTotalTimeRecordRespVO vo = new DeviceTotalTimeRecordRespVO();
vo.setId(deviceId);
// 安全设置统计值
vo.setTotalOfflineTime(getIntValue(stat, "totalofflinetime"));
vo.setTotalRunningTime(getIntValue(stat, "totalrunningtime"));
vo.setTotalStandbyTime(getIntValue(stat, "totalstandbytime"));
vo.setTotalFaultTime(getIntValue(stat, "totalfaulttime"));
statsMap.put(deviceId, vo);
}
return statsMap;
}
/**
*
*/
private Integer getIntValue(Map<String, Object> map, String key) {
Object value = map.get(key.toLowerCase());
if (value == null) {
return 0;
}
if (value instanceof Number) {
return ((Number) value).intValue();
} else if (value instanceof String) {
try {
return Integer.parseInt((String) value);
} catch (NumberFormatException e) {
log.warn("{} 值格式错误: {}", key, value);
return 0;
}
}
return 0;
}
}

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.service.devicewarinningrecord;
import cn.iocoder.yudao.module.iot.service.device.TDengineService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -34,6 +35,10 @@ public class DeviceWarinningRecordServiceImpl implements DeviceWarinningRecordSe
@Resource
private DeviceWarinningRecordMapper deviceWarinningRecordMapper;
@Resource
private TDengineService tDengineService;
@Override
public Long createDeviceWarinningRecord(DeviceWarinningRecordSaveReqVO createReqVO) {
// 插入
@ -73,7 +78,8 @@ public class DeviceWarinningRecordServiceImpl implements DeviceWarinningRecordSe
@Override
public PageResult<DeviceWarinningRecordDO> getDeviceWarinningRecordPage(DeviceWarinningRecordPageReqVO pageReqVO) {
return deviceWarinningRecordMapper.selectPage(pageReqVO);
// return deviceWarinningRecordMapper.selectPage(pageReqVO);
return tDengineService.selectRunngingRecordPage(pageReqVO);
}
@Override
@ -81,12 +87,14 @@ public class DeviceWarinningRecordServiceImpl implements DeviceWarinningRecordSe
// if (id == null) {
// return Collections.emptyList();
// }
return deviceWarinningRecordMapper.selectList(
Wrappers.<DeviceWarinningRecordDO>lambdaQuery()
.eq(id != null, DeviceWarinningRecordDO::getDeviceId, id)
.orderByDesc(DeviceWarinningRecordDO::getCreateTime)
.last("LIMIT 100")// 限制 100 条
);
// return deviceWarinningRecordMapper.selectList(
// Wrappers.<DeviceWarinningRecordDO>lambdaQuery()
// .eq(id != null, DeviceWarinningRecordDO::getDeviceId, id)
// .orderByDesc(DeviceWarinningRecordDO::getCreateTime)
// .last("LIMIT 100")// 限制 100 条
// );
return tDengineService.selectDeviceWarningList(id);
}
@Override

@ -192,5 +192,16 @@
AND mo.name LIKE CONCAT('%', #{lineName}, '%')
</if>
</select>
<select id="getTotalDeviceCount" resultType="java.lang.Integer">
SELECT COUNT(DISTINCT id)
FROM besure.iot_device
WHERE deleted = 0
</select>
<select id="getAllDeviceIds" resultType="java.lang.Long">
SELECT id
FROM besure.iot_device
WHERE deleted = 0
ORDER BY id
</select>
</mapper>

@ -157,4 +157,80 @@
GROUP BY ide.id, ide.device_code, ide.device_name
ORDER BY ide.id desc
</select>
<!-- 1. 从 MySQL 查询设备分页信息(只有设备基本信息) -->
<select id="selectDevicePageFromMySQL"
resultType="cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceTotalTimeRecordRespVO">
SELECT
ide.id,
ide.device_code AS deviceCode,
ide.device_name AS deviceName
FROM besure.iot_device ide
WHERE ide.deleted = 0
<!-- 设备编码筛选条件 -->
<if test="pageReqVO.deviceCode != null and pageReqVO.deviceCode != ''">
AND ide.device_code LIKE CONCAT('%', #{pageReqVO.deviceCode}, '%')
</if>
<!-- 设备名称筛选条件 -->
<if test="pageReqVO.deviceName != null and pageReqVO.deviceName != ''">
AND ide.device_name LIKE CONCAT('%', #{pageReqVO.deviceName}, '%')
</if>
<!-- id筛选条件 -->
<if test="pageReqVO.ids != null and pageReqVO.ids != ''">
AND FIND_IN_SET(ide.id, #{pageReqVO.ids})
</if>
ORDER BY ide.id DESC
</select>
<!-- 2. 从 TDengine 批量查询设备统计数据 -->
<select id="selectDeviceStatsFromTD" resultType="java.util.Map">
SELECT
device_id as deviceId,
-- 每条 rule='0' 的记录计数 * 60 秒
SUM(CASE WHEN rule = '0' THEN 1 ELSE 0 END) * 60 AS totalOfflineTime,
-- 每条 rule='1' 的记录计数 * 60 秒
SUM(CASE WHEN rule = '1' THEN 1 ELSE 0 END) * 60 AS totalRunningTime,
-- 每条 rule='2' 的记录计数 * 60 秒
SUM(CASE WHEN rule = '2' THEN 1 ELSE 0 END) * 60 AS totalStandbyTime,
-- 每条 rule='3' 的记录计数 * 60 秒
SUM(CASE WHEN rule = '3' THEN 1 ELSE 0 END) * 60 AS totalFaultTime
FROM besure_server.iot_device_operation_record
WHERE deleted = 0
AND device_id IN
<foreach item="deviceId" collection="deviceIds" open="(" separator="," close=")">
#{deviceId}
</foreach>
<!-- 时间筛选条件 -->
<if test="startTime != null">
AND create_time &gt;= #{startTime, jdbcType=TIMESTAMP}
</if>
<if test="endTime != null">
AND create_time &lt;= #{endTime, jdbcType=TIMESTAMP}
</if>
GROUP BY device_id
</select>
<select id="selectDeviceListFromMySQL"
resultType="cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceTotalTimeRecordRespVO">
SELECT
ide.id,
ide.device_code AS deviceCode,
ide.device_name AS deviceName
FROM besure.iot_device ide
WHERE ide.deleted = 0
<!-- 设备编码筛选条件 -->
<if test="deviceTotalTimeRecordReqVO.deviceCode != null and deviceTotalTimeRecordReqVO.deviceCode != ''">
AND ide.device_code LIKE CONCAT('%', #{deviceTotalTimeRecordReqVO.deviceCode}, '%')
</if>
<!-- 设备名称筛选条件 -->
<if test="deviceTotalTimeRecordReqVO.deviceName != null and deviceTotalTimeRecordReqVO.deviceName != ''">
AND ide.device_name LIKE CONCAT('%', #{deviceTotalTimeRecordReqVO.deviceName}, '%')
</if>
<!-- id筛选条件 -->
<if test="deviceTotalTimeRecordReqVO.ids != null and deviceTotalTimeRecordReqVO.ids != ''">
AND FIND_IN_SET(ide.id, #{deviceTotalTimeRecordReqVO.ids})
</if>
ORDER BY ide.id DESC
</select>
</mapper>

@ -52,4 +52,6 @@ public class DictDataRespVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
private LocalDateTime createTime;
@Schema(description = "字典标签(英文)", example = "我是一个角色")
private String labelEn;
}

@ -49,4 +49,7 @@ public class DictDataSaveReqVO {
@Schema(description = "备注", example = "我是一个角色")
private String remark;
@Schema(description = "字典标签(英文)", example = "btn-visible")
private String labelEn;
}

@ -22,4 +22,6 @@ public class DictDataSimpleRespVO {
@Schema(description = "css 样式", example = "btn-visible")
private String cssClass;
@Schema(description = "字典标签(英文)", example = "men")
private String labelEn;
}

@ -61,5 +61,9 @@ public class DictDataDO extends BaseDO {
*
*/
private String remark;
/**
*
*/
private String labelEn;
}

Loading…
Cancel
Save