Commit 33fed79820e1e042a8fc0db911308a9dc85506d9

Authored by YunaiV
1 parent 66b42367

reactor:【IoT 物联网】优化设备缓存的加载逻辑

Showing 26 changed files with 352 additions and 550 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
... ... @@ -16,4 +16,6 @@ public class DictTypeConstants {
16 16  
17 17 public static final String DEVICE_STATE = "iot_device_state";
18 18  
  19 + public static final String CODEC_TYPE = "iot_codec_type";
  20 +
19 21 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
... ... @@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.iot.api.device;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.RpcConstants;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
6 7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoReqDTO;
8   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
  8 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
  9 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
9 10 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
10 12 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
  13 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
11 14 import jakarta.annotation.Resource;
12 15 import jakarta.annotation.security.PermitAll;
13 16 import org.springframework.context.annotation.Primary;
... ... @@ -30,6 +33,8 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
30 33  
31 34 @Resource
32 35 private IotDeviceService deviceService;
  36 + @Resource
  37 + private IotProductService productService;
33 38  
34 39 @Override
35 40 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
... ... @@ -39,24 +44,17 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
39 44 }
40 45  
41 46 @Override
42   - @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/info")
  47 + @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST,实际更推荐 GET
43 48 @PermitAll
44   - public CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(@RequestBody IotDeviceInfoReqDTO infoReqDTO) {
45   - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
46   - infoReqDTO.getProductKey(), infoReqDTO.getDeviceName());
47   -
48   - if (device == null) {
49   - return success(null);
50   - }
51   -
52   - IotDeviceInfoRespDTO respDTO = new IotDeviceInfoRespDTO();
53   - respDTO.setDeviceId(device.getId());
54   - respDTO.setProductKey(device.getProductKey());
55   - respDTO.setDeviceName(device.getDeviceName());
56   - respDTO.setDeviceKey(device.getDeviceKey());
57   - respDTO.setTenantId(device.getTenantId());
58   -
59   - return success(respDTO);
  49 + public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
  50 + IotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
  51 + : deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
  52 + return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
  53 + IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
  54 + if (product != null) {
  55 + deviceDTO.setCodecType(product.getCodecType());
  56 + }
  57 + }));
60 58 }
61 59  
62 60 }
63 61 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
... ... @@ -71,6 +71,12 @@ public class IotProductDO extends TenantBaseDO {
71 71 private Integer netType;
72 72  
73 73 /**
  74 + * 编解码器类型
  75 + *
  76 + * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
  77 + */
  78 + private String codecType;
  79 + /**
74 80 * 接入网关协议
75 81 * <p>
76 82 * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotProtocolTypeEnum}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
... ... @@ -39,12 +39,21 @@ public interface RedisKeyConstants {
39 39 /**
40 40 * 设备信息的数据缓存,使用 Spring Cache 操作(忽略租户)
41 41 *
42   - * KEY 格式:device_${productKey}_${deviceName}
  42 + * KEY 格式 1:device_${id}
  43 + * KEY 格式 2:device_${productKey}_${deviceName}
43 44 * VALUE 数据类型:String(JSON)
44 45 */
45 46 String DEVICE = "iot:device";
46 47  
47 48 /**
  49 + * 产品信息的数据缓存,使用 Spring Cache 操作(忽略租户)
  50 + *
  51 + * KEY 格式:product_${id}
  52 + * VALUE 数据类型:String(JSON)
  53 + */
  54 + String PRODUCT = "iot:product";
  55 +
  56 + /**
48 57 * 物模型的数据缓存,使用 Spring Cache 操作(忽略租户)
49 58 *
50 59 * KEY 格式:thing_model_${productKey}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device;
2 2  
3 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  5 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
6 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
7 8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
... ... @@ -61,18 +62,19 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber&lt;IotDevic
61 62 return;
62 63 }
63 64  
64   - // 1.1 更新设备的最后时间
65   - // TODO 芋艿:后续加缓存;
66   - IotDeviceDO device = deviceService.validateDeviceExists(message.getDeviceId());
67   - devicePropertyService.updateDeviceReportTime(device.getProductKey(), device.getDeviceName(), LocalDateTime.now());
68   - // 1.2 更新设备的连接 server
69   - devicePropertyService.updateDeviceServerId(device.getProductKey(), device.getDeviceName(), message.getServerId());
  65 + TenantUtils.execute(message.getTenantId(), () -> {
  66 + // 1.1 更新设备的最后时间
  67 + IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
  68 + devicePropertyService.updateDeviceReportTime(device.getProductKey(), device.getDeviceName(), LocalDateTime.now());
  69 + // 1.2 更新设备的连接 server
  70 + devicePropertyService.updateDeviceServerId(device.getProductKey(), device.getDeviceName(), message.getServerId());
70 71  
71   - // 2. 未上线的设备,强制上线
72   - forceDeviceOnline(message, device);
  72 + // 2. 未上线的设备,强制上线
  73 + forceDeviceOnline(message, device);
73 74  
74   - // 3. 核心:处理消息
75   - deviceMessageService.handleUpstreamDeviceMessage(message, device);
  75 + // 3. 核心:处理消息
  76 + deviceMessageService.handleUpstreamDeviceMessage(message, device);
  77 + });
76 78 }
77 79  
78 80 private void forceDeviceOnline(IotDeviceMessage message, IotDeviceDO device) {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
... ... @@ -106,6 +106,14 @@ public interface IotDeviceService {
106 106 IotDeviceDO validateDeviceExists(Long id);
107 107  
108 108 /**
  109 + * 【缓存】校验设备是否存在
  110 + *
  111 + * @param id 设备 ID
  112 + * @return 设备对象
  113 + */
  114 + IotDeviceDO validateDeviceExistsFromCache(Long id);
  115 +
  116 + /**
109 117 * 获得设备
110 118 *
111 119 * @param id 编号
... ... @@ -114,6 +122,27 @@ public interface IotDeviceService {
114 122 IotDeviceDO getDevice(Long id);
115 123  
116 124 /**
  125 + * 【缓存】获得设备信息
  126 + * <p>
  127 + * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
  128 + *
  129 + * @param id 编号
  130 + * @return IoT 设备
  131 + */
  132 + IotDeviceDO getDeviceFromCache(Long id);
  133 +
  134 + /**
  135 + * 【缓存】根据产品 key 和设备名称,获得设备信息
  136 + * <p>
  137 + * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
  138 + *
  139 + * @param productKey 产品 key
  140 + * @param deviceName 设备名称
  141 + * @return 设备信息
  142 + */
  143 + IotDeviceDO getDeviceFromCache(String productKey, String deviceName);
  144 +
  145 + /**
117 146 * 根据设备 key 获得设备
118 147 *
119 148 * @param deviceKey 编号
... ... @@ -178,17 +207,6 @@ public interface IotDeviceService {
178 207 Long getDeviceCountByGroupId(Long groupId);
179 208  
180 209 /**
181   - * 【缓存】根据产品 key 和设备名称,获得设备信息
182   - * <p>
183   - * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
184   - *
185   - * @param productKey 产品 key
186   - * @param deviceName 设备名称
187   - * @return 设备信息
188   - */
189   - IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
190   -
191   - /**
192 210 * 导入设备
193 211 *
194 212 * @param importDevices 导入设备列表
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
... ... @@ -28,6 +28,7 @@ import jakarta.validation.ConstraintViolationException;
28 28 import lombok.extern.slf4j.Slf4j;
29 29 import org.springframework.cache.annotation.CacheEvict;
30 30 import org.springframework.cache.annotation.Cacheable;
  31 +import org.springframework.cache.annotation.Caching;
31 32 import org.springframework.context.annotation.Lazy;
32 33 import org.springframework.stereotype.Service;
33 34 import org.springframework.transaction.annotation.Transactional;
... ... @@ -221,6 +222,15 @@ public class IotDeviceServiceImpl implements IotDeviceService {
221 222 return device;
222 223 }
223 224  
  225 + @Override
  226 + public IotDeviceDO validateDeviceExistsFromCache(Long id) {
  227 + IotDeviceDO device = getSelf().getDeviceFromCache(id);
  228 + if (device == null) {
  229 + throw exception(DEVICE_NOT_EXISTS);
  230 + }
  231 + return device;
  232 + }
  233 +
224 234 /**
225 235 * 校验网关设备是否存在
226 236 *
... ... @@ -242,6 +252,20 @@ public class IotDeviceServiceImpl implements IotDeviceService {
242 252 }
243 253  
244 254 @Override
  255 + @Cacheable(value = RedisKeyConstants.DEVICE, key = "#id", unless = "#result == null")
  256 + @TenantIgnore // 忽略租户信息
  257 + public IotDeviceDO getDeviceFromCache(Long id) {
  258 + return deviceMapper.selectById(id);
  259 + }
  260 +
  261 + @Override
  262 + @Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
  263 + @TenantIgnore // 忽略租户信息,跨租户 productKey + deviceName 是唯一的
  264 + public IotDeviceDO getDeviceFromCache(String productKey, String deviceName) {
  265 + return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
  266 + }
  267 +
  268 + @Override
245 269 public IotDeviceDO getDeviceByDeviceKey(String deviceKey) {
246 270 return deviceMapper.selectByDeviceKey(deviceKey);
247 271 }
... ... @@ -308,13 +332,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
308 332 return deviceMapper.selectCountByGroupId(groupId);
309 333 }
310 334  
311   - @Override
312   - @Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
313   - @TenantIgnore // 忽略租户信息,跨租户 productKey + deviceName 是唯一的
314   - public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
315   - return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
316   - }
317   -
318 335 /**
319 336 * 生成 deviceKey
320 337 *
... ... @@ -425,14 +442,13 @@ public class IotDeviceServiceImpl implements IotDeviceService {
425 442 devices.forEach(this::deleteDeviceCache);
426 443 }
427 444  
428   - @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
  445 + @Caching(evict = {
  446 + @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.id"),
  447 + @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
  448 + })
429 449 public void deleteDeviceCache0(IotDeviceDO device) {
430 450 }
431 451  
432   - private IotDeviceServiceImpl getSelf() {
433   - return SpringUtil.getBean(getClass());
434   - }
435   -
436 452 @Override
437 453 public Long getDeviceCount(LocalDateTime createTime) {
438 454 return deviceMapper.selectCountByCreateTime(createTime);
... ... @@ -477,7 +493,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
477 493 }
478 494 String deviceName = deviceInfo.getDeviceName();
479 495 String productKey = deviceInfo.getProductKey();
480   - IotDeviceDO device = getSelf().getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
  496 + IotDeviceDO device = getSelf().getDeviceFromCache(productKey, deviceName);
481 497 if (device == null) {
482 498 log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
483 499 return false;
... ... @@ -492,4 +508,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
492 508 return true;
493 509 }
494 510  
  511 + private IotDeviceServiceImpl getSelf() {
  512 + return SpringUtil.getBean(getClass());
  513 + }
  514 +
495 515 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
... ... @@ -48,6 +48,16 @@ public interface IotProductService {
48 48 IotProductDO getProduct(Long id);
49 49  
50 50 /**
  51 + * 【缓存】获得产品
  52 + * <p>
  53 + * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
  54 + *
  55 + * @param id 编号
  56 + * @return 产品
  57 + */
  58 + IotProductDO getProductFromCache(Long id);
  59 +
  60 + /**
51 61 * 根据产品 key 获得产品
52 62 *
53 63 * @param productKey 产品 key
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
... ... @@ -2,15 +2,19 @@ package cn.iocoder.yudao.module.iot.service.product;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
5 6 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
6 7 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
7 8 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
8 9 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
9 10 import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
  11 +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
10 12 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
11 13 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
12 14 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
13 15 import jakarta.annotation.Resource;
  16 +import org.springframework.cache.annotation.CacheEvict;
  17 +import org.springframework.cache.annotation.Cacheable;
14 18 import org.springframework.context.annotation.Lazy;
15 19 import org.springframework.stereotype.Service;
16 20 import org.springframework.validation.annotation.Validated;
... ... @@ -56,6 +60,7 @@ public class IotProductServiceImpl implements IotProductService {
56 60 }
57 61  
58 62 @Override
  63 + @CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#updateReqVO.id")
59 64 public void updateProduct(IotProductSaveReqVO updateReqVO) {
60 65 updateReqVO.setProductKey(null); // 不更新产品标识
61 66 // 1.1 校验存在
... ... @@ -68,6 +73,7 @@ public class IotProductServiceImpl implements IotProductService {
68 73 }
69 74  
70 75 @Override
  76 + @CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#id")
71 77 public void deleteProduct(Long id) {
72 78 // 1.1 校验存在
73 79 IotProductDO iotProductDO = validateProductExists(id);
... ... @@ -107,6 +113,13 @@ public class IotProductServiceImpl implements IotProductService {
107 113 }
108 114  
109 115 @Override
  116 + @Cacheable(value = RedisKeyConstants.PRODUCT, key = "#id", unless = "#result == null")
  117 + @TenantIgnore // 忽略租户信息
  118 + public IotProductDO getProductFromCache(Long id) {
  119 + return productMapper.selectById(id);
  120 + }
  121 +
  122 + @Override
110 123 public IotProductDO getProductByProductKey(String productKey) {
111 124 return productMapper.selectByProductKey(productKey);
112 125 }
... ... @@ -118,6 +131,7 @@ public class IotProductServiceImpl implements IotProductService {
118 131  
119 132 @Override
120 133 @DSTransactional(rollbackFor = Exception.class)
  134 + @CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#id")
121 135 public void updateProductStatus(Long id, Integer status) {
122 136 // 1. 校验存在
123 137 validateProductExists(id);
... ... @@ -143,5 +157,4 @@ public class IotProductServiceImpl implements IotProductService {
143 157 return productMapper.selectCountByCreateTime(createTime);
144 158 }
145 159  
146   -
147 160 }
148 161 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneDeviceControlAction.java
... ... @@ -31,7 +31,7 @@ public class IotRuleSceneDeviceControlAction implements IotRuleSceneAction {
31 31 Assert.notNull(control, "设备控制配置不能为空");
32 32 // 遍历每个设备,下发消息
33 33 control.getDeviceNames().forEach(deviceName -> {
34   - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(control.getProductKey(), deviceName);
  34 + IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName);
35 35 if (device == null) {
36 36 log.error("[execute][message({}) config({}) 对应的设备不存在]", message, config);
37 37 return;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml
... ... @@ -9,7 +9,6 @@
9 9 ts TIMESTAMP,
10 10 id NCHAR(50),
11 11 report_time TIMESTAMP,
12   - device_id BIGINT,
13 12 tenant_id BIGINT,
14 13 server_id NCHAR(50),
15 14 upstream BOOL,
... ... @@ -29,21 +28,21 @@
29 28  
30 29 <insert id="insert">
31 30 INSERT INTO device_message_${deviceId} (
32   - ts, id, report_time, device_id, tenant_id,
33   - server_id, upstream, request_id, method, params,
34   - data, code
  31 + ts, id, report_time, tenant_id, server_id,
  32 + upstream, request_id, method, params, data,
  33 + code
35 34 )
36 35 USING device_message
37 36 TAGS (#{deviceId})
38 37 VALUES (
39   - #{ts}, #{id}, #{reportTime}, #{deviceId}, #{tenantId},
40   - #{serverId}, #{upstream}, #{requestId}, #{method}, #{params},
41   - #{data}, #{code}
  38 + #{ts}, #{id}, #{reportTime}, #{tenantId}, #{serverId},
  39 + #{upstream}, #{requestId}, #{method}, #{params}, #{data},
  40 + #{code}
42 41 )
43 42 </insert>
44 43  
45 44 <select id="selectPage" resultType="cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO">
46   - SELECT ts, id, report_time, device_id, tenant_id, server_id, upstream,
  45 + SELECT ts, id, report_time, device_id, tenant_id, server_id, upstream,
47 46 request_id, method, params, data, code
48 47 FROM device_message_${reqVO.deviceId}
49 48 <where>
... ... @@ -125,4 +124,4 @@
125 124 ORDER BY time ASC
126 125 </select>
127 126  
128   -</mapper>
129 127 \ No newline at end of file
  128 +</mapper>
130 129 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
... ... @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.iot.core.biz;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
5   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoReqDTO;
6   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
  5 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
  6 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
7 7  
8 8 /**
9 9 * IoT 设备通用 API
... ... @@ -26,6 +26,6 @@ public interface IotDeviceCommonApi {
26 26 * @param infoReqDTO 设备信息请求
27 27 * @return 设备信息
28 28 */
29   - CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(IotDeviceInfoReqDTO infoReqDTO);
  29 + CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO);
30 30  
31 31 }
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceInfoReqDTO.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceGetReqDTO.java
1 1 package cn.iocoder.yudao.module.iot.core.biz.dto;
2 2  
3   -import jakarta.validation.constraints.NotBlank;
4 3 import lombok.Data;
5 4  
6 5 /**
... ... @@ -9,18 +8,20 @@ import lombok.Data;
9 8 * @author 芋道源码
10 9 */
11 10 @Data
12   -public class IotDeviceInfoReqDTO {
  11 +public class IotDeviceGetReqDTO {
  12 +
  13 + /**
  14 + * 设备编号
  15 + */
  16 + private Long id;
13 17  
14 18 /**
15 19 * 产品标识
16 20 */
17   - @NotBlank(message = "产品标识不能为空")
18 21 private String productKey;
19   -
20 22 /**
21 23 * 设备名称
22 24 */
23   - @NotBlank(message = "设备名称不能为空")
24 25 private String deviceName;
25 26  
26 27 }
27 28 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceInfoRespDTO.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java
... ... @@ -8,31 +8,34 @@ import lombok.Data;
8 8 * @author 芋道源码
9 9 */
10 10 @Data
11   -public class IotDeviceInfoRespDTO {
  11 +public class IotDeviceRespDTO {
12 12  
13 13 /**
14 14 * 设备编号
15 15 */
16   - private Long deviceId;
17   -
  16 + private Long id;
18 17 /**
19 18 * 产品标识
20 19 */
21 20 private String productKey;
22   -
23 21 /**
24 22 * 设备名称
25 23 */
26 24 private String deviceName;
27   -
28 25 /**
29   - * 设备密钥
  26 + * 租户编号
30 27 */
31   - private String deviceKey;
  28 + private Long tenantId;
  29 +
  30 + // ========== 产品相关字段 ==========
32 31  
33 32 /**
34   - * 租户编号
  33 + * 产品编号
35 34 */
36   - private Long tenantId;
  35 + private Long productId;
  36 + /**
  37 + * 编解码器类型
  38 + */
  39 + private String codecType;
37 40  
38 41 }
39 42 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/enums/ErrorCodeConstants.java
... ... @@ -13,4 +13,7 @@ public interface ErrorCodeConstants {
13 13 ErrorCode DEVICE_AUTH_FAIL = new ErrorCode(1_051_001_000, "设备鉴权失败"); // 对应阿里云 20000
14 14 ErrorCode DEVICE_TOKEN_EXPIRED = new ErrorCode(1_051_001_002, "token 失效。需重新调用 auth 进行鉴权,获取token"); // 对应阿里云 20001
15 15  
  16 + // ========== 设备信息 1-050-002-000 ============
  17 + ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_051_002_001, "设备({}/{}) 不存在");
  18 +
16 19 }
17 20 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java
... ... @@ -36,14 +36,10 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
36 36 public final void handle(RoutingContext context) {
37 37 try {
38 38 // 1. 前置处理
39   - CommonResult<Object> result = beforeHandle(context);
40   - if (result != null) {
41   - writeResponse(context, result);
42   - return;
43   - }
  39 + beforeHandle(context);
44 40  
45 41 // 2. 执行逻辑
46   - result = handle0(context);
  42 + CommonResult<Object> result = handle0(context);
47 43 writeResponse(context, result);
48 44 } catch (ServiceException e) {
49 45 writeResponse(context, CommonResult.error(e.getCode(), e.getMessage()));
... ... @@ -55,11 +51,11 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
55 51  
56 52 protected abstract CommonResult<Object> handle0(RoutingContext context);
57 53  
58   - private CommonResult<Object> beforeHandle(RoutingContext context) {
  54 + private void beforeHandle(RoutingContext context) {
59 55 // 如果不需要认证,则不走前置处理
60 56 String path = context.request().path();
61 57 if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) {
62   - return null;
  58 + return;
63 59 }
64 60  
65 61 // 解析参数
... ... @@ -84,7 +80,6 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
84 80 || ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {
85 81 throw exception(FORBIDDEN);
86 82 }
87   - return null;
88 83 }
89 84  
90 85 @SuppressWarnings("deprecation")
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java
... ... @@ -9,11 +9,10 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 9 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
10 10 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
11 11 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
12   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
13 12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
14 13 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
15 14 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
16   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
  15 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
17 16 import io.vertx.core.json.JsonObject;
18 17 import io.vertx.ext.web.RoutingContext;
19 18  
... ... @@ -35,19 +34,16 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
35 34  
36 35 private final IotHttpUpstreamProtocol protocol;
37 36  
38   - private final IotDeviceMessageProducer deviceMessageProducer;
39   -
40 37 private final IotDeviceTokenService deviceTokenService;
41 38  
42   - private final IotDeviceCommonApi deviceClientService;
  39 + private final IotDeviceCommonApi deviceApi;
43 40  
44 41 private final IotDeviceMessageService deviceMessageService;
45 42  
46 43 public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) {
47 44 this.protocol = protocol;
48   - this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
49 45 this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
50   - this.deviceClientService = SpringUtil.getBean(IotDeviceCommonApi.class);
  46 + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
51 47 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
52 48 }
53 49  
... ... @@ -69,9 +65,9 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
69 65 }
70 66  
71 67 // 2.1 执行认证
72   - CommonResult<Boolean> result = deviceClientService.authDevice(new IotDeviceAuthReqDTO()
  68 + CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
73 69 .setClientId(clientId).setUsername(username).setPassword(password));
74   - result.checkError();;
  70 + result.checkError();
75 71 if (!BooleanUtil.isTrue(result.getData())) {
76 72 throw exception(DEVICE_AUTH_FAIL);
77 73 }
... ... @@ -82,9 +78,9 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
82 78 Assert.notBlank(token, "生成 token 不能为空位");
83 79  
84 80 // 3. 执行上线
85   - IotDeviceMessage message = deviceMessageService.buildDeviceMessageOfStateOnline(
  81 + IotDeviceMessage message = IotDeviceMessage.buildStateOnline();
  82 + deviceMessageService.sendDeviceMessage(message,
86 83 deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
87   - deviceMessageProducer.sendDeviceMessage(message);
88 84  
89 85 // 构建响应数据
90 86 return success(MapUtil.of("token", token));
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpUpstreamHandler.java
... ... @@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
8 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 9 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
10 10 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
11   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
  11 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
12 12 import io.vertx.ext.web.RoutingContext;
13 13 import lombok.RequiredArgsConstructor;
14 14 import lombok.extern.slf4j.Slf4j;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenServiceImpl.java
... ... @@ -61,7 +61,7 @@ public class IotDeviceTokenServiceImpl implements IotDeviceTokenService {
61 61 JSONObject payload = jwt.getPayloads();
62 62 // 检查过期时间
63 63 Long exp = payload.getLong("exp");
64   - if (exp == null || exp > System.currentTimeMillis() / 1000) {
  64 + if (exp == null || exp < System.currentTimeMillis() / 1000) {
65 65 throw exception(DEVICE_TOKEN_EXPIRED);
66 66 }
67 67 String productKey = payload.getStr("productKey");
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceCacheService.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.service.device;
2   -
3   -import lombok.AllArgsConstructor;
4   -import lombok.Data;
5   -import lombok.NoArgsConstructor;
6   -
7   -/**
8   - * IoT 设备缓存 Service 接口
9   - *
10   - * @author 芋道源码
11   - */
12   -public interface IotDeviceCacheService {
13   -
14   - /**
15   - * 设备信息
16   - */
17   - @Data
18   - @AllArgsConstructor
19   - @NoArgsConstructor
20   - class DeviceInfo {
21   - /**
22   - * 设备编号
23   - */
24   - private Long deviceId;
25   - /**
26   - * 产品标识
27   - */
28   - private String productKey;
29   - /**
30   - * 设备名称
31   - */
32   - private String deviceName;
33   - /**
34   - * 设备密钥
35   - */
36   - private String deviceKey;
37   - /**
38   - * 租户编号
39   - */
40   - private Long tenantId;
41   - }
42   -
43   - /**
44   - * 根据 productKey 和 deviceName 获取设备信息
45   - *
46   - * @param productKey 产品标识
47   - * @param deviceName 设备名称
48   - * @return 设备信息,如果不存在返回 null
49   - */
50   - DeviceInfo getDeviceInfo(String productKey, String deviceName);
51   -
52   - /**
53   - * 根据 deviceId 获取设备信息
54   - *
55   - * @param deviceId 设备编号
56   - * @return 设备信息,如果不存在返回 null
57   - */
58   - DeviceInfo getDeviceInfoById(Long deviceId);
59   -
60   - /**
61   - * 清除设备缓存
62   - *
63   - * @param deviceId 设备编号
64   - */
65   - void evictDeviceCache(Long deviceId);
66   -
67   - /**
68   - * 清除设备缓存
69   - *
70   - * @param productKey 产品标识
71   - * @param deviceName 设备名称
72   - */
73   - void evictDeviceCache(String productKey, String deviceName);
74   -
75   -}
76 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceCacheServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.service.device;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.hutool.extra.spring.SpringUtil;
5   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6   -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoReqDTO;
8   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
9   -import lombok.extern.slf4j.Slf4j;
10   -import org.springframework.stereotype.Service;
11   -
12   -import java.util.concurrent.ConcurrentHashMap;
13   -import java.util.concurrent.locks.ReentrantLock;
14   -
15   -/**
16   - * IoT 设备缓存 Service 实现类
17   - * <p>
18   - * 使用本地缓存 + 远程 API 的方式获取设备信息,提高性能并避免敏感信息传输
19   - *
20   - * @author 芋道源码
21   - */
22   -@Service
23   -@Slf4j
24   -public class IotDeviceCacheServiceImpl implements IotDeviceCacheService {
25   -
26   - /**
27   - * 设备信息本地缓存
28   - * Key: deviceId
29   - * Value: DeviceInfo
30   - */
31   - private final ConcurrentHashMap<Long, DeviceInfo> deviceIdCache = new ConcurrentHashMap<>();
32   -
33   - /**
34   - * 设备名称到设备ID的映射缓存
35   - * Key: productKey:deviceName
36   - * Value: deviceId
37   - */
38   - private final ConcurrentHashMap<String, Long> deviceNameCache = new ConcurrentHashMap<>();
39   -
40   - /**
41   - * 锁对象,防止并发请求同一设备信息
42   - */
43   - private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
44   -
45   - @Override
46   - public DeviceInfo getDeviceInfo(String productKey, String deviceName) {
47   - if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceName)) {
48   - log.warn("[getDeviceInfo][参数为空][productKey: {}][deviceName: {}]", productKey, deviceName);
49   - return null;
50   - }
51   -
52   - String cacheKey = buildDeviceNameCacheKey(productKey, deviceName);
53   -
54   - // 1. 先从缓存获取 deviceId
55   - Long deviceId = deviceNameCache.get(cacheKey);
56   - if (deviceId != null) {
57   - DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
58   - if (deviceInfo != null) {
59   - log.debug("[getDeviceInfo][缓存命中][productKey: {}][deviceName: {}][deviceId: {}]",
60   - productKey, deviceName, deviceId);
61   - return deviceInfo;
62   - }
63   - }
64   -
65   - // 2. 缓存未命中,从远程 API 获取
66   - return loadDeviceInfoFromApi(productKey, deviceName, cacheKey);
67   - }
68   -
69   - @Override
70   - public DeviceInfo getDeviceInfoById(Long deviceId) {
71   - if (deviceId == null) {
72   - log.warn("[getDeviceInfoById][deviceId 为空]");
73   - return null;
74   - }
75   -
76   - // 1. 先从缓存获取
77   - DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
78   - if (deviceInfo != null) {
79   - log.debug("[getDeviceInfoById][缓存命中][deviceId: {}]", deviceId);
80   - return deviceInfo;
81   - }
82   -
83   - // 2. 缓存未命中,从远程 API 获取
84   - return loadDeviceInfoByIdFromApi(deviceId);
85   - }
86   -
87   - @Override
88   - public void evictDeviceCache(Long deviceId) {
89   - if (deviceId == null) {
90   - return;
91   - }
92   -
93   - DeviceInfo deviceInfo = deviceIdCache.remove(deviceId);
94   - if (deviceInfo != null) {
95   - String cacheKey = buildDeviceNameCacheKey(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
96   - deviceNameCache.remove(cacheKey);
97   - log.info("[evictDeviceCache][清除设备缓存][deviceId: {}]", deviceId);
98   - }
99   - }
100   -
101   - @Override
102   - public void evictDeviceCache(String productKey, String deviceName) {
103   - if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceName)) {
104   - return;
105   - }
106   -
107   - String cacheKey = buildDeviceNameCacheKey(productKey, deviceName);
108   - Long deviceId = deviceNameCache.remove(cacheKey);
109   - if (deviceId != null) {
110   - deviceIdCache.remove(deviceId);
111   - log.info("[evictDeviceCache][清除设备缓存][productKey: {}][deviceName: {}]", productKey, deviceName);
112   - }
113   - }
114   -
115   - /**
116   - * 从远程 API 加载设备信息
117   - *
118   - * @param productKey 产品标识
119   - * @param deviceName 设备名称
120   - * @param cacheKey 缓存键
121   - * @return 设备信息
122   - */
123   - private DeviceInfo loadDeviceInfoFromApi(String productKey, String deviceName, String cacheKey) {
124   - // 使用锁防止并发请求同一设备信息
125   - ReentrantLock lock = lockMap.computeIfAbsent(cacheKey, k -> new ReentrantLock());
126   - lock.lock();
127   - try {
128   - // 双重检查,防止重复请求
129   - Long deviceId = deviceNameCache.get(cacheKey);
130   - if (deviceId != null) {
131   - DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
132   - if (deviceInfo != null) {
133   - return deviceInfo;
134   - }
135   - }
136   -
137   - log.info("[loadDeviceInfoFromApi][从远程API获取设备信息][productKey: {}][deviceName: {}]",
138   - productKey, deviceName);
139   -
140   - try {
141   - // 调用远程 API 获取设备信息
142   - IotDeviceCommonApi deviceCommonApi = SpringUtil.getBean(IotDeviceCommonApi.class);
143   - IotDeviceInfoReqDTO reqDTO = new IotDeviceInfoReqDTO();
144   - reqDTO.setProductKey(productKey);
145   - reqDTO.setDeviceName(deviceName);
146   -
147   - CommonResult<IotDeviceInfoRespDTO> result = deviceCommonApi.getDeviceInfo(reqDTO);
148   -
149   - if (result == null || !result.isSuccess() || result.getData() == null) {
150   - log.warn("[loadDeviceInfoFromApi][远程API调用失败][productKey: {}][deviceName: {}][result: {}]",
151   - productKey, deviceName, result);
152   - return null;
153   - }
154   -
155   - IotDeviceInfoRespDTO respDTO = result.getData();
156   - DeviceInfo deviceInfo = new DeviceInfo(
157   - respDTO.getDeviceId(),
158   - respDTO.getProductKey(),
159   - respDTO.getDeviceName(),
160   - respDTO.getDeviceKey(),
161   - respDTO.getTenantId());
162   -
163   - // 缓存设备信息
164   - cacheDeviceInfo(deviceInfo, cacheKey);
165   -
166   - log.info("[loadDeviceInfoFromApi][设备信息获取成功并已缓存][deviceInfo: {}]", deviceInfo);
167   - return deviceInfo;
168   -
169   - } catch (Exception e) {
170   - log.error("[loadDeviceInfoFromApi][远程API调用异常][productKey: {}][deviceName: {}]",
171   - productKey, deviceName, e);
172   - return null;
173   - }
174   - } finally {
175   - lock.unlock();
176   - // 清理锁对象,避免内存泄漏
177   - if (lockMap.size() > 1000) { // 简单的清理策略
178   - lockMap.entrySet().removeIf(entry -> !entry.getValue().hasQueuedThreads());
179   - }
180   - }
181   - }
182   -
183   - /**
184   - * 从远程 API 根据 deviceId 加载设备信息
185   - *
186   - * @param deviceId 设备编号
187   - * @return 设备信息
188   - */
189   - private DeviceInfo loadDeviceInfoByIdFromApi(Long deviceId) {
190   - String lockKey = "deviceId:" + deviceId;
191   - ReentrantLock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
192   - lock.lock();
193   - try {
194   - // 双重检查
195   - DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
196   - if (deviceInfo != null) {
197   - return deviceInfo;
198   - }
199   -
200   - log.info("[loadDeviceInfoByIdFromApi][从远程API获取设备信息][deviceId: {}]", deviceId);
201   -
202   - try {
203   - // TODO: 这里需要添加根据 deviceId 获取设备信息的 API
204   - // 暂时返回 null,等待 API 完善
205   - log.warn("[loadDeviceInfoByIdFromApi][根据deviceId获取设备信息的API尚未实现][deviceId: {}]", deviceId);
206   - return null;
207   -
208   - } catch (Exception e) {
209   - log.error("[loadDeviceInfoByIdFromApi][远程API调用异常][deviceId: {}]", deviceId, e);
210   - return null;
211   - }
212   - } finally {
213   - lock.unlock();
214   - }
215   - }
216   -
217   - /**
218   - * 缓存设备信息
219   - *
220   - * @param deviceInfo 设备信息
221   - * @param cacheKey 缓存键
222   - */
223   - private void cacheDeviceInfo(DeviceInfo deviceInfo, String cacheKey) {
224   - if (deviceInfo != null && deviceInfo.getDeviceId() != null) {
225   - deviceIdCache.put(deviceInfo.getDeviceId(), deviceInfo);
226   - deviceNameCache.put(cacheKey, deviceInfo.getDeviceId());
227   - }
228   - }
229   -
230   - /**
231   - * 构建设备名称缓存键
232   - *
233   - * @param productKey 产品标识
234   - * @param deviceName 设备名称
235   - * @return 缓存键
236   - */
237   - private String buildDeviceNameCacheKey(String productKey, String deviceName) {
238   - return productKey + ":" + deviceName;
239   - }
240   -
241   -}
242 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.service.device;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  4 +
  5 +/**
  6 + * IoT 设备信息 Service 接口
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +public interface IotDeviceService {
  11 +
  12 + /**
  13 + * 根据 productKey 和 deviceName 获取设备信息
  14 + *
  15 + * @param productKey 产品标识
  16 + * @param deviceName 设备名称
  17 + * @return 设备信息
  18 + */
  19 + IotDeviceRespDTO getDeviceFromCache(String productKey, String deviceName);
  20 +
  21 + /**
  22 + * 根据 id 获取设备信息
  23 + *
  24 + * @param id 设备编号
  25 + * @return 设备信息
  26 + */
  27 + IotDeviceRespDTO getDeviceFromCache(Long id);
  28 +
  29 +}
0 30 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.service.device;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  7 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  9 +import com.google.common.cache.CacheLoader;
  10 +import com.google.common.cache.LoadingCache;
  11 +import jakarta.annotation.Resource;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.stereotype.Service;
  14 +
  15 +import java.time.Duration;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
  18 +
  19 +/**
  20 + * IoT 设备信息 Service 实现类
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@Service
  25 +@Slf4j
  26 +public class IotDeviceServiceImpl implements IotDeviceService {
  27 +
  28 + private static final Duration CACHE_EXPIRE = Duration.ofMinutes(1);
  29 +
  30 + /**
  31 + * 通过 id 查询设备的缓存
  32 + */
  33 + private final LoadingCache<Long, IotDeviceRespDTO> deviceCaches = buildAsyncReloadingCache(
  34 + CACHE_EXPIRE,
  35 + new CacheLoader<>() {
  36 +
  37 + @Override
  38 + public IotDeviceRespDTO load(Long id) {
  39 + CommonResult<IotDeviceRespDTO> result = deviceApi.getDevice(new IotDeviceGetReqDTO().setId(id));
  40 + IotDeviceRespDTO device = result.getCheckedData();
  41 + Assert.notNull(device, "设备({}) 不能为空", id);
  42 + // 相互缓存
  43 + deviceCaches2.put(new KeyValue<>(device.getProductKey(), device.getDeviceName()), device);
  44 + return device;
  45 + }
  46 +
  47 + });
  48 +
  49 + /**
  50 + * 通过 productKey + deviceName 查询设备的缓存
  51 + */
  52 + private final LoadingCache<KeyValue<String, String>, IotDeviceRespDTO> deviceCaches2 = buildAsyncReloadingCache(
  53 + CACHE_EXPIRE,
  54 + new CacheLoader<>() {
  55 +
  56 + @Override
  57 + public IotDeviceRespDTO load(KeyValue<String, String> kv) {
  58 + CommonResult<IotDeviceRespDTO> result = deviceApi.getDevice(new IotDeviceGetReqDTO()
  59 + .setProductKey(kv.getKey()).setDeviceName(kv.getValue()));
  60 + IotDeviceRespDTO device = result.getCheckedData();
  61 + Assert.notNull(device, "设备({}/{}) 不能为空", kv.getKey(), kv.getValue());
  62 + // 相互缓存
  63 + deviceCaches.put(device.getId(), device);
  64 + return device;
  65 + }
  66 + });
  67 +
  68 + @Resource
  69 + private IotDeviceCommonApi deviceApi;
  70 +
  71 + @Override
  72 + public IotDeviceRespDTO getDeviceFromCache(String productKey, String deviceName) {
  73 + return deviceCaches2.getUnchecked(new KeyValue<>(productKey, deviceName));
  74 + }
  75 +
  76 + @Override
  77 + public IotDeviceRespDTO getDeviceFromCache(Long id) {
  78 + return deviceCaches.getUnchecked(id);
  79 + }
  80 +
  81 +}
0 82 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/message/IotDeviceMessageService.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java
1   -package cn.iocoder.yudao.module.iot.gateway.service.message;
  1 +package cn.iocoder.yudao.module.iot.gateway.service.device.message;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 4  
... ... @@ -26,30 +26,20 @@ public interface IotDeviceMessageService {
26 26 * @param bytes 消息内容
27 27 * @param productKey 产品 Key
28 28 * @param deviceName 设备名称
29   - * @param serverId 设备连接的 serverId
30 29 * @return 解码后的消息内容
31 30 */
32 31 IotDeviceMessage decodeDeviceMessage(byte[] bytes,
33   - String productKey, String deviceName, String serverId);
  32 + String productKey, String deviceName);
34 33  
35 34 /**
36   - * 构建【设备上线】消息
  35 + * 发送消息
37 36 *
  37 + * @param message 消息
38 38 * @param productKey 产品 Key
39 39 * @param deviceName 设备名称
40   - * @param serverId 设备连接的 serverId
41   - * @return 消息
  40 + * @param serverId 设备连接的 serverId
42 41 */
43   - IotDeviceMessage buildDeviceMessageOfStateOnline(String productKey, String deviceName, String serverId);
44   -
45   - /**
46   - * 构建【设备下线】消息
47   - *
48   - * @param productKey 产品 Key
49   - * @param deviceName 设备名称
50   - * @param serverId 设备连接的 serverId
51   - * @return 消息
52   - */
53   - IotDeviceMessage buildDeviceMessageOfStateOffline(String productKey, String deviceName, String serverId);
  42 + void sendDeviceMessage(IotDeviceMessage message,
  43 + String productKey, String deviceName, String serverId);
54 44  
55 45 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/message/IotDeviceMessageServiceImpl.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java
1   -package cn.iocoder.yudao.module.iot.gateway.service.message;
  1 +package cn.iocoder.yudao.module.iot.gateway.service.device.message;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5   -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  5 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
6 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7 8 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 9 import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceCacheService;
  10 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
10 11 import jakarta.annotation.Resource;
11 12 import lombok.extern.slf4j.Slf4j;
12 13 import org.springframework.stereotype.Service;
... ... @@ -15,6 +16,9 @@ import java.time.LocalDateTime;
15 16 import java.util.List;
16 17 import java.util.Map;
17 18  
  19 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS;
  21 +
18 22 /**
19 23 * IoT 设备消息 Service 实现类
20 24 *
... ... @@ -30,7 +34,10 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
30 34 private final Map<String, IotAlinkDeviceMessageCodec> codes;
31 35  
32 36 @Resource
33   - private IotDeviceCacheService deviceCacheService;
  37 + private IotDeviceService deviceService;
  38 +
  39 + @Resource
  40 + private IotDeviceMessageProducer deviceMessageProducer;
34 41  
35 42 public IotDeviceMessageServiceImpl(List<IotAlinkDeviceMessageCodec> codes) {
36 43 this.codes = CollectionUtils.convertMap(codes, IotAlinkDeviceMessageCodec::type);
... ... @@ -39,82 +46,51 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
39 46 @Override
40 47 public byte[] encodeDeviceMessage(IotDeviceMessage message,
41 48 String productKey, String deviceName) {
42   - // 获取设备信息以确定编解码类型
43   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
44   - if (deviceInfo == null) {
45   - log.warn("[encodeDeviceMessage][设备信息不存在][productKey: {}][deviceName: {}]",
46   - productKey, deviceName);
47   - return null;
  49 + // 1.1 获取设备信息
  50 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
  51 + if (device == null) {
  52 + throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
48 53 }
49   -
50   - String codecType = "alink"; // 默认使用 alink 编解码器
51   - IotAlinkDeviceMessageCodec codec = codes.get(codecType);
  54 + // 1.2 获取编解码器
  55 + IotAlinkDeviceMessageCodec codec = codes.get(device.getCodecType());
52 56 if (codec == null) {
53   - log.error("[encodeDeviceMessage][编解码器不存在][codecType: {}]", codecType);
54   - return null;
  57 + throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType()));
55 58 }
56 59  
  60 + // 2. 编码消息
57 61 return codec.encode(message);
58 62 }
59 63  
60 64 @Override
61 65 public IotDeviceMessage decodeDeviceMessage(byte[] bytes,
62   - String productKey, String deviceName, String serverId) {
63   - // 获取设备信息
64   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
65   - if (deviceInfo == null) {
66   - log.warn("[decodeDeviceMessage][设备信息不存在][productKey: {}][deviceName: {}]",
67   - productKey, deviceName);
68   - return null;
  66 + String productKey, String deviceName) {
  67 + // 1.1 获取设备信息
  68 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
  69 + if (device == null) {
  70 + throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
69 71 }
70   -
71   - String codecType = "alink"; // 默认使用 alink 编解码器
72   - IotAlinkDeviceMessageCodec codec = codes.get(codecType);
  72 + // 1.2 获取编解码器
  73 + IotAlinkDeviceMessageCodec codec = codes.get(device.getCodecType());
73 74 if (codec == null) {
74   - log.error("[decodeDeviceMessage][编解码器不存在][codecType: {}]", codecType);
75   - return null;
76   - }
77   -
78   - IotDeviceMessage message = codec.decode(bytes);
79   - if (message == null) {
80   - log.warn("[decodeDeviceMessage][消息解码失败][productKey: {}][deviceName: {}]",
81   - productKey, deviceName);
82   - return null;
  75 + throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType()));
83 76 }
84 77  
85   - // 补充后端字段
86   - return appendDeviceMessage(message, deviceInfo, serverId);
87   - }
88   -
89   - @Override
90   - public IotDeviceMessage buildDeviceMessageOfStateOnline(String productKey, String deviceName, String serverId) {
91   - // 获取设备信息
92   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
93   - if (deviceInfo == null) {
94   - log.warn("[buildDeviceMessageOfStateOnline][设备信息不存在][productKey: {}][deviceName: {}]",
95   - productKey, deviceName);
96   - return null;
97   - }
98   -
99   - IotDeviceMessage message = IotDeviceMessage.requestOf(null,
100   - IotDeviceMessageMethodEnum.STATE_ONLINE.getMethod(), null);
101   -
102   - return appendDeviceMessage(message, deviceInfo, serverId);
  78 + // 2. 解码消息
  79 + return codec.decode(bytes);
103 80 }
104 81  
105 82 @Override
106   - public IotDeviceMessage buildDeviceMessageOfStateOffline(String productKey, String deviceName, String serverId) {
107   - // 获取设备信息
108   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
109   - if (deviceInfo == null) {
110   - log.warn("[buildDeviceMessageOfStateOffline][设备信息不存在][productKey: {}][deviceName: {}]",
111   - productKey, deviceName);
112   - return null;
  83 + public void sendDeviceMessage(IotDeviceMessage message,
  84 + String productKey, String deviceName, String serverId) {
  85 + // 1. 获取设备信息
  86 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
  87 + if (device == null) {
  88 + throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
113 89 }
114 90  
115   - IotDeviceMessage message = IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.STATE_OFFLINE.getMethod(),
116   - null);
117   - return appendDeviceMessage(message, deviceInfo, serverId);
  91 + // 2. 发送消息
  92 + appendDeviceMessage(message, device, serverId);
  93 + deviceMessageProducer.sendDeviceMessage(message);
118 94 }
119 95  
120 96 /**
... ... @@ -126,9 +102,9 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
126 102 * @return 消息
127 103 */
128 104 private IotDeviceMessage appendDeviceMessage(IotDeviceMessage message,
129   - IotDeviceCacheService.DeviceInfo device, String serverId) {
  105 + IotDeviceRespDTO device, String serverId) {
130 106 message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now())
131   - .setDeviceId(device.getDeviceId()).setTenantId(device.getTenantId()).setServerId(serverId);
  107 + .setDeviceId(device.getId()).setTenantId(device.getTenantId()).setServerId(serverId);
132 108 // 特殊:如果设备没有指定 requestId,则使用 messageId
133 109 if (StrUtil.isEmpty(message.getRequestId())) {
134 110 message.setRequestId(message.getId());
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/IotDeviceClientServiceImpl.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java
1   -package cn.iocoder.yudao.module.iot.gateway.service.device;
  1 +package cn.iocoder.yudao.module.iot.gateway.service.device.remote;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4   -import cn.hutool.core.bean.BeanUtil;
5 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 5 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
8   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoReqDTO;
9   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
  7 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
10 9 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
11 10 import jakarta.annotation.PostConstruct;
12 11 import jakarta.annotation.Resource;
... ... @@ -19,8 +18,6 @@ import org.springframework.http.ResponseEntity;
19 18 import org.springframework.stereotype.Service;
20 19 import org.springframework.web.client.RestTemplate;
21 20  
22   -import java.util.LinkedHashMap;
23   -
24 21 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
25 22  
26 23 /**
... ... @@ -30,7 +27,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
30 27 */
31 28 @Service
32 29 @Slf4j
33   -public class IotDeviceClientServiceImpl implements IotDeviceCommonApi {
  30 +public class IotDeviceApiImpl implements IotDeviceCommonApi {
34 31  
35 32 @Resource
36 33 private IotGatewayProperties gatewayProperties;
... ... @@ -49,57 +46,27 @@ public class IotDeviceClientServiceImpl implements IotDeviceCommonApi {
49 46  
50 47 @Override
51 48 public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) {
52   - return doPost("/auth", authReqDTO);
  49 + return doPost("/auth", authReqDTO, new ParameterizedTypeReference<>() { });
53 50 }
54 51  
55 52 @Override
56   - public CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(IotDeviceInfoReqDTO infoReqDTO) {
57   - return doPostForDeviceInfo("/info", infoReqDTO);
  53 + public CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO getReqDTO) {
  54 + return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { });
58 55 }
59 56  
60   - @SuppressWarnings("unchecked")
61   - private <T> CommonResult<Boolean> doPost(String url, T requestBody) {
  57 + private <T, R> CommonResult<R> doPost(String url, T body,
  58 + ParameterizedTypeReference<CommonResult<R>> responseType) {
62 59 try {
63   - CommonResult<Boolean> result = restTemplate.postForObject(url, requestBody,
64   - (Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
65   - log.info("[doPost][url({}) requestBody({}) result({})]", url, requestBody, result);
  60 + // 请求
  61 + HttpEntity<T> requestEntity = new HttpEntity<>(body);
  62 + ResponseEntity<CommonResult<R>> response = restTemplate.exchange(
  63 + url, HttpMethod.POST, requestEntity, responseType);
  64 + // 响应
  65 + CommonResult<R> result = response.getBody();
66 66 Assert.notNull(result, "请求结果不能为空");
67 67 return result;
68 68 } catch (Exception e) {
69   - log.error("[doPost][url({}) requestBody({}) 发生异常]", url, requestBody, e);
70   - return CommonResult.error(INTERNAL_SERVER_ERROR);
71   - }
72   - }
73   -
74   - @SuppressWarnings("unchecked")
75   - private <T> CommonResult<IotDeviceInfoRespDTO> doPostForDeviceInfo(String url, T requestBody) {
76   - try {
77   - // 使用 ParameterizedTypeReference 来处理泛型类型
78   - ParameterizedTypeReference<CommonResult<LinkedHashMap<String, Object>>> typeRef = new ParameterizedTypeReference<CommonResult<LinkedHashMap<String, Object>>>() {
79   - };
80   -
81   - HttpEntity<T> requestEntity = new HttpEntity<>(requestBody);
82   - ResponseEntity<CommonResult<LinkedHashMap<String, Object>>> response = restTemplate.exchange(url,
83   - HttpMethod.POST, requestEntity, typeRef);
84   -
85   - CommonResult<LinkedHashMap<String, Object>> rawResult = response.getBody();
86   - log.info("[doPostForDeviceInfo][url({}) requestBody({}) rawResult({})]", url, requestBody, rawResult);
87   - Assert.notNull(rawResult, "请求结果不能为空");
88   -
89   - // 手动转换数据类型
90   - CommonResult<IotDeviceInfoRespDTO> result = new CommonResult<>();
91   - result.setCode(rawResult.getCode());
92   - result.setMsg(rawResult.getMsg());
93   -
94   - if (rawResult.isSuccess() && rawResult.getData() != null) {
95   - // 将 LinkedHashMap 转换为 IotDeviceInfoRespDTO
96   - IotDeviceInfoRespDTO deviceInfo = BeanUtil.toBean(rawResult.getData(), IotDeviceInfoRespDTO.class);
97   - result.setData(deviceInfo);
98   - }
99   -
100   - return result;
101   - } catch (Exception e) {
102   - log.error("[doPostForDeviceInfo][url({}) requestBody({}) 发生异常]", url, requestBody, e);
  69 + log.error("[doPost][url({}) body({}) 发生异常]", url, body, e);
103 70 return CommonResult.error(INTERNAL_SERVER_ERROR);
104 71 }
105 72 }
... ...