Commit 6e9b58cbcd4442af5b0a8343d6ead3e86788ddce

Authored by haohao
1 parent 666afcc3

refactor(iot): 影子透传按 serializeType 序列化,reply 对齐 CommonResult

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/shadow/IotDeviceShadowServiceImpl.java
... ... @@ -97,7 +97,8 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService {
97 97  
98 98 private void handleDeviceShadowUpdate0(IotDeviceDO device, IotDeviceMessage message) {
99 99 // 1. 解析请求(影子 body 当前按 JSON 建模,非 JSON 设备在 gateway 透传层已拒绝)
100   - IotDeviceShadowUpdateReqDTO req = JsonUtils.convertObject(message.getParams(), IotDeviceShadowUpdateReqDTO.class);
  100 + IotDeviceShadowUpdateReqDTO req = JsonUtils.convertObject(message.getParams(),
  101 + IotDeviceShadowUpdateReqDTO.class);
101 102 if (req == null) {
102 103 sendShadowReplySafely(device, message.getServerId(), IotDeviceShadowReplyDTO.errorOf(
103 104 extractInnerMethod(message.getParams()), 0L,
... ... @@ -165,7 +166,8 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService {
165 166 if (req.getVersion() != null && req.getVersion() == -1L) {
166 167 try {
167 168 resetShadowWithRetry(deviceId);
168   - sendShadowReplySafely(device, serverId, IotDeviceShadowReplyDTO.updateReplyOf(0L, DateUtil.currentSeconds()));
  169 + sendShadowReplySafely(device, serverId,
  170 + IotDeviceShadowReplyDTO.updateReplyOf(0L, DateUtil.currentSeconds()));
169 171 } catch (ShadowCasExhaustedException ex) {
170 172 handleCasExhaustedForDevice(device, serverId, ex);
171 173 }
... ... @@ -266,10 +268,12 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService {
266 268 }
267 269 Map<String, Object> mergedReported = mergeMap(latest.getReported(), req.getState().getReported());
268 270 if (mergedReported.size() > MAX_PROPERTY_COUNT) {
269   - throw new ShadowUpdateRejectedException(latest.getVersion(), IotDeviceShadowErrorCodeEnum.PROPERTY_COUNT_EXCEEDED);
  271 + throw new ShadowUpdateRejectedException(latest.getVersion(),
  272 + IotDeviceShadowErrorCodeEnum.PROPERTY_COUNT_EXCEEDED);
270 273 }
271 274 long now = DateUtil.currentSeconds();
272   - Map<String, Object> metadata = latest.getMetadata() != null ? new HashMap<>(latest.getMetadata()) : new HashMap<>();
  275 + Map<String, Object> metadata = latest.getMetadata() != null ? new HashMap<>(latest.getMetadata())
  276 + : new HashMap<>();
273 277 IotDeviceShadowProtocolUtils.putReportedTimestamps(metadata, req.getState().getReported().keySet(), now);
274 278 return IotDeviceShadowDO.builder()
275 279 .reported(mergedReported)
... ... @@ -282,7 +286,8 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService {
282 286 private IotDeviceShadowDO buildDesiredUpdate(IotDeviceShadowDO latest, Map<String, Object> desired) {
283 287 long now = DateUtil.currentSeconds();
284 288 Map<String, Object> mergedDesired = mergeMap(latest.getDesired(), desired);
285   - Map<String, Object> metadata = latest.getMetadata() != null ? new HashMap<>(latest.getMetadata()) : new HashMap<>();
  289 + Map<String, Object> metadata = latest.getMetadata() != null ? new HashMap<>(latest.getMetadata())
  290 + : new HashMap<>();
286 291 IotDeviceShadowProtocolUtils.putDesiredTimestamps(metadata, desired.keySet(), now);
287 292 return IotDeviceShadowDO.builder()
288 293 .desired(mergedDesired)
... ... @@ -307,7 +312,8 @@ public class IotDeviceShadowServiceImpl implements IotDeviceShadowService {
307 312 *
308 313 * @return 写入成功后的 update DO(回填未更新字段,便于构建全量回复)
309 314 */
310   - private IotDeviceShadowDO casUpdateShadow(Long deviceId, Function<IotDeviceShadowDO, IotDeviceShadowDO> updateBuilder) {
  315 + private IotDeviceShadowDO casUpdateShadow(Long deviceId,
  316 + Function<IotDeviceShadowDO, IotDeviceShadowDO> updateBuilder) {
311 317 Long lastKnownVersion = 0L;
312 318 for (int i = 0; i < OPTIMISTIC_LOCK_RETRY; i++) {
313 319 // 首次尝试走缓存,重试时直读 DB 获取最新 version
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/shadow/IotDeviceShadowServiceImplTest.java
... ... @@ -339,16 +339,17 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest {
339 339 // 前两次 select 返回 v=1 的快照,之后返回 v=2 的最新快照
340 340 return selectCount.incrementAndGet() <= 2 ? staleShadow : latestShadow;
341 341 });
342   - when(deviceShadowMapper.updateByDeviceIdAndVersion(any(), eq(device.getId()), anyLong())).thenAnswer(invocation -> {
343   - Long expectVersion = invocation.getArgument(2);
344   - if (expectVersion == 1L) {
345   - return 0;
346   - }
347   - if (expectVersion == 2L) {
348   - return 1;
349   - }
350   - return 0;
351   - });
  342 + when(deviceShadowMapper.updateByDeviceIdAndVersion(any(), eq(device.getId()), anyLong()))
  343 + .thenAnswer(invocation -> {
  344 + Long expectVersion = invocation.getArgument(2);
  345 + if (expectVersion == 1L) {
  346 + return 0;
  347 + }
  348 + if (expectVersion == 2L) {
  349 + return 1;
  350 + }
  351 + return 0;
  352 + });
352 353  
353 354 Map<String, Object> reported = new HashMap<>();
354 355 reported.put("Temperature", 25);
... ... @@ -364,7 +365,8 @@ public class IotDeviceShadowServiceImplTest extends BaseMockitoUnitTest {
364 365 service.handleDeviceShadowUpdate(device, message);
365 366  
366 367 ArgumentCaptor<IotDeviceShadowDO> updateCaptor = ArgumentCaptor.forClass(IotDeviceShadowDO.class);
367   - verify(deviceShadowMapper, atLeastOnce()).updateByDeviceIdAndVersion(updateCaptor.capture(), eq(device.getId()), any());
  368 + verify(deviceShadowMapper, atLeastOnce()).updateByDeviceIdAndVersion(updateCaptor.capture(), eq(device.getId()),
  369 + any());
368 370 IotDeviceShadowDO finalUpdate = updateCaptor.getAllValues().get(updateCaptor.getAllValues().size() - 1);
369 371 // 重试时应基于 v=2 的最新 reported 合并,保留 Humidity=50
370 372 assertEquals(25, finalUpdate.getReported().get("Temperature"));
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/shadow/IotDeviceShadowErrorCodeEnum.java
... ... @@ -6,8 +6,10 @@ import lombok.Getter;
6 6 /**
7 7 * IoT 设备影子设备侧错误码枚举
8 8 * <p>
9   - * 设备侧 wire code,仅用于回填 {@code update_reply.code/msg},不走 {@link cn.iocoder.yudao.framework.common.pojo.CommonResult}。
10   - * 服务端内部异常请使用 {@link cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants}(如 {@code DEVICE_SHADOW_NOT_ENABLED})。
  9 + * 设备侧 wire code,仅用于回填 {@code update_reply.code/msg},不走
  10 + * {@link cn.iocoder.yudao.framework.common.pojo.CommonResult}。
  11 + * 服务端内部异常请使用 {@link cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants}(如
  12 + * {@code DEVICE_SHADOW_NOT_ENABLED})。
11 13 *
12 14 * @author 芋道源码
13 15 */
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/shadow/IotDeviceShadowReplyDTO.java
... ... @@ -14,8 +14,10 @@ import static cn.iocoder.yudao.module.iot.core.util.IotDeviceShadowProtocolUtils
14 14 /**
15 15 * IoT 设备影子下行 Reply DTO
16 16 * <p>
17   - * 扁平对齐 CommonResult + method,用于 {@link IotDeviceMessageMethodEnum#SHADOW_GET} 透传线 body。
18   - * 错误回复的 {@link #data} 仍携带 version 供设备 resync,有意偏离纯 CommonResult(error 时 data 通常为 null)。
  17 + * 扁平对齐 CommonResult + method,用于 {@link IotDeviceMessageMethodEnum#SHADOW_GET}
  18 + * 透传线 body。
  19 + * 错误回复的 {@link #data} 仍携带 version 供设备 resync,有意偏离纯 CommonResult(error 时 data
  20 + * 通常为 null)。
19 21 *
20 22 * @author 芋道源码
21 23 */
... ... @@ -40,12 +42,13 @@ public class IotDeviceShadowReplyDTO {
40 42 private String msg;
41 43  
42 44 /**
43   - * 业务数据:get_reply 为 {state, version, timestamp};update_reply 为 {version, timestamp}
  45 + * 业务数据:get_reply 为 {state, version, timestamp};update_reply 为 {version,
  46 + * timestamp}
44 47 */
45 48 private Object data;
46 49  
47 50 public static IotDeviceShadowReplyDTO getReplyOf(Map<String, Object> desired, Map<String, Object> reported,
48   - Long version, Long timestamp) {
  51 + Long version, Long timestamp) {
49 52 Map<String, Object> state = new HashMap<>();
50 53 state.put(STATE_DESIRED, desired != null ? desired : new HashMap<>());
51 54 state.put(STATE_REPORTED, reported != null ? reported : new HashMap<>());
... ... @@ -57,11 +60,12 @@ public class IotDeviceShadowReplyDTO {
57 60 }
58 61  
59 62 public static IotDeviceShadowReplyDTO updateReplyOf(Long version, Long timestamp) {
60   - return of(INNER_METHOD_UPDATE_REPLY, SUCCESS.getCode(), SUCCESS.getMsg(), dataWithVersionTimestamp(version, timestamp));
  63 + return of(INNER_METHOD_UPDATE_REPLY, SUCCESS.getCode(), SUCCESS.getMsg(),
  64 + dataWithVersionTimestamp(version, timestamp));
61 65 }
62 66  
63 67 public static IotDeviceShadowReplyDTO errorOf(String innerMethod, Long version, Integer errorCode,
64   - String errorMsg, Long timestamp) {
  68 + String errorMsg, Long timestamp) {
65 69 return of(toReplyMethod(innerMethod), errorCode, errorMsg, dataWithVersionTimestamp(version, timestamp));
66 70 }
67 71  
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java
... ... @@ -43,7 +43,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
43 43  
44 44 @Override
45 45 public byte[] serializeDeviceMessage(IotDeviceMessage message,
46   - String productKey, String deviceName) {
  46 + String productKey, String deviceName) {
47 47 if (IotDeviceMessageMethodEnum.isPassthrough(message.getMethod())) {
48 48 IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
49 49 if (device == null) {
... ... @@ -67,7 +67,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
67 67  
68 68 @Override
69 69 public byte[] serializeDeviceMessage(IotDeviceMessage message,
70   - IotSerializeTypeEnum serializeType) {
  70 + IotSerializeTypeEnum serializeType) {
71 71 if (IotDeviceMessageMethodEnum.isPassthrough(message.getMethod())) {
72 72 return encodePassthrough(message, serializeType, null, null);
73 73 }
... ... @@ -83,7 +83,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
83 83  
84 84 @Override
85 85 public IotDeviceMessage deserializeDeviceMessage(byte[] bytes,
86   - String productKey, String deviceName) {
  86 + String productKey, String deviceName) {
87 87 // 1.1 获取设备信息
88 88 IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
89 89 if (device == null) {
... ... @@ -111,7 +111,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
111 111  
112 112 @Override
113 113 public IotDeviceMessage deserializePassthroughMessage(byte[] payload, String method,
114   - String productKey, String deviceName) {
  114 + String productKey, String deviceName) {
115 115 IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
116 116 if (device == null) {
117 117 throw exception(DEVICE_NOT_EXISTS, productKey, deviceName);
... ... @@ -128,7 +128,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
128 128  
129 129 @Override
130 130 public void sendDeviceMessage(IotDeviceMessage message,
131   - String productKey, String deviceName, String serverId) {
  131 + String productKey, String deviceName, String serverId) {
132 132 // 1. 获取设备信息
133 133 IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName);
134 134 if (device == null) {
... ... @@ -148,7 +148,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
148 148 * @param serverId 设备连接的 serverId
149 149 */
150 150 private void appendDeviceMessage(IotDeviceMessage message,
151   - IotDeviceRespDTO device, String serverId) {
  151 + IotDeviceRespDTO device, String serverId) {
152 152 message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now())
153 153 .setDeviceId(device.getId()).setTenantId(device.getTenantId()).setServerId(serverId);
154 154 // 特殊:如果设备没有指定 requestId,则使用 messageId
... ... @@ -161,7 +161,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
161 161 * 影子透传编码;不支持的 serializeType 返回 null,由下行发布方跳过发送
162 162 */
163 163 private byte[] encodePassthrough(IotDeviceMessage message, IotSerializeTypeEnum serializeType,
164   - String productKey, String deviceName) {
  164 + String productKey, String deviceName) {
165 165 return passthroughRaw("encodePassthrough", serializeType, productKey, deviceName, false,
166 166 serializer -> serializer.encodeRaw(message.getParams()));
167 167 }
... ... @@ -170,14 +170,14 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
170 170 * 影子透传解码;解析失败或不支持的 serializeType 返回 null
171 171 */
172 172 private Map<String, Object> decodePassthrough(byte[] bytes, IotSerializeTypeEnum serializeType,
173   - String productKey, String deviceName) {
  173 + String productKey, String deviceName) {
174 174 return passthroughRaw("decodePassthrough", serializeType, productKey, deviceName, true,
175 175 serializer -> serializer.decodeRaw(bytes));
176 176 }
177 177  
178 178 private <T> T passthroughRaw(String op, IotSerializeTypeEnum serializeType,
179   - String productKey, String deviceName, boolean decode,
180   - Function<IotMessageSerializer, T> action) {
  179 + String productKey, String deviceName, boolean decode,
  180 + Function<IotMessageSerializer, T> action) {
181 181 IotMessageSerializer serializer = getPassthroughSerializer(serializeType);
182 182 if (serializer == null) {
183 183 warnPassthroughUnsupported(op, serializeType, productKey, deviceName);
... ... @@ -204,7 +204,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
204 204 }
205 205  
206 206 private void warnPassthroughUnsupported(String op, IotSerializeTypeEnum serializeType,
207   - String productKey, String deviceName) {
  207 + String productKey, String deviceName) {
208 208 if (productKey != null && deviceName != null) {
209 209 log.warn("[{}][影子透传跳过:设备({}/{}) serializeType={} 不支持,目前仅 JSON 协议设备可用]",
210 210 op, productKey, deviceName, serializeType);
... ...