Commit 8865481b2e9d2357998b09196510fc174d662299
1 parent
6e9b58cb
refactor(iot): 设备影子改回标准 IotDeviceMessage 封套,移除透传链路
- 上行/下行统一走 Serializer 标准封套,删除 passthrough 与 encodeRaw/decodeRaw - 删除 IotDeviceShadowReplyDTO,回复改用 replyOf + ProtocolUtils.buildGetData/buildUpdateData - 下行回复走 thing/shadow/update_reply,移除 SHADOW_GET - 同步调整 biz/gateway 测试
Showing
18 changed files
with
156 additions
and
544 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/shadow/IotDeviceShadowServiceImpl.java
| @@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | @@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 8 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 9 | import cn.iocoder.yudao.module.iot.core.enums.shadow.IotDeviceShadowErrorCodeEnum; | 9 | import cn.iocoder.yudao.module.iot.core.enums.shadow.IotDeviceShadowErrorCodeEnum; |
| 10 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 10 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 11 | -import cn.iocoder.yudao.module.iot.core.topic.shadow.IotDeviceShadowReplyDTO; | ||
| 12 | import cn.iocoder.yudao.module.iot.core.topic.shadow.IotDeviceShadowUpdateReqDTO; | 11 | import cn.iocoder.yudao.module.iot.core.topic.shadow.IotDeviceShadowUpdateReqDTO; |
| 13 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils; | 12 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils; |
| 14 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 13 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| @@ -31,8 +30,10 @@ import java.util.HashMap; | @@ -31,8 +30,10 @@ import java.util.HashMap; | ||
| 31 | import java.util.Map; | 30 | import java.util.Map; |
| 32 | import java.util.function.Function; | 31 | import java.util.function.Function; |
| 33 | 32 | ||
| 33 | +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS; | ||
| 34 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 34 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 35 | -import static cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils.*; | 35 | +import static cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils.INNER_METHOD_GET; |
| 36 | +import static cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils.INNER_METHOD_UPDATE; | ||
| 36 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_SHADOW_NOT_ENABLED; | 37 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_SHADOW_NOT_ENABLED; |
| 37 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_SHADOW_VERSION_CONFLICT; | 38 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_SHADOW_VERSION_CONFLICT; |
| 38 | 39 | ||
| @@ -88,29 +89,23 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -88,29 +89,23 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 88 | } catch (Exception ex) { | 89 | } catch (Exception ex) { |
| 89 | log.error("[handleDeviceShadowUpdate][device({}) message({}) 处理异常]", device.getId(), message, ex); | 90 | log.error("[handleDeviceShadowUpdate][device({}) message({}) 处理异常]", device.getId(), message, ex); |
| 90 | Long version = resolveShadowVersionSafely(device.getId()); | 91 | Long version = resolveShadowVersionSafely(device.getId()); |
| 91 | - sendShadowReplySafely(device, message.getServerId(), IotDeviceShadowReplyDTO.errorOf( | ||
| 92 | - extractInnerMethod(message.getParams()), version, | ||
| 93 | - IotDeviceShadowErrorCodeEnum.SERVER_ERROR.getCode(), | ||
| 94 | - IotDeviceShadowErrorCodeEnum.SERVER_ERROR.getMessage(), DateUtil.currentSeconds())); | 92 | + sendShadowErrorReply(device, message.getServerId(), message.getRequestId(), |
| 93 | + IotDeviceShadowErrorCodeEnum.SERVER_ERROR, version); | ||
| 95 | } | 94 | } |
| 96 | } | 95 | } |
| 97 | 96 | ||
| 98 | private void handleDeviceShadowUpdate0(IotDeviceDO device, IotDeviceMessage message) { | 97 | private void handleDeviceShadowUpdate0(IotDeviceDO device, IotDeviceMessage message) { |
| 99 | - // 1. 解析请求(影子 body 当前按 JSON 建模,非 JSON 设备在 gateway 透传层已拒绝) | 98 | + // 1. 解析请求(影子 params 按 JSON 建模,设备须配置 serializeType=JSON) |
| 100 | IotDeviceShadowUpdateReqDTO req = JsonUtils.convertObject(message.getParams(), | 99 | IotDeviceShadowUpdateReqDTO req = JsonUtils.convertObject(message.getParams(), |
| 101 | IotDeviceShadowUpdateReqDTO.class); | 100 | IotDeviceShadowUpdateReqDTO.class); |
| 102 | if (req == null) { | 101 | if (req == null) { |
| 103 | - sendShadowReplySafely(device, message.getServerId(), IotDeviceShadowReplyDTO.errorOf( | ||
| 104 | - extractInnerMethod(message.getParams()), 0L, | ||
| 105 | - IotDeviceShadowErrorCodeEnum.JSON_FORMAT_ERROR.getCode(), | ||
| 106 | - IotDeviceShadowErrorCodeEnum.JSON_FORMAT_ERROR.getMessage(), DateUtil.currentSeconds())); | 102 | + sendShadowErrorReply(device, message.getServerId(), message.getRequestId(), |
| 103 | + IotDeviceShadowErrorCodeEnum.JSON_FORMAT_ERROR, 0L); | ||
| 107 | return; | 104 | return; |
| 108 | } | 105 | } |
| 109 | if (StrUtil.isEmpty(req.getMethod())) { | 106 | if (StrUtil.isEmpty(req.getMethod())) { |
| 110 | - sendShadowReplySafely(device, message.getServerId(), IotDeviceShadowReplyDTO.errorOf( | ||
| 111 | - INNER_METHOD_UPDATE, resolveShadowVersionSafely(device.getId()), | ||
| 112 | - IotDeviceShadowErrorCodeEnum.METHOD_MISSING.getCode(), | ||
| 113 | - IotDeviceShadowErrorCodeEnum.METHOD_MISSING.getMessage(), DateUtil.currentSeconds())); | 107 | + sendShadowErrorReply(device, message.getServerId(), message.getRequestId(), |
| 108 | + IotDeviceShadowErrorCodeEnum.METHOD_MISSING, resolveShadowVersionSafely(device.getId())); | ||
| 114 | return; | 109 | return; |
| 115 | } | 110 | } |
| 116 | 111 | ||
| @@ -123,42 +118,35 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -123,42 +118,35 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 123 | 118 | ||
| 124 | // 3. 内层 method 分发 | 119 | // 3. 内层 method 分发 |
| 125 | if (INNER_METHOD_GET.equals(req.getMethod())) { | 120 | if (INNER_METHOD_GET.equals(req.getMethod())) { |
| 126 | - sendShadowReplySafely(device, message.getServerId(), IotDeviceShadowReplyDTO.getReplyOf( | ||
| 127 | - shadow.getDesired(), shadow.getReported(), shadow.getVersion(), shadow.getShadowTime())); | 121 | + sendShadowGetReply(device, message.getServerId(), message.getRequestId(), |
| 122 | + shadow.getDesired(), shadow.getReported(), shadow.getVersion(), shadow.getShadowTime()); | ||
| 128 | return; | 123 | return; |
| 129 | } | 124 | } |
| 130 | if (INNER_METHOD_UPDATE.equals(req.getMethod())) { | 125 | if (INNER_METHOD_UPDATE.equals(req.getMethod())) { |
| 131 | - handleReportedUpdate(device, message.getServerId(), req); | 126 | + handleReportedUpdate(device, message.getServerId(), message.getRequestId(), req); |
| 132 | return; | 127 | return; |
| 133 | } | 128 | } |
| 134 | - sendShadowReplySafely(device, message.getServerId(), IotDeviceShadowReplyDTO.errorOf( | ||
| 135 | - req.getMethod(), shadow.getVersion(), | ||
| 136 | - IotDeviceShadowErrorCodeEnum.INVALID_METHOD.getCode(), | ||
| 137 | - IotDeviceShadowErrorCodeEnum.INVALID_METHOD.getMessage(), DateUtil.currentSeconds())); | 129 | + sendShadowErrorReply(device, message.getServerId(), message.getRequestId(), |
| 130 | + IotDeviceShadowErrorCodeEnum.INVALID_METHOD, shadow.getVersion()); | ||
| 138 | } | 131 | } |
| 139 | 132 | ||
| 140 | - private void handleReportedUpdate(IotDeviceDO device, String serverId, IotDeviceShadowUpdateReqDTO req) { | 133 | + private void handleReportedUpdate(IotDeviceDO device, String serverId, String requestId, |
| 134 | + IotDeviceShadowUpdateReqDTO req) { | ||
| 141 | Long deviceId = device.getId(); | 135 | Long deviceId = device.getId(); |
| 142 | 136 | ||
| 143 | if (req.getState() == null) { | 137 | if (req.getState() == null) { |
| 144 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.errorOf( | ||
| 145 | - INNER_METHOD_UPDATE, resolveShadowVersionSafely(deviceId), | ||
| 146 | - IotDeviceShadowErrorCodeEnum.STATE_MISSING.getCode(), | ||
| 147 | - IotDeviceShadowErrorCodeEnum.STATE_MISSING.getMessage(), DateUtil.currentSeconds())); | 138 | + sendShadowErrorReply(device, serverId, requestId, |
| 139 | + IotDeviceShadowErrorCodeEnum.STATE_MISSING, resolveShadowVersionSafely(deviceId)); | ||
| 148 | return; | 140 | return; |
| 149 | } | 141 | } |
| 150 | if (req.getState().getReported() == null) { | 142 | if (req.getState().getReported() == null) { |
| 151 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.errorOf( | ||
| 152 | - INNER_METHOD_UPDATE, resolveShadowVersionSafely(deviceId), | ||
| 153 | - IotDeviceShadowErrorCodeEnum.REPORTED_MISSING.getCode(), | ||
| 154 | - IotDeviceShadowErrorCodeEnum.REPORTED_MISSING.getMessage(), DateUtil.currentSeconds())); | 143 | + sendShadowErrorReply(device, serverId, requestId, |
| 144 | + IotDeviceShadowErrorCodeEnum.REPORTED_MISSING, resolveShadowVersionSafely(deviceId)); | ||
| 155 | return; | 145 | return; |
| 156 | } | 146 | } |
| 157 | if (CollUtil.isEmpty(req.getState().getReported())) { | 147 | if (CollUtil.isEmpty(req.getState().getReported())) { |
| 158 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.errorOf( | ||
| 159 | - INNER_METHOD_UPDATE, resolveShadowVersionSafely(deviceId), | ||
| 160 | - IotDeviceShadowErrorCodeEnum.REPORTED_EMPTY.getCode(), | ||
| 161 | - IotDeviceShadowErrorCodeEnum.REPORTED_EMPTY.getMessage(), DateUtil.currentSeconds())); | 148 | + sendShadowErrorReply(device, serverId, requestId, |
| 149 | + IotDeviceShadowErrorCodeEnum.REPORTED_EMPTY, resolveShadowVersionSafely(deviceId)); | ||
| 162 | return; | 150 | return; |
| 163 | } | 151 | } |
| 164 | 152 | ||
| @@ -166,24 +154,20 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -166,24 +154,20 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 166 | if (req.getVersion() != null && req.getVersion() == -1L) { | 154 | if (req.getVersion() != null && req.getVersion() == -1L) { |
| 167 | try { | 155 | try { |
| 168 | resetShadowWithRetry(deviceId); | 156 | resetShadowWithRetry(deviceId); |
| 169 | - sendShadowReplySafely(device, serverId, | ||
| 170 | - IotDeviceShadowReplyDTO.updateReplyOf(0L, DateUtil.currentSeconds())); | 157 | + sendShadowUpdateReply(device, serverId, requestId, 0L, DateUtil.currentSeconds()); |
| 171 | } catch (ShadowCasExhaustedException ex) { | 158 | } catch (ShadowCasExhaustedException ex) { |
| 172 | - handleCasExhaustedForDevice(device, serverId, ex); | 159 | + handleCasExhaustedForDevice(device, serverId, requestId, ex); |
| 173 | } | 160 | } |
| 174 | return; | 161 | return; |
| 175 | } | 162 | } |
| 176 | 163 | ||
| 177 | try { | 164 | try { |
| 178 | IotDeviceShadowDO committed = casUpdateShadow(deviceId, latest -> buildReportedUpdate(latest, req)); | 165 | IotDeviceShadowDO committed = casUpdateShadow(deviceId, latest -> buildReportedUpdate(latest, req)); |
| 179 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.updateReplyOf(committed.getVersion(), | ||
| 180 | - committed.getShadowTime())); | 166 | + sendShadowUpdateReply(device, serverId, requestId, committed.getVersion(), committed.getShadowTime()); |
| 181 | } catch (ShadowUpdateRejectedException ex) { | 167 | } catch (ShadowUpdateRejectedException ex) { |
| 182 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.errorOf( | ||
| 183 | - INNER_METHOD_UPDATE, ex.getVersion(), | ||
| 184 | - ex.getErrorCode().getCode(), ex.getErrorCode().getMessage(), DateUtil.currentSeconds())); | 168 | + sendShadowErrorReply(device, serverId, requestId, ex.getErrorCode(), ex.getVersion()); |
| 185 | } catch (ShadowCasExhaustedException ex) { | 169 | } catch (ShadowCasExhaustedException ex) { |
| 186 | - handleCasExhaustedForDevice(device, serverId, ex); | 170 | + handleCasExhaustedForDevice(device, serverId, requestId, ex); |
| 187 | } | 171 | } |
| 188 | } | 172 | } |
| 189 | 173 | ||
| @@ -203,9 +187,9 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -203,9 +187,9 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 203 | // 设备在线则主动推送全量影子(复用 CAS 返回值,避免二次查库) | 187 | // 设备在线则主动推送全量影子(复用 CAS 返回值,避免二次查库) |
| 204 | String serverId = devicePropertyService.getDeviceServerId(deviceId); | 188 | String serverId = devicePropertyService.getDeviceServerId(deviceId); |
| 205 | if (StrUtil.isNotEmpty(serverId)) { | 189 | if (StrUtil.isNotEmpty(serverId)) { |
| 206 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.getReplyOf( | 190 | + sendShadowGetReply(device, serverId, null, |
| 207 | committed.getDesired(), committed.getReported(), | 191 | committed.getDesired(), committed.getReported(), |
| 208 | - committed.getVersion(), committed.getShadowTime())); | 192 | + committed.getVersion(), committed.getShadowTime()); |
| 209 | } | 193 | } |
| 210 | } catch (ShadowCasExhaustedException ex) { | 194 | } catch (ShadowCasExhaustedException ex) { |
| 211 | throw exception(DEVICE_SHADOW_VERSION_CONFLICT); | 195 | throw exception(DEVICE_SHADOW_VERSION_CONFLICT); |
| @@ -337,7 +321,7 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -337,7 +321,7 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 337 | } | 321 | } |
| 338 | 322 | ||
| 339 | /** | 323 | /** |
| 340 | - * 将 CAS 成功写入的局部字段与 latest 快照合并,供全量 get_reply 使用 | 324 | + * 将 CAS 成功写入的局部字段与 latest 快照合并,供全量 get 回复使用 |
| 341 | */ | 325 | */ |
| 342 | private static void enrichCommittedShadow(IotDeviceShadowDO committed, IotDeviceShadowDO latest) { | 326 | private static void enrichCommittedShadow(IotDeviceShadowDO committed, IotDeviceShadowDO latest) { |
| 343 | if (committed.getDesired() == null) { | 327 | if (committed.getDesired() == null) { |
| @@ -351,22 +335,19 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -351,22 +335,19 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 351 | } | 335 | } |
| 352 | } | 336 | } |
| 353 | 337 | ||
| 354 | - private void handleCasExhaustedForDevice(IotDeviceDO device, String serverId, ShadowCasExhaustedException ex) { | 338 | + private void handleCasExhaustedForDevice(IotDeviceDO device, String serverId, String requestId, |
| 339 | + ShadowCasExhaustedException ex) { | ||
| 355 | if (ex.isShadowNotFound()) { | 340 | if (ex.isShadowNotFound()) { |
| 356 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.errorOf( | ||
| 357 | - INNER_METHOD_UPDATE, 0L, | ||
| 358 | - IotDeviceShadowErrorCodeEnum.SERVER_ERROR.getCode(), | ||
| 359 | - IotDeviceShadowErrorCodeEnum.SERVER_ERROR.getMessage(), DateUtil.currentSeconds())); | 341 | + sendShadowErrorReply(device, serverId, requestId, |
| 342 | + IotDeviceShadowErrorCodeEnum.SERVER_ERROR, 0L); | ||
| 360 | return; | 343 | return; |
| 361 | } | 344 | } |
| 362 | - sendVersionConflictReply(device, serverId, ex.getLastKnownVersion()); | 345 | + sendVersionConflictReply(device, serverId, requestId, ex.getLastKnownVersion()); |
| 363 | } | 346 | } |
| 364 | 347 | ||
| 365 | - private void sendVersionConflictReply(IotDeviceDO device, String serverId, Long version) { | ||
| 366 | - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.errorOf( | ||
| 367 | - INNER_METHOD_UPDATE, version, | ||
| 368 | - IotDeviceShadowErrorCodeEnum.VERSION_CONFLICT.getCode(), | ||
| 369 | - IotDeviceShadowErrorCodeEnum.VERSION_CONFLICT.getMessage(), DateUtil.currentSeconds())); | 348 | + private void sendVersionConflictReply(IotDeviceDO device, String serverId, String requestId, Long version) { |
| 349 | + sendShadowErrorReply(device, serverId, requestId, | ||
| 350 | + IotDeviceShadowErrorCodeEnum.VERSION_CONFLICT, version); | ||
| 370 | } | 351 | } |
| 371 | 352 | ||
| 372 | private Long resolveShadowVersionSafely(Long deviceId) { | 353 | private Long resolveShadowVersionSafely(Long deviceId) { |
| @@ -407,15 +388,41 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | @@ -407,15 +388,41 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService { | ||
| 407 | return shadow; | 388 | return shadow; |
| 408 | } | 389 | } |
| 409 | 390 | ||
| 391 | + private void sendShadowGetReply(IotDeviceDO device, String serverId, String requestId, | ||
| 392 | + Map<String, Object> desired, Map<String, Object> reported, Long version, Long timestamp) { | ||
| 393 | + sendShadowReplySafely(device, serverId, requestId, SUCCESS.getCode(), SUCCESS.getMsg(), | ||
| 394 | + IotDeviceShadowProtocolUtils.buildGetData(desired, reported, version, timestamp)); | ||
| 395 | + } | ||
| 396 | + | ||
| 397 | + private void sendShadowUpdateReply(IotDeviceDO device, String serverId, String requestId, | ||
| 398 | + Long version, Long timestamp) { | ||
| 399 | + sendShadowReplySafely(device, serverId, requestId, SUCCESS.getCode(), SUCCESS.getMsg(), | ||
| 400 | + IotDeviceShadowProtocolUtils.buildUpdateData(version, timestamp)); | ||
| 401 | + } | ||
| 402 | + | ||
| 403 | + /** | ||
| 404 | + * 错误回复:data 仍带 version 供设备 resync(有意偏离纯 CommonResult 的 error-data-null 约定) | ||
| 405 | + */ | ||
| 406 | + private void sendShadowErrorReply(IotDeviceDO device, String serverId, String requestId, | ||
| 407 | + IotDeviceShadowErrorCodeEnum errorCode, Long version) { | ||
| 408 | + sendShadowReplySafely(device, serverId, requestId, errorCode.getCode(), errorCode.getMessage(), | ||
| 409 | + IotDeviceShadowProtocolUtils.buildUpdateData(version, DateUtil.currentSeconds())); | ||
| 410 | + } | ||
| 411 | + | ||
| 410 | /** | 412 | /** |
| 411 | - * 下发影子回复,内部吞掉异常,保证上行处理链路不向上抛 | 413 | + * 下发影子回复(标准 IotDeviceMessage 封套),内部吞掉异常,保证上行处理链路不向上抛 |
| 414 | + * <p> | ||
| 415 | + * 回复 method 取上行的 {@link IotDeviceMessageMethodEnum#SHADOW_UPDATE}(与框架"回复用原请求 method"一致): | ||
| 416 | + * 带 code 的回复经 IotDeviceMessageUtils#isUpstreamMessage 取反 SHADOW_UPDATE.upstream(true) → 判定为下行, | ||
| 417 | + * 正确投递到设备;下行 Topic 为 thing/shadow/update_reply。 | ||
| 412 | */ | 418 | */ |
| 413 | - private void sendShadowReplySafely(IotDeviceDO device, String serverId, IotDeviceShadowReplyDTO replyDTO) { | ||
| 414 | - IotDeviceMessage shadowGetMsg = IotDeviceMessage.requestOf( | ||
| 415 | - device.getId(), device.getTenantId(), serverId, | ||
| 416 | - IotDeviceMessageMethodEnum.SHADOW_GET.getMethod(), replyDTO); | 419 | + private void sendShadowReplySafely(IotDeviceDO device, String serverId, String requestId, |
| 420 | + Integer code, String msg, Map<String, Object> data) { | ||
| 421 | + IotDeviceMessage shadowReplyMsg = IotDeviceMessage.replyOf( | ||
| 422 | + requestId, IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), data, code, msg); | ||
| 423 | + shadowReplyMsg.setDeviceId(device.getId()).setTenantId(device.getTenantId()).setServerId(serverId); | ||
| 417 | try { | 424 | try { |
| 418 | - deviceMessageService.sendDeviceMessage(shadowGetMsg, device, serverId); | 425 | + deviceMessageService.sendDeviceMessage(shadowReplyMsg, device, serverId); |
| 419 | } catch (Exception ex) { | 426 | } catch (Exception ex) { |
| 420 | log.error("[sendShadowReply][device({}) 下发影子回复失败]", device.getId(), ex); | 427 | log.error("[sendShadowReply][device({}) 下发影子回复失败]", device.getId(), ex); |
| 421 | } | 428 | } |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/shadow/IotDeviceShadowServiceImplTest.java
| @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; | @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||
| 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | 5 | import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; |
| 6 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 6 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 8 | -import cn.iocoder.yudao.module.iot.core.topic.shadow.IotDeviceShadowReplyDTO; | ||
| 9 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 8 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 10 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceShadowDO; | 9 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceShadowDO; |
| 11 | import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceShadowMapper; | 10 | import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceShadowMapper; |
| @@ -76,15 +75,18 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -76,15 +75,18 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 76 | 75 | ||
| 77 | Map<String, Object> params = new HashMap<>(); | 76 | Map<String, Object> params = new HashMap<>(); |
| 78 | params.put("method", "get"); | 77 | params.put("method", "get"); |
| 79 | - IotDeviceMessage message = IotDeviceMessage.requestOf(device.getId(), 1L, "server-1", | 78 | + IotDeviceMessage message = IotDeviceMessage.requestOf("req-get-1", |
| 80 | IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), params); | 79 | IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), params); |
| 80 | + message.setDeviceId(device.getId()).setTenantId(1L).setServerId("server-1"); | ||
| 81 | 81 | ||
| 82 | service.handleDeviceShadowUpdate(device, message); | 82 | service.handleDeviceShadowUpdate(device, message); |
| 83 | 83 | ||
| 84 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 84 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 85 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 85 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 86 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | ||
| 87 | - assertEquals("get_reply", reply.getMethod()); | 86 | + IotDeviceMessage reply = captor.getValue(); |
| 87 | + assertEquals(IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), reply.getMethod()); | ||
| 88 | + assertEquals("req-get-1", reply.getRequestId()); | ||
| 89 | + assertEquals(0, reply.getCode()); | ||
| 88 | assertEquals(2L, getReplyVersion(reply)); | 90 | assertEquals(2L, getReplyVersion(reply)); |
| 89 | } | 91 | } |
| 90 | 92 | ||
| @@ -141,7 +143,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -141,7 +143,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 141 | 143 | ||
| 142 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 144 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 143 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 145 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 144 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | 146 | + IotDeviceMessage reply = captor.getValue(); |
| 147 | + assertEquals(IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), reply.getMethod()); | ||
| 145 | assertEquals(409, reply.getCode()); | 148 | assertEquals(409, reply.getCode()); |
| 146 | verify(deviceShadowMapper, never()).updateByDeviceIdAndVersion(any(), any(), any()); | 149 | verify(deviceShadowMapper, never()).updateByDeviceIdAndVersion(any(), any(), any()); |
| 147 | } | 150 | } |
| @@ -192,8 +195,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -192,8 +195,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 192 | 195 | ||
| 193 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 196 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 194 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 197 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 195 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | ||
| 196 | - assertEquals("update_reply", reply.getMethod()); | 198 | + IotDeviceMessage reply = captor.getValue(); |
| 199 | + assertEquals(IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), reply.getMethod()); | ||
| 197 | assertEquals(409, reply.getCode()); | 200 | assertEquals(409, reply.getCode()); |
| 198 | verify(deviceShadowMapper, never()).updateByDeviceIdAndVersion(any(), any(), any()); | 201 | verify(deviceShadowMapper, never()).updateByDeviceIdAndVersion(any(), any(), any()); |
| 199 | } | 202 | } |
| @@ -262,8 +265,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -262,8 +265,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 262 | 265 | ||
| 263 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 266 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 264 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 267 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 265 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | ||
| 266 | - assertEquals("get_reply", reply.getMethod()); | 268 | + IotDeviceMessage reply = captor.getValue(); |
| 269 | + assertEquals(IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), reply.getMethod()); | ||
| 267 | assertEquals(2L, getReplyVersion(reply)); | 270 | assertEquals(2L, getReplyVersion(reply)); |
| 268 | // getOrCreateShadow + CAS 各一次 SELECT,推送不再额外查库 | 271 | // getOrCreateShadow + CAS 各一次 SELECT,推送不再额外查库 |
| 269 | verify(deviceShadowMapper, times(2)).selectByDeviceId(deviceId); | 272 | verify(deviceShadowMapper, times(2)).selectByDeviceId(deviceId); |
| @@ -299,7 +302,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -299,7 +302,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 299 | 302 | ||
| 300 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 303 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 301 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 304 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 302 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | 305 | + IotDeviceMessage reply = captor.getValue(); |
| 306 | + assertEquals(IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), reply.getMethod()); | ||
| 303 | assertEquals(400, reply.getCode()); | 307 | assertEquals(400, reply.getCode()); |
| 304 | } | 308 | } |
| 305 | 309 | ||
| @@ -319,7 +323,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -319,7 +323,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 319 | 323 | ||
| 320 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 324 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 321 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 325 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 322 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | 326 | + IotDeviceMessage reply = captor.getValue(); |
| 323 | assertEquals(401, reply.getCode()); | 327 | assertEquals(401, reply.getCode()); |
| 324 | } | 328 | } |
| 325 | 329 | ||
| @@ -375,7 +379,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -375,7 +379,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 375 | 379 | ||
| 376 | ArgumentCaptor<IotDeviceMessage> replyCaptor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 380 | ArgumentCaptor<IotDeviceMessage> replyCaptor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 377 | verify(deviceMessageService).sendDeviceMessage(replyCaptor.capture(), eq(device), eq("server-1")); | 381 | verify(deviceMessageService).sendDeviceMessage(replyCaptor.capture(), eq(device), eq("server-1")); |
| 378 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) replyCaptor.getValue().getParams(); | 382 | + IotDeviceMessage reply = replyCaptor.getValue(); |
| 379 | assertEquals(3L, getReplyVersion(reply)); | 383 | assertEquals(3L, getReplyVersion(reply)); |
| 380 | } | 384 | } |
| 381 | 385 | ||
| @@ -439,7 +443,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -439,7 +443,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 439 | 443 | ||
| 440 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 444 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 441 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 445 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 442 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | 446 | + IotDeviceMessage reply = captor.getValue(); |
| 443 | assertEquals(0L, getReplyVersion(reply)); | 447 | assertEquals(0L, getReplyVersion(reply)); |
| 444 | assertEquals(500, reply.getCode()); | 448 | assertEquals(500, reply.getCode()); |
| 445 | } | 449 | } |
| @@ -466,7 +470,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -466,7 +470,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 466 | 470 | ||
| 467 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); | 471 | ArgumentCaptor<IotDeviceMessage> captor = ArgumentCaptor.forClass(IotDeviceMessage.class); |
| 468 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); | 472 | verify(deviceMessageService).sendDeviceMessage(captor.capture(), eq(device), eq("server-1")); |
| 469 | - IotDeviceShadowReplyDTO reply = (IotDeviceShadowReplyDTO) captor.getValue().getParams(); | 473 | + IotDeviceMessage reply = captor.getValue(); |
| 470 | assertEquals(500, reply.getCode()); | 474 | assertEquals(500, reply.getCode()); |
| 471 | verify(deviceShadowMapper, never()).updateByDeviceIdAndVersion(any(), any(), any()); | 475 | verify(deviceShadowMapper, never()).updateByDeviceIdAndVersion(any(), any(), any()); |
| 472 | } | 476 | } |
| @@ -501,7 +505,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | @@ -501,7 +505,7 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest { | ||
| 501 | } | 505 | } |
| 502 | 506 | ||
| 503 | @SuppressWarnings("unchecked") | 507 | @SuppressWarnings("unchecked") |
| 504 | - private static Long getReplyVersion(IotDeviceShadowReplyDTO reply) { | 508 | + private static Long getReplyVersion(IotDeviceMessage reply) { |
| 505 | if (!(reply.getData() instanceof Map)) { | 509 | if (!(reply.getData() instanceof Map)) { |
| 506 | return null; | 510 | return null; |
| 507 | } | 511 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
| @@ -70,7 +70,6 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | @@ -70,7 +70,6 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | ||
| 70 | // ========== 设备影子 ========== | 70 | // ========== 设备影子 ========== |
| 71 | 71 | ||
| 72 | SHADOW_UPDATE("thing.shadow.update", "设备影子更新", true), | 72 | SHADOW_UPDATE("thing.shadow.update", "设备影子更新", true), |
| 73 | - SHADOW_GET("thing.shadow.get", "设备影子获取", false), | ||
| 74 | 73 | ||
| 75 | ; | 74 | ; |
| 76 | 75 | ||
| @@ -83,15 +82,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | @@ -83,15 +82,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | ||
| 83 | public static final Set<String> REPLY_DISABLED = SetUtils.asSet( | 82 | public static final Set<String> REPLY_DISABLED = SetUtils.asSet( |
| 84 | STATE_UPDATE.getMethod(), | 83 | STATE_UPDATE.getMethod(), |
| 85 | OTA_PROGRESS.getMethod(), // 参考阿里云,OTA 升级进度上报,不进行回复 | 84 | OTA_PROGRESS.getMethod(), // 参考阿里云,OTA 升级进度上报,不进行回复 |
| 86 | - SHADOW_UPDATE.getMethod() // 设备影子由影子 Service 主动下发 thing.shadow.get | ||
| 87 | - ); | ||
| 88 | - | ||
| 89 | - /** | ||
| 90 | - * 透传方法集合:设备侧使用原始线格式,不套 IotDeviceMessage 封套 | ||
| 91 | - */ | ||
| 92 | - public static final Set<String> PASSTHROUGH_METHODS = SetUtils.asSet( | ||
| 93 | - SHADOW_UPDATE.getMethod(), | ||
| 94 | - SHADOW_GET.getMethod() | 85 | + SHADOW_UPDATE.getMethod() // 设备影子由影子 Service 主动下发回复(thing/shadow/update_reply) |
| 95 | ); | 86 | ); |
| 96 | 87 | ||
| 97 | private final String method; | 88 | private final String method; |
| @@ -114,8 +105,4 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | @@ -114,8 +105,4 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { | ||
| 114 | return REPLY_DISABLED.contains(method); | 105 | return REPLY_DISABLED.contains(method); |
| 115 | } | 106 | } |
| 116 | 107 | ||
| 117 | - public static boolean isPassthrough(String method) { | ||
| 118 | - return PASSTHROUGH_METHODS.contains(method); | ||
| 119 | - } | ||
| 120 | - | ||
| 121 | } | 108 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/shadow/IotDeviceShadowErrorCodeEnum.java
| @@ -6,7 +6,7 @@ import lombok.Getter; | @@ -6,7 +6,7 @@ import lombok.Getter; | ||
| 6 | /** | 6 | /** |
| 7 | * IoT 设备影子设备侧错误码枚举 | 7 | * IoT 设备影子设备侧错误码枚举 |
| 8 | * <p> | 8 | * <p> |
| 9 | - * 设备侧 wire code,仅用于回填 {@code update_reply.code/msg},不走 | 9 | + * 设备侧 wire code,仅用于回填影子下行回复(thing.shadow.update_reply)的 code/msg,不走 |
| 10 | * {@link cn.iocoder.yudao.framework.common.pojo.CommonResult}。 | 10 | * {@link cn.iocoder.yudao.framework.common.pojo.CommonResult}。 |
| 11 | * 服务端内部异常请使用 {@link cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants}(如 | 11 | * 服务端内部异常请使用 {@link cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants}(如 |
| 12 | * {@code DEVICE_SHADOW_NOT_ENABLED})。 | 12 | * {@code DEVICE_SHADOW_NOT_ENABLED})。 |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/shadow/IotDeviceShadowReplyDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.core.topic.shadow; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 4 | -import lombok.AllArgsConstructor; | ||
| 5 | -import lombok.Data; | ||
| 6 | -import lombok.NoArgsConstructor; | ||
| 7 | - | ||
| 8 | -import java.util.HashMap; | ||
| 9 | -import java.util.Map; | ||
| 10 | - | ||
| 11 | -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.SUCCESS; | ||
| 12 | -import static cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils.*; | ||
| 13 | - | ||
| 14 | -/** | ||
| 15 | - * IoT 设备影子下行 Reply DTO | ||
| 16 | - * <p> | ||
| 17 | - * 扁平对齐 CommonResult + method,用于 {@link IotDeviceMessageMethodEnum#SHADOW_GET} | ||
| 18 | - * 透传线 body。 | ||
| 19 | - * 错误回复的 {@link #data} 仍携带 version 供设备 resync,有意偏离纯 CommonResult(error 时 data | ||
| 20 | - * 通常为 null)。 | ||
| 21 | - * | ||
| 22 | - * @author 芋道源码 | ||
| 23 | - */ | ||
| 24 | -@Data | ||
| 25 | -@NoArgsConstructor | ||
| 26 | -@AllArgsConstructor | ||
| 27 | -public class IotDeviceShadowReplyDTO { | ||
| 28 | - | ||
| 29 | - /** | ||
| 30 | - * 内层方法:get_reply / update_reply | ||
| 31 | - */ | ||
| 32 | - private String method; | ||
| 33 | - | ||
| 34 | - /** | ||
| 35 | - * 结果码,0 表示成功 | ||
| 36 | - */ | ||
| 37 | - private Integer code; | ||
| 38 | - | ||
| 39 | - /** | ||
| 40 | - * 结果描述 | ||
| 41 | - */ | ||
| 42 | - private String msg; | ||
| 43 | - | ||
| 44 | - /** | ||
| 45 | - * 业务数据:get_reply 为 {state, version, timestamp};update_reply 为 {version, | ||
| 46 | - * timestamp} | ||
| 47 | - */ | ||
| 48 | - private Object data; | ||
| 49 | - | ||
| 50 | - public static IotDeviceShadowReplyDTO getReplyOf(Map<String, Object> desired, Map<String, Object> reported, | ||
| 51 | - Long version, Long timestamp) { | ||
| 52 | - Map<String, Object> state = new HashMap<>(); | ||
| 53 | - state.put(STATE_DESIRED, desired != null ? desired : new HashMap<>()); | ||
| 54 | - state.put(STATE_REPORTED, reported != null ? reported : new HashMap<>()); | ||
| 55 | - Map<String, Object> data = new HashMap<>(); | ||
| 56 | - data.put(PAYLOAD_STATE, state); | ||
| 57 | - data.put(PAYLOAD_VERSION, version); | ||
| 58 | - data.put(METADATA_TIMESTAMP, timestamp); | ||
| 59 | - return of(INNER_METHOD_GET_REPLY, SUCCESS.getCode(), SUCCESS.getMsg(), data); | ||
| 60 | - } | ||
| 61 | - | ||
| 62 | - public static IotDeviceShadowReplyDTO updateReplyOf(Long version, Long timestamp) { | ||
| 63 | - return of(INNER_METHOD_UPDATE_REPLY, SUCCESS.getCode(), SUCCESS.getMsg(), | ||
| 64 | - dataWithVersionTimestamp(version, timestamp)); | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - public static IotDeviceShadowReplyDTO errorOf(String innerMethod, Long version, Integer errorCode, | ||
| 68 | - String errorMsg, Long timestamp) { | ||
| 69 | - return of(toReplyMethod(innerMethod), errorCode, errorMsg, dataWithVersionTimestamp(version, timestamp)); | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - private static IotDeviceShadowReplyDTO of(String method, Integer code, String msg, Map<String, Object> data) { | ||
| 73 | - return new IotDeviceShadowReplyDTO().setMethod(method).setCode(code).setMsg(msg).setData(data); | ||
| 74 | - } | ||
| 75 | - | ||
| 76 | - private static Map<String, Object> dataWithVersionTimestamp(Long version, Long timestamp) { | ||
| 77 | - Map<String, Object> data = new HashMap<>(); | ||
| 78 | - data.put(PAYLOAD_VERSION, version); | ||
| 79 | - data.put(METADATA_TIMESTAMP, timestamp); | ||
| 80 | - return data; | ||
| 81 | - } | ||
| 82 | - | ||
| 83 | -} |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/shadow/IotDeviceShadowUpdateReqDTO.java
| @@ -10,7 +10,7 @@ import java.util.Map; | @@ -10,7 +10,7 @@ import java.util.Map; | ||
| 10 | /** | 10 | /** |
| 11 | * IoT 设备影子上行 Request DTO | 11 | * IoT 设备影子上行 Request DTO |
| 12 | * <p> | 12 | * <p> |
| 13 | - * 用于 {@link IotDeviceMessageMethodEnum#SHADOW_UPDATE} 消息的 params 参数(透传原始线 body) | 13 | + * 用于 {@link IotDeviceMessageMethodEnum#SHADOW_UPDATE} 标准封套的 params 参数 |
| 14 | * | 14 | * |
| 15 | * @author 芋道源码 | 15 | * @author 芋道源码 |
| 16 | */ | 16 | */ |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceShadowProtocolUtils.java
| @@ -9,7 +9,7 @@ import java.util.Set; | @@ -9,7 +9,7 @@ import java.util.Set; | ||
| 9 | /** | 9 | /** |
| 10 | * IoT 设备影子协议工具 | 10 | * IoT 设备影子协议工具 |
| 11 | * <p> | 11 | * <p> |
| 12 | - * 内层 method、state 键名、payload 字段等与设备侧透传 JSON 对齐;并提供 metadata 读写。 | 12 | + * 内层 method、state 键名、reply data 字段等与设备侧标准封套 params/data 对齐;并提供 metadata 读写。 |
| 13 | * | 13 | * |
| 14 | * @author 芋道源码 | 14 | * @author 芋道源码 |
| 15 | */ | 15 | */ |
| @@ -18,12 +18,10 @@ public final class IotDeviceShadowProtocolUtils { | @@ -18,12 +18,10 @@ public final class IotDeviceShadowProtocolUtils { | ||
| 18 | private IotDeviceShadowProtocolUtils() { | 18 | private IotDeviceShadowProtocolUtils() { |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | - // ========== 内层 method ========== | 21 | + // ========== 内层 method(上行 params.method) ========== |
| 22 | 22 | ||
| 23 | public static final String INNER_METHOD_GET = "get"; | 23 | public static final String INNER_METHOD_GET = "get"; |
| 24 | public static final String INNER_METHOD_UPDATE = "update"; | 24 | public static final String INNER_METHOD_UPDATE = "update"; |
| 25 | - public static final String INNER_METHOD_GET_REPLY = "get_reply"; | ||
| 26 | - public static final String INNER_METHOD_UPDATE_REPLY = "update_reply"; | ||
| 27 | 25 | ||
| 28 | // ========== state / metadata 键 ========== | 26 | // ========== state / metadata 键 ========== |
| 29 | 27 | ||
| @@ -37,24 +35,28 @@ public final class IotDeviceShadowProtocolUtils { | @@ -37,24 +35,28 @@ public final class IotDeviceShadowProtocolUtils { | ||
| 37 | public static final String PAYLOAD_VERSION = "version"; | 35 | public static final String PAYLOAD_VERSION = "version"; |
| 38 | 36 | ||
| 39 | /** | 37 | /** |
| 40 | - * 由内层 method 推导 reply method(get → get_reply,其余 → update_reply) | 38 | + * 构建 get 响应 data:{ state:{desired,reported}, version, timestamp } |
| 41 | */ | 39 | */ |
| 42 | - public static String toReplyMethod(String innerMethod) { | ||
| 43 | - if (INNER_METHOD_GET.equals(innerMethod)) { | ||
| 44 | - return INNER_METHOD_GET_REPLY; | ||
| 45 | - } | ||
| 46 | - return INNER_METHOD_UPDATE_REPLY; | 40 | + public static Map<String, Object> buildGetData(Map<String, Object> desired, Map<String, Object> reported, |
| 41 | + Long version, Long timestamp) { | ||
| 42 | + Map<String, Object> state = new HashMap<>(); | ||
| 43 | + state.put(STATE_DESIRED, desired != null ? desired : new HashMap<>()); | ||
| 44 | + state.put(STATE_REPORTED, reported != null ? reported : new HashMap<>()); | ||
| 45 | + Map<String, Object> data = new HashMap<>(); | ||
| 46 | + data.put(PAYLOAD_STATE, state); | ||
| 47 | + data.put(PAYLOAD_VERSION, version); | ||
| 48 | + data.put(METADATA_TIMESTAMP, timestamp); | ||
| 49 | + return data; | ||
| 47 | } | 50 | } |
| 48 | 51 | ||
| 49 | /** | 52 | /** |
| 50 | - * 从透传 params 提取内层 method;无法解析时默认 update | 53 | + * 构建 update 响应 / 错误 data:{ version, timestamp } |
| 51 | */ | 54 | */ |
| 52 | - public static String extractInnerMethod(Object params) { | ||
| 53 | - if (!(params instanceof Map)) { | ||
| 54 | - return INNER_METHOD_UPDATE; | ||
| 55 | - } | ||
| 56 | - Object method = ((Map<?, ?>) params).get("method"); | ||
| 57 | - return method != null ? String.valueOf(method) : INNER_METHOD_UPDATE; | 55 | + public static Map<String, Object> buildUpdateData(Long version, Long timestamp) { |
| 56 | + Map<String, Object> data = new HashMap<>(); | ||
| 57 | + data.put(PAYLOAD_VERSION, version); | ||
| 58 | + data.put(METADATA_TIMESTAMP, timestamp); | ||
| 59 | + return data; | ||
| 58 | } | 60 | } |
| 59 | 61 | ||
| 60 | /** | 62 | /** |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/handler/downstream/IotEmqxDownstreamHandler.java
| @@ -55,11 +55,6 @@ public class IotEmqxDownstreamHandler { | @@ -55,11 +55,6 @@ public class IotEmqxDownstreamHandler { | ||
| 55 | // 2.2 构建载荷 | 55 | // 2.2 构建载荷 |
| 56 | byte[] payload = deviceMessageService.serializeDeviceMessage(message, deviceInfo.getProductKey(), | 56 | byte[] payload = deviceMessageService.serializeDeviceMessage(message, deviceInfo.getProductKey(), |
| 57 | deviceInfo.getDeviceName()); | 57 | deviceInfo.getDeviceName()); |
| 58 | - if (payload == null) { | ||
| 59 | - log.warn("[handle][影子下行跳过,设备 ID: {},method: {},非 JSON 协议设备不支持影子透传]", | ||
| 60 | - message.getDeviceId(), message.getMethod()); | ||
| 61 | - return; | ||
| 62 | - } | ||
| 63 | 58 | ||
| 64 | // 3. 发布消息 | 59 | // 3. 发布消息 |
| 65 | protocol.publishMessage(topic, payload); | 60 | protocol.publishMessage(topic, payload); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/handler/upstream/IotEmqxUpstreamHandler.java
| @@ -3,16 +3,12 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.handler.upstream; | @@ -3,16 +3,12 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.handler.upstream; | ||
| 3 | import cn.hutool.core.util.ArrayUtil; | 3 | import cn.hutool.core.util.ArrayUtil; |
| 4 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 5 | import cn.hutool.extra.spring.SpringUtil; | 5 | import cn.hutool.extra.spring.SpringUtil; |
| 6 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 8 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 7 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 9 | import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; | 8 | import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; |
| 10 | import io.vertx.mqtt.messages.MqttPublishMessage; | 9 | import io.vertx.mqtt.messages.MqttPublishMessage; |
| 11 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
| 12 | 11 | ||
| 13 | -import java.util.Arrays; | ||
| 14 | -import java.util.stream.Collectors; | ||
| 15 | - | ||
| 16 | /** | 12 | /** |
| 17 | * IoT 网关 EMQX 上行消息处理器 | 13 | * IoT 网关 EMQX 上行消息处理器 |
| 18 | * | 14 | * |
| @@ -47,25 +43,14 @@ public class IotEmqxUpstreamHandler { | @@ -47,25 +43,14 @@ public class IotEmqxUpstreamHandler { | ||
| 47 | return; | 43 | return; |
| 48 | } | 44 | } |
| 49 | 45 | ||
| 50 | - // 2.1 反序列化消息 | ||
| 51 | - String topicMethod = Arrays.stream(topicParts, 4, topicParts.length) | ||
| 52 | - .collect(Collectors.joining(".")); | ||
| 53 | - IotDeviceMessage message; | ||
| 54 | - if (IotDeviceMessageMethodEnum.isPassthrough(topicMethod)) { | ||
| 55 | - message = deviceMessageService.deserializePassthroughMessage(payload, topicMethod, productKey, deviceName); | ||
| 56 | - if (message == null) { | ||
| 57 | - log.warn("[handle][topic({}) 影子透传 payload 解析失败]", topic); | ||
| 58 | - return; | ||
| 59 | - } | ||
| 60 | - } else { | ||
| 61 | - message = deviceMessageService.deserializeDeviceMessage(payload, productKey, deviceName); | ||
| 62 | - if (message == null) { | ||
| 63 | - log.warn("[handle][topic({}) payload({}) 消息解码失败]", topic, new String(payload)); | ||
| 64 | - return; | ||
| 65 | - } | ||
| 66 | - // 2.2 标准化回复消息的 method(MQTT 协议中,设备回复消息的 method 会携带 _reply 后缀) | ||
| 67 | - IotMqttTopicUtils.normalizeReplyMethod(message); | 46 | + // 2.1 反序列化消息(标准 IotDeviceMessage 封套,method 来自 body) |
| 47 | + IotDeviceMessage message = deviceMessageService.deserializeDeviceMessage(payload, productKey, deviceName); | ||
| 48 | + if (message == null) { | ||
| 49 | + log.warn("[handle][topic({}) payload({}) 消息解码失败]", topic, new String(payload)); | ||
| 50 | + return; | ||
| 68 | } | 51 | } |
| 52 | + // 2.2 标准化回复消息的 method(MQTT 协议中,设备回复消息的 method 会携带 _reply 后缀) | ||
| 53 | + IotMqttTopicUtils.normalizeReplyMethod(message); | ||
| 69 | 54 | ||
| 70 | // 3. 发送消息到队列 | 55 | // 3. 发送消息到队列 |
| 71 | deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); | 56 | deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamHandler.java
| @@ -45,11 +45,6 @@ public class IotMqttDownstreamHandler { | @@ -45,11 +45,6 @@ public class IotMqttDownstreamHandler { | ||
| 45 | // 2.1 序列化消息 | 45 | // 2.1 序列化消息 |
| 46 | byte[] payload = deviceMessageService.serializeDeviceMessage(message, connectionInfo.getProductKey(), | 46 | byte[] payload = deviceMessageService.serializeDeviceMessage(message, connectionInfo.getProductKey(), |
| 47 | connectionInfo.getDeviceName()); | 47 | connectionInfo.getDeviceName()); |
| 48 | - if (payload == null) { | ||
| 49 | - log.warn("[handle][影子下行跳过,设备 ID: {},method: {},非 JSON 协议设备不支持影子透传]", | ||
| 50 | - message.getDeviceId(), message.getMethod()); | ||
| 51 | - return; | ||
| 52 | - } | ||
| 53 | Assert.isTrue(payload.length > 0, "消息编码结果不能为空"); | 48 | Assert.isTrue(payload.length > 0, "消息编码结果不能为空"); |
| 54 | // 2.2 构建主题 | 49 | // 2.2 构建主题 |
| 55 | Assert.notBlank(message.getMethod(), "消息方法不能为空"); | 50 | Assert.notBlank(message.getMethod(), "消息方法不能为空"); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
| @@ -72,11 +72,6 @@ public abstract class IotMqttAbstractHandler { | @@ -72,11 +72,6 @@ public abstract class IotMqttAbstractHandler { | ||
| 72 | try { | 72 | try { |
| 73 | // 1.1 序列化消息(根据设备配置的序列化类型) | 73 | // 1.1 序列化消息(根据设备配置的序列化类型) |
| 74 | byte[] encodedData = deviceMessageService.serializeDeviceMessage(responseMessage, productKey, deviceName); | 74 | byte[] encodedData = deviceMessageService.serializeDeviceMessage(responseMessage, productKey, deviceName); |
| 75 | - if (encodedData == null) { | ||
| 76 | - // 影子透传在非 JSON 设备上 encode 返回 null,跳过发布避免 NPE | ||
| 77 | - log.warn("[writeResponse][影子下行跳过,客户端 ID: {},method: {}]", endpoint.clientIdentifier(), method); | ||
| 78 | - return; | ||
| 79 | - } | ||
| 80 | // 1.2 构建响应主题 | 75 | // 1.2 构建响应主题 |
| 81 | String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true); | 76 | String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true); |
| 82 | 77 |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttUpstreamHandler.java
| @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.lang.Assert; | 3 | import cn.hutool.core.lang.Assert; |
| 4 | import cn.hutool.core.util.ArrayUtil; | 4 | import cn.hutool.core.util.ArrayUtil; |
| 5 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; | 6 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; |
| 8 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 7 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| @@ -10,10 +9,6 @@ import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; | @@ -10,10 +9,6 @@ import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils; | ||
| 10 | import io.vertx.mqtt.MqttEndpoint; | 9 | import io.vertx.mqtt.MqttEndpoint; |
| 11 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
| 12 | 11 | ||
| 13 | -import java.util.Arrays; | ||
| 14 | -import java.util.stream.Collectors; | ||
| 15 | - | ||
| 16 | - | ||
| 17 | /** | 12 | /** |
| 18 | * IoT 网关 MQTT 上行消息处理器:处理业务消息(属性上报、事件上报等) | 13 | * IoT 网关 MQTT 上行消息处理器:处理业务消息(属性上报、事件上报等) |
| 19 | * | 14 | * |
| @@ -62,25 +57,14 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { | @@ -62,25 +57,14 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { | ||
| 62 | return; | 57 | return; |
| 63 | } | 58 | } |
| 64 | 59 | ||
| 65 | - // 2.1 反序列化消息 | ||
| 66 | - String topicMethod = Arrays.stream(topicParts, 4, topicParts.length) | ||
| 67 | - .collect(Collectors.joining(".")); | ||
| 68 | - IotDeviceMessage message; | ||
| 69 | - if (IotDeviceMessageMethodEnum.isPassthrough(topicMethod)) { | ||
| 70 | - message = deviceMessageService.deserializePassthroughMessage(payload, topicMethod, productKey, deviceName); | ||
| 71 | - if (message == null) { | ||
| 72 | - log.warn("[handleBusinessRequest][影子透传 payload 解析失败,客户端 ID: {},主题: {}]", clientId, topic); | ||
| 73 | - return; | ||
| 74 | - } | ||
| 75 | - } else { | ||
| 76 | - message = deviceMessageService.deserializeDeviceMessage(payload, productKey, deviceName); | ||
| 77 | - if (message == null) { | ||
| 78 | - log.warn("[handleBusinessRequest][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic); | ||
| 79 | - return; | ||
| 80 | - } | ||
| 81 | - // 2.2 标准化回复消息的 method(MQTT 协议中,设备回复消息的 method 会携带 _reply 后缀) | ||
| 82 | - IotMqttTopicUtils.normalizeReplyMethod(message); | 60 | + // 2.1 反序列化消息(标准 IotDeviceMessage 封套,method 来自 body) |
| 61 | + IotDeviceMessage message = deviceMessageService.deserializeDeviceMessage(payload, productKey, deviceName); | ||
| 62 | + if (message == null) { | ||
| 63 | + log.warn("[handleBusinessRequest][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic); | ||
| 64 | + return; | ||
| 83 | } | 65 | } |
| 66 | + // 2.2 标准化回复消息的 method(MQTT 协议中,设备回复消息的 method 会携带 _reply 后缀) | ||
| 67 | + IotMqttTopicUtils.normalizeReplyMethod(message); | ||
| 84 | 68 | ||
| 85 | // 3. 处理业务消息 | 69 | // 3. 处理业务消息 |
| 86 | deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); | 70 | deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/IotMessageSerializer.java
| @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.iot.gateway.serialize; | @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.iot.gateway.serialize; | ||
| 3 | import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | 5 | ||
| 6 | -import java.util.Map; | ||
| 7 | - | ||
| 8 | /** | 6 | /** |
| 9 | * IoT 设备消息序列化器接口 | 7 | * IoT 设备消息序列化器接口 |
| 10 | * | 8 | * |
| @@ -37,24 +35,4 @@ public interface IotMessageSerializer { | @@ -37,24 +35,4 @@ public interface IotMessageSerializer { | ||
| 37 | */ | 35 | */ |
| 38 | IotSerializeTypeEnum getType(); | 36 | IotSerializeTypeEnum getType(); |
| 39 | 37 | ||
| 40 | - /** | ||
| 41 | - * 透传原始 body 编码(影子等不走 {@link IotDeviceMessage} 封套的场景) | ||
| 42 | - * | ||
| 43 | - * @param value 待编码对象(通常为 Map 或 DTO) | ||
| 44 | - * @return 线格式字节 | ||
| 45 | - */ | ||
| 46 | - default byte[] encodeRaw(Object value) { | ||
| 47 | - throw new UnsupportedOperationException("该序列化器不支持影子透传编码"); | ||
| 48 | - } | ||
| 49 | - | ||
| 50 | - /** | ||
| 51 | - * 透传原始 body 解码 | ||
| 52 | - * | ||
| 53 | - * @param bytes 线格式字节 | ||
| 54 | - * @return 透传 params | ||
| 55 | - */ | ||
| 56 | - default Map<String, Object> decodeRaw(byte[] bytes) { | ||
| 57 | - throw new UnsupportedOperationException("该序列化器不支持影子透传解码"); | ||
| 58 | - } | ||
| 59 | - | ||
| 60 | } | 38 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/json/IotJsonSerializer.java
| @@ -6,8 +6,6 @@ import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | @@ -6,8 +6,6 @@ import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | 7 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; |
| 8 | 8 | ||
| 9 | -import java.util.Map; | ||
| 10 | - | ||
| 11 | /** | 9 | /** |
| 12 | * JSON 格式的消息序列化器 | 10 | * JSON 格式的消息序列化器 |
| 13 | * | 11 | * |
| @@ -36,17 +34,4 @@ public class IotJsonSerializer implements IotMessageSerializer { | @@ -36,17 +34,4 @@ public class IotJsonSerializer implements IotMessageSerializer { | ||
| 36 | return message; | 34 | return message; |
| 37 | } | 35 | } |
| 38 | 36 | ||
| 39 | - @Override | ||
| 40 | - public byte[] encodeRaw(Object value) { | ||
| 41 | - Assert.notNull(value, "待编码数据不能为空"); | ||
| 42 | - return JsonUtils.toJsonByte(value); | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - @Override | ||
| 46 | - @SuppressWarnings("unchecked") | ||
| 47 | - public Map<String, Object> decodeRaw(byte[] bytes) { | ||
| 48 | - Assert.notNull(bytes, "待解码数据不能为空"); | ||
| 49 | - return JsonUtils.parseObject(bytes, Map.class); | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | } | 37 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java
| @@ -62,16 +62,4 @@ public interface IotDeviceMessageService { | @@ -62,16 +62,4 @@ public interface IotDeviceMessageService { | ||
| 62 | void sendDeviceMessage(IotDeviceMessage message, | 62 | void sendDeviceMessage(IotDeviceMessage message, |
| 63 | String productKey, String deviceName, String serverId); | 63 | String productKey, String deviceName, String serverId); |
| 64 | 64 | ||
| 65 | - /** | ||
| 66 | - * 透传上行组装:按设备 serializeType 解码 body,提取 id 作 requestId,构建消息 | ||
| 67 | - * | ||
| 68 | - * @param payload 原始 body | ||
| 69 | - * @param method 透传 method(来自 topic 等,如 thing.shadow.update) | ||
| 70 | - * @param productKey 产品 Key | ||
| 71 | - * @param deviceName 设备名称 | ||
| 72 | - * @return 设备消息;解码失败返回 null | ||
| 73 | - */ | ||
| 74 | - IotDeviceMessage deserializePassthroughMessage(byte[] payload, String method, | ||
| 75 | - String productKey, String deviceName); | ||
| 76 | - | ||
| 77 | } | 65 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.service.device.message; | 1 | package cn.iocoder.yudao.module.iot.gateway.service.device.message; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.convert.Convert; | ||
| 4 | import cn.hutool.core.lang.Assert; | 3 | import cn.hutool.core.lang.Assert; |
| 5 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 7 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; |
| 9 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 10 | import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; | 8 | import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; |
| @@ -17,8 +15,6 @@ import lombok.extern.slf4j.Slf4j; | @@ -17,8 +15,6 @@ import lombok.extern.slf4j.Slf4j; | ||
| 17 | import org.springframework.stereotype.Service; | 15 | import org.springframework.stereotype.Service; |
| 18 | 16 | ||
| 19 | import java.time.LocalDateTime; | 17 | import java.time.LocalDateTime; |
| 20 | -import java.util.Map; | ||
| 21 | -import java.util.function.Function; | ||
| 22 | 18 | ||
| 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 19 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 24 | import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS; | 20 | import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS; |
| @@ -44,14 +40,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -44,14 +40,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 44 | @Override | 40 | @Override |
| 45 | public byte[] serializeDeviceMessage(IotDeviceMessage message, | 41 | public byte[] serializeDeviceMessage(IotDeviceMessage message, |
| 46 | String productKey, String deviceName) { | 42 | String productKey, String deviceName) { |
| 47 | - if (IotDeviceMessageMethodEnum.isPassthrough(message.getMethod())) { | ||
| 48 | - IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); | ||
| 49 | - if (device == null) { | ||
| 50 | - throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); | ||
| 51 | - } | ||
| 52 | - IotSerializeTypeEnum serializeType = IotSerializeTypeEnum.of(device.getSerializeType()); | ||
| 53 | - return encodePassthrough(message, serializeType, productKey, deviceName); | ||
| 54 | - } | ||
| 55 | // 1.1 获取设备信息 | 43 | // 1.1 获取设备信息 |
| 56 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); | 44 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); |
| 57 | if (device == null) { | 45 | if (device == null) { |
| @@ -68,9 +56,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -68,9 +56,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 68 | @Override | 56 | @Override |
| 69 | public byte[] serializeDeviceMessage(IotDeviceMessage message, | 57 | public byte[] serializeDeviceMessage(IotDeviceMessage message, |
| 70 | IotSerializeTypeEnum serializeType) { | 58 | IotSerializeTypeEnum serializeType) { |
| 71 | - if (IotDeviceMessageMethodEnum.isPassthrough(message.getMethod())) { | ||
| 72 | - return encodePassthrough(message, serializeType, null, null); | ||
| 73 | - } | ||
| 74 | // 1. 获取序列化器 | 59 | // 1. 获取序列化器 |
| 75 | IotMessageSerializer serializer = messageSerializerManager.get(serializeType); | 60 | IotMessageSerializer serializer = messageSerializerManager.get(serializeType); |
| 76 | if (serializer == null) { | 61 | if (serializer == null) { |
| @@ -110,23 +95,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -110,23 +95,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 110 | } | 95 | } |
| 111 | 96 | ||
| 112 | @Override | 97 | @Override |
| 113 | - public IotDeviceMessage deserializePassthroughMessage(byte[] payload, String method, | ||
| 114 | - String productKey, String deviceName) { | ||
| 115 | - IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); | ||
| 116 | - if (device == null) { | ||
| 117 | - throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); | ||
| 118 | - } | ||
| 119 | - IotSerializeTypeEnum serializeType = IotSerializeTypeEnum.of(device.getSerializeType()); | ||
| 120 | - Map<String, Object> params = decodePassthrough(payload, serializeType, productKey, deviceName); | ||
| 121 | - if (params == null) { | ||
| 122 | - // 非法/非对象 body:仍投递,由 biz 层回 JSON_FORMAT_ERROR(400) | ||
| 123 | - return IotDeviceMessage.requestOf(null, method, null); | ||
| 124 | - } | ||
| 125 | - String requestId = Convert.toStr(params.get("id"), null); | ||
| 126 | - return IotDeviceMessage.requestOf(requestId, method, params); | ||
| 127 | - } | ||
| 128 | - | ||
| 129 | - @Override | ||
| 130 | public void sendDeviceMessage(IotDeviceMessage message, | 98 | public void sendDeviceMessage(IotDeviceMessage message, |
| 131 | String productKey, String deviceName, String serverId) { | 99 | String productKey, String deviceName, String serverId) { |
| 132 | // 1. 获取设备信息 | 100 | // 1. 获取设备信息 |
| @@ -157,60 +125,4 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | @@ -157,60 +125,4 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | ||
| 157 | } | 125 | } |
| 158 | } | 126 | } |
| 159 | 127 | ||
| 160 | - /** | ||
| 161 | - * 影子透传编码;不支持的 serializeType 返回 null,由下行发布方跳过发送 | ||
| 162 | - */ | ||
| 163 | - private byte[] encodePassthrough(IotDeviceMessage message, IotSerializeTypeEnum serializeType, | ||
| 164 | - String productKey, String deviceName) { | ||
| 165 | - return passthroughRaw("encodePassthrough", serializeType, productKey, deviceName, false, | ||
| 166 | - serializer -> serializer.encodeRaw(message.getParams())); | ||
| 167 | - } | ||
| 168 | - | ||
| 169 | - /** | ||
| 170 | - * 影子透传解码;解析失败或不支持的 serializeType 返回 null | ||
| 171 | - */ | ||
| 172 | - private Map<String, Object> decodePassthrough(byte[] bytes, IotSerializeTypeEnum serializeType, | ||
| 173 | - String productKey, String deviceName) { | ||
| 174 | - return passthroughRaw("decodePassthrough", serializeType, productKey, deviceName, true, | ||
| 175 | - serializer -> serializer.decodeRaw(bytes)); | ||
| 176 | - } | ||
| 177 | - | ||
| 178 | - private <T> T passthroughRaw(String op, IotSerializeTypeEnum serializeType, | ||
| 179 | - String productKey, String deviceName, boolean decode, | ||
| 180 | - Function<IotMessageSerializer, T> action) { | ||
| 181 | - IotMessageSerializer serializer = getPassthroughSerializer(serializeType); | ||
| 182 | - if (serializer == null) { | ||
| 183 | - warnPassthroughUnsupported(op, serializeType, productKey, deviceName); | ||
| 184 | - return null; | ||
| 185 | - } | ||
| 186 | - try { | ||
| 187 | - return action.apply(serializer); | ||
| 188 | - } catch (UnsupportedOperationException ex) { | ||
| 189 | - warnPassthroughUnsupported(op, serializeType, productKey, deviceName); | ||
| 190 | - return null; | ||
| 191 | - } catch (RuntimeException ex) { | ||
| 192 | - if (decode) { | ||
| 193 | - log.warn("[{}][影子透传 payload 解析失败,productKey={}, deviceName={}]", | ||
| 194 | - op, productKey, deviceName, ex); | ||
| 195 | - return null; | ||
| 196 | - } | ||
| 197 | - throw ex; | ||
| 198 | - } | ||
| 199 | - } | ||
| 200 | - | ||
| 201 | - private IotMessageSerializer getPassthroughSerializer(IotSerializeTypeEnum serializeType) { | ||
| 202 | - IotSerializeTypeEnum type = serializeType != null ? serializeType : IotSerializeTypeEnum.JSON; | ||
| 203 | - return messageSerializerManager.get(type); | ||
| 204 | - } | ||
| 205 | - | ||
| 206 | - private void warnPassthroughUnsupported(String op, IotSerializeTypeEnum serializeType, | ||
| 207 | - String productKey, String deviceName) { | ||
| 208 | - if (productKey != null && deviceName != null) { | ||
| 209 | - log.warn("[{}][影子透传跳过:设备({}/{}) serializeType={} 不支持,目前仅 JSON 协议设备可用]", | ||
| 210 | - op, productKey, deviceName, serializeType); | ||
| 211 | - } else { | ||
| 212 | - log.warn("[{}][影子透传跳过:serializeType={} 不支持,目前仅 JSON 协议设备可用]", op, serializeType); | ||
| 213 | - } | ||
| 214 | - } | ||
| 215 | - | ||
| 216 | } | 128 | } |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceShadowPassthroughTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.gateway.service.device.message; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 4 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | ||
| 6 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 7 | -import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | ||
| 8 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 9 | -import cn.iocoder.yudao.module.iot.core.topic.shadow.IotDeviceShadowReplyDTO; | ||
| 10 | -import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager; | ||
| 11 | -import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; | ||
| 12 | -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; | ||
| 13 | -import org.junit.jupiter.api.BeforeEach; | ||
| 14 | -import org.junit.jupiter.api.Test; | ||
| 15 | -import org.mockito.InjectMocks; | ||
| 16 | -import org.mockito.Mock; | ||
| 17 | -import org.mockito.Spy; | ||
| 18 | - | ||
| 19 | -import java.util.HashMap; | ||
| 20 | -import java.util.Map; | ||
| 21 | - | ||
| 22 | -import static org.junit.jupiter.api.Assertions.*; | ||
| 23 | -import static org.mockito.ArgumentMatchers.eq; | ||
| 24 | -import static org.mockito.Mockito.when; | ||
| 25 | - | ||
| 26 | -/** | ||
| 27 | - * 设备影子网关透传序列化单元测试 | ||
| 28 | - */ | ||
| 29 | -public class IotDeviceShadowPassthroughTest extends BaseMockitoUnitTest { | ||
| 30 | - | ||
| 31 | - @InjectMocks | ||
| 32 | - private IotDeviceMessageServiceImpl deviceMessageService; | ||
| 33 | - | ||
| 34 | - @Mock | ||
| 35 | - private IotDeviceService deviceService; | ||
| 36 | - | ||
| 37 | - @Spy | ||
| 38 | - private IotMessageSerializerManager messageSerializerManager = new IotMessageSerializerManager(); | ||
| 39 | - | ||
| 40 | - @BeforeEach | ||
| 41 | - public void setUpManager() { | ||
| 42 | - // Spy 需手动触发构造逻辑 | ||
| 43 | - if (messageSerializerManager == null) { | ||
| 44 | - messageSerializerManager = new IotMessageSerializerManager(); | ||
| 45 | - } | ||
| 46 | - } | ||
| 47 | - | ||
| 48 | - @Test | ||
| 49 | - public void testSerializeDeviceMessage_passthrough_flatReply() { | ||
| 50 | - IotDeviceShadowReplyDTO reply = IotDeviceShadowReplyDTO.getReplyOf(new HashMap<>(), new HashMap<>(), 2L, 1469564576L); | ||
| 51 | - IotDeviceMessage message = IotDeviceMessage.requestOf( | ||
| 52 | - IotDeviceMessageMethodEnum.SHADOW_GET.getMethod(), reply); | ||
| 53 | - | ||
| 54 | - byte[] bytes = deviceMessageService.serializeDeviceMessage(message, IotSerializeTypeEnum.JSON); | ||
| 55 | - | ||
| 56 | - @SuppressWarnings("unchecked") | ||
| 57 | - Map<String, Object> result = JsonUtils.parseObject(bytes, Map.class); | ||
| 58 | - assertEquals("get_reply", result.get("method")); | ||
| 59 | - assertEquals(0, ((Number) result.get("code")).intValue()); | ||
| 60 | - assertNull(result.get("payload")); | ||
| 61 | - assertNull(result.get("id")); | ||
| 62 | - @SuppressWarnings("unchecked") | ||
| 63 | - Map<String, Object> data = (Map<String, Object>) result.get("data"); | ||
| 64 | - assertEquals(2, ((Number) data.get("version")).intValue()); | ||
| 65 | - } | ||
| 66 | - | ||
| 67 | - @Test | ||
| 68 | - public void testDeserializePassthroughMessage_fromTopicMethod() { | ||
| 69 | - IotDeviceRespDTO device = new IotDeviceRespDTO().setId(1L).setSerializeType(IotSerializeTypeEnum.JSON.getType()); | ||
| 70 | - when(deviceService.getDeviceFromCache(eq("pk"), eq("dn"))).thenReturn(device); | ||
| 71 | - | ||
| 72 | - byte[] payload = JsonUtils.toJsonByte(Map.of("method", "get", "id", "req-1")); | ||
| 73 | - IotDeviceMessage message = deviceMessageService.deserializePassthroughMessage(payload, | ||
| 74 | - IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), "pk", "dn"); | ||
| 75 | - | ||
| 76 | - assertNotNull(message); | ||
| 77 | - assertEquals(IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), message.getMethod()); | ||
| 78 | - assertEquals("req-1", message.getRequestId()); | ||
| 79 | - assertEquals("get", ((Map<?, ?>) message.getParams()).get("method")); | ||
| 80 | - } | ||
| 81 | - | ||
| 82 | - @Test | ||
| 83 | - public void testSerializeUpdateReply_passthrough() { | ||
| 84 | - IotDeviceShadowReplyDTO reply = IotDeviceShadowReplyDTO.updateReplyOf(3L, 1469564576L); | ||
| 85 | - IotDeviceMessage message = IotDeviceMessage.requestOf( | ||
| 86 | - IotDeviceMessageMethodEnum.SHADOW_GET.getMethod(), reply); | ||
| 87 | - | ||
| 88 | - byte[] bytes = deviceMessageService.serializeDeviceMessage(message, IotSerializeTypeEnum.JSON); | ||
| 89 | - @SuppressWarnings("unchecked") | ||
| 90 | - Map<String, Object> result = JsonUtils.parseObject(bytes, Map.class); | ||
| 91 | - assertEquals("update_reply", result.get("method")); | ||
| 92 | - assertEquals(0, ((Number) result.get("code")).intValue()); | ||
| 93 | - assertNull(result.get("id")); | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - @Test | ||
| 97 | - public void testJsonSerializer_encodeRawDecodeRaw() { | ||
| 98 | - IotJsonSerializer serializer = new IotJsonSerializer(); | ||
| 99 | - Map<String, Object> params = Map.of("method", "get"); | ||
| 100 | - byte[] bytes = serializer.encodeRaw(params); | ||
| 101 | - Map<String, Object> decoded = serializer.decodeRaw(bytes); | ||
| 102 | - assertNotNull(decoded); | ||
| 103 | - assertEquals("get", decoded.get("method")); | ||
| 104 | - } | ||
| 105 | - | ||
| 106 | - @Test | ||
| 107 | - public void testSerializeDeviceMessage_passthrough_binaryReturnsNull() { | ||
| 108 | - IotDeviceMessage message = IotDeviceMessage.requestOf( | ||
| 109 | - IotDeviceMessageMethodEnum.SHADOW_GET.getMethod(), Map.of("method", "get_reply")); | ||
| 110 | - assertNull(deviceMessageService.serializeDeviceMessage(message, IotSerializeTypeEnum.BINARY)); | ||
| 111 | - } | ||
| 112 | - | ||
| 113 | - @Test | ||
| 114 | - public void testBinarySerializer_rawNotSupported() { | ||
| 115 | - cn.iocoder.yudao.module.iot.gateway.serialize.binary.IotBinarySerializer serializer = | ||
| 116 | - new cn.iocoder.yudao.module.iot.gateway.serialize.binary.IotBinarySerializer(); | ||
| 117 | - assertThrows(UnsupportedOperationException.class, () -> serializer.encodeRaw(Map.of("method", "update"))); | ||
| 118 | - } | ||
| 119 | - | ||
| 120 | - @Test | ||
| 121 | - public void testDeserializePassthroughMessage_numericId() { | ||
| 122 | - IotDeviceRespDTO device = new IotDeviceRespDTO().setId(1L).setSerializeType(IotSerializeTypeEnum.JSON.getType()); | ||
| 123 | - when(deviceService.getDeviceFromCache(eq("pk"), eq("dn"))).thenReturn(device); | ||
| 124 | - | ||
| 125 | - byte[] payload = JsonUtils.toJsonByte(Map.of("method", "get", "id", 123)); | ||
| 126 | - IotDeviceMessage message = deviceMessageService.deserializePassthroughMessage(payload, | ||
| 127 | - IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), "pk", "dn"); | ||
| 128 | - | ||
| 129 | - assertNotNull(message); | ||
| 130 | - assertEquals("123", message.getRequestId()); | ||
| 131 | - } | ||
| 132 | - | ||
| 133 | - @Test | ||
| 134 | - public void testDeserializePassthroughMessage_invalidJsonStillDelivered() { | ||
| 135 | - IotDeviceRespDTO device = new IotDeviceRespDTO().setId(1L).setSerializeType(IotSerializeTypeEnum.JSON.getType()); | ||
| 136 | - when(deviceService.getDeviceFromCache(eq("pk"), eq("dn"))).thenReturn(device); | ||
| 137 | - | ||
| 138 | - IotDeviceMessage message = deviceMessageService.deserializePassthroughMessage("{invalid".getBytes(), | ||
| 139 | - IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), "pk", "dn"); | ||
| 140 | - | ||
| 141 | - assertNotNull(message); | ||
| 142 | - assertNull(message.getParams()); | ||
| 143 | - } | ||
| 144 | - | ||
| 145 | - @Test | ||
| 146 | - public void testErrorOf_getMethodUsesGetReply() { | ||
| 147 | - IotDeviceShadowReplyDTO reply = IotDeviceShadowReplyDTO.errorOf("get", 1L, 400, "JSON 格式错误", 1000L); | ||
| 148 | - assertEquals("get_reply", reply.getMethod()); | ||
| 149 | - assertEquals(400, reply.getCode()); | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | -} |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtilsTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.util; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 4 | +import org.junit.jupiter.api.Test; | ||
| 5 | + | ||
| 6 | +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * {@link IotMqttTopicUtils} 单元测试 | ||
| 10 | + */ | ||
| 11 | +class IotMqttTopicUtilsTest { | ||
| 12 | + | ||
| 13 | + private static final String PRODUCT_KEY = "pk"; | ||
| 14 | + private static final String DEVICE_NAME = "dn"; | ||
| 15 | + | ||
| 16 | + @Test | ||
| 17 | + void testBuildTopicByMethod_shadowUpdateReplyAppendsReplySuffix() { | ||
| 18 | + String topic = IotMqttTopicUtils.buildTopicByMethod( | ||
| 19 | + IotDeviceMessageMethodEnum.SHADOW_UPDATE.getMethod(), PRODUCT_KEY, DEVICE_NAME, true); | ||
| 20 | + assertEquals("/sys/pk/dn/thing/shadow/update_reply", topic); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + @Test | ||
| 24 | + void testBuildTopicByMethod_serviceInvokeReplyAppendsReplySuffix() { | ||
| 25 | + String topic = IotMqttTopicUtils.buildTopicByMethod( | ||
| 26 | + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(), PRODUCT_KEY, DEVICE_NAME, true); | ||
| 27 | + assertEquals("/sys/pk/dn/thing/service/invoke_reply", topic); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | +} |