Browse Source

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

YunaiV 6 months ago
parent
commit
a364153d4a

+ 4 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotThingModelMapper.java

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

+ 17 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java

@@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
 public interface RedisKeyConstants {
 
     /**
-     * 设备属性数据缓存,采用 HASH 结构
+     * 设备属性数据缓存,采用 HASH 结构
      * <p>
      * KEY 格式:device_property:{deviceKey}
      * HASH KEY:identifier 属性标识
@@ -26,4 +26,20 @@ public interface RedisKeyConstants {
      */
     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";
+
 }

+ 4 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java

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

+ 42 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java

@@ -4,22 +4,27 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.RandomUtil;
 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.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 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.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.IotDeviceGroupDO;
 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.redis.RedisKeyConstants;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import jakarta.annotation.Resource;
 import jakarta.validation.ConstraintViolationException;
 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.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -78,6 +83,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
 
         // 2.1 转换 VO 为 DO
+        // TODO @芋艿:state 相关的参数。另外,到底叫 state,还是 status 好!
         IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> {
             o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
             // 生成并设置必要的字段
@@ -109,6 +115,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         // 2. 更新到数据库
         IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
         deviceMapper.updateById(updateObj);
+
+        // 3. 清空对应缓存
+        deleteDeviceCache(device);
     }
 
     @Override
@@ -125,6 +134,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         // 3. 更新设备分组
         deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO()
                 .setId(device.getId()).setGroupIds(updateReqVO.getGroupIds())));
+
+        // 4. 清空对应缓存
+        deleteDeviceCache(devices);
     }
 
     @Override
@@ -138,6 +150,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
 
         // 2. 删除设备
         deviceMapper.deleteById(id);
+
+        // 3. 清空对应缓存
+        deleteDeviceCache(device);
     }
 
     @Override
@@ -160,6 +175,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
 
         // 2. 删除设备
         deviceMapper.deleteByIds(ids);
+
+        // 3. 清空对应缓存
+        deleteDeviceCache(devices);
     }
 
     /**
@@ -213,6 +231,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
 
     @Override
     public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
+        // TODO @芋艿:state 相关的参数。另外,到底叫 state,还是 status 好!
+        // TODO @芋艿:各种时间,需要 check 下,优化处理下!
         // 1. 校验存在
         IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
 
@@ -233,6 +253,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         }
         // 2.3 更新到数据库
         deviceMapper.updateById(updateDevice);
+
+        // 3. 清空对应缓存
+        deleteDeviceCache(device);
     }
 
     @Override
@@ -246,7 +269,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     }
 
     @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);
     }
 
@@ -367,4 +392,20 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         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());
+    }
+
 }

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java

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

+ 2 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/upstream/IotDeviceUpstreamServiceImpl.java

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

+ 10 - 9
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java

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

+ 33 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java

@@ -2,8 +2,11 @@ package cn.iocoder.yudao.module.iot.service.thingmodel;
 
 import cn.hutool.core.collection.CollUtil;
 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.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.ThingModelParam;
 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.thingmodel.IotThingModelDO;
 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.thingmodel.*;
 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import jakarta.annotation.Resource;
 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.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -69,6 +75,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
             createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
         }
         // TODO @puhui999: 服务和事件的情况 method 怎么设置?在前端设置还是后端设置?
+
+        // 7. 删除缓存
+        deleteThingModelListCache(createReqVO.getProductKey());
         return thingModel.getId();
     }
 
@@ -92,6 +101,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
         if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
             createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
         }
+
+        // 6. 删除缓存
+        deleteThingModelListCache(updateReqVO.getProductKey());
     }
 
     @Override
@@ -113,6 +125,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
         if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
             createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
         }
+
+        // 4. 删除缓存
+        deleteThingModelListCache(thingModel.getProductKey());
     }
 
     @Override
@@ -126,13 +141,15 @@ public class IotThingModelServiceImpl implements IotThingModelService {
     }
 
     @Override
-    public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
-        return thingModelMapper.selectPage(pageReqVO);
+    @TenantIgnore
+    @Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
+    public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) {
+        return thingModelMapper.selectListByProductKey(productKey);
     }
 
     @Override
-    public List<IotThingModelDO> getProductThingModelListByProductKey(String productKey) {
-        return thingModelMapper.selectListByProductKey(productKey);
+    public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
+        return thingModelMapper.selectPage(pageReqVO);
     }
 
     @Override
@@ -333,4 +350,16 @@ public class IotThingModelServiceImpl implements IotThingModelService {
                         .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());
+    }
+
 }