Commit a364153d4a9a34375151d8527a5ad54ed60f6c22

Authored by YunaiV
1 parent 7745035f

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

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> {
49 49 return selectList(IotThingModelDO::getProductId, productId);
50 50 }
51 51  
  52 + default List<IotThingModelDO> selectListByProductKey(String productKey) {
  53 + return selectList(IotThingModelDO::getProductKey, productKey);
  54 + }
  55 +
52 56 default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
53 57 return selectList(IotThingModelDO::getProductId, productId,
54 58 IotThingModelDO::getType, type);
... ... @@ -68,8 +72,4 @@ public interface IotThingModelMapper extends BaseMapperX&lt;IotThingModelDO&gt; {
68 72 IotThingModelDO::getName, name);
69 73 }
70 74  
71   - default List<IotThingModelDO> selectListByProductKey(String productKey) {
72   - return selectList(IotThingModelDO::getProductKey, productKey);
73   - }
74   -
75 75 }
76 76 \ No newline at end of file
... ...
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;
10 10 public interface RedisKeyConstants {
11 11  
12 12 /**
13   - * 设备属性数据缓存,采用 HASH 结构
  13 + * 设备属性的数据缓存,采用 HASH 结构
14 14 * <p>
15 15 * KEY 格式:device_property:{deviceKey}
16 16 * HASH KEY:identifier 属性标识
... ... @@ -26,4 +26,20 @@ public interface RedisKeyConstants {
26 26 */
27 27 String DEVICE_REPORT_TIME = "device_report_time";
28 28  
  29 + /**
  30 + * 设备信息的数据缓存,使用 Spring Cache 操作
  31 + *
  32 + * KEY 格式:device_${productKey}_${deviceKey}
  33 + * VALUE 数据类型:String(JSON)
  34 + */
  35 + String DEVICE = "device";
  36 +
  37 + /**
  38 + * 物模型的数据缓存,使用 Spring Cache 操作
  39 + *
  40 + * KEY 格式:thing_model_${productKey}
  41 + * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO} 列表
  42 + */
  43 + String THING_MODEL_LIST = "thing_model_list";
  44 +
29 45 }
... ...
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 {
113 113 */
114 114 Long getDeviceCountByGroupId(Long groupId);
115 115  
116   - // TODO @芋艿:增加缓存
117 116 /**
118   - * 根据产品 key 和设备名称,获得设备信息
  117 + * 【缓存】根据产品 key 和设备名称,获得设备信息
  118 + *
  119 + * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
119 120 *
120 121 * @param productKey 产品 key
121 122 * @param deviceName 设备名称
122 123 * @return 设备信息
123 124 */
124   - IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
  125 + IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
125 126  
126 127 /**
127 128 * 导入设备
... ...
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;
4 4 import cn.hutool.core.util.IdUtil;
5 5 import cn.hutool.core.util.RandomUtil;
6 6 import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.extra.spring.SpringUtil;
7 8 import cn.iocoder.yudao.framework.common.exception.ServiceException;
8 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 11 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  12 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
11 13 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 14 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
13 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
14 16 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
15 17 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
16 18 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
  19 +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
17 20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
18 21 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
19 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
20 23 import jakarta.annotation.Resource;
21 24 import jakarta.validation.ConstraintViolationException;
22 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.cache.annotation.CacheEvict;
  27 +import org.springframework.cache.annotation.Cacheable;
23 28 import org.springframework.context.annotation.Lazy;
24 29 import org.springframework.stereotype.Service;
25 30 import org.springframework.transaction.annotation.Transactional;
... ... @@ -78,6 +83,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
78 83 deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
79 84  
80 85 // 2.1 转换 VO 为 DO
  86 + // TODO @芋艿:state 相关的参数。另外,到底叫 state,还是 status 好!
81 87 IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> {
82 88 o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
83 89 // 生成并设置必要的字段
... ... @@ -109,6 +115,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
109 115 // 2. 更新到数据库
110 116 IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
111 117 deviceMapper.updateById(updateObj);
  118 +
  119 + // 3. 清空对应缓存
  120 + deleteDeviceCache(device);
112 121 }
113 122  
114 123 @Override
... ... @@ -125,6 +134,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
125 134 // 3. 更新设备分组
126 135 deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO()
127 136 .setId(device.getId()).setGroupIds(updateReqVO.getGroupIds())));
  137 +
  138 + // 4. 清空对应缓存
  139 + deleteDeviceCache(devices);
128 140 }
129 141  
130 142 @Override
... ... @@ -138,6 +150,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
138 150  
139 151 // 2. 删除设备
140 152 deviceMapper.deleteById(id);
  153 +
  154 + // 3. 清空对应缓存
  155 + deleteDeviceCache(device);
141 156 }
142 157  
143 158 @Override
... ... @@ -160,6 +175,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
160 175  
161 176 // 2. 删除设备
162 177 deviceMapper.deleteByIds(ids);
  178 +
  179 + // 3. 清空对应缓存
  180 + deleteDeviceCache(devices);
163 181 }
164 182  
165 183 /**
... ... @@ -213,6 +231,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
213 231  
214 232 @Override
215 233 public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
  234 + // TODO @芋艿:state 相关的参数。另外,到底叫 state,还是 status 好!
  235 + // TODO @芋艿:各种时间,需要 check 下,优化处理下!
216 236 // 1. 校验存在
217 237 IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
218 238  
... ... @@ -233,6 +253,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
233 253 }
234 254 // 2.3 更新到数据库
235 255 deviceMapper.updateById(updateDevice);
  256 +
  257 + // 3. 清空对应缓存
  258 + deleteDeviceCache(device);
236 259 }
237 260  
238 261 @Override
... ... @@ -246,7 +269,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
246 269 }
247 270  
248 271 @Override
249   - public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) {
  272 + @TenantIgnore
  273 + @Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
  274 + public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
250 275 return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
251 276 }
252 277  
... ... @@ -367,4 +392,20 @@ public class IotDeviceServiceImpl implements IotDeviceService {
367 392 return respVO;
368 393 }
369 394  
  395 + private void deleteDeviceCache(IotDeviceDO device) {
  396 + // 保证在 @CacheEvict 之前,忽略租户
  397 + TenantUtils.executeIgnore(() -> getSelf().deleteDeviceCache0(device));
  398 + }
  399 +
  400 + private void deleteDeviceCache(List<IotDeviceDO> devices) {
  401 + devices.forEach(this::deleteDeviceCache);
  402 + }
  403 +
  404 + @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
  405 + public void deleteDeviceCache0(IotDeviceDO device) {}
  406 +
  407 + private IotDeviceServiceImpl getSelf() {
  408 + return SpringUtil.getBean(getClass());
  409 + }
  410 +
370 411 }
371 412 \ No newline at end of file
... ...
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 {
118 118 }
119 119  
120 120 @Override
121   - @TenantIgnore // TODO @芋艿:租户的缓存问题,需要考虑下。因为会存在一会又 tenantId,一会没有!
  121 + @TenantIgnore
122 122 public void saveDeviceProperty(IotDeviceMessage message) {
123 123 if (!(message.getData() instanceof Map)) {
124 124 log.error("[saveDeviceProperty][消息内容({}) 的 data 类型不正确]", message);
125 125 return;
126 126 }
127 127 // 1. 获得设备信息
128   - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(message.getProductKey(), message.getDeviceName());
  128 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(message.getProductKey(), message.getDeviceName());
129 129 if (device == null) {
130 130 log.error("[saveDeviceProperty][消息({}) 对应的设备不存在]", message);
131 131 return;
132 132 }
133 133  
134 134 // 2. 根据物模型,拼接合法的属性
135   - List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductId(device.getProductId());
  135 + List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductKeyFromCache(device.getProductKey());
136 136 Map<String, Object> properties = new HashMap<>();
137 137 ((Map<?, ?>) message.getData()).forEach((key, value) -> {
138 138 if (CollUtil.findOne(thingModels, thingModel -> thingModel.getIdentifier().equals(key)) == null) {
... ...
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;
3 3 import cn.hutool.core.util.IdUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
7 6 import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO;
8 7 import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
9 8 import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
... ... @@ -47,7 +46,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
47 46 public void reportDevicePropertyData(IotDevicePropertyReportReqDTO reportReqDTO) {
48 47 // 1.1 获得设备
49 48 log.info("[reportDevicePropertyData][上报设备属性数据: {}]", reportReqDTO);
50   - IotDeviceDO device = getDevice(reportReqDTO);
  49 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
  50 + reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
51 51 if (device == null) {
52 52 log.error("[reportDevicePropertyData][设备({}/{})不存在]",
53 53 reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
... ... @@ -71,11 +71,6 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
71 71 // TODO 芋艿:待实现
72 72 }
73 73  
74   - private IotDeviceDO getDevice(IotDeviceUpstreamAbstractReqDTO reqDTO) {
75   - return TenantUtils.executeIgnore(() -> // 需要忽略租户,因为请求时,未带租户编号
76   - deviceService.getDeviceByProductKeyAndDeviceName(reqDTO.getProductKey(), reqDTO.getDeviceName()));
77   - }
78   -
79 74 private void updateDeviceLastTime(IotDeviceDO deviceDO, IotDeviceUpstreamAbstractReqDTO reqDTO) {
80 75 // TODO 芋艿:插件状态
81 76 // TODO 芋艿:操作时间
... ...
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 {
46 46 */
47 47 IotThingModelDO getThingModel(Long id);
48 48  
49   - // TODO @芋艿:增加缓存
50 49 /**
51 50 * 获得产品物模型列表
52 51 *
... ... @@ -56,20 +55,22 @@ public interface IotThingModelService {
56 55 List<IotThingModelDO> getThingModelListByProductId(Long productId);
57 56  
58 57 /**
59   - * 获得产品物模型分页
  58 + * 【缓存】获得产品物模型列表
60 59 *
61   - * @param pageReqVO 分页查询
62   - * @return 产品物模型分页
  60 + * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
  61 + *
  62 + * @param productKey 产品标识
  63 + * @return 产品物模型列表
63 64 */
64   - PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO);
  65 + List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey);
65 66  
66 67 /**
67   - * 获得产品物模型列表
  68 + * 获得产品物模型分页
68 69 *
69   - * @param productKey 产品 Key
70   - * @return 产品物模型列表
  70 + * @param pageReqVO 分页查询
  71 + * @return 产品物模型分页
71 72 */
72   - List<IotThingModelDO> getProductThingModelListByProductKey(String productKey);
  73 + PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO);
73 74  
74 75 /**
75 76 * 获得产品物模型列表
... ...
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;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
  5 +import cn.hutool.extra.spring.SpringUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  9 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
7 10 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
8 11 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam;
9 12 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
... ... @@ -14,11 +17,14 @@ import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
14 17 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
15 18 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
16 19 import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
  20 +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
17 21 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
18 22 import cn.iocoder.yudao.module.iot.enums.thingmodel.*;
19 23 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
20 24 import jakarta.annotation.Resource;
21 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.cache.annotation.CacheEvict;
  27 +import org.springframework.cache.annotation.Cacheable;
22 28 import org.springframework.stereotype.Service;
23 29 import org.springframework.transaction.annotation.Transactional;
24 30 import org.springframework.validation.annotation.Validated;
... ... @@ -69,6 +75,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
69 75 createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
70 76 }
71 77 // TODO @puhui999: 服务和事件的情况 method 怎么设置?在前端设置还是后端设置?
  78 +
  79 + // 7. 删除缓存
  80 + deleteThingModelListCache(createReqVO.getProductKey());
72 81 return thingModel.getId();
73 82 }
74 83  
... ... @@ -92,6 +101,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
92 101 if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
93 102 createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
94 103 }
  104 +
  105 + // 6. 删除缓存
  106 + deleteThingModelListCache(updateReqVO.getProductKey());
95 107 }
96 108  
97 109 @Override
... ... @@ -113,6 +125,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
113 125 if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
114 126 createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
115 127 }
  128 +
  129 + // 4. 删除缓存
  130 + deleteThingModelListCache(thingModel.getProductKey());
116 131 }
117 132  
118 133 @Override
... ... @@ -126,13 +141,15 @@ public class IotThingModelServiceImpl implements IotThingModelService {
126 141 }
127 142  
128 143 @Override
129   - public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
130   - return thingModelMapper.selectPage(pageReqVO);
  144 + @TenantIgnore
  145 + @Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
  146 + public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) {
  147 + return thingModelMapper.selectListByProductKey(productKey);
131 148 }
132 149  
133 150 @Override
134   - public List<IotThingModelDO> getProductThingModelListByProductKey(String productKey) {
135   - return thingModelMapper.selectListByProductKey(productKey);
  151 + public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
  152 + return thingModelMapper.selectPage(pageReqVO);
136 153 }
137 154  
138 155 @Override
... ... @@ -333,4 +350,16 @@ public class IotThingModelServiceImpl implements IotThingModelService {
333 350 .setDirection(direction.getDirection()));
334 351 }
335 352  
  353 + private void deleteThingModelListCache(String productKey) {
  354 + // 保证在 @CacheEvict 之前,忽略租户
  355 + TenantUtils.executeIgnore(() -> getSelf().deleteThingModelListCache0(productKey));
  356 + }
  357 +
  358 + @CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
  359 + public void deleteThingModelListCache0(String productKey) {}
  360 +
  361 + private IotThingModelServiceImpl getSelf() {
  362 + return SpringUtil.getBean(getClass());
  363 + }
  364 +
336 365 }
... ...