reactor:【IoT 物联网】优化设备缓存的加载逻辑
This commit is contained in:
parent
66b42367cb
commit
33fed79820
|
@ -16,4 +16,6 @@ public class DictTypeConstants {
|
||||||
|
|
||||||
public static final String DEVICE_STATE = "iot_device_state";
|
public static final String DEVICE_STATE = "iot_device_state";
|
||||||
|
|
||||||
|
public static final String CODEC_TYPE = "iot_codec_type";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.enums.RpcConstants;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
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.IotDeviceCommonApi;
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
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.IotDeviceGetReqDTO;
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
|
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.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.device.IotDeviceService;
|
||||||
|
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.annotation.security.PermitAll;
|
import jakarta.annotation.security.PermitAll;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
|
@ -30,6 +33,8 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IotDeviceService deviceService;
|
private IotDeviceService deviceService;
|
||||||
|
@Resource
|
||||||
|
private IotProductService productService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
|
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
|
||||||
|
@ -39,24 +44,17 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/info")
|
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST,实际更推荐 GET
|
||||||
@PermitAll
|
@PermitAll
|
||||||
public CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(@RequestBody IotDeviceInfoReqDTO infoReqDTO) {
|
public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
|
||||||
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
|
IotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
|
||||||
infoReqDTO.getProductKey(), infoReqDTO.getDeviceName());
|
: deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
|
||||||
|
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
|
||||||
if (device == null) {
|
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
|
||||||
return success(null);
|
if (product != null) {
|
||||||
}
|
deviceDTO.setCodecType(product.getCodecType());
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -70,6 +70,12 @@ public class IotProductDO extends TenantBaseDO {
|
||||||
*/
|
*/
|
||||||
private Integer netType;
|
private Integer netType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编解码器类型
|
||||||
|
*
|
||||||
|
* 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
|
||||||
|
*/
|
||||||
|
private String codecType;
|
||||||
/**
|
/**
|
||||||
* 接入网关协议
|
* 接入网关协议
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -39,11 +39,20 @@ public interface RedisKeyConstants {
|
||||||
/**
|
/**
|
||||||
* 设备信息的数据缓存,使用 Spring Cache 操作(忽略租户)
|
* 设备信息的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||||
*
|
*
|
||||||
* KEY 格式:device_${productKey}_${deviceName}
|
* KEY 格式 1:device_${id}
|
||||||
|
* KEY 格式 2:device_${productKey}_${deviceName}
|
||||||
* VALUE 数据类型:String(JSON)
|
* VALUE 数据类型:String(JSON)
|
||||||
*/
|
*/
|
||||||
String DEVICE = "iot:device";
|
String DEVICE = "iot:device";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品信息的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||||
|
*
|
||||||
|
* KEY 格式:product_${id}
|
||||||
|
* VALUE 数据类型:String(JSON)
|
||||||
|
*/
|
||||||
|
String PRODUCT = "iot:product";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 物模型的数据缓存,使用 Spring Cache 操作(忽略租户)
|
* 物模型的数据缓存,使用 Spring Cache 操作(忽略租户)
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
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.IotDeviceMessageMethodEnum;
|
||||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||||
|
@ -61,18 +62,19 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber<IotDevic
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.1 更新设备的最后时间
|
TenantUtils.execute(message.getTenantId(), () -> {
|
||||||
// TODO 芋艿:后续加缓存;
|
// 1.1 更新设备的最后时间
|
||||||
IotDeviceDO device = deviceService.validateDeviceExists(message.getDeviceId());
|
IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
|
||||||
devicePropertyService.updateDeviceReportTime(device.getProductKey(), device.getDeviceName(), LocalDateTime.now());
|
devicePropertyService.updateDeviceReportTime(device.getProductKey(), device.getDeviceName(), LocalDateTime.now());
|
||||||
// 1.2 更新设备的连接 server
|
// 1.2 更新设备的连接 server
|
||||||
devicePropertyService.updateDeviceServerId(device.getProductKey(), device.getDeviceName(), message.getServerId());
|
devicePropertyService.updateDeviceServerId(device.getProductKey(), device.getDeviceName(), message.getServerId());
|
||||||
|
|
||||||
// 2. 未上线的设备,强制上线
|
// 2. 未上线的设备,强制上线
|
||||||
forceDeviceOnline(message, device);
|
forceDeviceOnline(message, device);
|
||||||
|
|
||||||
// 3. 核心:处理消息
|
// 3. 核心:处理消息
|
||||||
deviceMessageService.handleUpstreamDeviceMessage(message, device);
|
deviceMessageService.handleUpstreamDeviceMessage(message, device);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forceDeviceOnline(IotDeviceMessage message, IotDeviceDO device) {
|
private void forceDeviceOnline(IotDeviceMessage message, IotDeviceDO device) {
|
||||||
|
|
|
@ -105,6 +105,14 @@ public interface IotDeviceService {
|
||||||
*/
|
*/
|
||||||
IotDeviceDO validateDeviceExists(Long id);
|
IotDeviceDO validateDeviceExists(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【缓存】校验设备是否存在
|
||||||
|
*
|
||||||
|
* @param id 设备 ID
|
||||||
|
* @return 设备对象
|
||||||
|
*/
|
||||||
|
IotDeviceDO validateDeviceExistsFromCache(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得设备
|
* 获得设备
|
||||||
*
|
*
|
||||||
|
@ -113,6 +121,27 @@ public interface IotDeviceService {
|
||||||
*/
|
*/
|
||||||
IotDeviceDO getDevice(Long id);
|
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 获得设备
|
* 根据设备 key 获得设备
|
||||||
*
|
*
|
||||||
|
@ -177,17 +206,6 @@ public interface IotDeviceService {
|
||||||
*/
|
*/
|
||||||
Long getDeviceCountByGroupId(Long groupId);
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
import org.springframework.cache.annotation.Caching;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -221,6 +222,15 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||||
return device;
|
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);
|
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
|
@Override
|
||||||
public IotDeviceDO getDeviceByDeviceKey(String deviceKey) {
|
public IotDeviceDO getDeviceByDeviceKey(String deviceKey) {
|
||||||
return deviceMapper.selectByDeviceKey(deviceKey);
|
return deviceMapper.selectByDeviceKey(deviceKey);
|
||||||
|
@ -308,13 +332,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||||
return deviceMapper.selectCountByGroupId(groupId);
|
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
|
* 生成 deviceKey
|
||||||
*
|
*
|
||||||
|
@ -425,14 +442,13 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||||
devices.forEach(this::deleteDeviceCache);
|
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) {
|
public void deleteDeviceCache0(IotDeviceDO device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private IotDeviceServiceImpl getSelf() {
|
|
||||||
return SpringUtil.getBean(getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getDeviceCount(LocalDateTime createTime) {
|
public Long getDeviceCount(LocalDateTime createTime) {
|
||||||
return deviceMapper.selectCountByCreateTime(createTime);
|
return deviceMapper.selectCountByCreateTime(createTime);
|
||||||
|
@ -477,7 +493,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||||
}
|
}
|
||||||
String deviceName = deviceInfo.getDeviceName();
|
String deviceName = deviceInfo.getDeviceName();
|
||||||
String productKey = deviceInfo.getProductKey();
|
String productKey = deviceInfo.getProductKey();
|
||||||
IotDeviceDO device = getSelf().getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
|
IotDeviceDO device = getSelf().getDeviceFromCache(productKey, deviceName);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
|
log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
|
||||||
return false;
|
return false;
|
||||||
|
@ -492,4 +508,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IotDeviceServiceImpl getSelf() {
|
||||||
|
return SpringUtil.getBean(getClass());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,16 @@ public interface IotProductService {
|
||||||
*/
|
*/
|
||||||
IotProductDO getProduct(Long id);
|
IotProductDO getProduct(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 【缓存】获得产品
|
||||||
|
* <p>
|
||||||
|
* 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
|
||||||
|
*
|
||||||
|
* @param id 编号
|
||||||
|
* @return 产品
|
||||||
|
*/
|
||||||
|
IotProductDO getProductFromCache(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据产品 key 获得产品
|
* 根据产品 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.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
import cn.iocoder.yudao.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.IotProductPageReqVO;
|
||||||
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
|
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.dataobject.product.IotProductDO;
|
||||||
import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
|
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.enums.product.IotProductStatusEnum;
|
||||||
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
|
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
|
||||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||||
import jakarta.annotation.Resource;
|
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.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
@ -56,6 +60,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#updateReqVO.id")
|
||||||
public void updateProduct(IotProductSaveReqVO updateReqVO) {
|
public void updateProduct(IotProductSaveReqVO updateReqVO) {
|
||||||
updateReqVO.setProductKey(null); // 不更新产品标识
|
updateReqVO.setProductKey(null); // 不更新产品标识
|
||||||
// 1.1 校验存在
|
// 1.1 校验存在
|
||||||
|
@ -68,6 +73,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#id")
|
||||||
public void deleteProduct(Long id) {
|
public void deleteProduct(Long id) {
|
||||||
// 1.1 校验存在
|
// 1.1 校验存在
|
||||||
IotProductDO iotProductDO = validateProductExists(id);
|
IotProductDO iotProductDO = validateProductExists(id);
|
||||||
|
@ -106,6 +112,13 @@ public class IotProductServiceImpl implements IotProductService {
|
||||||
return productMapper.selectById(id);
|
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
|
@Override
|
||||||
public IotProductDO getProductByProductKey(String productKey) {
|
public IotProductDO getProductByProductKey(String productKey) {
|
||||||
return productMapper.selectByProductKey(productKey);
|
return productMapper.selectByProductKey(productKey);
|
||||||
|
@ -118,6 +131,7 @@ public class IotProductServiceImpl implements IotProductService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DSTransactional(rollbackFor = Exception.class)
|
@DSTransactional(rollbackFor = Exception.class)
|
||||||
|
@CacheEvict(value = RedisKeyConstants.PRODUCT, key = "#id")
|
||||||
public void updateProductStatus(Long id, Integer status) {
|
public void updateProductStatus(Long id, Integer status) {
|
||||||
// 1. 校验存在
|
// 1. 校验存在
|
||||||
validateProductExists(id);
|
validateProductExists(id);
|
||||||
|
@ -143,5 +157,4 @@ public class IotProductServiceImpl implements IotProductService {
|
||||||
return productMapper.selectCountByCreateTime(createTime);
|
return productMapper.selectCountByCreateTime(createTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -31,7 +31,7 @@ public class IotRuleSceneDeviceControlAction implements IotRuleSceneAction {
|
||||||
Assert.notNull(control, "设备控制配置不能为空");
|
Assert.notNull(control, "设备控制配置不能为空");
|
||||||
// 遍历每个设备,下发消息
|
// 遍历每个设备,下发消息
|
||||||
control.getDeviceNames().forEach(deviceName -> {
|
control.getDeviceNames().forEach(deviceName -> {
|
||||||
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(control.getProductKey(), deviceName);
|
IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
log.error("[execute][message({}) config({}) 对应的设备不存在]", message, config);
|
log.error("[execute][message({}) config({}) 对应的设备不存在]", message, config);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
ts TIMESTAMP,
|
ts TIMESTAMP,
|
||||||
id NCHAR(50),
|
id NCHAR(50),
|
||||||
report_time TIMESTAMP,
|
report_time TIMESTAMP,
|
||||||
device_id BIGINT,
|
|
||||||
tenant_id BIGINT,
|
tenant_id BIGINT,
|
||||||
server_id NCHAR(50),
|
server_id NCHAR(50),
|
||||||
upstream BOOL,
|
upstream BOOL,
|
||||||
|
@ -29,21 +28,21 @@
|
||||||
|
|
||||||
<insert id="insert">
|
<insert id="insert">
|
||||||
INSERT INTO device_message_${deviceId} (
|
INSERT INTO device_message_${deviceId} (
|
||||||
ts, id, report_time, device_id, tenant_id,
|
ts, id, report_time, tenant_id, server_id,
|
||||||
server_id, upstream, request_id, method, params,
|
upstream, request_id, method, params, data,
|
||||||
data, code
|
code
|
||||||
)
|
)
|
||||||
USING device_message
|
USING device_message
|
||||||
TAGS (#{deviceId})
|
TAGS (#{deviceId})
|
||||||
VALUES (
|
VALUES (
|
||||||
#{ts}, #{id}, #{reportTime}, #{deviceId}, #{tenantId},
|
#{ts}, #{id}, #{reportTime}, #{tenantId}, #{serverId},
|
||||||
#{serverId}, #{upstream}, #{requestId}, #{method}, #{params},
|
#{upstream}, #{requestId}, #{method}, #{params}, #{data},
|
||||||
#{data}, #{code}
|
#{code}
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<select id="selectPage" resultType="cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO">
|
<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
|
request_id, method, params, data, code
|
||||||
FROM device_message_${reqVO.deviceId}
|
FROM device_message_${reqVO.deviceId}
|
||||||
<where>
|
<where>
|
||||||
|
@ -125,4 +124,4 @@
|
||||||
ORDER BY time ASC
|
ORDER BY time ASC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
|
@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.iot.core.biz;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
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.IotDeviceGetReqDTO;
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IoT 设备通用 API
|
* IoT 设备通用 API
|
||||||
|
@ -26,6 +26,6 @@ public interface IotDeviceCommonApi {
|
||||||
* @param infoReqDTO 设备信息请求
|
* @param infoReqDTO 设备信息请求
|
||||||
* @return 设备信息
|
* @return 设备信息
|
||||||
*/
|
*/
|
||||||
CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(IotDeviceInfoReqDTO infoReqDTO);
|
CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cn.iocoder.yudao.module.iot.core.biz.dto;
|
package cn.iocoder.yudao.module.iot.core.biz.dto;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,18 +8,20 @@ import lombok.Data;
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class IotDeviceInfoReqDTO {
|
public class IotDeviceGetReqDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备编号
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品标识
|
* 产品标识
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "产品标识不能为空")
|
|
||||||
private String productKey;
|
private String productKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备名称
|
* 设备名称
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "设备名称不能为空")
|
|
||||||
private String deviceName;
|
private String deviceName;
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,31 +8,34 @@ import lombok.Data;
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class IotDeviceInfoRespDTO {
|
public class IotDeviceRespDTO {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备编号
|
* 设备编号
|
||||||
*/
|
*/
|
||||||
private Long deviceId;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品标识
|
* 产品标识
|
||||||
*/
|
*/
|
||||||
private String productKey;
|
private String productKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备名称
|
* 设备名称
|
||||||
*/
|
*/
|
||||||
private String deviceName;
|
private String deviceName;
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备密钥
|
|
||||||
*/
|
|
||||||
private String deviceKey;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户编号
|
* 租户编号
|
||||||
*/
|
*/
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|
||||||
|
// ========== 产品相关字段 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品编号
|
||||||
|
*/
|
||||||
|
private Long productId;
|
||||||
|
/**
|
||||||
|
* 编解码器类型
|
||||||
|
*/
|
||||||
|
private String codecType;
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,4 +13,7 @@ public interface ErrorCodeConstants {
|
||||||
ErrorCode DEVICE_AUTH_FAIL = new ErrorCode(1_051_001_000, "设备鉴权失败"); // 对应阿里云 20000
|
ErrorCode DEVICE_AUTH_FAIL = new ErrorCode(1_051_001_000, "设备鉴权失败"); // 对应阿里云 20000
|
||||||
ErrorCode DEVICE_TOKEN_EXPIRED = new ErrorCode(1_051_001_002, "token 失效。需重新调用 auth 进行鉴权,获取token"); // 对应阿里云 20001
|
ErrorCode DEVICE_TOKEN_EXPIRED = new ErrorCode(1_051_001_002, "token 失效。需重新调用 auth 进行鉴权,获取token"); // 对应阿里云 20001
|
||||||
|
|
||||||
|
// ========== 设备信息 1-050-002-000 ============
|
||||||
|
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_051_002_001, "设备({}/{}) 不存在");
|
||||||
|
|
||||||
}
|
}
|
|
@ -36,14 +36,10 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext>
|
||||||
public final void handle(RoutingContext context) {
|
public final void handle(RoutingContext context) {
|
||||||
try {
|
try {
|
||||||
// 1. 前置处理
|
// 1. 前置处理
|
||||||
CommonResult<Object> result = beforeHandle(context);
|
beforeHandle(context);
|
||||||
if (result != null) {
|
|
||||||
writeResponse(context, result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 执行逻辑
|
// 2. 执行逻辑
|
||||||
result = handle0(context);
|
CommonResult<Object> result = handle0(context);
|
||||||
writeResponse(context, result);
|
writeResponse(context, result);
|
||||||
} catch (ServiceException e) {
|
} catch (ServiceException e) {
|
||||||
writeResponse(context, CommonResult.error(e.getCode(), e.getMessage()));
|
writeResponse(context, CommonResult.error(e.getCode(), e.getMessage()));
|
||||||
|
@ -55,11 +51,11 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext>
|
||||||
|
|
||||||
protected abstract CommonResult<Object> handle0(RoutingContext context);
|
protected abstract CommonResult<Object> handle0(RoutingContext context);
|
||||||
|
|
||||||
private CommonResult<Object> beforeHandle(RoutingContext context) {
|
private void beforeHandle(RoutingContext context) {
|
||||||
// 如果不需要认证,则不走前置处理
|
// 如果不需要认证,则不走前置处理
|
||||||
String path = context.request().path();
|
String path = context.request().path();
|
||||||
if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) {
|
if (ObjUtil.equal(path, IotHttpAuthHandler.PATH)) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析参数
|
// 解析参数
|
||||||
|
@ -84,7 +80,6 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext>
|
||||||
|| ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {
|
|| ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {
|
||||||
throw exception(FORBIDDEN);
|
throw exception(FORBIDDEN);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
|
|
|
@ -9,11 +9,10 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
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.IotDeviceAuthReqDTO;
|
||||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
||||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
||||||
import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
|
import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
|
||||||
import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
|
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
|
@ -35,19 +34,16 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
|
||||||
|
|
||||||
private final IotHttpUpstreamProtocol protocol;
|
private final IotHttpUpstreamProtocol protocol;
|
||||||
|
|
||||||
private final IotDeviceMessageProducer deviceMessageProducer;
|
|
||||||
|
|
||||||
private final IotDeviceTokenService deviceTokenService;
|
private final IotDeviceTokenService deviceTokenService;
|
||||||
|
|
||||||
private final IotDeviceCommonApi deviceClientService;
|
private final IotDeviceCommonApi deviceApi;
|
||||||
|
|
||||||
private final IotDeviceMessageService deviceMessageService;
|
private final IotDeviceMessageService deviceMessageService;
|
||||||
|
|
||||||
public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) {
|
public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) {
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
|
|
||||||
this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
|
this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
|
||||||
this.deviceClientService = SpringUtil.getBean(IotDeviceCommonApi.class);
|
this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
|
||||||
this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
|
this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +65,9 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.1 执行认证
|
// 2.1 执行认证
|
||||||
CommonResult<Boolean> result = deviceClientService.authDevice(new IotDeviceAuthReqDTO()
|
CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
|
||||||
.setClientId(clientId).setUsername(username).setPassword(password));
|
.setClientId(clientId).setUsername(username).setPassword(password));
|
||||||
result.checkError();;
|
result.checkError();
|
||||||
if (!BooleanUtil.isTrue(result.getData())) {
|
if (!BooleanUtil.isTrue(result.getData())) {
|
||||||
throw exception(DEVICE_AUTH_FAIL);
|
throw exception(DEVICE_AUTH_FAIL);
|
||||||
}
|
}
|
||||||
|
@ -82,9 +78,9 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
|
||||||
Assert.notBlank(token, "生成 token 不能为空位");
|
Assert.notBlank(token, "生成 token 不能为空位");
|
||||||
|
|
||||||
// 3. 执行上线
|
// 3. 执行上线
|
||||||
IotDeviceMessage message = deviceMessageService.buildDeviceMessageOfStateOnline(
|
IotDeviceMessage message = IotDeviceMessage.buildStateOnline();
|
||||||
|
deviceMessageService.sendDeviceMessage(message,
|
||||||
deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
|
deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
|
||||||
deviceMessageProducer.sendDeviceMessage(message);
|
|
||||||
|
|
||||||
// 构建响应数据
|
// 构建响应数据
|
||||||
return success(MapUtil.of("token", token));
|
return success(MapUtil.of("token", token));
|
||||||
|
|
|
@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||||
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||||
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
|
||||||
import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
|
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class IotDeviceTokenServiceImpl implements IotDeviceTokenService {
|
||||||
JSONObject payload = jwt.getPayloads();
|
JSONObject payload = jwt.getPayloads();
|
||||||
// 检查过期时间
|
// 检查过期时间
|
||||||
Long exp = payload.getLong("exp");
|
Long exp = payload.getLong("exp");
|
||||||
if (exp == null || exp > System.currentTimeMillis() / 1000) {
|
if (exp == null || exp < System.currentTimeMillis() / 1000) {
|
||||||
throw exception(DEVICE_TOKEN_EXPIRED);
|
throw exception(DEVICE_TOKEN_EXPIRED);
|
||||||
}
|
}
|
||||||
String productKey = payload.getStr("productKey");
|
String productKey = payload.getStr("productKey");
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.iot.gateway.service.device;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IoT 设备缓存 Service 接口
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public interface IotDeviceCacheService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备信息
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
class DeviceInfo {
|
|
||||||
/**
|
|
||||||
* 设备编号
|
|
||||||
*/
|
|
||||||
private Long deviceId;
|
|
||||||
/**
|
|
||||||
* 产品标识
|
|
||||||
*/
|
|
||||||
private String productKey;
|
|
||||||
/**
|
|
||||||
* 设备名称
|
|
||||||
*/
|
|
||||||
private String deviceName;
|
|
||||||
/**
|
|
||||||
* 设备密钥
|
|
||||||
*/
|
|
||||||
private String deviceKey;
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*/
|
|
||||||
private Long tenantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据 productKey 和 deviceName 获取设备信息
|
|
||||||
*
|
|
||||||
* @param productKey 产品标识
|
|
||||||
* @param deviceName 设备名称
|
|
||||||
* @return 设备信息,如果不存在返回 null
|
|
||||||
*/
|
|
||||||
DeviceInfo getDeviceInfo(String productKey, String deviceName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据 deviceId 获取设备信息
|
|
||||||
*
|
|
||||||
* @param deviceId 设备编号
|
|
||||||
* @return 设备信息,如果不存在返回 null
|
|
||||||
*/
|
|
||||||
DeviceInfo getDeviceInfoById(Long deviceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除设备缓存
|
|
||||||
*
|
|
||||||
* @param deviceId 设备编号
|
|
||||||
*/
|
|
||||||
void evictDeviceCache(Long deviceId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除设备缓存
|
|
||||||
*
|
|
||||||
* @param productKey 产品标识
|
|
||||||
* @param deviceName 设备名称
|
|
||||||
*/
|
|
||||||
void evictDeviceCache(String productKey, String deviceName);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,241 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.iot.gateway.service.device;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoReqDTO;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceInfoRespDTO;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IoT 设备缓存 Service 实现类
|
|
||||||
* <p>
|
|
||||||
* 使用本地缓存 + 远程 API 的方式获取设备信息,提高性能并避免敏感信息传输
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class IotDeviceCacheServiceImpl implements IotDeviceCacheService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备信息本地缓存
|
|
||||||
* Key: deviceId
|
|
||||||
* Value: DeviceInfo
|
|
||||||
*/
|
|
||||||
private final ConcurrentHashMap<Long, DeviceInfo> deviceIdCache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备名称到设备ID的映射缓存
|
|
||||||
* Key: productKey:deviceName
|
|
||||||
* Value: deviceId
|
|
||||||
*/
|
|
||||||
private final ConcurrentHashMap<String, Long> deviceNameCache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 锁对象,防止并发请求同一设备信息
|
|
||||||
*/
|
|
||||||
private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeviceInfo getDeviceInfo(String productKey, String deviceName) {
|
|
||||||
if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceName)) {
|
|
||||||
log.warn("[getDeviceInfo][参数为空][productKey: {}][deviceName: {}]", productKey, deviceName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String cacheKey = buildDeviceNameCacheKey(productKey, deviceName);
|
|
||||||
|
|
||||||
// 1. 先从缓存获取 deviceId
|
|
||||||
Long deviceId = deviceNameCache.get(cacheKey);
|
|
||||||
if (deviceId != null) {
|
|
||||||
DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
|
|
||||||
if (deviceInfo != null) {
|
|
||||||
log.debug("[getDeviceInfo][缓存命中][productKey: {}][deviceName: {}][deviceId: {}]",
|
|
||||||
productKey, deviceName, deviceId);
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 缓存未命中,从远程 API 获取
|
|
||||||
return loadDeviceInfoFromApi(productKey, deviceName, cacheKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeviceInfo getDeviceInfoById(Long deviceId) {
|
|
||||||
if (deviceId == null) {
|
|
||||||
log.warn("[getDeviceInfoById][deviceId 为空]");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 先从缓存获取
|
|
||||||
DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
|
|
||||||
if (deviceInfo != null) {
|
|
||||||
log.debug("[getDeviceInfoById][缓存命中][deviceId: {}]", deviceId);
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 缓存未命中,从远程 API 获取
|
|
||||||
return loadDeviceInfoByIdFromApi(deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void evictDeviceCache(Long deviceId) {
|
|
||||||
if (deviceId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceInfo deviceInfo = deviceIdCache.remove(deviceId);
|
|
||||||
if (deviceInfo != null) {
|
|
||||||
String cacheKey = buildDeviceNameCacheKey(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
|
|
||||||
deviceNameCache.remove(cacheKey);
|
|
||||||
log.info("[evictDeviceCache][清除设备缓存][deviceId: {}]", deviceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void evictDeviceCache(String productKey, String deviceName) {
|
|
||||||
if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String cacheKey = buildDeviceNameCacheKey(productKey, deviceName);
|
|
||||||
Long deviceId = deviceNameCache.remove(cacheKey);
|
|
||||||
if (deviceId != null) {
|
|
||||||
deviceIdCache.remove(deviceId);
|
|
||||||
log.info("[evictDeviceCache][清除设备缓存][productKey: {}][deviceName: {}]", productKey, deviceName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从远程 API 加载设备信息
|
|
||||||
*
|
|
||||||
* @param productKey 产品标识
|
|
||||||
* @param deviceName 设备名称
|
|
||||||
* @param cacheKey 缓存键
|
|
||||||
* @return 设备信息
|
|
||||||
*/
|
|
||||||
private DeviceInfo loadDeviceInfoFromApi(String productKey, String deviceName, String cacheKey) {
|
|
||||||
// 使用锁防止并发请求同一设备信息
|
|
||||||
ReentrantLock lock = lockMap.computeIfAbsent(cacheKey, k -> new ReentrantLock());
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
// 双重检查,防止重复请求
|
|
||||||
Long deviceId = deviceNameCache.get(cacheKey);
|
|
||||||
if (deviceId != null) {
|
|
||||||
DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
|
|
||||||
if (deviceInfo != null) {
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("[loadDeviceInfoFromApi][从远程API获取设备信息][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 调用远程 API 获取设备信息
|
|
||||||
IotDeviceCommonApi deviceCommonApi = SpringUtil.getBean(IotDeviceCommonApi.class);
|
|
||||||
IotDeviceInfoReqDTO reqDTO = new IotDeviceInfoReqDTO();
|
|
||||||
reqDTO.setProductKey(productKey);
|
|
||||||
reqDTO.setDeviceName(deviceName);
|
|
||||||
|
|
||||||
CommonResult<IotDeviceInfoRespDTO> result = deviceCommonApi.getDeviceInfo(reqDTO);
|
|
||||||
|
|
||||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
|
||||||
log.warn("[loadDeviceInfoFromApi][远程API调用失败][productKey: {}][deviceName: {}][result: {}]",
|
|
||||||
productKey, deviceName, result);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IotDeviceInfoRespDTO respDTO = result.getData();
|
|
||||||
DeviceInfo deviceInfo = new DeviceInfo(
|
|
||||||
respDTO.getDeviceId(),
|
|
||||||
respDTO.getProductKey(),
|
|
||||||
respDTO.getDeviceName(),
|
|
||||||
respDTO.getDeviceKey(),
|
|
||||||
respDTO.getTenantId());
|
|
||||||
|
|
||||||
// 缓存设备信息
|
|
||||||
cacheDeviceInfo(deviceInfo, cacheKey);
|
|
||||||
|
|
||||||
log.info("[loadDeviceInfoFromApi][设备信息获取成功并已缓存][deviceInfo: {}]", deviceInfo);
|
|
||||||
return deviceInfo;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[loadDeviceInfoFromApi][远程API调用异常][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
// 清理锁对象,避免内存泄漏
|
|
||||||
if (lockMap.size() > 1000) { // 简单的清理策略
|
|
||||||
lockMap.entrySet().removeIf(entry -> !entry.getValue().hasQueuedThreads());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从远程 API 根据 deviceId 加载设备信息
|
|
||||||
*
|
|
||||||
* @param deviceId 设备编号
|
|
||||||
* @return 设备信息
|
|
||||||
*/
|
|
||||||
private DeviceInfo loadDeviceInfoByIdFromApi(Long deviceId) {
|
|
||||||
String lockKey = "deviceId:" + deviceId;
|
|
||||||
ReentrantLock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
// 双重检查
|
|
||||||
DeviceInfo deviceInfo = deviceIdCache.get(deviceId);
|
|
||||||
if (deviceInfo != null) {
|
|
||||||
return deviceInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("[loadDeviceInfoByIdFromApi][从远程API获取设备信息][deviceId: {}]", deviceId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: 这里需要添加根据 deviceId 获取设备信息的 API
|
|
||||||
// 暂时返回 null,等待 API 完善
|
|
||||||
log.warn("[loadDeviceInfoByIdFromApi][根据deviceId获取设备信息的API尚未实现][deviceId: {}]", deviceId);
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[loadDeviceInfoByIdFromApi][远程API调用异常][deviceId: {}]", deviceId, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缓存设备信息
|
|
||||||
*
|
|
||||||
* @param deviceInfo 设备信息
|
|
||||||
* @param cacheKey 缓存键
|
|
||||||
*/
|
|
||||||
private void cacheDeviceInfo(DeviceInfo deviceInfo, String cacheKey) {
|
|
||||||
if (deviceInfo != null && deviceInfo.getDeviceId() != null) {
|
|
||||||
deviceIdCache.put(deviceInfo.getDeviceId(), deviceInfo);
|
|
||||||
deviceNameCache.put(cacheKey, deviceInfo.getDeviceId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建设备名称缓存键
|
|
||||||
*
|
|
||||||
* @param productKey 产品标识
|
|
||||||
* @param deviceName 设备名称
|
|
||||||
* @return 缓存键
|
|
||||||
*/
|
|
||||||
private String buildDeviceNameCacheKey(String productKey, String deviceName) {
|
|
||||||
return productKey + ":" + deviceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.iot.gateway.service.device;
|
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|
||||||
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.gateway.config.IotGatewayProperties;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iot 设备信息 Service 实现类:调用远程的 device http 接口,进行设备认证、设备获取等
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class IotDeviceClientServiceImpl implements IotDeviceCommonApi {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IotGatewayProperties gatewayProperties;
|
|
||||||
|
|
||||||
private RestTemplate restTemplate;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
IotGatewayProperties.RpcProperties rpc = gatewayProperties.getRpc();
|
|
||||||
restTemplate = new RestTemplateBuilder()
|
|
||||||
.rootUri(rpc.getUrl() + "/rpc-api/iot/device")
|
|
||||||
.readTimeout(rpc.getReadTimeout())
|
|
||||||
.connectTimeout(rpc.getConnectTimeout())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) {
|
|
||||||
return doPost("/auth", authReqDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommonResult<IotDeviceInfoRespDTO> getDeviceInfo(IotDeviceInfoReqDTO infoReqDTO) {
|
|
||||||
return doPostForDeviceInfo("/info", infoReqDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> CommonResult<Boolean> doPost(String url, T requestBody) {
|
|
||||||
try {
|
|
||||||
CommonResult<Boolean> result = restTemplate.postForObject(url, requestBody,
|
|
||||||
(Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
|
|
||||||
log.info("[doPost][url({}) requestBody({}) result({})]", url, requestBody, result);
|
|
||||||
Assert.notNull(result, "请求结果不能为空");
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[doPost][url({}) requestBody({}) 发生异常]", url, requestBody, e);
|
|
||||||
return CommonResult.error(INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> CommonResult<IotDeviceInfoRespDTO> doPostForDeviceInfo(String url, T requestBody) {
|
|
||||||
try {
|
|
||||||
// 使用 ParameterizedTypeReference 来处理泛型类型
|
|
||||||
ParameterizedTypeReference<CommonResult<LinkedHashMap<String, Object>>> typeRef = new ParameterizedTypeReference<CommonResult<LinkedHashMap<String, Object>>>() {
|
|
||||||
};
|
|
||||||
|
|
||||||
HttpEntity<T> requestEntity = new HttpEntity<>(requestBody);
|
|
||||||
ResponseEntity<CommonResult<LinkedHashMap<String, Object>>> response = restTemplate.exchange(url,
|
|
||||||
HttpMethod.POST, requestEntity, typeRef);
|
|
||||||
|
|
||||||
CommonResult<LinkedHashMap<String, Object>> rawResult = response.getBody();
|
|
||||||
log.info("[doPostForDeviceInfo][url({}) requestBody({}) rawResult({})]", url, requestBody, rawResult);
|
|
||||||
Assert.notNull(rawResult, "请求结果不能为空");
|
|
||||||
|
|
||||||
// 手动转换数据类型
|
|
||||||
CommonResult<IotDeviceInfoRespDTO> result = new CommonResult<>();
|
|
||||||
result.setCode(rawResult.getCode());
|
|
||||||
result.setMsg(rawResult.getMsg());
|
|
||||||
|
|
||||||
if (rawResult.isSuccess() && rawResult.getData() != null) {
|
|
||||||
// 将 LinkedHashMap 转换为 IotDeviceInfoRespDTO
|
|
||||||
IotDeviceInfoRespDTO deviceInfo = BeanUtil.toBean(rawResult.getData(), IotDeviceInfoRespDTO.class);
|
|
||||||
result.setData(deviceInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[doPostForDeviceInfo][url({}) requestBody({}) 发生异常]", url, requestBody, e);
|
|
||||||
return CommonResult.error(INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.gateway.service.device;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IoT 设备信息 Service 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface IotDeviceService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 productKey 和 deviceName 获取设备信息
|
||||||
|
*
|
||||||
|
* @param productKey 产品标识
|
||||||
|
* @param deviceName 设备名称
|
||||||
|
* @return 设备信息
|
||||||
|
*/
|
||||||
|
IotDeviceRespDTO getDeviceFromCache(String productKey, String deviceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 id 获取设备信息
|
||||||
|
*
|
||||||
|
* @param id 设备编号
|
||||||
|
* @return 设备信息
|
||||||
|
*/
|
||||||
|
IotDeviceRespDTO getDeviceFromCache(Long id);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.gateway.service.device;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IoT 设备信息 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class IotDeviceServiceImpl implements IotDeviceService {
|
||||||
|
|
||||||
|
private static final Duration CACHE_EXPIRE = Duration.ofMinutes(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 id 查询设备的缓存
|
||||||
|
*/
|
||||||
|
private final LoadingCache<Long, IotDeviceRespDTO> deviceCaches = buildAsyncReloadingCache(
|
||||||
|
CACHE_EXPIRE,
|
||||||
|
new CacheLoader<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IotDeviceRespDTO load(Long id) {
|
||||||
|
CommonResult<IotDeviceRespDTO> result = deviceApi.getDevice(new IotDeviceGetReqDTO().setId(id));
|
||||||
|
IotDeviceRespDTO device = result.getCheckedData();
|
||||||
|
Assert.notNull(device, "设备({}) 不能为空", id);
|
||||||
|
// 相互缓存
|
||||||
|
deviceCaches2.put(new KeyValue<>(device.getProductKey(), device.getDeviceName()), device);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 productKey + deviceName 查询设备的缓存
|
||||||
|
*/
|
||||||
|
private final LoadingCache<KeyValue<String, String>, IotDeviceRespDTO> deviceCaches2 = buildAsyncReloadingCache(
|
||||||
|
CACHE_EXPIRE,
|
||||||
|
new CacheLoader<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IotDeviceRespDTO load(KeyValue<String, String> kv) {
|
||||||
|
CommonResult<IotDeviceRespDTO> result = deviceApi.getDevice(new IotDeviceGetReqDTO()
|
||||||
|
.setProductKey(kv.getKey()).setDeviceName(kv.getValue()));
|
||||||
|
IotDeviceRespDTO device = result.getCheckedData();
|
||||||
|
Assert.notNull(device, "设备({}/{}) 不能为空", kv.getKey(), kv.getValue());
|
||||||
|
// 相互缓存
|
||||||
|
deviceCaches.put(device.getId(), device);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IotDeviceCommonApi deviceApi;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IotDeviceRespDTO getDeviceFromCache(String productKey, String deviceName) {
|
||||||
|
return deviceCaches2.getUnchecked(new KeyValue<>(productKey, deviceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IotDeviceRespDTO getDeviceFromCache(Long id) {
|
||||||
|
return deviceCaches.getUnchecked(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package cn.iocoder.yudao.module.iot.gateway.service.message;
|
package cn.iocoder.yudao.module.iot.gateway.service.device.message;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||||
|
|
||||||
|
@ -26,30 +26,20 @@ public interface IotDeviceMessageService {
|
||||||
* @param bytes 消息内容
|
* @param bytes 消息内容
|
||||||
* @param productKey 产品 Key
|
* @param productKey 产品 Key
|
||||||
* @param deviceName 设备名称
|
* @param deviceName 设备名称
|
||||||
* @param serverId 设备连接的 serverId
|
|
||||||
* @return 解码后的消息内容
|
* @return 解码后的消息内容
|
||||||
*/
|
*/
|
||||||
IotDeviceMessage decodeDeviceMessage(byte[] bytes,
|
IotDeviceMessage decodeDeviceMessage(byte[] bytes,
|
||||||
String productKey, String deviceName, String serverId);
|
String productKey, String deviceName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建【设备上线】消息
|
* 发送消息
|
||||||
*
|
*
|
||||||
|
* @param message 消息
|
||||||
* @param productKey 产品 Key
|
* @param productKey 产品 Key
|
||||||
* @param deviceName 设备名称
|
* @param deviceName 设备名称
|
||||||
* @param serverId 设备连接的 serverId
|
* @param serverId 设备连接的 serverId
|
||||||
* @return 消息
|
|
||||||
*/
|
*/
|
||||||
IotDeviceMessage buildDeviceMessageOfStateOnline(String productKey, String deviceName, String serverId);
|
void sendDeviceMessage(IotDeviceMessage message,
|
||||||
|
String productKey, String deviceName, String serverId);
|
||||||
/**
|
|
||||||
* 构建【设备下线】消息
|
|
||||||
*
|
|
||||||
* @param productKey 产品 Key
|
|
||||||
* @param deviceName 设备名称
|
|
||||||
* @param serverId 设备连接的 serverId
|
|
||||||
* @return 消息
|
|
||||||
*/
|
|
||||||
IotDeviceMessage buildDeviceMessageOfStateOffline(String productKey, String deviceName, String serverId);
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.gateway.service.device.message;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||||
|
import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
|
||||||
|
import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IoT 设备消息 Service 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编解码器
|
||||||
|
*/
|
||||||
|
private final Map<String, IotAlinkDeviceMessageCodec> codes;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IotDeviceService deviceService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IotDeviceMessageProducer deviceMessageProducer;
|
||||||
|
|
||||||
|
public IotDeviceMessageServiceImpl(List<IotAlinkDeviceMessageCodec> codes) {
|
||||||
|
this.codes = CollectionUtils.convertMap(codes, IotAlinkDeviceMessageCodec::type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encodeDeviceMessage(IotDeviceMessage message,
|
||||||
|
String productKey, String deviceName) {
|
||||||
|
// 1.1 获取设备信息
|
||||||
|
IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
|
||||||
|
if (device == null) {
|
||||||
|
throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
|
||||||
|
}
|
||||||
|
// 1.2 获取编解码器
|
||||||
|
IotAlinkDeviceMessageCodec codec = codes.get(device.getCodecType());
|
||||||
|
if (codec == null) {
|
||||||
|
throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 编码消息
|
||||||
|
return codec.encode(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IotDeviceMessage decodeDeviceMessage(byte[] bytes,
|
||||||
|
String productKey, String deviceName) {
|
||||||
|
// 1.1 获取设备信息
|
||||||
|
IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
|
||||||
|
if (device == null) {
|
||||||
|
throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
|
||||||
|
}
|
||||||
|
// 1.2 获取编解码器
|
||||||
|
IotAlinkDeviceMessageCodec codec = codes.get(device.getCodecType());
|
||||||
|
if (codec == null) {
|
||||||
|
throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 解码消息
|
||||||
|
return codec.decode(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendDeviceMessage(IotDeviceMessage message,
|
||||||
|
String productKey, String deviceName, String serverId) {
|
||||||
|
// 1. 获取设备信息
|
||||||
|
IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
|
||||||
|
if (device == null) {
|
||||||
|
throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 发送消息
|
||||||
|
appendDeviceMessage(message, device, serverId);
|
||||||
|
deviceMessageProducer.sendDeviceMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 补充消息的后端字段
|
||||||
|
*
|
||||||
|
* @param message 消息
|
||||||
|
* @param device 设备信息
|
||||||
|
* @param serverId 设备连接的 serverId
|
||||||
|
* @return 消息
|
||||||
|
*/
|
||||||
|
private IotDeviceMessage appendDeviceMessage(IotDeviceMessage message,
|
||||||
|
IotDeviceRespDTO device, String serverId) {
|
||||||
|
message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now())
|
||||||
|
.setDeviceId(device.getId()).setTenantId(device.getTenantId()).setServerId(serverId);
|
||||||
|
// 特殊:如果设备没有指定 requestId,则使用 messageId
|
||||||
|
if (StrUtil.isEmpty(message.getRequestId())) {
|
||||||
|
message.setRequestId(message.getId());
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.gateway.service.device.remote;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
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.IotDeviceGetReqDTO;
|
||||||
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iot 设备信息 Service 实现类:调用远程的 device http 接口,进行设备认证、设备获取等
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class IotDeviceApiImpl implements IotDeviceCommonApi {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IotGatewayProperties gatewayProperties;
|
||||||
|
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
IotGatewayProperties.RpcProperties rpc = gatewayProperties.getRpc();
|
||||||
|
restTemplate = new RestTemplateBuilder()
|
||||||
|
.rootUri(rpc.getUrl() + "/rpc-api/iot/device")
|
||||||
|
.readTimeout(rpc.getReadTimeout())
|
||||||
|
.connectTimeout(rpc.getConnectTimeout())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) {
|
||||||
|
return doPost("/auth", authReqDTO, new ParameterizedTypeReference<>() { });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO getReqDTO) {
|
||||||
|
return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { });
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T, R> CommonResult<R> doPost(String url, T body,
|
||||||
|
ParameterizedTypeReference<CommonResult<R>> responseType) {
|
||||||
|
try {
|
||||||
|
// 请求
|
||||||
|
HttpEntity<T> requestEntity = new HttpEntity<>(body);
|
||||||
|
ResponseEntity<CommonResult<R>> response = restTemplate.exchange(
|
||||||
|
url, HttpMethod.POST, requestEntity, responseType);
|
||||||
|
// 响应
|
||||||
|
CommonResult<R> result = response.getBody();
|
||||||
|
Assert.notNull(result, "请求结果不能为空");
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[doPost][url({}) body({}) 发生异常]", url, body, e);
|
||||||
|
return CommonResult.error(INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,139 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.iot.gateway.service.message;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
|
||||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
|
||||||
import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
|
|
||||||
import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceCacheService;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IoT 设备消息 Service 实现类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编解码器
|
|
||||||
*/
|
|
||||||
private final Map<String, IotAlinkDeviceMessageCodec> codes;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private IotDeviceCacheService deviceCacheService;
|
|
||||||
|
|
||||||
public IotDeviceMessageServiceImpl(List<IotAlinkDeviceMessageCodec> codes) {
|
|
||||||
this.codes = CollectionUtils.convertMap(codes, IotAlinkDeviceMessageCodec::type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] encodeDeviceMessage(IotDeviceMessage message,
|
|
||||||
String productKey, String deviceName) {
|
|
||||||
// 获取设备信息以确定编解码类型
|
|
||||||
IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
|
|
||||||
if (deviceInfo == null) {
|
|
||||||
log.warn("[encodeDeviceMessage][设备信息不存在][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String codecType = "alink"; // 默认使用 alink 编解码器
|
|
||||||
IotAlinkDeviceMessageCodec codec = codes.get(codecType);
|
|
||||||
if (codec == null) {
|
|
||||||
log.error("[encodeDeviceMessage][编解码器不存在][codecType: {}]", codecType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return codec.encode(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IotDeviceMessage decodeDeviceMessage(byte[] bytes,
|
|
||||||
String productKey, String deviceName, String serverId) {
|
|
||||||
// 获取设备信息
|
|
||||||
IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
|
|
||||||
if (deviceInfo == null) {
|
|
||||||
log.warn("[decodeDeviceMessage][设备信息不存在][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String codecType = "alink"; // 默认使用 alink 编解码器
|
|
||||||
IotAlinkDeviceMessageCodec codec = codes.get(codecType);
|
|
||||||
if (codec == null) {
|
|
||||||
log.error("[decodeDeviceMessage][编解码器不存在][codecType: {}]", codecType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IotDeviceMessage message = codec.decode(bytes);
|
|
||||||
if (message == null) {
|
|
||||||
log.warn("[decodeDeviceMessage][消息解码失败][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 补充后端字段
|
|
||||||
return appendDeviceMessage(message, deviceInfo, serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IotDeviceMessage buildDeviceMessageOfStateOnline(String productKey, String deviceName, String serverId) {
|
|
||||||
// 获取设备信息
|
|
||||||
IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
|
|
||||||
if (deviceInfo == null) {
|
|
||||||
log.warn("[buildDeviceMessageOfStateOnline][设备信息不存在][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IotDeviceMessage message = IotDeviceMessage.requestOf(null,
|
|
||||||
IotDeviceMessageMethodEnum.STATE_ONLINE.getMethod(), null);
|
|
||||||
|
|
||||||
return appendDeviceMessage(message, deviceInfo, serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IotDeviceMessage buildDeviceMessageOfStateOffline(String productKey, String deviceName, String serverId) {
|
|
||||||
// 获取设备信息
|
|
||||||
IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfo(productKey, deviceName);
|
|
||||||
if (deviceInfo == null) {
|
|
||||||
log.warn("[buildDeviceMessageOfStateOffline][设备信息不存在][productKey: {}][deviceName: {}]",
|
|
||||||
productKey, deviceName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IotDeviceMessage message = IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.STATE_OFFLINE.getMethod(),
|
|
||||||
null);
|
|
||||||
return appendDeviceMessage(message, deviceInfo, serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 补充消息的后端字段
|
|
||||||
*
|
|
||||||
* @param message 消息
|
|
||||||
* @param device 设备信息
|
|
||||||
* @param serverId 设备连接的 serverId
|
|
||||||
* @return 消息
|
|
||||||
*/
|
|
||||||
private IotDeviceMessage appendDeviceMessage(IotDeviceMessage message,
|
|
||||||
IotDeviceCacheService.DeviceInfo device, String serverId) {
|
|
||||||
message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now())
|
|
||||||
.setDeviceId(device.getDeviceId()).setTenantId(device.getTenantId()).setServerId(serverId);
|
|
||||||
// 特殊:如果设备没有指定 requestId,则使用 messageId
|
|
||||||
if (StrUtil.isEmpty(message.getRequestId())) {
|
|
||||||
message.setRequestId(message.getId());
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue