【功能优化】IoT:device 和 thingmodel 读取增加缓存

This commit is contained in:
YunaiV 2025-01-27 22:23:31 +08:00
parent 7745035fa4
commit a364153d4a
8 changed files with 115 additions and 32 deletions

View File

@ -49,6 +49,10 @@ public interface IotThingModelMapper extends BaseMapperX<IotThingModelDO> {
return selectList(IotThingModelDO::getProductId, productId); return selectList(IotThingModelDO::getProductId, productId);
} }
default List<IotThingModelDO> selectListByProductKey(String productKey) {
return selectList(IotThingModelDO::getProductKey, productKey);
}
default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) { default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
return selectList(IotThingModelDO::getProductId, productId, return selectList(IotThingModelDO::getProductId, productId,
IotThingModelDO::getType, type); IotThingModelDO::getType, type);
@ -68,8 +72,4 @@ public interface IotThingModelMapper extends BaseMapperX<IotThingModelDO> {
IotThingModelDO::getName, name); IotThingModelDO::getName, name);
} }
default List<IotThingModelDO> selectListByProductKey(String productKey) {
return selectList(IotThingModelDO::getProductKey, productKey);
}
} }

View File

@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
public interface RedisKeyConstants { public interface RedisKeyConstants {
/** /**
* 设备属性数据缓存采用 HASH 结构 * 设备属性数据缓存采用 HASH 结构
* <p> * <p>
* KEY 格式device_property:{deviceKey} * KEY 格式device_property:{deviceKey}
* HASH KEYidentifier 属性标识 * HASH KEYidentifier 属性标识
@ -26,4 +26,20 @@ public interface RedisKeyConstants {
*/ */
String DEVICE_REPORT_TIME = "device_report_time"; String DEVICE_REPORT_TIME = "device_report_time";
/**
* 设备信息的数据缓存使用 Spring Cache 操作
*
* KEY 格式device_${productKey}_${deviceKey}
* VALUE 数据类型String(JSON)
*/
String DEVICE = "device";
/**
* 物模型的数据缓存使用 Spring Cache 操作
*
* KEY 格式thing_model_${productKey}
* VALUE 数据类型String 数组(JSON) {@link cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO} 列表
*/
String THING_MODEL_LIST = "thing_model_list";
} }

View File

@ -113,15 +113,16 @@ public interface IotDeviceService {
*/ */
Long getDeviceCountByGroupId(Long groupId); Long getDeviceCountByGroupId(Long groupId);
// TODO @芋艿增加缓存
/** /**
* 根据产品 key 和设备名称获得设备信息 * 缓存根据产品 key 和设备名称获得设备信息
*
* 注意该方法会忽略租户信息所以调用时需要确认会不会有跨租户访问的风险
* *
* @param productKey 产品 key * @param productKey 产品 key
* @param deviceName 设备名称 * @param deviceName 设备名称
* @return 设备信息 * @return 设备信息
*/ */
IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName); IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
/** /**
* 导入设备 * 导入设备

View File

@ -4,22 +4,27 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper; import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import cn.iocoder.yudao.module.iot.service.product.IotProductService; import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -78,6 +83,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds()); deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
// 2.1 转换 VO DO // 2.1 转换 VO DO
// TODO @芋艿state 相关的参数另外到底叫 state还是 status
IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> { IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> {
o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType()); o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
// 生成并设置必要的字段 // 生成并设置必要的字段
@ -109,6 +115,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 2. 更新到数据库 // 2. 更新到数据库
IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class); IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
deviceMapper.updateById(updateObj); deviceMapper.updateById(updateObj);
// 3. 清空对应缓存
deleteDeviceCache(device);
} }
@Override @Override
@ -125,6 +134,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 3. 更新设备分组 // 3. 更新设备分组
deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO() deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO()
.setId(device.getId()).setGroupIds(updateReqVO.getGroupIds()))); .setId(device.getId()).setGroupIds(updateReqVO.getGroupIds())));
// 4. 清空对应缓存
deleteDeviceCache(devices);
} }
@Override @Override
@ -138,6 +150,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 2. 删除设备 // 2. 删除设备
deviceMapper.deleteById(id); deviceMapper.deleteById(id);
// 3. 清空对应缓存
deleteDeviceCache(device);
} }
@Override @Override
@ -160,6 +175,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
// 2. 删除设备 // 2. 删除设备
deviceMapper.deleteByIds(ids); deviceMapper.deleteByIds(ids);
// 3. 清空对应缓存
deleteDeviceCache(devices);
} }
/** /**
@ -213,6 +231,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
@Override @Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) { public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
// TODO @芋艿state 相关的参数另外到底叫 state还是 status
// TODO @芋艿各种时间需要 check 优化处理下
// 1. 校验存在 // 1. 校验存在
IotDeviceDO device = validateDeviceExists(updateReqVO.getId()); IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
@ -233,6 +253,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
} }
// 2.3 更新到数据库 // 2.3 更新到数据库
deviceMapper.updateById(updateDevice); deviceMapper.updateById(updateDevice);
// 3. 清空对应缓存
deleteDeviceCache(device);
} }
@Override @Override
@ -246,7 +269,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
} }
@Override @Override
public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) { @TenantIgnore
@Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName); return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
} }
@ -367,4 +392,20 @@ public class IotDeviceServiceImpl implements IotDeviceService {
return respVO; return respVO;
} }
private void deleteDeviceCache(IotDeviceDO device) {
// 保证在 @CacheEvict 之前忽略租户
TenantUtils.executeIgnore(() -> getSelf().deleteDeviceCache0(device));
}
private void deleteDeviceCache(List<IotDeviceDO> devices) {
devices.forEach(this::deleteDeviceCache);
}
@CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
public void deleteDeviceCache0(IotDeviceDO device) {}
private IotDeviceServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
} }

View File

@ -118,21 +118,21 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
} }
@Override @Override
@TenantIgnore // TODO @芋艿租户的缓存问题需要考虑下因为会存在一会又 tenantId一会没有 @TenantIgnore
public void saveDeviceProperty(IotDeviceMessage message) { public void saveDeviceProperty(IotDeviceMessage message) {
if (!(message.getData() instanceof Map)) { if (!(message.getData() instanceof Map)) {
log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message); log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
return; return;
} }
// 1. 获得设备信息 // 1. 获得设备信息
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(message.getProductKey(), message.getDeviceName()); IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(message.getProductKey(), message.getDeviceName());
if (device == null) { if (device == null) {
log.error("[saveDeviceProperty][消息({}) 对应的设备不存在]", message); log.error("[saveDeviceProperty][消息({}) 对应的设备不存在]", message);
return; return;
} }
// 2. 根据物模型拼接合法的属性 // 2. 根据物模型拼接合法的属性
List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductId(device.getProductId()); List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductKeyFromCache(device.getProductKey());
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
((Map<?, ?>) message.getData()).forEach((key, value) -> { ((Map<?, ?>) message.getData()).forEach((key, value) -> {
if (CollUtil.findOne(thingModels, thingModel -> thingModel.getIdentifier().equals(key)) == null) { if (CollUtil.findOne(thingModels, thingModel -> thingModel.getIdentifier().equals(key)) == null) {

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.service.device.upstream;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
@ -47,7 +46,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
public void reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) { public void reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) {
// 1.1 获得设备 // 1.1 获得设备
log.info("[reportDevicePropertyData][上报设备属性数据: {}]", reportReqDTO); log.info("[reportDevicePropertyData][上报设备属性数据: {}]", reportReqDTO);
IotDeviceDO device = getDevice(reportReqDTO); IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
if (device == null) { if (device == null) {
log.error("[reportDevicePropertyData][设备({}/{})不存在]", log.error("[reportDevicePropertyData][设备({}/{})不存在]",
reportReqDTO.getProductKey(), reportReqDTO.getDeviceName()); reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
@ -71,11 +71,6 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
// TODO 芋艿待实现 // TODO 芋艿待实现
} }
private IotDeviceDO getDevice(IotDeviceUpstreamAbstractReqDTO reqDTO) {
return TenantUtils.executeIgnore(() -> // 需要忽略租户因为请求时未带租户编号
deviceService.getDeviceByProductKeyAndDeviceName(reqDTO.getProductKey(), reqDTO.getDeviceName()));
}
private void updateDeviceLastTime(IotDeviceDO deviceDO, IotDeviceUpstreamAbstractReqDTO reqDTO) { private void updateDeviceLastTime(IotDeviceDO deviceDO, IotDeviceUpstreamAbstractReqDTO reqDTO) {
// TODO 芋艿插件状态 // TODO 芋艿插件状态
// TODO 芋艿操作时间 // TODO 芋艿操作时间

View File

@ -46,7 +46,6 @@ public interface IotThingModelService {
*/ */
IotThingModelDO getThingModel(Long id); IotThingModelDO getThingModel(Long id);
// TODO @芋艿增加缓存
/** /**
* 获得产品物模型列表 * 获得产品物模型列表
* *
@ -55,6 +54,16 @@ public interface IotThingModelService {
*/ */
List<IotThingModelDO> getThingModelListByProductId(Long productId); List<IotThingModelDO> getThingModelListByProductId(Long productId);
/**
* 缓存获得产品物模型列表
*
* 注意该方法会忽略租户信息所以调用时需要确认会不会有跨租户访问的风险
*
* @param productKey 产品标识
* @return 产品物模型列表
*/
List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey);
/** /**
* 获得产品物模型分页 * 获得产品物模型分页
* *
@ -63,14 +72,6 @@ public interface IotThingModelService {
*/ */
PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO); PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO);
/**
* 获得产品物模型列表
*
* @param productKey 产品 Key
* @return 产品物模型列表
*/
List<IotThingModelDO> getProductThingModelListByProductKey(String productKey);
/** /**
* 获得产品物模型列表 * 获得产品物模型列表
* *

View File

@ -2,8 +2,11 @@ package cn.iocoder.yudao.module.iot.service.thingmodel;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
@ -14,11 +17,14 @@ import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper; import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
import cn.iocoder.yudao.module.iot.enums.thingmodel.*; import cn.iocoder.yudao.module.iot.enums.thingmodel.*;
import cn.iocoder.yudao.module.iot.service.product.IotProductService; import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -69,6 +75,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey()); createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
} }
// TODO @puhui999: 服务和事件的情况 method 怎么设置在前端设置还是后端设置 // TODO @puhui999: 服务和事件的情况 method 怎么设置在前端设置还是后端设置
// 7. 删除缓存
deleteThingModelListCache(createReqVO.getProductKey());
return thingModel.getId(); return thingModel.getId();
} }
@ -92,6 +101,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) { if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey()); createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
} }
// 6. 删除缓存
deleteThingModelListCache(updateReqVO.getProductKey());
} }
@Override @Override
@ -113,6 +125,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) { if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey()); createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
} }
// 4. 删除缓存
deleteThingModelListCache(thingModel.getProductKey());
} }
@Override @Override
@ -126,13 +141,15 @@ public class IotThingModelServiceImpl implements IotThingModelService {
} }
@Override @Override
public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) { @TenantIgnore
return thingModelMapper.selectPage(pageReqVO); @Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) {
return thingModelMapper.selectListByProductKey(productKey);
} }
@Override @Override
public List<IotThingModelDO> getProductThingModelListByProductKey(String productKey) { public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
return thingModelMapper.selectListByProductKey(productKey); return thingModelMapper.selectPage(pageReqVO);
} }
@Override @Override
@ -333,4 +350,16 @@ public class IotThingModelServiceImpl implements IotThingModelService {
.setDirection(direction.getDirection())); .setDirection(direction.getDirection()));
} }
private void deleteThingModelListCache(String productKey) {
// 保证在 @CacheEvict 之前忽略租户
TenantUtils.executeIgnore(() -> getSelf().deleteThingModelListCache0(productKey));
}
@CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
public void deleteThingModelListCache0(String productKey) {}
private IotThingModelServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
} }