2025-9-29学习笔记
目录
接收设备端数据(续):
查询设备的物模型数据:
代码提交:
智能床位:
获取所有有智能设备的楼层:
获取房间中的智能设备及数据:
接收设备端数据(续):
AmqpClient:
@Autowiredprivate IDeviceDataService deviceDataService;/*** 在这里处理您收到消息后的具体业务逻辑。*/private void processMessage(Message message) {String contentStr;try {contentStr = message.getBody(String.class);String topic = message.getStringProperty("topic");String messageId = message.getStringProperty("messageId");log.info("receive message,\n topic = {},\n messageId = {},\n content = {}", topic, messageId, contentStr);} catch (JMSException e) {throw new RuntimeException("服务器错误");}// 将消息转换为json格式,数据为空,直接返回JSONObject jsonMsg = JSONUtil.parseObj(contentStr);// 取出消息中的notify_data字段,数据为空,直接返回JSONObject jsonNotifyData = jsonMsg.getJSONObject("notify_data");if (ObjectUtil.isEmpty(jsonNotifyData)) {return;}// 将json字符串转换为对象,如果属性上报为空,程序结束IotMsgNotifyData iotMsgNotifyData = JSONUtil.toBean(jsonNotifyData, IotMsgNotifyData.class);if (ObjectUtil.isEmpty(iotMsgNotifyData.getBody()) || ObjectUtil.isEmpty(iotMsgNotifyData.getBody().getServices())) {return;}deviceDataService.batchInsertDeviceData(iotMsgNotifyData);}
DeviceDataServiceImpl:
@Autowired
private DeviceMapper deviceMapper;/*** 批量保存设备数据* @param iotMsgNotifyData*/
@Override
public void batchInsertDeviceData(IotMsgNotifyData iotMsgNotifyData) {String iotId = iotMsgNotifyData.getHeader().getDeviceId();// 查询设备信息Device device = deviceMapper.selectOne(Wrappers.<Device>lambdaQuery().eq(Device::getIotId, iotId));if(ObjectUtil.isEmpty(device)) {log.error("设备不存在");return;}// 批量保存设备数据iotMsgNotifyData.getBody().getServices().forEach(s -> {// 判断属性是否为空Map<String, Object> properties = s.getProperties();if(CollUtil.isEmpty(properties)) {return;}// 上报时间处理String eventTimeStr = s.getEventTime();LocalDateTime localDateTime = LocalDateTimeUtil.parse(eventTimeStr, "yyyyMMdd'T'HHmmss'Z'");LocalDateTime eventTime = DateTimeZoneConverter.utcToShanghai(localDateTime);List<DeviceData> list = new ArrayList<>();// key:属性id,value:属性值properties.forEach((k,v) -> {DeviceData deviceData = BeanUtil.toBean(device, DeviceData.class);deviceData.setId(null);deviceData.setAlarmTime(eventTime);deviceData.setFunctionId(k);deviceData.setDataValue(v + "");list.add(deviceData);});// 批量保存设备数据saveBatch(list);});}
此时由于字段自动填充导致MP调用批量插入的时候报错,解决方法如下:用try-catch报错时返回true,这样就不会执行下面create_by字段的填充了
MyMetaObjectHandler:
public boolean isExclude() {try {String requestURI = request.getRequestURI();if(requestURI.startsWith("/member")) {return true;}} catch (Exception e) {return true;}return false;}
效果展示:
当设备增多后,数据库连接池可能会很快耗尽,可以适当增加连接数数量和等待时间
可能的错误提示:
wait millis 60003, active 20, maxActive 20, creating 0
application.yml:
查询设备的物模型数据:
由于若依生成的代码当中用的是DeviceData接收参数,该类没有起止时间以及分页信息,导致接口效果不符合预期,现修改代码:
新增DeviceDataPageReqDto:
package com.zzyl.nursing.dto;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;import java.time.LocalDateTime;@Data
@ApiModel("设备数据分页查询请求模型")
public class DeviceDataPageReqDto {@ApiModelProperty(value = "设备名称", required = false)private String deviceName;@ApiModelProperty(value = "功能ID", required = false)private String functionId;@ApiModelProperty(value = "开始时间", required = false)@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@ApiModelProperty(value = "结束时间", required = false)@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;@ApiModelProperty(value = "页码", required = true, example = "1")private Integer pageNum;@ApiModelProperty(value = "页面大小", required = true, example = "10")private Integer pageSize;}
DeviceDataController:
/*** 查询设备数据列表*/
@PreAuthorize("@ss.hasPermi('elder:data:list')")
@GetMapping("/list")
@ApiOperation("查询设备数据列表")
public TableDataInfo list(DeviceDataPageReqDto deviceDataPageReqDto)
{return deviceDataService.selectDeviceDataList(deviceDataPageReqDto);
}/*** 导出设备数据列表 删除这个方法*/
@ApiOperation("导出设备数据列表")
@PreAuthorize("@ss.hasPermi('nursing:deviceData:export')")
@Log(title = "设备数据", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, DeviceData deviceData)
{List<DeviceData> list = deviceDataService.selectDeviceDataList(deviceData);ExcelUtil<DeviceData> util = new ExcelUtil<DeviceData>(DeviceData.class);util.exportExcel(response, list, "设备数据数据");
}
IDeviceDataService及其实现类:
/*** 查询设备数据列表* * @param dto 设备数据* @return 设备数据集合*/public TableDataInfo selectDeviceDataList(DeviceDataPageReqDto dto);/*** 查询设备数据列表* * @param dto 设备数据* @return 设备数据*/@Overridepublic TableDataInfo selectDeviceDataList(DeviceDataPageReqDto dto){Page<DeviceData> page = new Page<>(dto.getPageNum(), dto.getPageSize());LambdaQueryWrapper<DeviceData> queryWrapper = Wrappers.<DeviceData>lambdaQuery().like(StringUtils.isNotEmpty(dto.getDeviceName()), DeviceData::getDeviceName, dto.getDeviceName()).eq(StringUtils.isNotEmpty(dto.getFunctionId()),DeviceData::getFunctionId,dto.getFunctionId()).between(dto.getStartTime() != null && dto.getEndTime() != null, DeviceData::getAlarmTime, dto.getStartTime(), dto.getEndTime());page = page(page,queryWrapper);return getTableDataInfo(page);}private TableDataInfo getTableDataInfo(Page<DeviceData> page) {TableDataInfo tableDataInfo = new TableDataInfo<>();tableDataInfo.setCode(HttpStatus.SUCCESS);tableDataInfo.setMsg("请求成功");tableDataInfo.setRows(page.getRecords());tableDataInfo.setTotal(page.getTotal());return tableDataInfo;}
效果展示:
代码提交:
智能床位:
代码实现:
获取所有有智能设备的楼层:
FloorController:
@ApiOperation("获取所有带有智能设备的楼层")@GetMapping("/getAllFloorsWithDevice")public R<List<FloorVo>> getAllFloorsWithDevice(){return R.ok(floorService.getAllFloorsWithDevice());}
FloorService:
/*** 查询智能楼层* @return List<FloorVo>*/List<FloorVo> getAllFloorsWithDevice();
FloorServiceImpl:
/*** 查询智能楼层** @return List<Floor>*/@Overridepublic List<FloorVo> getAllFloorsWithDevice() {return floorMapper.getAllFloorsWithDevice();}
FloorMapper:
/*** 获取所有带有智能设备的楼层* @return 列表*/List<FloorVo> getAllFloorsWithDevice();
FloorMapper.xml:
<select id="getAllFloorsWithDevice" resultType="com.zzyl.nursing.vo.FloorVo">select f.id, f.name, f.codefrom floor fleft join room r on f.id = r.floor_idleft join bed b on r.id = b.room_idleft join device rd on rd.binding_location = r.id and rd.location_type = 1 and rd.physical_location_type = 1left join device bd on bd.binding_location = b.id and bd.location_type = 1 and bd.physical_location_type = 2where (rd.id is not null or bd.id is not null)group by f.id</select>
效果展示:
获取房间中的智能设备及数据:
为了提高查找性能,我们用缓存替代mysql中复杂的查表:
DeviceDataServiceImpl:
@Autowired
private RedisTemplate<String, String> redisTemplate;/*** 批量保存** @param data*/
@Override
public void batchInsertDeviceData(IotMsgNotifyData data) {// 根据iotId查询设备Device device = deviceMapper.selectOne(Wrappers.<Device>lambdaQuery().eq(Device::getIotId, data.getHeader().getDeviceId()));if(ObjectUtil.isEmpty(device)){log.info("设备不存在,设备id:{}",data.getHeader().getDeviceId());return;}// 构建设备数据,可能会有多条List<IotMsgService> services = data.getBody().getServices();if(CollUtil.isEmpty(services)){log.info("设备数据为空");return;}// 遍历servicesservices.forEach(s->{//处理上报时间String eventTimeStr = s.getEventTime();LocalDateTime localDateTime = LocalDateTimeUtil.parse(eventTimeStr, "yyyyMMdd'T'HHmmss'Z'");LocalDateTime eventTime = DateTimeZoneConverter.utcToShanghai(localDateTime);List<DeviceData> list = new ArrayList<>();s.getProperties().forEach((k,v)->{//属性拷贝,从设备拷贝到设备数据DeviceData deviceData = BeanUtil.toBean(device, DeviceData.class);deviceData.setId(null);deviceData.setCreateTime(null);deviceData.setFunctionId(k);deviceData.setDataValue(v+"");deviceData.setAlarmTime(eventTime);list.add(deviceData);});//批量保存saveBatch(list);redisTemplate.opsForHash().put(CacheConstants.IOT_DEVICE_LAST_DATA, device.getIotId(), JSONUtil.toJsonStr(list));});
}
common-constan-CacheConstants:
/*** iot设备最新数据缓存key*/public static final String IOT_DEVICE_LAST_DATA = "iot:device_last_data";
打开模拟数据上报的项目,修改deviceId以及deviceSecret进行上报
效果展示:
按照前面分析的思路实现,结合接口需要的响应结果,需要准备基础数据并返回,基础数据就包括了房间、床位、老人、设备(房间或床位),其中的设备数据从Redis中获取
select r.*, -- 房间数据b.id bid,-- 床位数据b.bed_number,b.sort,b.bed_status,b.room_id,b.create_by,b.update_by,b.remark,b.create_time,b.update_time,e.name ename,-- 老人数据e.id eid,d.id as r_did,-- 房间关联的设备d.iot_id,d.product_key as product_key,d.device_name,d.product_name,dd.id as b_did,-- 床位关联的设备dd.iot_id b_iot_id,dd.product_key as b_product_key,dd.device_name as b_device_name,dd.product_name as b_product_name
from room rleft join bed b on r.id = b.room_idleft join elder e on b.id = e.bed_idleft join device d on d.binding_location = r.id and d.location_type = 1 and d.physical_location_type = 1left join device dd on dd.binding_location = b.id and dd.location_type = 1 and dd.physical_location_type = 2
where r.floor_id = 1 and (d.id is not null or dd.id is not null)
新建DeviceInfo:
package com.zzyl.nursing.vo;import com.zzyl.nursing.domain.DeviceData;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.util.List;@ApiModel("设备信息响应模型")
@Data
public class DeviceInfo {@ApiModelProperty(value = "主键")private Long id;@ApiModelProperty(value = "物联网设备ID")private String iotId;@ApiModelProperty(value = "设备名称")private String deviceName;@ApiModelProperty(value = "产品key")public String productKey;@ApiModelProperty(value = "产品名称")public String productName;@ApiModelProperty(value = "设备数据")private List<DeviceData> deviceDataVos;
}
其中的设备数据deviceDataVos
字段,是不需要从数据库中查询数据的,后面会到redis中查询设备数据
在RoomVo
和BedVo
中两个类中分别添加一个deviceVos
设备集合属性:
/*** 关联的设备*/
private List<DeviceInfo> deviceVos;
RoomController:
@GetMapping("/getRoomsWithDeviceByFloorId/{floorId}")
@ApiOperation("获取所有房间(智能床位)")
public R<List<RoomVo>> getRoomsWithDeviceByFloorId(@PathVariable(name = "floorId") Long floorId) {return R.ok(roomService.getRoomsWithDeviceByFloorId(floorId));
}
IRoomService:
/*** 获取所有房间(负责老人)* @param floorId* @return*/List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId);
RoomServiceImpl:
/*** 获取所有房间(负责老人)** @param floorId* @return*/@Overridepublic List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId) {// redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA,"");// SQL返回的数据是基础数据,找到的是房间、床位、设备(房间|床位)List<RoomVo> roomVos = roomMapper.getRoomsWithDeviceByFloorId(floorId);roomVos.forEach(roomVo -> {// 遍历的是房间数据List<DeviceInfo> deviceVos = roomVo.getDeviceVos();// 房间设备所对应的设备上报的数据deviceVos.forEach(deviceInfo -> {String jsonStr = (String) redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA, deviceInfo.getIotId());if(StringUtils.isEmpty(jsonStr)) {return; // 跳出本次循环,并不是结束方法}deviceInfo.setDeviceDataVos(JSONUtil.toList(jsonStr, DeviceData.class));});// 遍历的是床位数据roomVo.getBedVoList().forEach(bedVo -> {// 获取床位对应的设备列表bedVo.getDeviceVos().forEach(deviceInfo -> {String jsonStr = (String) redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA, deviceInfo.getIotId());if(StringUtils.isEmpty(jsonStr)) {return; // 跳出本次循环,并不是结束方法}deviceInfo.setDeviceDataVos(JSONUtil.toList(jsonStr, DeviceData.class));});});});return roomVos;}
RoomMapper:
/*** 根据楼层id查询房间信息(智能床位)* @param floorId* @return*/List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId);
RoomMapper.xml:
<resultMap id="roomVoDeviceByFloorIdResultMap" type="com.zzyl.nursing.vo.RoomVo"><id column="id" property="id"></id><result column="code" property="code"></result><collection property="bedVoList" ofType="com.zzyl.nursing.vo.BedVo"><id column="bid" property="id"/><result column="bed_number" property="bedNumber"></result><result column="bed_status" property="bedStatus"></result><result column="eid" property="elderId"></result><result column="ename" property="ename"></result><collection property="deviceVos" ofType="com.zzyl.nursing.vo.DeviceInfo"><id column="b_did" property="id"/><result column="b_iot_id" property="iotId"></result><result column="b_device_name" property="deviceName"></result><result column="b_product_key" property="productKey"></result><result column="b_product_name" property="productName"></result></collection></collection><collection property="deviceVos" ofType="com.zzyl.nursing.vo.DeviceInfo"><id column="did" property="id"/><result column="iot_id" property="iotId"></result><result column="device_name" property="deviceName"></result><result column="product_key" property="productKey"></result><result column="product_name" property="productName"></result></collection>
</resultMap>
<select id="getRoomsWithDeviceByFloorId" resultMap="roomVoDeviceByFloorIdResultMap" >select r.id ,r.code,b.id bid,b.bed_number,b.bed_status,e.id eid,e.name ename,d.id did ,d.iot_id ,d.device_name,d.product_key,d.product_name,dd.id b_did ,dd.iot_id b_iot_id,dd.device_name b_device_name,dd.product_key b_product_key,dd.product_name b_product_namefrom room r left join bed b on r.id = b.room_idleft join elder e on b.id = e.bed_idleft join device d on d.binding_location = r.id and d.location_type = 1 and d.physical_location_type = 1left join device dd on dd.binding_location = b.id and dd.location_type = 1 and dd.physical_location_type = 2where r.floor_id = #{floorId} and (d.id is not null or dd.id is not null)
</select>
效果展示: