reactor:【IoT 物联网】优化设备缓存的加载逻辑
This commit is contained in:
@@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.iot.api.device;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
@@ -30,6 +33,8 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
|
||||
@Override
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
|
||||
@@ -39,24 +44,17 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/info")
|
||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST,实际更推荐 GET
|
||||
@PermitAll
|
||||
public CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(@RequestBody IotDeviceInfoReqDTO infoReqDTO) {
|
||||
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
|
||||
infoReqDTO.getProductKey(), infoReqDTO.getDeviceName());
|
||||
|
||||
if (device == null) {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
IotDeviceInfoRespDTO respDTO = new IotDeviceInfoRespDTO();
|
||||
respDTO.setDeviceId(device.getId());
|
||||
respDTO.setProductKey(device.getProductKey());
|
||||
respDTO.setDeviceName(device.getDeviceName());
|
||||
respDTO.setDeviceKey(device.getDeviceKey());
|
||||
respDTO.setTenantId(device.getTenantId());
|
||||
|
||||
return success(respDTO);
|
||||
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
|
||||
IotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
|
||||
: deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
|
||||
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
|
||||
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
|
||||
if (product != null) {
|
||||
deviceDTO.setCodecType(product.getCodecType());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,6 +70,12 @@ public class IotProductDO extends TenantBaseDO {
|
||||
*/
|
||||
private Integer netType;
|
||||
|
||||
/**
|
||||
* 编解码器类型
|
||||
*
|
||||
* 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
|
||||
*/
|
||||
private String codecType;
|
||||
/**
|
||||
* 接入网关协议
|
||||
* <p>
|
||||
|
||||
@@ -39,11 +39,20 @@ public interface RedisKeyConstants {
|
||||
/**
|
||||
* 设备信息的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||
*
|
||||
* KEY 格式:device_${productKey}_${deviceName}
|
||||
* KEY 格式 1:device_${id}
|
||||
* KEY 格式 2:device_${productKey}_${deviceName}
|
||||
* VALUE 数据类型:String(JSON)
|
||||
*/
|
||||
String DEVICE = "iot:device";
|
||||
|
||||
/**
|
||||
* 产品信息的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||
*
|
||||
* KEY 格式:product_${id}
|
||||
* VALUE 数据类型:String(JSON)
|
||||
*/
|
||||
String PRODUCT = "iot:product";
|
||||
|
||||
/**
|
||||
* 物模型的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
@@ -61,18 +62,19 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber<IotDevic
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 更新设备的最后时间
|
||||
// TODO 芋艿:后续加缓存;
|
||||
IotDeviceDO device = deviceService.validateDeviceExists(message.getDeviceId());
|
||||
devicePropertyService.updateDeviceReportTime(device.getProductKey(), device.getDeviceName(), LocalDateTime.now());
|
||||
// 1.2 更新设备的连接 server
|
||||
devicePropertyService.updateDeviceServerId(device.getProductKey(), device.getDeviceName(), message.getServerId());
|
||||
TenantUtils.execute(message.getTenantId(), () -> {
|
||||
// 1.1 更新设备的最后时间
|
||||
IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
|
||||
devicePropertyService.updateDeviceReportTime(device.getProductKey(), device.getDeviceName(), LocalDateTime.now());
|
||||
// 1.2 更新设备的连接 server
|
||||
devicePropertyService.updateDeviceServerId(device.getProductKey(), device.getDeviceName(), message.getServerId());
|
||||
|
||||
// 2. 未上线的设备,强制上线
|
||||
forceDeviceOnline(message, device);
|
||||
// 2. 未上线的设备,强制上线
|
||||
forceDeviceOnline(message, device);
|
||||
|
||||
// 3. 核心:处理消息
|
||||
deviceMessageService.handleUpstreamDeviceMessage(message, device);
|
||||
// 3. 核心:处理消息
|
||||
deviceMessageService.handleUpstreamDeviceMessage(message, device);
|
||||
});
|
||||
}
|
||||
|
||||
private void forceDeviceOnline(IotDeviceMessage message, IotDeviceDO device) {
|
||||
|
||||
@@ -105,6 +105,14 @@ public interface IotDeviceService {
|
||||
*/
|
||||
IotDeviceDO validateDeviceExists(Long id);
|
||||
|
||||
/**
|
||||
* 【缓存】校验设备是否存在
|
||||
*
|
||||
* @param id 设备 ID
|
||||
* @return 设备对象
|
||||
*/
|
||||
IotDeviceDO validateDeviceExistsFromCache(Long id);
|
||||
|
||||
/**
|
||||
* 获得设备
|
||||
*
|
||||
@@ -113,6 +121,27 @@ public interface IotDeviceService {
|
||||
*/
|
||||
IotDeviceDO getDevice(Long id);
|
||||
|
||||
/**
|
||||
* 【缓存】获得设备信息
|
||||
* <p>
|
||||
* 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
|
||||
*
|
||||
* @param id 编号
|
||||
* @return IoT 设备
|
||||
*/
|
||||
IotDeviceDO getDeviceFromCache(Long id);
|
||||
|
||||
/**
|
||||
* 【缓存】根据产品 key 和设备名称,获得设备信息
|
||||
* <p>
|
||||
* 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
|
||||
*
|
||||
* @param productKey 产品 key
|
||||
* @param deviceName 设备名称
|
||||
* @return 设备信息
|
||||
*/
|
||||
IotDeviceDO getDeviceFromCache(String productKey, String deviceName);
|
||||
|
||||
/**
|
||||
* 根据设备 key 获得设备
|
||||
*
|
||||
@@ -177,17 +206,6 @@ public interface IotDeviceService {
|
||||
*/
|
||||
Long getDeviceCountByGroupId(Long groupId);
|
||||
|
||||
/**
|
||||
* 【缓存】根据产品 key 和设备名称,获得设备信息
|
||||
* <p>
|
||||
* 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
|
||||
*
|
||||
* @param productKey 产品 key
|
||||
* @param deviceName 设备名称
|
||||
* @return 设备信息
|
||||
*/
|
||||
IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
|
||||
|
||||
/**
|
||||
* 导入设备
|
||||
*
|
||||
|
||||
@@ -28,6 +28,7 @@ import jakarta.validation.ConstraintViolationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cache.annotation.Caching;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -221,6 +222,15 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotDeviceDO validateDeviceExistsFromCache(Long id) {
|
||||
IotDeviceDO device = getSelf().getDeviceFromCache(id);
|
||||
if (device == null) {
|
||||
throw exception(DEVICE_NOT_EXISTS);
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验网关设备是否存在
|
||||
*
|
||||
@@ -241,6 +251,20 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return deviceMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.DEVICE, key = "#id", unless = "#result == null")
|
||||
@TenantIgnore // 忽略租户信息
|
||||
public IotDeviceDO getDeviceFromCache(Long id) {
|
||||
return deviceMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
|
||||
@TenantIgnore // 忽略租户信息,跨租户 productKey + deviceName 是唯一的
|
||||
public IotDeviceDO getDeviceFromCache(String productKey, String deviceName) {
|
||||
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotDeviceDO getDeviceByDeviceKey(String deviceKey) {
|
||||
return deviceMapper.selectByDeviceKey(deviceKey);
|
||||
@@ -308,13 +332,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return deviceMapper.selectCountByGroupId(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
|
||||
@TenantIgnore // 忽略租户信息,跨租户 productKey + deviceName 是唯一的
|
||||
public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
|
||||
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 deviceKey
|
||||
*
|
||||
@@ -425,14 +442,13 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
devices.forEach(this::deleteDeviceCache);
|
||||
}
|
||||
|
||||
@CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
|
||||
@Caching(evict = {
|
||||
@CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.id"),
|
||||
@CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
|
||||
})
|
||||
public void deleteDeviceCache0(IotDeviceDO device) {
|
||||
}
|
||||
|
||||
private IotDeviceServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDeviceCount(LocalDateTime createTime) {
|
||||
return deviceMapper.selectCountByCreateTime(createTime);
|
||||
@@ -477,7 +493,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
}
|
||||
String deviceName = deviceInfo.getDeviceName();
|
||||
String productKey = deviceInfo.getProductKey();
|
||||
IotDeviceDO device = getSelf().getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
|
||||
IotDeviceDO device = getSelf().getDeviceFromCache(productKey, deviceName);
|
||||
if (device == null) {
|
||||
log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
|
||||
return false;
|
||||
@@ -492,4 +508,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private IotDeviceServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ public interface IotProductService {
|
||||
*/
|
||||
IotProductDO getProduct(Long id);
|
||||
|
||||
/**
|
||||
* 【缓存】获得产品
|
||||
* <p>
|
||||
* 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 产品
|
||||
*/
|
||||
IotProductDO getProductFromCache(Long id);
|
||||
|
||||
/**
|
||||
* 根据产品 key 获得产品
|
||||
*
|
||||
|
||||
@@ -2,15 +2,19 @@ package cn.iocoder.yudao.module.iot.service.product;
|
||||
|
||||
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.product.vo.product.IotProductPageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
|
||||
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import jakarta.annotation.Resource;
|
||||
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.validation.annotation.Validated;
|
||||
@@ -56,6 +60,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#updateReqVO.id")
|
||||
public void updateProduct(IotProductSaveReqVO updateReqVO) {
|
||||
updateReqVO.setProductKey(null); // 不更新产品标识
|
||||
// 1.1 校验存在
|
||||
@@ -68,6 +73,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#id")
|
||||
public void deleteProduct(Long id) {
|
||||
// 1.1 校验存在
|
||||
IotProductDO iotProductDO = validateProductExists(id);
|
||||
@@ -106,6 +112,13 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
return productMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.PRODUCT, key = "#id", unless = "#result == null")
|
||||
@TenantIgnore // 忽略租户信息
|
||||
public IotProductDO getProductFromCache(Long id) {
|
||||
return productMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotProductDO getProductByProductKey(String productKey) {
|
||||
return productMapper.selectByProductKey(productKey);
|
||||
@@ -118,6 +131,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
|
||||
@Override
|
||||
@DSTransactional(rollbackFor = Exception.class)
|
||||
@CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#id")
|
||||
public void updateProductStatus(Long id, Integer status) {
|
||||
// 1. 校验存在
|
||||
validateProductExists(id);
|
||||
@@ -143,5 +157,4 @@ public class IotProductServiceImpl implements IotProductService {
|
||||
return productMapper.selectCountByCreateTime(createTime);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public class IotRuleSceneDeviceControlAction implements IotRuleSceneAction {
|
||||
Assert.notNull(control, "设备控制配置不能为空");
|
||||
// 遍历每个设备,下发消息
|
||||
control.getDeviceNames().forEach(deviceName -> {
|
||||
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(control.getProductKey(), deviceName);
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName);
|
||||
if (device == null) {
|
||||
log.error("[execute][message({}) config({}) 对应的设备不存在]", message, config);
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
ts TIMESTAMP,
|
||||
id NCHAR(50),
|
||||
report_time TIMESTAMP,
|
||||
device_id BIGINT,
|
||||
tenant_id BIGINT,
|
||||
server_id NCHAR(50),
|
||||
upstream BOOL,
|
||||
@@ -29,21 +28,21 @@
|
||||
|
||||
<insert id="insert">
|
||||
INSERT INTO device_message_${deviceId} (
|
||||
ts, id, report_time, device_id, tenant_id,
|
||||
server_id, upstream, request_id, method, params,
|
||||
data, code
|
||||
ts, id, report_time, tenant_id, server_id,
|
||||
upstream, request_id, method, params, data,
|
||||
code
|
||||
)
|
||||
USING device_message
|
||||
TAGS (#{deviceId})
|
||||
VALUES (
|
||||
#{ts}, #{id}, #{reportTime}, #{deviceId}, #{tenantId},
|
||||
#{serverId}, #{upstream}, #{requestId}, #{method}, #{params},
|
||||
#{data}, #{code}
|
||||
#{ts}, #{id}, #{reportTime}, #{tenantId}, #{serverId},
|
||||
#{upstream}, #{requestId}, #{method}, #{params}, #{data},
|
||||
#{code}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="selectPage" resultType="cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO">
|
||||
SELECT ts, id, report_time, device_id, tenant_id, server_id, upstream,
|
||||
SELECT ts, id, report_time, device_id, tenant_id, server_id, upstream,
|
||||
request_id, method, params, data, code
|
||||
FROM device_message_${reqVO.deviceId}
|
||||
<where>
|
||||
@@ -125,4 +124,4 @@
|
||||
ORDER BY time ASC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user