Commit 0caf6464028614824ab4dc7c491815e5b45897d7
1 parent
50adebd1
fix(iot): 使用复合主键修复设备消息日志同毫秒覆盖问题
- 将 device_message 改为 ts + id 复合主键,保留同毫秒的不同消息 - 消息 id 为空时生成 UUID,并补充兜底单测 - 查询按 ts、id 倒序排序,保证同毫秒数据分页稳定 验证:消息服务 14 个单测通过,真实 TDengine 3.3.8.4 集成验证通过 注意:已有消息表需单独迁移,服务端需支持 VARCHAR 复合键 相关讨论:https://t.zsxq.com/YP2ao 相关 PR:https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1598
Showing
3 changed files
with
22 additions
and
4 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java
| ... | ... | @@ -95,6 +95,9 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { |
| 95 | 95 | if (messageDO.getData() != null) { |
| 96 | 96 | messageDO.setData(JsonUtils.toJsonString(messageDO.getData())); |
| 97 | 97 | } |
| 98 | + if (StrUtil.isEmpty(messageDO.getId())) { | |
| 99 | + messageDO.setId(IotDeviceMessageUtils.generateMessageId()); | |
| 100 | + } | |
| 98 | 101 | if (messageDO.getTs() == null) { |
| 99 | 102 | messageDO.setTs(System.currentTimeMillis()); |
| 100 | 103 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | <update id="createSTable"> |
| 8 | 8 | CREATE STABLE IF NOT EXISTS device_message ( |
| 9 | 9 | ts TIMESTAMP, |
| 10 | - id NCHAR(50), | |
| 10 | + id VARCHAR(50) COMPOSITE KEY, | |
| 11 | 11 | report_time TIMESTAMP, |
| 12 | 12 | tenant_id BIGINT, |
| 13 | 13 | server_id NCHAR(50), |
| ... | ... | @@ -63,7 +63,7 @@ |
| 63 | 63 | AND identifier = #{reqVO.identifier} |
| 64 | 64 | </if> |
| 65 | 65 | </where> |
| 66 | - ORDER BY ts DESC | |
| 66 | + ORDER BY ts DESC, id DESC | |
| 67 | 67 | </select> |
| 68 | 68 | |
| 69 | 69 | <select id="selectListByRequestIdsAndReply" resultType="cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO"> |
| ... | ... | @@ -76,7 +76,7 @@ |
| 76 | 76 | <foreach collection="requestIds" item="requestId" open="(" close=")" separator=","> |
| 77 | 77 | #{requestId} |
| 78 | 78 | </foreach> |
| 79 | - ORDER BY ts DESC | |
| 79 | + ORDER BY ts DESC, id DESC | |
| 80 | 80 | </select> |
| 81 | 81 | |
| 82 | 82 | <select id="selectCountByCreateTime" resultType="Long"> |
| ... | ... | @@ -106,4 +106,4 @@ |
| 106 | 106 | GROUP BY TIMETRUNCATE(ts, 1h) |
| 107 | 107 | </select> |
| 108 | 108 | |
| 109 | -</mapper> | |
| 110 | 109 | \ No newline at end of file |
| 110 | +</mapper> | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImplTest.java
| ... | ... | @@ -104,6 +104,21 @@ public class IotDeviceMessageServiceImplTest extends BaseMockitoUnitTest { |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | @Test |
| 107 | + public void testCreateDeviceLogAsync_idFallback_whenBlank() { | |
| 108 | + // 准备:构造一条 id 为空的消息,复合主键字段不能为 null | |
| 109 | + IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()) | |
| 110 | + .setId(null); | |
| 111 | + | |
| 112 | + // 调用 | |
| 113 | + service.createDeviceLogAsync(message); | |
| 114 | + | |
| 115 | + // 断言:mapper.insert 接收到的 messageDO 已补充 id | |
| 116 | + ArgumentCaptor<IotDeviceMessageDO> captor = ArgumentCaptor.forClass(IotDeviceMessageDO.class); | |
| 117 | + verify(deviceMessageMapper).insert(captor.capture()); | |
| 118 | + assertNotNull(captor.getValue().getId(), "id 不应为空"); | |
| 119 | + } | |
| 120 | + | |
| 121 | + @Test | |
| 107 | 122 | public void testCreateDeviceLogAsync_swallowMapperException() { |
| 108 | 123 | // 准备:mapper.insert 抛异常,验证 @Async 方法内部 try/catch 兜底,不向上抛 |
| 109 | 124 | IotDeviceMessage message = buildMessage(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); | ... | ... |