Commit ca9575226637284b00fc07545b0b1082267aa743

Authored by 安浩浩
1 parent 8e7bbfe0

【功能新增】IoT: 添加 EMQX 插件,支持设备连接认证和 MQTT 连接参数获取,优化配置文件

Showing 32 changed files with 683 additions and 161 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
@@ -71,6 +71,14 @@ public interface IotDeviceUpstreamApi { @@ -71,6 +71,14 @@ public interface IotDeviceUpstreamApi {
71 @PostMapping(PREFIX + "/add-topology") 71 @PostMapping(PREFIX + "/add-topology")
72 CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO); 72 CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO);
73 73
  74 + /**
  75 + * 认证 Emqx 连接
  76 + *
  77 + * @param authReqDTO 认证 Emqx 连接 DTO
  78 + */
  79 + @PostMapping(PREFIX + "/authenticate-emqx-connection")
  80 + CommonResult<Boolean> authenticateEmqxConnection(@Valid @RequestBody IotDeviceEmqxAuthReqDTO authReqDTO);
  81 +
74 // ========== 插件相关 ========== 82 // ========== 插件相关 ==========
75 83
76 /** 84 /**
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEmqxAuthReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * IoT 认证 Emqx 连接 Request DTO
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Data
  12 +public class IotDeviceEmqxAuthReqDTO {
  13 +
  14 + /**
  15 + * 客户端 ID
  16 + */
  17 + @NotEmpty(message = "客户端 ID 不能为空")
  18 + private String clientId;
  19 +
  20 + /**
  21 + * 用户名
  22 + */
  23 + @NotEmpty(message = "用户名不能为空")
  24 + private String username;
  25 +
  26 + /**
  27 + * 密码
  28 + */
  29 + @NotEmpty(message = "密码不能为空")
  30 + private String password;
  31 +
  32 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
@@ -4,15 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -4,15 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*; 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
5 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService; 5 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
6 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; 6 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
  7 +import jakarta.annotation.Resource;
7 import org.springframework.validation.annotation.Validated; 8 import org.springframework.validation.annotation.Validated;
8 import org.springframework.web.bind.annotation.RestController; 9 import org.springframework.web.bind.annotation.RestController;
9 10
10 -import jakarta.annotation.Resource;  
11 -  
12 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 11 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
13 12
14 /** 13 /**
15 - * * 设备数据 Upstream 上行 API 实现类 14 + * * 设备数据 Upstream 上行 API 实现类
16 */ 15 */
17 @RestController 16 @RestController
18 @Validated 17 @Validated
@@ -61,6 +60,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi { @@ -61,6 +60,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
61 return success(true); 60 return success(true);
62 } 61 }
63 62
  63 + @Override
  64 + public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  65 + Boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
  66 + return success(result);
  67 + }
  68 +
64 // ========== 插件相关 ========== 69 // ========== 插件相关 ==========
65 70
66 @Override 71 @Override
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
@@ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO; 9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
10 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;  
11 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO; 10 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
  11 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
13 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 13 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
14 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService; 14 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService;
@@ -177,4 +177,11 @@ public class IotDeviceController { @@ -177,4 +177,11 @@ public class IotDeviceController {
177 return success(true); 177 return success(true);
178 } 178 }
179 179
  180 + @GetMapping("/mqtt-connection-params")
  181 + @Operation(summary = "获取 MQTT 连接参数")
  182 + @PreAuthorize("@ss.hasPermission('iot:device:mqtt-connection-params')")
  183 + public CommonResult<IotDeviceMqttConnectionParamsRespVO> getMqttConnectionParams(@RequestParam("deviceId") Long deviceId) {
  184 + return success(deviceService.getMqttConnectionParams(deviceId));
  185 + }
  186 +
180 } 187 }
181 \ No newline at end of file 188 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
@@ -168,4 +168,12 @@ public interface IotDeviceService { @@ -168,4 +168,12 @@ public interface IotDeviceService {
168 */ 168 */
169 IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport); 169 IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport);
170 170
  171 + /**
  172 + * 获取 MQTT 连接参数
  173 + *
  174 + * @param deviceId 设备 ID
  175 + * @return MQTT 连接参数
  176 + */
  177 + IotDeviceMqttConnectionParamsRespVO getMqttConnectionParams(Long deviceId);
  178 +
171 } 179 }
172 \ No newline at end of file 180 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; @@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; 20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
21 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; 21 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
22 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  23 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils;
  24 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils.MqttSignResult;
23 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
24 import jakarta.validation.ConstraintViolationException; 26 import jakarta.validation.ConstraintViolationException;
25 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
@@ -123,10 +125,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -123,10 +125,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
123 .setDeviceType(product.getDeviceType()); 125 .setDeviceType(product.getDeviceType());
124 // 生成并设置必要的字段 126 // 生成并设置必要的字段
125 // TODO @芋艿:各种 mqtt 是不是可以简化! 127 // TODO @芋艿:各种 mqtt 是不是可以简化!
126 - device.setDeviceSecret(generateDeviceSecret())  
127 - .setMqttClientId(generateMqttClientId())  
128 - .setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()))  
129 - .setMqttPassword(generateMqttPassword()); 128 + // clientId、username、password 根据规则实时生成
  129 + device.setDeviceSecret(generateDeviceSecret());
130 // 设置设备状态为未激活 130 // 设置设备状态为未激活
131 device.setState(IotDeviceStateEnum.INACTIVE.getState()); 131 device.setState(IotDeviceStateEnum.INACTIVE.getState());
132 } 132 }
@@ -318,35 +318,6 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -318,35 +318,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
318 return IdUtil.fastSimpleUUID(); 318 return IdUtil.fastSimpleUUID();
319 } 319 }
320 320
321 - /**  
322 - * 生成 MQTT Client ID  
323 - *  
324 - * @return 生成的 MQTT Client ID  
325 - */  
326 - private String generateMqttClientId() {  
327 - return IdUtil.fastSimpleUUID();  
328 - }  
329 -  
330 - /**  
331 - * 生成 MQTT Username  
332 - *  
333 - * @param deviceName 设备名称  
334 - * @param productKey 产品 Key  
335 - * @return 生成的 MQTT Username  
336 - */  
337 - private String generateMqttUsername(String deviceName, String productKey) {  
338 - return deviceName + "&" + productKey;  
339 - }  
340 -  
341 - /**  
342 - * 生成 MQTT Password  
343 - *  
344 - * @return 生成的 MQTT Password  
345 - */  
346 - private String generateMqttPassword() {  
347 - return RandomUtil.randomString(32);  
348 - }  
349 -  
350 @Override 321 @Override
351 @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 322 @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
352 public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) { 323 public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
@@ -417,6 +388,17 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -417,6 +388,17 @@ public class IotDeviceServiceImpl implements IotDeviceService {
417 return respVO; 388 return respVO;
418 } 389 }
419 390
  391 + @Override
  392 + public IotDeviceMqttConnectionParamsRespVO getMqttConnectionParams(Long deviceId) {
  393 + IotDeviceDO device = validateDeviceExists(deviceId);
  394 + MqttSignResult mqttSignResult = MqttSignUtils.calculate(device.getProductKey(), device.getDeviceName(),
  395 + device.getDeviceSecret());
  396 + return new IotDeviceMqttConnectionParamsRespVO()
  397 + .setMqttClientId(mqttSignResult.getClientId())
  398 + .setMqttUsername(mqttSignResult.getUsername())
  399 + .setMqttPassword(mqttSignResult.getPassword());
  400 + }
  401 +
420 private void deleteDeviceCache(IotDeviceDO device) { 402 private void deleteDeviceCache(IotDeviceDO device) {
421 // 保证 Spring AOP 触发 403 // 保证 Spring AOP 触发
422 getSelf().deleteDeviceCache0(device); 404 getSelf().deleteDeviceCache0(device);
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java
@@ -62,4 +62,11 @@ public interface IotDeviceUpstreamService { @@ -62,4 +62,11 @@ public interface IotDeviceUpstreamService {
62 */ 62 */
63 void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO); 63 void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO);
64 64
  65 + /**
  66 + * Emqx 连接认证
  67 + *
  68 + * @param authReqDTO Emqx 连接认证 DTO
  69 + */
  70 + Boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO);
  71 +
65 } 72 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
@@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; @@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
21 import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; 21 import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
22 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; 22 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
  23 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils;
  24 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils.MqttSignResult;
23 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
24 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
25 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
@@ -58,25 +60,26 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -58,25 +60,26 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
58 // 2.1 情况一:属性上报 60 // 2.1 情况一:属性上报
59 String requestId = IdUtil.fastSimpleUUID(); 61 String requestId = IdUtil.fastSimpleUUID();
60 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) { 62 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) {
61 - reportDeviceProperty(((IotDevicePropertyReportReqDTO)  
62 - new IotDevicePropertyReportReqDTO().setRequestId(requestId).setReportTime(LocalDateTime.now())  
63 - .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())) 63 + reportDeviceProperty(((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
  64 + .setRequestId(requestId).setReportTime(LocalDateTime.now())
  65 + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
64 .setProperties((Map<String, Object>) simulatorReqVO.getData())); 66 .setProperties((Map<String, Object>) simulatorReqVO.getData()));
65 return; 67 return;
66 } 68 }
67 // 2.2 情况二:事件上报 69 // 2.2 情况二:事件上报
68 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.EVENT.getType())) { 70 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.EVENT.getType())) {
69 - reportDeviceEvent(((IotDeviceEventReportReqDTO)  
70 - new IotDeviceEventReportReqDTO().setRequestId(requestId).setReportTime(LocalDateTime.now())  
71 - .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))  
72 - .setIdentifier(simulatorReqVO.getIdentifier()).setParams((Map<String, Object>) simulatorReqVO.getData())); 71 + reportDeviceEvent(((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId)
  72 + .setReportTime(LocalDateTime.now())
  73 + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
  74 + .setIdentifier(simulatorReqVO.getIdentifier())
  75 + .setParams((Map<String, Object>) simulatorReqVO.getData()));
73 return; 76 return;
74 } 77 }
75 // 2.3 情况三:状态变更 78 // 2.3 情况三:状态变更
76 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.STATE.getType())) { 79 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.STATE.getType())) {
77 - updateDeviceState(((IotDeviceStateUpdateReqDTO)  
78 - new IotDeviceStateUpdateReqDTO().setRequestId(IdUtil.fastSimpleUUID()).setReportTime(LocalDateTime.now())  
79 - .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())) 80 + updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
  81 + .setRequestId(IdUtil.fastSimpleUUID()).setReportTime(LocalDateTime.now())
  82 + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
80 .setState((Integer) simulatorReqVO.getData())); 83 .setState((Integer) simulatorReqVO.getData()));
81 return; 84 return;
82 } 85 }
@@ -277,6 +280,37 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -277,6 +280,37 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
277 sendDeviceMessage(message, device); 280 sendDeviceMessage(message, device);
278 } 281 }
279 282
  283 + @Override
  284 + public Boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  285 + log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO);
  286 + // 1. 校验设备是否存在
  287 + // username 格式:${DeviceName}&${ProductKey}
  288 + String[] usernameParts = authReqDTO.getUsername().split("&");
  289 + if (usernameParts.length != 2) {
  290 + log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]");
  291 + return Boolean.FALSE;
  292 + }
  293 + String deviceName = usernameParts[0];
  294 + String productKey = usernameParts[1];
  295 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
  296 + productKey, deviceName);
  297 + if (device == null) {
  298 + log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]",
  299 + productKey, deviceName);
  300 + return Boolean.FALSE;
  301 + }
  302 + // 2. 校验密码
  303 + String deviceSecret = device.getDeviceSecret();
  304 + String clientId = authReqDTO.getClientId();
  305 + MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId);
  306 + if (!StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {
  307 + log.error("[authenticateEmqxConnection][认证失败,密码不正确]");
  308 + return Boolean.FALSE;
  309 + }
  310 + log.info("[authenticateEmqxConnection][认证成功]");
  311 + return Boolean.TRUE;
  312 + }
  313 +
280 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { 314 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
281 // 1. 【异步】记录设备与插件实例的映射 315 // 1. 【异步】记录设备与插件实例的映射
282 pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId()); 316 pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MqttSignUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.util;
  2 +
  3 +import lombok.Getter;
  4 +
  5 +import javax.crypto.Mac;
  6 +import javax.crypto.spec.SecretKeySpec;
  7 +import java.nio.charset.StandardCharsets;
  8 +
  9 +/**
  10 + * MQTT 签名工具类
  11 + * 提供静态方法来计算 MQTT 连接参数。
  12 + */
  13 +public class MqttSignUtils {
  14 +
  15 + private static final String SIGN_METHOD = "hmacsha256";
  16 +
  17 + /**
  18 + * 计算 MQTT 连接参数
  19 + *
  20 + * @param productKey 产品密钥
  21 + * @param deviceName 设备名称
  22 + * @param deviceSecret 设备密钥
  23 + * @return 包含 clientId, username, password 的结果对象
  24 + */
  25 + public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) {
  26 + String clientId = productKey + "." + deviceName;
  27 + String username = deviceName + "&" + productKey;
  28 + String signContent = String.format("clientId%sdeviceName%sdeviceSecret%sproductKey%s",
  29 + clientId, deviceName, deviceSecret, productKey);
  30 +
  31 + String password = sign(signContent, deviceSecret);
  32 +
  33 + return new MqttSignResult(clientId, username, password);
  34 + }
  35 +
  36 + /**
  37 + * 计算 MQTT 连接参数
  38 + *
  39 + * @param productKey 产品密钥
  40 + * @param deviceName 设备名称
  41 + * @param deviceSecret 设备密钥
  42 + * @param clientId 客户端 ID
  43 + * @return 包含 clientId, username, password 的结果对象
  44 + */
  45 + public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) {
  46 + String username = deviceName + "&" + productKey;
  47 + String signContentBuilder = "clientId" + clientId +
  48 + "deviceName" + deviceName +
  49 + "deviceSecret" + deviceSecret +
  50 + "productKey" + productKey;
  51 +
  52 + String password = sign(signContentBuilder, deviceSecret);
  53 +
  54 + return new MqttSignResult(clientId, username, password);
  55 + }
  56 +
  57 + private static String sign(String content, String key) {
  58 + try {
  59 + Mac mac = Mac.getInstance(SIGN_METHOD);
  60 + mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), SIGN_METHOD));
  61 + byte[] signData = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
  62 + return bytesToHex(signData);
  63 + } catch (Exception e) {
  64 + throw new RuntimeException("Failed to sign content with HmacSHA256", e);
  65 + }
  66 + }
  67 +
  68 + private static String bytesToHex(byte[] bytes) {
  69 + StringBuilder hexString = new StringBuilder(bytes.length * 2);
  70 + for (byte b : bytes) {
  71 + String hex = Integer.toHexString(0xFF & b);
  72 + if (hex.length() == 1) {
  73 + hexString.append('0');
  74 + }
  75 + hexString.append(hex);
  76 + }
  77 + return hexString.toString();
  78 + }
  79 +
  80 + /**
  81 + * MQTT 签名结果类
  82 + */
  83 + @Getter
  84 + public static class MqttSignResult {
  85 + private final String clientId;
  86 + private final String username;
  87 + private final String password;
  88 +
  89 + public MqttSignResult(String clientId, String username, String password) {
  90 + this.clientId = clientId;
  91 + this.username = username;
  92 + this.password = password;
  93 + }
  94 +
  95 + }
  96 +}
0 \ No newline at end of file 97 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonAutoConfiguration.java
@@ -44,8 +44,8 @@ public class IotPluginCommonAutoConfiguration { @@ -44,8 +44,8 @@ public class IotPluginCommonAutoConfiguration {
44 44
45 @Bean(initMethod = "init", destroyMethod = "stop") 45 @Bean(initMethod = "init", destroyMethod = "stop")
46 public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob( 46 public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(
47 - IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer) {  
48 - return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer); 47 + IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer, IotPluginCommonProperties commonProperties) {
  48 + return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer, commonProperties);
49 } 49 }
50 50
51 } 51 }
52 \ No newline at end of file 52 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonProperties.java
@@ -7,6 +7,11 @@ import org.springframework.validation.annotation.Validated; @@ -7,6 +7,11 @@ import org.springframework.validation.annotation.Validated;
7 7
8 import java.time.Duration; 8 import java.time.Duration;
9 9
  10 +/**
  11 + * IoT 插件的通用配置类
  12 + *
  13 + * @author haohao
  14 + */
10 @ConfigurationProperties(prefix = "yudao.iot.plugin.common") 15 @ConfigurationProperties(prefix = "yudao.iot.plugin.common")
11 @Validated 16 @Validated
12 @Data 17 @Data
@@ -45,4 +50,10 @@ public class IotPluginCommonProperties { @@ -45,4 +50,10 @@ public class IotPluginCommonProperties {
45 */ 50 */
46 private Integer downstreamPort = DOWNSTREAM_PORT_RANDOM; 51 private Integer downstreamPort = DOWNSTREAM_PORT_RANDOM;
47 52
  53 + /**
  54 + * 插件包标识符
  55 + */
  56 + @NotEmpty(message = "插件包标识符不能为空")
  57 + private String pluginKey;
  58 +
48 } 59 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceConfigSetVertxHandler.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17
18 /** 18 /**
19 - * IOT 设备配置设置 Vertx Handler 19 + * IoT 设备配置设置 Vertx Handler
20 * 20 *
21 * 芋道源码 21 * 芋道源码
22 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceOtaUpgradeVertxHandler.java
@@ -5,14 +5,19 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceOt @@ -5,14 +5,19 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceOt
5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler; 5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; 6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
7 import io.vertx.core.Handler; 7 import io.vertx.core.Handler;
  8 +import io.vertx.core.json.JsonObject;
8 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
9 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 -import io.vertx.core.json.JsonObject;  
12 12
13 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; 13 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
14 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 14 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
15 15
  16 +/**
  17 + * IoT 设备 OTA 升级 Vertx Handler
  18 + * <p>
  19 + * 芋道源码
  20 + */
16 @Slf4j 21 @Slf4j
17 @RequiredArgsConstructor 22 @RequiredArgsConstructor
18 public class IotDeviceOtaUpgradeVertxHandler implements Handler<RoutingContext> { 23 public class IotDeviceOtaUpgradeVertxHandler implements Handler<RoutingContext> {
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertyGetVertxHandler.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17
18 /** 18 /**
19 - * IOT 设备服务获取 Vertx Handler 19 + * IoT 设备服务获取 Vertx Handler
20 * 20 *
21 * 芋道源码 21 * 芋道源码
22 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertySetVertxHandler.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17
18 /** 18 /**
19 - * IOT 设备服务设置 Vertx Handler 19 + * IoT 设备服务设置 Vertx Handler
20 * 20 *
21 * 芋道源码 21 * 芋道源码
22 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceServiceInvokeVertxHandler.java
@@ -5,17 +5,18 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceSe @@ -5,17 +5,18 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceSe
5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler; 5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; 6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
7 import io.vertx.core.Handler; 7 import io.vertx.core.Handler;
  8 +import io.vertx.core.json.JsonObject;
8 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
9 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 -import io.vertx.core.json.JsonObject; 12 +
12 import java.util.Map; 13 import java.util.Map;
13 14
14 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; 15 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
15 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
16 17
17 /** 18 /**
18 - * IOT 设备服务调用 Vertx Handler 19 + * IoT 设备服务调用 Vertx Handler
19 * 20 *
20 * 芋道源码 21 * 芋道源码
21 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/heartbeat/IotPluginInstanceHeartbeatJob.java
@@ -4,6 +4,7 @@ import cn.hutool.system.SystemUtil; @@ -4,6 +4,7 @@ import cn.hutool.system.SystemUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO; 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
  7 +import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
7 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer; 8 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
8 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; 9 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
9 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
@@ -23,6 +24,7 @@ public class IotPluginInstanceHeartbeatJob { @@ -23,6 +24,7 @@ public class IotPluginInstanceHeartbeatJob {
23 24
24 private final IotDeviceUpstreamApi deviceUpstreamApi; 25 private final IotDeviceUpstreamApi deviceUpstreamApi;
25 private final IotDeviceDownstreamServer deviceDownstreamServer; 26 private final IotDeviceDownstreamServer deviceDownstreamServer;
  27 + private final IotPluginCommonProperties commonProperties;
26 28
27 public void init() { 29 public void init() {
28 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true)); 30 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true));
@@ -41,9 +43,8 @@ public class IotPluginInstanceHeartbeatJob { @@ -41,9 +43,8 @@ public class IotPluginInstanceHeartbeatJob {
41 } 43 }
42 44
43 private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(Boolean online) { 45 private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(Boolean online) {
44 - // TODO @haohao:pluginKey 的获取???  
45 return new IotPluginInstanceHeartbeatReqDTO() 46 return new IotPluginInstanceHeartbeatReqDTO()
46 - .setPluginKey("yudao-module-iot-plugin-http").setProcessId(IotPluginCommonUtils.getProcessId()) 47 + .setPluginKey(commonProperties.getPluginKey()).setProcessId(IotPluginCommonUtils.getProcessId())
47 .setHostIp(SystemUtil.getHostInfo().getAddress()).setDownstreamPort(deviceDownstreamServer.getPort()) 48 .setHostIp(SystemUtil.getHostInfo().getAddress()).setDownstreamPort(deviceDownstreamServer.getPort())
48 .setOnline(online); 49 .setOnline(online);
49 } 50 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/upstream/IotDeviceUpstreamClient.java
@@ -58,6 +58,12 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi { @@ -58,6 +58,12 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
58 } 58 }
59 59
60 @Override 60 @Override
  61 + public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  62 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/authenticate-emqx-connection";
  63 + return doPost(url, authReqDTO);
  64 + }
  65 +
  66 + @Override
61 public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) { 67 public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
62 String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property"; 68 String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";
63 return doPost(url, reportReqDTO); 69 return doPost(url, reportReqDTO);
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/util/IotPluginCommonUtils.java
@@ -41,4 +41,12 @@ public class IotPluginCommonUtils { @@ -41,4 +41,12 @@ public class IotPluginCommonUtils {
41 .end(JsonUtils.toJsonString(result)); 41 .end(JsonUtils.toJsonString(result));
42 } 42 }
43 43
  44 + @SuppressWarnings("deprecation")
  45 + public static void writeJson(RoutingContext routingContext, String result) {
  46 + routingContext.response()
  47 + .setStatusCode(200)
  48 + .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
  49 + .end(result);
  50 + }
  51 +
44 } 52 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/plugin.properties
1 -plugin.id=plugin-emqx  
2 -plugin.class=cn.iocoder.yudao.module.iot.plugin.EmqxPlugin 1 +plugin.id=yudao-module-iot-plugin-emqx
  2 +plugin.class=cn.iocoder.yudao.module.iot.plugin.emqx.config.IotEmqxPlugin
3 plugin.version=1.0.0 3 plugin.version=1.0.0
4 -plugin.provider=ahh 4 +plugin.provider=yudao
5 plugin.dependencies= 5 plugin.dependencies=
6 -plugin.description=plugin-emqx-1.0.0 6 +plugin.description=yudao-module-iot-plugin-emqx-1.0.0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/pom.xml
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 <packaging>jar</packaging> 12 <packaging>jar</packaging>
13 13
14 <artifactId>yudao-module-iot-plugin-emqx</artifactId> 14 <artifactId>yudao-module-iot-plugin-emqx</artifactId>
  15 + <version>1.0.0</version>
15 16
16 <name>${project.artifactId}</name> 17 <name>${project.artifactId}</name>
17 <description> 18 <description>
@@ -21,36 +22,16 @@ @@ -21,36 +22,16 @@
21 <properties> 22 <properties>
22 <!-- 插件相关 --> 23 <!-- 插件相关 -->
23 <plugin.id>emqx-plugin</plugin.id> 24 <plugin.id>emqx-plugin</plugin.id>
24 - <plugin.class>cn.iocoder.yudao.module.iot.plugin.EmqxPlugin</plugin.class>  
25 - <plugin.version>0.0.1</plugin.version>  
26 - <plugin.provider>ahh</plugin.provider>  
27 - <plugin.description>emqx-plugin-0.0.1</plugin.description> 25 + <plugin.class>cn.iocoder.yudao.module.iot.plugin.emqx.config.IotEmqxPlugin</plugin.class>
  26 + <plugin.version>${project.version}</plugin.version>
  27 + <plugin.provider>yudao</plugin.provider>
  28 + <plugin.description>${project.artifactId}-${project.version}</plugin.description>
28 <plugin.dependencies/> 29 <plugin.dependencies/>
29 </properties> 30 </properties>
30 31
31 <build> 32 <build>
32 <plugins> 33 <plugins>
33 - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)  
34 - <plugin>  
35 - <groupId>org.codehaus.mojo</groupId>  
36 - <artifactId>properties-maven-plugin</artifactId>  
37 - <version>1.0-alpha-2</version>  
38 - <executions>  
39 - <execution>  
40 - <phase>initialize</phase>  
41 - <goals>  
42 - <goal>read-project-properties</goal>  
43 - </goals>  
44 - <configuration>  
45 - <files>  
46 - <file>plugin.properties</file>  
47 - </files>  
48 - </configuration>  
49 - </execution>  
50 - </executions>  
51 - </plugin>  
52 - -->  
53 - 34 + <!-- 插件模式 zip -->
54 <plugin> 35 <plugin>
55 <groupId>org.apache.maven.plugins</groupId> 36 <groupId>org.apache.maven.plugins</groupId>
56 <artifactId>maven-antrun-plugin</artifactId> 37 <artifactId>maven-antrun-plugin</artifactId>
@@ -94,6 +75,7 @@ @@ -94,6 +75,7 @@
94 </executions> 75 </executions>
95 </plugin> 76 </plugin>
96 77
  78 + <!-- 插件模式 jar -->
97 <plugin> 79 <plugin>
98 <groupId>org.apache.maven.plugins</groupId> 80 <groupId>org.apache.maven.plugins</groupId>
99 <artifactId>maven-jar-plugin</artifactId> 81 <artifactId>maven-jar-plugin</artifactId>
@@ -111,54 +93,72 @@ @@ -111,54 +93,72 @@
111 </archive> 93 </archive>
112 </configuration> 94 </configuration>
113 </plugin> 95 </plugin>
  96 + <!-- <plugin>-->
  97 + <!-- <groupId>org.apache.maven.plugins</groupId>-->
  98 + <!-- <artifactId>maven-shade-plugin</artifactId>-->
  99 + <!-- <version>3.6.0</version>-->
  100 + <!-- <executions>-->
  101 + <!-- <execution>-->
  102 + <!-- <phase>package</phase>-->
  103 + <!-- <goals>-->
  104 + <!-- <goal>shade</goal>-->
  105 + <!-- </goals>-->
  106 + <!-- <configuration>-->
  107 + <!-- <minimizeJar>true</minimizeJar>-->
  108 + <!-- </configuration>-->
  109 + <!-- </execution>-->
  110 + <!-- </executions>-->
  111 + <!-- <configuration>-->
  112 + <!-- <archive>-->
  113 + <!-- <manifestEntries>-->
  114 + <!-- <Plugin-Id>${plugin.id}</Plugin-Id>-->
  115 + <!-- <Plugin-Class>${plugin.class}</Plugin-Class>-->
  116 + <!-- <Plugin-Version>${plugin.version}</Plugin-Version>-->
  117 + <!-- <Plugin-Provider>${plugin.provider}</Plugin-Provider>-->
  118 + <!-- <Plugin-Description>${plugin.description}</Plugin-Description>-->
  119 + <!-- <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>-->
  120 + <!-- </manifestEntries>-->
  121 + <!-- </archive>-->
  122 + <!-- </configuration>-->
  123 + <!-- </plugin>-->
114 124
  125 + <!-- 独立模式 -->
115 <plugin> 126 <plugin>
116 - <artifactId>maven-deploy-plugin</artifactId>  
117 - <configuration>  
118 - <skip>true</skip>  
119 - </configuration> 127 + <groupId>org.springframework.boot</groupId>
  128 + <artifactId>spring-boot-maven-plugin</artifactId>
  129 + <version>${spring.boot.version}</version>
  130 + <executions>
  131 + <execution>
  132 + <goals>
  133 + <goal>repackage</goal>
  134 + </goals>
  135 + <configuration>
  136 + <classifier>-standalone</classifier>
  137 + </configuration>
  138 + </execution>
  139 + </executions>
120 </plugin> 140 </plugin>
  141 +
121 </plugins> 142 </plugins>
122 </build> 143 </build>
123 144
124 <dependencies> 145 <dependencies>
125 - <!-- 其他依赖项 -->  
126 - <dependency>  
127 - <groupId>org.springframework.boot</groupId>  
128 - <artifactId>spring-boot-starter-web</artifactId>  
129 - </dependency>  
130 - <!-- PF4J Spring 集成 -->  
131 - <dependency>  
132 - <groupId>org.pf4j</groupId>  
133 - <artifactId>pf4j-spring</artifactId>  
134 - <scope>provided</scope>  
135 - </dependency>  
136 - <!-- 项目依赖 -->  
137 <dependency> 146 <dependency>
138 <groupId>cn.iocoder.boot</groupId> 147 <groupId>cn.iocoder.boot</groupId>
139 - <artifactId>yudao-module-iot-api</artifactId> 148 + <artifactId>yudao-module-iot-plugin-common</artifactId>
140 <version>${revision}</version> 149 <version>${revision}</version>
141 </dependency> 150 </dependency>
  151 +
  152 + <!-- Web 相关 -->
142 <dependency> 153 <dependency>
143 - <groupId>org.projectlombok</groupId>  
144 - <artifactId>lombok</artifactId>  
145 - <version>${lombok.version}</version>  
146 - <scope>provided</scope>  
147 - </dependency>  
148 - <!-- Vert.x 核心依赖 -->  
149 - <dependency>  
150 - <groupId>io.vertx</groupId>  
151 - <artifactId>vertx-core</artifactId> 154 + <groupId>org.springframework.boot</groupId>
  155 + <artifactId>spring-boot-starter-web</artifactId>
152 </dependency> 156 </dependency>
153 - <!-- Vert.x Web 模块 --> 157 +
  158 + <!-- 工具类相关 -->
154 <dependency> 159 <dependency>
155 <groupId>io.vertx</groupId> 160 <groupId>io.vertx</groupId>
156 <artifactId>vertx-web</artifactId> 161 <artifactId>vertx-web</artifactId>
157 </dependency> 162 </dependency>
158 - <!-- MQTT -->  
159 - <dependency>  
160 - <groupId>org.eclipse.paho</groupId>  
161 - <artifactId>org.eclipse.paho.client.mqttv3</artifactId>  
162 - </dependency>  
163 </dependencies> 163 </dependencies>
164 </project> 164 </project>
165 \ No newline at end of file 165 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/EmqxPlugin.java deleted
1 -package cn.iocoder.yudao.module.iot.plugin;  
2 -  
3 -import cn.hutool.extra.spring.SpringUtil;  
4 -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;  
5 -import lombok.extern.slf4j.Slf4j;  
6 -import org.pf4j.Plugin;  
7 -import org.pf4j.PluginWrapper;  
8 -  
9 -import java.util.concurrent.ExecutorService;  
10 -import java.util.concurrent.Executors;  
11 -  
12 -@Slf4j  
13 -public class EmqxPlugin extends Plugin {  
14 -  
15 - private ExecutorService executorService;  
16 -  
17 - public EmqxPlugin(PluginWrapper wrapper) {  
18 - super(wrapper);  
19 - this.executorService = Executors.newSingleThreadExecutor();  
20 - }  
21 -  
22 - @Override  
23 - public void start() {  
24 - log.info("EmqxPlugin.start()");  
25 -  
26 - if (executorService.isShutdown() || executorService.isTerminated()) {  
27 - executorService = Executors.newSingleThreadExecutor();  
28 - }  
29 -  
30 - IotDeviceUpstreamApi deviceDataApi = SpringUtil.getBean(IotDeviceUpstreamApi.class);  
31 - if (deviceDataApi == null) {  
32 - log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");  
33 - return;  
34 - }  
35 -  
36 - }  
37 -  
38 - @Override  
39 - public void stop() {  
40 - log.info("EmqxPlugin.stop()");  
41 - }  
42 -}  
43 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/IotEmqxPluginApplication.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.springframework.boot.SpringApplication;
  5 +import org.springframework.boot.WebApplicationType;
  6 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  7 +
  8 +/**
  9 + * IoT Emqx 插件的独立运行入口
  10 + */
  11 +@Slf4j
  12 +@SpringBootApplication
  13 +public class IotEmqxPluginApplication {
  14 +
  15 + public static void main(String[] args) {
  16 + SpringApplication application = new SpringApplication(IotEmqxPluginApplication.class);
  17 + application.setWebApplicationType(WebApplicationType.NONE);
  18 + application.run(args);
  19 + log.info("[main][独立模式启动完成]");
  20 + }
  21 +
  22 +}
0 \ No newline at end of file 23 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotEmqxPlugin.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.config;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.pf4j.PluginWrapper;
  6 +import org.pf4j.spring.SpringPlugin;
  7 +import org.springframework.context.ApplicationContext;
  8 +import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  9 +
  10 +// TODO @芋艿:完善注释
  11 +
  12 +/**
  13 + * 负责插件的启动和停止
  14 + */
  15 +@Slf4j
  16 +public class IotEmqxPlugin extends SpringPlugin {
  17 +
  18 + public IotEmqxPlugin(PluginWrapper wrapper) {
  19 + super(wrapper);
  20 + }
  21 +
  22 + @Override
  23 + public void start() {
  24 + log.info("[EmqxPlugin][EmqxPlugin 插件启动开始...]");
  25 + try {
  26 +
  27 + log.info("[EmqxPlugin][EmqxPlugin 插件启动成功...]");
  28 + } catch (Exception e) {
  29 + log.error("[EmqxPlugin][EmqxPlugin 插件开启动异常...]", e);
  30 + }
  31 + }
  32 +
  33 + @Override
  34 + public void stop() {
  35 + log.info("[EmqxPlugin][EmqxPlugin 插件停止开始...]");
  36 + try {
  37 + log.info("[EmqxPlugin][EmqxPlugin 插件停止成功...]");
  38 + } catch (Exception e) {
  39 + log.error("[EmqxPlugin][EmqxPlugin 插件停止异常...]", e);
  40 + }
  41 + }
  42 +
  43 + @Override
  44 + protected ApplicationContext createApplicationContext() {
  45 + // 创建插件自己的 ApplicationContext
  46 + AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext();
  47 + // 设置父容器为主应用的 ApplicationContext (确保主应用中提供的类可用)
  48 + pluginContext.setParent(SpringUtil.getApplicationContext());
  49 + // 继续使用插件自己的 ClassLoader 以加载插件内部的类
  50 + pluginContext.setClassLoader(getWrapper().getPluginClassLoader());
  51 + // 扫描当前插件的自动配置包
  52 + pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.emqx.config");
  53 + pluginContext.refresh();
  54 + return pluginContext;
  55 + }
  56 +
  57 +}
0 \ No newline at end of file 58 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  4 +import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
  5 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
  6 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
  7 +import cn.iocoder.yudao.module.iot.plugin.emqx.downstream.IotDeviceDownstreamHandlerImpl;
  8 +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.IotDeviceUpstreamServer;
  9 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  10 +import org.springframework.context.annotation.Bean;
  11 +import org.springframework.context.annotation.Configuration;
  12 +
  13 +/**
  14 + * IoT 插件 Emqx 的专用自动配置类
  15 + *
  16 + * @author haohao
  17 + */
  18 +@Configuration
  19 +@EnableConfigurationProperties(IotPluginEmqxProperties.class)
  20 +public class IotPluginEmqxAutoConfiguration {
  21 +
  22 + @Bean(initMethod = "start", destroyMethod = "stop")
  23 + public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
  24 + IotPluginCommonProperties commonProperties,
  25 + IotPluginEmqxProperties emqxProperties,
  26 + IotDeviceDownstreamServer deviceDownstreamServer) {
  27 + return new IotDeviceUpstreamServer(commonProperties, emqxProperties, deviceUpstreamApi, deviceDownstreamServer);
  28 + }
  29 +
  30 + @Bean
  31 + public IotDeviceDownstreamHandler deviceDownstreamHandler() {
  32 + return new IotDeviceDownstreamHandlerImpl();
  33 + }
  34 +
  35 +}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +/**
  8 + * 物联网插件 - EMQX 配置
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@ConfigurationProperties(prefix = "yudao.iot.plugin.emqx")
  13 +@Validated
  14 +@Data
  15 +public class IotPluginEmqxProperties {
  16 +
  17 + /**
  18 + * 服务主机
  19 + */
  20 + private String host;
  21 +
  22 + /**
  23 + * 服务端口
  24 + */
  25 + private int port;
  26 +
  27 + /**
  28 + * 是否启用 SSL
  29 + */
  30 + private boolean ssl;
  31 +
  32 + /**
  33 + * 订阅的主题
  34 + */
  35 + private String topics;
  36 +
  37 + /**
  38 + * 认证端口
  39 + */
  40 + private int authPort;
  41 +
  42 +}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/downstream/IotDeviceDownstreamHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.downstream;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
  5 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
  6 +
  7 +/**
  8 + * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
  9 + * <p>
  10 + * 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!
  11 + * 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
  16 +
  17 + @Override
  18 + public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
  19 + return CommonResult.success(true);
  20 + }
  21 +
  22 + @Override
  23 + public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
  24 + return CommonResult.success(true);
  25 + }
  26 +
  27 + @Override
  28 + public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
  29 + return CommonResult.success(true);
  30 + }
  31 +
  32 + @Override
  33 + public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
  34 + return CommonResult.success(true);
  35 + }
  36 +
  37 + @Override
  38 + public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
  39 + return CommonResult.success(true);
  40 + }
  41 +
  42 +}
0 \ No newline at end of file 43 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  4 +import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
  5 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
  6 +import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
  7 +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
  8 +import io.vertx.core.Vertx;
  9 +import io.vertx.core.http.HttpServer;
  10 +import io.vertx.ext.web.Router;
  11 +import io.vertx.ext.web.handler.BodyHandler;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +
  14 +/**
  15 + * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
  16 + * <p>
  17 + * 协议:HTTP
  18 + *
  19 + * @author haohao
  20 + */
  21 +@Slf4j
  22 +public class IotDeviceUpstreamServer {
  23 +
  24 + private final Vertx vertx;
  25 + private final HttpServer server;
  26 + private final IotPluginEmqxProperties emqxProperties;
  27 +
  28 + public IotDeviceUpstreamServer(IotPluginCommonProperties commonProperties,
  29 + IotPluginEmqxProperties emqxProperties,
  30 + IotDeviceUpstreamApi deviceUpstreamApi,
  31 + IotDeviceDownstreamServer deviceDownstreamServer) {
  32 + this.emqxProperties = emqxProperties;
  33 + // 创建 Vertx 实例
  34 + this.vertx = Vertx.vertx();
  35 + // 创建 Router 实例
  36 + Router router = Router.router(vertx);
  37 + router.route().handler(BodyHandler.create()); // 处理 Body
  38 + router.post(IotDeviceAuthVertxHandler.PATH)
  39 + .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
  40 + // 创建 HttpServer 实例
  41 + this.server = vertx.createHttpServer().requestHandler(router);
  42 + }
  43 +
  44 + /**
  45 + * 启动 HTTP 服务器
  46 + */
  47 + public void start() {
  48 + log.info("[start][开始启动]");
  49 + server.listen(emqxProperties.getAuthPort())
  50 + .toCompletionStage()
  51 + .toCompletableFuture()
  52 + .join();
  53 + log.info("[start][启动完成,端口({})]", this.server.actualPort());
  54 + }
  55 +
  56 + /**
  57 + * 停止所有
  58 + */
  59 + public void stop() {
  60 + log.info("[stop][开始关闭]");
  61 + try {
  62 + // 关闭 HTTP 服务器
  63 + if (server != null) {
  64 + server.close()
  65 + .toCompletionStage()
  66 + .toCompletableFuture()
  67 + .join();
  68 + }
  69 +
  70 + // 关闭 Vertx 实例
  71 + if (vertx != null) {
  72 + vertx.close()
  73 + .toCompletionStage()
  74 + .toCompletableFuture()
  75 + .join();
  76 + }
  77 + log.info("[stop][关闭完成]");
  78 + } catch (Exception e) {
  79 + log.error("[stop][关闭异常]", e);
  80 + throw new RuntimeException(e);
  81 + }
  82 + }
  83 +}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceAuthVertxHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  5 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO;
  6 +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
  7 +import io.vertx.core.Handler;
  8 +import io.vertx.core.json.JsonObject;
  9 +import io.vertx.ext.web.RoutingContext;
  10 +import lombok.RequiredArgsConstructor;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +/**
  14 + * IoT Emqx 连接认证的 Vert.x Handler
  15 + * <a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">...</a>
  16 + *
  17 + * @author haohao
  18 + */
  19 +@RequiredArgsConstructor
  20 +@Slf4j
  21 +public class IotDeviceAuthVertxHandler implements Handler<RoutingContext> {
  22 +
  23 + public static final String PATH = "/mqtt/auth";
  24 +
  25 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  26 +
  27 + @Override
  28 + @SuppressWarnings("unchecked")
  29 + public void handle(RoutingContext routingContext) {
  30 +
  31 + JsonObject json = routingContext.body().asJsonObject();
  32 + String clientId = json.getString("clientid");
  33 + String username = json.getString("username");
  34 + String password = json.getString("password");
  35 +
  36 + IotDeviceEmqxAuthReqDTO authReqDTO = buildDeviceEmqxAuthReqDTO(clientId, username, password);
  37 +
  38 + CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
  39 + if (authResult.getCode() != 0 || !authResult.getData()) {
  40 + denyAccess(routingContext);
  41 + return;
  42 + }
  43 + IotPluginCommonUtils.writeJson(routingContext, "{\"result\": \"allow\"}");
  44 + }
  45 +
  46 + private void denyAccess(RoutingContext routingContext) {
  47 + IotPluginCommonUtils.writeJson(routingContext, "{\"result\": \"deny\"}");
  48 + }
  49 +
  50 + private IotDeviceEmqxAuthReqDTO buildDeviceEmqxAuthReqDTO(String clientId, String username, String password) {
  51 + return new IotDeviceEmqxAuthReqDTO().setClientId(clientId).setUsername(username).setPassword(password);
  52 + }
  53 +
  54 +}
0 \ No newline at end of file 55 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml 0 → 100644
  1 +spring:
  2 + application:
  3 + name: yudao-module-iot-plugin-emqx
  4 +
  5 +yudao:
  6 + iot:
  7 + plugin:
  8 + common:
  9 + upstream-url: http://127.0.0.1:48080
  10 + downstream-port: 8100
  11 + plugin-key: yudao-module-iot-plugin-emqx
  12 + emqx:
  13 + host: 127.0.0.1
  14 + port: 1883
  15 + ssl: false
  16 + topics: "/sys/#"
  17 + auth-port: 8101
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDeviceEventReportVertxHandler.java
@@ -21,7 +21,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -21,7 +21,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
21 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 21 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
22 22
23 /** 23 /**
24 - * IoT 设备设备上报的 Vert.x Handler 24 + * IoT 设备事件上报的 Vert.x Handler
25 */ 25 */
26 @RequiredArgsConstructor 26 @RequiredArgsConstructor
27 @Slf4j 27 @Slf4j
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml
@@ -8,5 +8,6 @@ yudao: @@ -8,5 +8,6 @@ yudao:
8 common: 8 common:
9 upstream-url: http://127.0.0.1:48080 9 upstream-url: http://127.0.0.1:48080
10 downstream-port: 8093 10 downstream-port: 8093
  11 + plugin-key: yudao-module-iot-plugin-http
11 http: 12 http:
12 server-port: 8092 13 server-port: 8092