Parcourir la source

【代码新增】IoT:实现 device 下行服务调用的逻辑

YunaiV il y a 6 mois
Parent
commit
b454069897
17 fichiers modifiés avec 289 ajouts et 36 suppressions
  1. 14 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceServiceInvokeReqDTO.java
  2. 0 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java
  3. 2 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  4. 14 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.http
  5. 2 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
  6. 6 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java
  7. 6 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java
  8. 2 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamService.java
  9. 143 15
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java
  10. 9 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java
  11. 10 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
  12. 3 1
      yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogMapper.xml
  13. 18 0
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/IotDeviceDownstreamHandler.java
  14. 35 2
      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
  15. 13 0
      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
  16. 7 0
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/IotPluginHttpAutoConfiguration.java
  17. 5 11
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/downstream/IotDeviceDownstreamHandlerImpl.java

+ 14 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceServiceInvokeReqDTO.java

@@ -1,7 +1,10 @@
 package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
 
+import jakarta.validation.constraints.NotEmpty;
 import lombok.Data;
 
+import java.util.Map;
+
 /**
  * IoT 设备【服务】调用 Request DTO
  *
@@ -9,4 +12,15 @@ import lombok.Data;
  */
 @Data
 public class IotDeviceServiceInvokeReqDTO extends IotDeviceDownstreamAbstractReqDTO {
+
+    /**
+     * 服务标识
+     */
+    @NotEmpty(message = "服务标识不能为空")
+    private String identifier;
+    /**
+     * 调用参数
+     */
+    private Map<String, Object> params;
+
 }

+ 0 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java

@@ -19,7 +19,6 @@ public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO
     /**
      * 事件参数
      */
-    @NotEmpty(message = "事件参数不能为空")
     private Map<String, Object> params;
 
 }

+ 2 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -30,6 +30,7 @@ public interface ErrorCodeConstants {
     ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
     ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
     ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
+    ErrorCode DEVICE_DOWNSTREAM_FAILED = new ErrorCode(1_050_003_007, "执行失败,原因:{}");
 
     // ========== 产品分类 1-050-004-000 ==========
     ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
@@ -46,6 +47,6 @@ public interface ErrorCodeConstants {
     ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效");
 
     // ========== 插件实例 1-050-007-000 ==========
-    ErrorCode PLUGIN_INSTANCE_NOT_EXISTS = new ErrorCode(1_050_007_000, "插件实例不存在");
+
 
 }

+ 14 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.http

@@ -0,0 +1,14 @@
+### 请求 /iot/device/simulation-downstream 接口 => 成功
+POST {{baseUrl}}/iot/device/simulation-downstream
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+Authorization: Bearer {{token}}
+
+{
+  "id": 25,
+  "type": "service",
+  "identifier": "temperature",
+  "data": {
+    "xx": "yy"
+  }
+}

+ 2 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceSimulationDownstreamReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceSimulationUpstreamReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
@@ -171,7 +172,7 @@ public class IotDeviceController {
     @Operation(summary = "模拟设备下行")
     @PreAuthorize("@ss.hasPermission('iot:device:simulation')")
     public CommonResult<Boolean> simulationDownstreamDevice(
-            @Valid @RequestBody IotDeviceSimulationUpstreamReqVO downstreamReqVO) {
+            @Valid @RequestBody IotDeviceSimulationDownstreamReqVO downstreamReqVO) {
         deviceDownstreamService.simulationDeviceDownstream(downstreamReqVO);
         return success(true);
     }

+ 6 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceLogDO.java

@@ -75,6 +75,12 @@ public class IotDeviceLogDO {
      * 存储具体的消息数据内容,通常是 JSON 格式
      */
     private String content;
+    /**
+     * 响应码
+     *
+     * 目前只有 server 下行消息给 device 设备时,才会有响应码
+     */
+    private Integer code;
 
     /**
      * 上报时间戳

+ 6 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java

@@ -55,12 +55,16 @@ public class IotDeviceMessage {
      * 例如说:属性上报的 properties、事件上报的 params
      */
     private Object data;
+    /**
+     * 响应码
+     *
+     * 目前只有 server 下行消息给 device 设备时,才会有响应码
+     */
+    private Integer code;
 
     /**
      * 上报时间
      */
     private LocalDateTime reportTime;
 
-    // TODO @芋艿 code;
-
 }

+ 2 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamService.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.iot.service.device.control;
 
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceSimulationUpstreamReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceSimulationDownstreamReqVO;
 import jakarta.validation.Valid;
 
 /**
@@ -17,6 +17,6 @@ public interface IotDeviceDownstreamService {
      *
      * @param downstreamReqVO 设备下行请求 VO
      */
-    void simulationDeviceDownstream(@Valid IotDeviceSimulationUpstreamReqVO downstreamReqVO);
+    void simulationDeviceDownstream(@Valid IotDeviceSimulationDownstreamReqVO downstreamReqVO);
 
 }

+ 143 - 15
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java

@@ -1,17 +1,36 @@
 package cn.iocoder.yudao.module.iot.service.device.control;
 
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceSimulationUpstreamReqVO;
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.IdUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceDownstreamAbstractReqDTO;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceServiceInvokeReqDTO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceSimulationDownstreamReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
+import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.web.client.RestTemplate;
 
+import java.time.LocalDateTime;
+import java.util.Map;
 import java.util.Objects;
 
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_DOWNSTREAM_FAILED;
+
 /**
  * 设备下行 Service 实现类
  *
@@ -24,9 +43,17 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
 
     @Resource
     private IotDeviceService deviceService;
+    @Resource
+    private IotPluginInstanceService pluginInstanceService;
+
+    @Resource
+    private RestTemplate restTemplate;
+
+    @Resource
+    private IotDeviceProducer deviceProducer;
 
     @Override
-    public void simulationDeviceDownstream(IotDeviceSimulationUpstreamReqVO downstreamReqVO) {
+    public void simulationDeviceDownstream(IotDeviceSimulationDownstreamReqVO downstreamReqVO) {
         // 校验设备是否存在
         IotDeviceDO device = deviceService.validateDeviceExists(downstreamReqVO.getId());
         // TODO 芋艿:父设备的处理
@@ -40,12 +67,14 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
         // 属性相关
         if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType()))
             // 属性设置
-            if (Objects.equals(downstreamReqVO.getIdentifier(), IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier())) {
+            if (Objects.equals(downstreamReqVO.getIdentifier(),
+                    IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier())) {
                 setDeviceProperty(downstreamReqVO, device, parentDevice);
                 return;
             }
         // 属性设置
-        if (Objects.equals(downstreamReqVO.getIdentifier(), IotDeviceMessageIdentifierEnum.PROPERTY_GET.getIdentifier())) {
+        if (Objects.equals(downstreamReqVO.getIdentifier(),
+                IotDeviceMessageIdentifierEnum.PROPERTY_GET.getIdentifier())) {
             getDeviceProperty(downstreamReqVO, device, parentDevice);
             return;
         }
@@ -53,22 +82,121 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
         // TODO 芋艿:配置下发
     }
 
-    private void invokeDeviceService(IotDeviceSimulationUpstreamReqVO downstreamReqVO,
-                                     IotDeviceDO device, IotDeviceDO parentDevice) {
-        // 校验服务是否存在
-        // TODO 芋艿:这里需要校验服务是否存在
-        // 调用服务
-        // TODO 芋艿:这里需要调用服务
+    /**
+     * 调用设备服务
+     *
+     * @param downstreamReqVO 下行请求
+     * @param device          设备
+     * @param parentDevice    父设备
+     */
+    @SuppressWarnings("unchecked")
+    private void invokeDeviceService(IotDeviceSimulationDownstreamReqVO downstreamReqVO,
+            IotDeviceDO device, IotDeviceDO parentDevice) {
+        // 1. 参数校验
+        if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) {
+            throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 Map 类型");
+        }
+
+        // 2. 发送请求
+        String url = String.format( "sys/%s/%s/thing/service/%s",
+                getProductKey(device, parentDevice), getDeviceName(device, parentDevice), downstreamReqVO.getIdentifier());
+         IotDeviceServiceInvokeReqDTO reqDTO = new IotDeviceServiceInvokeReqDTO()
+                .setParams((Map<String, Object>) downstreamReqVO.getData());
+        CommonResult<Boolean> result = requestPlugin(url, reqDTO, device);
+
+        // 3. 发送设备消息
+        IotDeviceMessage message = new IotDeviceMessage().setRequestId(reqDTO.getRequestId())
+                .setType(IotDeviceMessageTypeEnum.SERVICE.getType()).setIdentifier(reqDTO.getIdentifier())
+                .setData(reqDTO.getParams());
+        sendDeviceMessage(message, device, result.getCode());
+
+        // 4. 如果不成功,抛出异常,提示用户
+        if (result.isError()) {
+            log.error("[invokeDeviceService][设备({})服务调用失败,请求参数:({}),响应结果:({})]",
+                    device.getDeviceKey(), reqDTO, result);
+            throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg());
+        }
     }
 
-    private void setDeviceProperty(IotDeviceSimulationUpstreamReqVO downstreamReqVO,
-                                   IotDeviceDO device, IotDeviceDO parentDevice) {
-        // TODO 芋艿:这里需要设置设备属性
+    /**
+     * 设置设备属性
+     *
+     * @param downstreamReqVO 下行请求
+     * @param device          设备
+     * @param parentDevice    父设备
+     */
+    private void setDeviceProperty(IotDeviceSimulationDownstreamReqVO downstreamReqVO,
+            IotDeviceDO device, IotDeviceDO parentDevice) {
+        // TODO 1. 请求
+
+        // TODO 2. 发送消息
     }
 
-    private void getDeviceProperty(IotDeviceSimulationUpstreamReqVO downstreamReqVO,
-                                   IotDeviceDO device, IotDeviceDO parentDevice) {
+    private void getDeviceProperty(IotDeviceSimulationDownstreamReqVO downstreamReqVO,
+            IotDeviceDO device, IotDeviceDO parentDevice) {
         // TODO 芋艿:这里需要获取设备属性
     }
 
+    /**
+     * 请求插件
+     *
+     * @param url     URL
+     * @param reqDTO  请求参数,只需要设置子类的参数!
+     * @param device  设备
+     * @return 响应结果
+     */
+    @SuppressWarnings({"unchecked", "HttpUrlsUsage"})
+    private CommonResult<Boolean> requestPlugin(String url, IotDeviceDownstreamAbstractReqDTO reqDTO, IotDeviceDO device) {
+        // 获得设备对应的插件实例
+        IotPluginInstanceDO pluginInstance = pluginInstanceService.getPluginInstanceByDeviceKey(device.getDeviceKey());
+        if (pluginInstance == null) {
+            throw exception(DEVICE_DOWNSTREAM_FAILED, "设备找不到对应的插件实例");
+        }
+
+        // 补充通用参数
+        reqDTO.setRequestId(IdUtil.fastSimpleUUID());
+
+        // 执行请求
+        ResponseEntity<CommonResult<Boolean>> responseEntity;
+        try {
+            responseEntity = restTemplate.postForEntity(
+                    String.format("http://%s:%d/%s", pluginInstance.getHostIp(), pluginInstance.getDownstreamPort(), url),
+                    reqDTO, (Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
+            Assert.isTrue(responseEntity.getStatusCode().is2xxSuccessful(),
+                    "HTTP 状态码不是 2xx,而是" + responseEntity.getStatusCode());
+            Assert.notNull(responseEntity.getBody(), "响应结果不能为空");
+        } catch (Exception ex) {
+            log.error("[requestPlugin][设备({}) url({}) 下行消息失败,请求参数({})]", device.getDeviceKey(), url, reqDTO, ex);
+            throw exception(DEVICE_DOWNSTREAM_FAILED, ExceptionUtil.getMessage(ex));
+        }
+        return responseEntity.getBody();
+    }
+
+    private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device, Integer code) {
+        // 1. 完善消息
+        message.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())
+                .setDeviceKey(device.getDeviceKey());
+        Assert.notNull(message.getRequestId(), "requestId 不能为空");
+        if (message.getReportTime() == null) {
+            message.setReportTime(LocalDateTime.now());
+        }
+        message.setCode(code);
+
+        // 2. 发送消息
+        try {
+            deviceProducer.sendDeviceMessage(message);
+            log.info("[sendDeviceMessage][message({}) 发送消息成功]", message);
+        } catch (Exception e) {
+            log.error("[sendDeviceMessage][message({}) 发送消息失败]", message, e);
+        }
+    }
+
+    private String getDeviceName(IotDeviceDO device, IotDeviceDO parentDevice) {
+        return parentDevice != null ? parentDevice.getDeviceName() : device.getDeviceName();
+    }
+
+    private String getProductKey(IotDeviceDO device, IotDeviceDO parentDevice) {
+        return parentDevice != null ? parentDevice.getProductKey() : device.getProductKey();
+    }
+
 }

+ 9 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.plugin;
 
 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.time.LocalDateTime;
@@ -67,4 +68,12 @@ public interface IotPluginInstanceService {
      */
     void updateDevicePluginInstanceProcessIdAsync(String deviceKey, String processId);
 
+    /**
+     * 获得设备对应的插件实例
+     *
+     * @param deviceKey 设备 Key
+     * @return 插件实例
+     */
+    IotPluginInstanceDO getPluginInstanceByDeviceKey(String deviceKey);
+
 }

+ 10 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.plugin;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
@@ -203,4 +204,13 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
         devicePluginProcessIdRedisDAO.put(deviceKey, processId);
     }
 
+    @Override
+    public IotPluginInstanceDO getPluginInstanceByDeviceKey(String deviceKey) {
+        String processId = devicePluginProcessIdRedisDAO.get(deviceKey);
+        if (StrUtil.isEmpty(processId)) {
+            return null;
+        }
+        return pluginInstanceMapper.selectByProcessId(processId);
+    }
+
 }

+ 3 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceLogMapper.xml

@@ -13,6 +13,7 @@
                 type NCHAR(50),
                 identifier NCHAR(255),
                 content NCHAR(1024),
+                code INT,
                 report_time TIMESTAMP
             ) TAGS (
                 device_key NCHAR(50)
@@ -24,7 +25,7 @@
     </select>
 
     <insert id="insert">
-        INSERT INTO device_log_${deviceKey} (ts, id, product_key, device_name, type, identifier, content, report_time)
+        INSERT INTO device_log_${deviceKey} (ts, id, product_key, device_name, type, identifier, content, code, report_time)
         USING device_log
         TAGS ('${deviceKey}')
         VALUES (
@@ -35,6 +36,7 @@
             #{type},
             #{identifier},
             #{content},
+            #{code},
             #{reportTime}
         )
     </insert>

+ 18 - 0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/IotDeviceDownstreamHandler.java

@@ -14,10 +14,28 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceSe
  */
 public interface IotDeviceDownstreamHandler {
 
+    /**
+     * 调用设备服务
+     *
+     * @param invokeReqDTO 调用设备服务的请求
+     * @return 是否成功
+     */
     CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO);
 
+    /**
+     * 获取设备属性
+     *
+     * @param getReqDTO 获取设备属性的请求
+     * @return 是否成功
+     */
     CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO);
 
+    /**
+     * 设置设备属性
+     *
+     * @param setReqDTO 设置设备属性的请求
+     * @return 是否成功
+     */
     CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO);
 
 }

+ 35 - 2
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

@@ -1,10 +1,18 @@
 package cn.iocoder.yudao.module.iot.plugin.common.downstream.router;
 
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceServiceInvokeReqDTO;
 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
 import io.vertx.core.Handler;
 import io.vertx.ext.web.RoutingContext;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import io.vertx.core.json.JsonObject;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -15,9 +23,34 @@ public class IotDeviceServiceInvokeVertxHandler implements Handler<RoutingContex
     private final IotDeviceDownstreamHandler deviceDownstreamHandler;
 
     @Override
+    @SuppressWarnings("unchecked")
     public void handle(RoutingContext routingContext) {
-        // TODO 芋艿:这里没实现
-        deviceDownstreamHandler.invokeDeviceService(null);
+        // 1. 解析参数
+        IotDeviceServiceInvokeReqDTO invokeReqDTO;
+        try {
+            String productKey = routingContext.pathParam("productKey");
+            String deviceName = routingContext.pathParam("deviceName");
+            String identifier = routingContext.pathParam("identifier");
+            JsonObject body = routingContext.body().asJsonObject();
+            String requestId = body.getString("requestId");
+            Map<String, Object> params = (Map<String, Object>) body.getMap().get("params");
+            invokeReqDTO = ((IotDeviceServiceInvokeReqDTO) new IotDeviceServiceInvokeReqDTO()
+                    .setRequestId(requestId).setProductKey(productKey).setDeviceName(deviceName))
+                    .setIdentifier(identifier).setParams(params);
+        } catch (Exception e) {
+            log.error("[handle][解析参数失败]", e);
+            IotPluginCommonUtils.writeJson(routingContext, CommonResult.error(BAD_REQUEST));
+            return;
+        }
+
+        // 2. 调用下游处理器
+        try {
+            CommonResult<Boolean> result = deviceDownstreamHandler.invokeDeviceService(invokeReqDTO);
+            IotPluginCommonUtils.writeJson(routingContext, result);
+        } catch (Exception e) {
+            log.error("[handle][请求参数({}) 服务调用异常]", invokeReqDTO, e);
+            IotPluginCommonUtils.writeJson(routingContext, CommonResult.error(INTERNAL_SERVER_ERROR));
+        }
     }
 
 }

+ 13 - 0
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

@@ -3,6 +3,11 @@ package cn.iocoder.yudao.module.iot.plugin.common.util;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.system.SystemUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.RoutingContext;
+import org.springframework.http.MediaType;
 
 /**
  * IoT 插件的通用工具类
@@ -28,4 +33,12 @@ public class IotPluginCommonUtils {
                 SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID(), IdUtil.fastSimpleUUID());
     }
 
+    @SuppressWarnings("deprecation")
+    public static void writeJson(RoutingContext routingContext, CommonResult<?> result) {
+        routingContext.response()
+                .setStatusCode(200)
+                .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
+                .end(JsonUtils.toJsonString(result));
+    }
+
 }

+ 7 - 0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/IotPluginHttpAutoConfiguration.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.iot.plugin.http.config;
 
 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
+import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.plugin.http.downstream.IotDeviceDownstreamHandlerImpl;
 import cn.iocoder.yudao.module.iot.plugin.http.upstream.IotDeviceUpstreamServer;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
@@ -26,4 +28,9 @@ public class IotPluginHttpAutoConfiguration {
         return new IotDeviceUpstreamServer(port, deviceUpstreamApi);
     }
 
+    @Bean
+    public IotDeviceDownstreamHandler deviceDownstreamHandler() {
+        return new IotDeviceDownstreamHandlerImpl();
+    }
+
 }

+ 5 - 11
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/downstream/IotDeviceDownstreamHandlerImpl.java

@@ -5,7 +5,8 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDevicePr
 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDevicePropertySetReqDTO;
 import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceServiceInvokeReqDTO;
 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
-import org.springframework.stereotype.Component;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
 
 /**
  * HTTP 插件的 {@link IotDeviceDownstreamHandler} 实现类
@@ -15,28 +16,21 @@ import org.springframework.stereotype.Component;
  *
  * @author 芋道源码
  */
-@Component // TODO @芋艿:后续统一处理
 public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
 
     @Override
     public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
-        // TODO @芋艿:待实现
-        System.out.println();
-        return null;
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持调用设备服务");
     }
 
     @Override
     public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
-        // TODO @芋艿:待实现
-        System.out.println();
-        return null;
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持获取设备属性");
     }
 
     @Override
     public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
-        // TODO @芋艿:待实现
-        System.out.println();
-        return null;
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
     }
 
 }