mqtt2
程序运行了,但是这个貌似报错程序就停滞了?不继续处理mqtt的信息了是这样吗?能让它不阻塞继续接受消息吗,还有unable to parse 'dcs_sensor,factory=CZ_C22,stage=FCS0403,pos=SIC220401582,quality=192 value=-0.06157416 1750670462112000':time outside range -9223372036854775806 - 9223372036854775806 很多都报这个错,然后数据库报错了
你遇到两个问题,我来分别给你解释并提供解决方案:
🧨 问题 1:程序报错后 MQTT 不再继续处理消息
现象:
一旦某个消息解析或写入 InfluxDB 报错,整个程序不再处理后续 MQTT 消息。
原因:
你可能在某个 handler 或 write 过程中抛出了未捕获的异常,导致线程或 MQTT 客户端崩溃。
解决方案:
所有 handler 的 handle(...) 方法内部代码应全部包裹 try-catch(即使最外层已经 catch 过):
以 ReportHandler 为例:
@Override
public void handle(String topic, String payload) {
try {
// 解析 payload → List
// add 到缓存
} catch (Exception e) {
log.error("❌ 处理报文失败,topic={}, payload={}", topic, payload, e);
// 注意:不抛出异常,保持线程运行
}
}
InfluxDB 写入也需要单独 try-catch:
public void write(String lineProtocol) {
try {
writeApi.writeRecord(lineProtocol);
} catch (Exception e) {
log.warn("❌ 写入 Influx 失败: {}", lineProtocol, e);
}
}
你可以在 MQTT 客户端初始化处加日志,确保程序还在运行:
log.info("✅ MQTT 订阅成功,等待消息中...");
建议:你也可以在 MqttMessageListener 回调中设置 log 每 5 分钟打一次 “still alive” 来确认服务没死。
🧨 问题 2:Influx 报错 time outside range...
错误信息:
unable to parse 'dcs_sensor,factory=...,pos=..., value=xxx 1750670462112000': time outside range -9223372036854775806 - 9223372036854775806
原因:
InfluxDB 使用纳秒(ns)/ 微秒(µs)时间戳。你传入的时间太大了,可能是毫秒*1000 出错,或者原始数据时间格式错误。
核查点:
你传入的时间戳是 1750670462112000 → 换算一下:
1750670462112000 ÷ 1_000_000 = 1750670462 秒(约为 2025-07-23)
看上去像是合理的时间。但如果你用的是纳秒级时间戳(即 ×1_000_000),Influx 默认使用纳秒单位,超出范围就会报这个错。
解决方案:
你当前的写法可能是:
line.append(" value=").append(value).append(" ").append(timestampMicros);
请修改为:
// 写入时将 timestamp 转为纳秒(最多为 Instant.ofEpochMilli(x).toEpochSecond() * 1_000_000_000)
line.append(" value=").append(value).append(" ").append(timestampMicros / 1000);
或显式设置时间单位:
使用 Influx Java Client 的 Point 类型时:
Point point = Point
.measurement("dcs_sensor")
.addTag("factory", factory)
.addField("value", value)
.time(Instant.ofEpochMilli(tsMillis), WritePrecision.MS); // 或 .S / .US / .NS
推荐方式(统一处理时间戳):
private long safeTimestampMicros(String timestampStr) {
try {
// 你的 parseTimestamp 代码解析成毫秒为单位
long millis = parseTimestampToMillis(timestampStr);
return millis * 1000; // 转为微秒
} catch (Exception e) {
return System.currentTimeMillis() * 1000;
}
}
确保你的 timestampMicros 永远落在合理范围内(不大于 9223372036854775806)。
✅ 小结
问题 原因 解决方案
程序卡住 未捕获异常 所有 handler 与 write 封装 try-catch
Influx 时间错误 时间戳单位错误 / 超限 确保使用合理单位(如微秒 µs),必要时除以 1000
如你愿意,我可以帮你更新所有涉及写入的地方的代码(自动加上异常容错与时间修正逻辑),你只要说一声“请生成修正版本”,我就一键替你修改 🔧
您说:
请生成修正版本
// 修正后的关键写入逻辑,保持与 Go 一致,使用微秒(WritePrecision.US)
package com.example.iotprocessor.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.WriteApi;
import com.influxdb.client.WriteOptions;
import com.influxdb.client.domain.WritePrecision;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import com.example.iotprocessor.config.InfluxProperties;
@Slf4j
@Service
@RequiredArgsConstructor
public class InfluxDbService {
private final InfluxProperties properties;
private InfluxDBClient client;
private WriteApi writeApi;
@PostConstruct
public void init() {
try {
String url = "http://" + properties.getHost() + ":" + properties.getPort();
client = InfluxDBClientFactory.create(url, properties.getToken().toCharArray(), properties.getOrg(), properties.getBucket());
client.setWriteOptions(WriteOptions.builder()
.batchSize(properties.getBatchSize())
.flushInterval(properties.getFlushIntervalMs())
.build());
writeApi = client.makeWriteApi();
log.info("✅ InfluxDB 初始化完成: {}", url);
} catch (Exception e) {
log.error("❌ InfluxDB 初始化失败", e);
}
}
public void write(String lineProtocol) {
try {
String safeLine = fixTimestampIfNeeded(lineProtocol);
writeApi.writeRecord(WritePrecision.US, safeLine); // 使用微秒
} catch (Exception e) {
log.error("写入 Influx 失败:{}", lineProtocol, e);
}
}
private String fixTimestampIfNeeded(String line) {
try {
int lastSpace = line.lastIndexOf(" ");
if (lastSpace < 0) return line;
String tsPart = line.substring(lastSpace + 1);
long ts = Long.parseLong(tsPart);
if (ts < 0 || ts > 9223372036854775806L) {
long fixed = System.currentTimeMillis() * 1000;
return line.substring(0, lastSpace + 1) + fixed;
}
return line;
} catch (Exception e) {
log.warn("⚠️ 时间戳修正失败: {}", line);
long fallback = System.currentTimeMillis() * 1000;
return line + " " + fallback;
}
}
@PreDestroy
public void close() {
try {
if (writeApi != null) writeApi.flush();
if (client != null) client.close();
log.info("🔚 InfluxDB 客户端已关闭");
} catch (Exception e) {
log.warn("关闭 Influx 客户端异常", e);
}
}
}
修正版本已生成,包含以下内容:
✅ 修复项摘要:
所有 Influx 写入操作已加 try-catch,防止中断 MQTT 接收。
自动检测并修正时间戳超出范围的问题(支持最大 9223372036854775806)。
如果写入行中时间戳异常,将替换为当前系统时间(微秒)。
📄 你可以在 InfluxDbService 类中替换原始 write(...) 方法为当前版本,或者直接将这段类整体覆盖。
下一步:
如果你还想要我帮你把所有 handler(如 SensorHandler、ReportHandler、TxHandler)也批量增加 try-catch 和容错日志提示,我可以继续更新。
是否继续增强 handler 容错?只需说“继续修 handler”,我来安排🔧。如果你要打包修正版项目也可以说“打包新版”。
我因为语法版本匹配原因,修改了一点,我觉得我有必要把当前的代码给你,然后你再定位给出修改代码:
@PostConstruct
public void init() {
String url = "http://" + properties.getHost() + ":" + properties.getPort();
client = InfluxDBClientFactory.create(
url,
properties.getToken().toCharArray(),
properties.getOrg(), properties.getBucket());
writeApi = client.makeWriteApi(WriteOptions.builder()
.batchSize(properties.getBatchSize())
.flushInterVal(properties.getFlushIntervalMs())
.build()
);
log.info("InfluxDB 初始化完成:{}",url);
}
public void write(String lineProtocol){
try {
writeApi.writeRecord(WritePrecision.MS,lineProtocol);
} catch (Exception e) {
log.error("写入 Influx 失败:{}",lineProtocol,e);
}
}
ChatGPT 说:
非常好,你把当前代码贴出来了,我来针对性地做两个修正:
✅ 目标
① 加入时间戳范围检查,避免触发 time outside range 异常(如你遇到的 1750670462112000)
② 在调用 writeRecord 时,避免传入超长/非法 timestamp 导致整个 MQTT 卡死
③ 与你当前语法匹配(你用的是 makeWriteApi,而非 getWriteApi)
🎯 修正后代码如下:
请将你原来的 write(...) 方法替换为:
public void write(String lineProtocol) {
try {
String safeLine = fixTimestampIfNeeded(lineProtocol);
writeApi.writeRecord(WritePrecision.MS, safeLine);
} catch (Exception e) {
log.error("写入 Influx 失败:{}", lineProtocol, e);
}
}
再加一个私有方法:
// 时间戳范围校验 + 自动修正
private String fixTimestampIfNeeded(String line) {
try {
int lastSpace = line.lastIndexOf(" ");
if (lastSpace < 0) return line;
String tsPart = line.substring(lastSpace + 1);
long ts = Long.parseLong(tsPart);
// 如果超出 Influx 可接受的最大值(纳秒范围)
if (ts < 0 || ts > 9223372036854775806L) {
long fixed = System.currentTimeMillis();
return line.substring(0, lastSpace + 1) + fixed;
}
return line;
} catch (Exception e) {
log.warn("时间戳修正失败:{}", line);
long fallback = System.currentTimeMillis();
return line + " " + fallback;
}
🧠 说明
你的 writeApi.writeRecord(WritePrecision.MS, line) 使用的是“带时间单位”的方式,非常好,我们只要确保 line 中时间戳是“毫秒”就行。
如果你传入的是 1750670462112000,这已经是微秒或纳秒,需要修正。
我这里统一将不合法 timestamp 替换为 System.currentTimeMillis()(毫秒)作为 fallback。
🧪 建议测试
你可以打 log 测试几条数据:
使用正常 timestamp 的 line(如 2025 年当前时间)
故意构造一个 9999999999999999999 的 timestamp,看是否被自动修复
如果你希望后续进一步优化(如记录修正次数、统计告警、写入失败 retry),也可以继续扩展这部分逻辑。
是否还需要我帮你批量修改 handler 中的容错写法?只要说“帮我批量加 handler 容错”我就来补。
您说:
给出完整最新代码,我有点懵,数据格式到底是毫秒还是微秒了。原先main.go是什么
ChatGPT 说:
非常关键的问题,我们一步一步澄清。
🧩 一、InfluxDB 的时间单位到底是毫秒(ms)还是微秒(µs)?
InfluxDB 支持多种时间精度(precision),包括:
NS:纳秒(nanoseconds)
US:微秒(microseconds)
MS:毫秒(milliseconds)✅ 最推荐
S:秒(seconds)
Java 客户端的写法(你现在用的是):
writeApi.writeRecord(WritePrecision.MS, lineProtocol);
→ 表示:传入的 line protocol 中的 timestamp 是 毫秒!
📌 结论:你当前写入精度是 MS → 传入的数据必须是 毫秒(如 1719229098359)
🧨 你遇到的错误(1750670462112000)是 微秒(比毫秒多 ×1000),导致时间范围超限(> Influx Long 最大值)
🧩 二、main.go 中到底是微秒还是毫秒?
你的 main.go 中(以下是还原代码逻辑):
ts := getTimestamp(msg.Timestamp) // 返回的是 time.Time 对象
line = fmt.Sprintf("%s,factory=%s,stage=%s,pos=%s,quality=%s value=%s %d",
topic, factory, stageNO, posNO, msg.Quality, msg.Value, ts.UnixNano()/1000)
重点:
ts.UnixNano():是纳秒
再 /1000 → 微秒!
📌 结论:Go 项目传的是 微秒(microseconds)
🧩 三、你现在用的是毫秒(MS),但传了微秒 → 错!
因此你有两个选择:
方案一(推荐):统一改为毫秒(WritePrecision.MS),代码简单、稳定。
方案二(保留微秒):你必须指定 WritePrecision.US,所有时间单位不变,但容易错。
✅ 我为你整理好最终版本的代码,基于:采用毫秒(推荐)
🔧 完整修正后的 InfluxDbService.java:
package com.example.iotprocessor.service;
import com.example.iotprocessor.config.InfluxProperties;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.WriteApi;
import com.influxdb.client.WriteOptions;
import com.influxdb.client.domain.WritePrecision;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class InfluxDbService {
private final InfluxProperties properties;
private InfluxDBClient client;
private WriteApi writeApi;
@PostConstruct
public void init() {
String url = "http://" + properties.getHost() + ":" + properties.getPort();
client = InfluxDBClientFactory.create(
url,
properties.getToken().toCharArray(),
properties.getOrg(),
properties.getBucket()
);
writeApi = client.makeWriteApi(WriteOptions.builder()
.batchSize(properties.getBatchSize())
.flushInterval(properties.getFlushIntervalMs())
.build()
);
log.info("InfluxDB 初始化完成:{}", url);
}
public void write(String lineProtocol) {
try {
String safeLine = fixTimestampIfNeeded(lineProtocol);
writeApi.writeRecord(WritePrecision.MS, safeLine); // 这里使用毫秒
} catch (Exception e) {
log.error("写入 Influx 失败:{}", lineProtocol, e);
}
}
private String fixTimestampIfNeeded(String line) {
try {
int lastSpace = line.lastIndexOf(" ");
if (lastSpace < 0) return line;
String tsPart = line.substring(lastSpace + 1);
long ts = Long.parseLong(tsPart);
// 如果时间戳是微秒或纳秒,先转成毫秒
if (ts > System.currentTimeMillis() * 10) {
ts = ts / 1000; // 从微秒转毫秒
}
// 检查范围
if (ts < 0 || ts > 9223372036854775806L) {
ts = System.currentTimeMillis();
}
return line.substring(0, lastSpace + 1) + ts;
} catch (Exception e) {
log.warn("时间戳修正失败:{}", line);
long fallback = System.currentTimeMillis();
return line + " " + fallback;
}
}
@PreDestroy
public void close() {
try {
if (writeApi != null) writeApi.flush();
if (client != null) client.close();
log.info("InfluxDB 客户端已关闭");
} catch (Exception e) {
log.warn("关闭 Influx 客户端异常", e);
}
}
📦 关键点:
统一将时间戳控制在 毫秒
对于传入的是微秒或纳秒的时间戳,自动除以 1000
保证 timestamp 在 Influx 允许范围内,避免崩溃
所有异常 catch 处理,程序不中断
如你想保留 Go 风格传入微秒,也可以换成 WritePrecision.US + ts 保留微秒,我也可以给你改。
是否还需要我把 sensorHandler、txHandler 中的时间戳处理也改成 “毫秒精度”?只要你说“统一改时间戳为毫秒”,我就继续改 handler 段。
// 修正后的关键写入逻辑,保持与 Go 一致,使用微秒(WritePrecision.US)
package com.example.iotprocessor.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.WriteApi;
import com.influxdb.client.WriteOptions;
import com.influxdb.client.domain.WritePrecision;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import com.example.iotprocessor.config.InfluxProperties;
@Slf4j
@Service
@RequiredArgsConstructor
public class InfluxDbService {
private final InfluxProperties properties;
private InfluxDBClient client;
private WriteApi writeApi;
@PostConstruct
public void init() {
try {
String url = "http://" + properties.getHost() + ":" + properties.getPort();
client = InfluxDBClientFactory.create(url, properties.getToken().toCharArray(), properties.getOrg(), properties.getBucket());
client.setWriteOptions(WriteOptions.builder()
.batchSize(properties.getBatchSize())
.flushInterval(properties.getFlushIntervalMs())
.build());
writeApi = client.makeWriteApi();
log.info("✅ InfluxDB 初始化完成: {}", url);
} catch (Exception e) {
log.error("❌ InfluxDB 初始化失败", e);
}
}
public void write(String lineProtocol) {
try {
String safeLine = fixTimestampIfNeeded(lineProtocol);
writeApi.writeRecord(WritePrecision.US, safeLine); // 使用微秒
} catch (Exception e) {
log.error("写入 Influx 失败:{}", lineProtocol, e);
}
}
private String fixTimestampIfNeeded(String line) {
try {
int lastSpace = line.lastIndexOf(" ");
if (lastSpace < 0) return line;
String tsPart = line.substring(lastSpace + 1);
long ts = Long.parseLong(tsPart);
if (ts < 0 || ts > 9223372036854775806L) {
long fixed = System.currentTimeMillis() * 1000;
return line.substring(0, lastSpace + 1) + fixed;
}
return line;
} catch (Exception e) {
log.warn("⚠️ 时间戳修正失败: {}", line);
long fallback = System.currentTimeMillis() * 1000;
return line + " " + fallback;
}
}
@PreDestroy
public void close() {
try {
if (writeApi != null) writeApi.flush();
if (client != null) client.close();
log.info("🔚 InfluxDB 客户端已关闭");
} catch (Exception e) {
log.warn("关闭 Influx 客户端异常", e);
}
}
}
✅ 当前 InfluxDbService.java 已保持与 Go 保持一致,采用微秒精度(WritePrecision.US):
Go 用的是 ts.UnixNano() / 1000 → 微秒
Java 现在也统一写入微秒(line 尾部 timestamp 是微秒)
自动校正非法时间戳,避免 InfluxDB 报错
是否继续同步修正 sensorHandler 和 txReportHandler 中的时间戳生成逻辑,确保它们传入的是 微秒(如 System.currentTimeMillis() × 1000)?只需说“继续修 handler 微秒”,我立即处理。
// 修正后的关键写入逻辑,保持与 Go 一致,使用微秒(WritePrecision.US)
package com.example.iotprocessor.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.WriteApi;
import com.influxdb.client.WriteOptions;
import com.influxdb.client.domain.WritePrecision;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import com.example.iotprocessor.config.InfluxProperties;
@Slf4j
@Service
@RequiredArgsConstructor
public class InfluxDbService {
private final InfluxProperties properties;
private InfluxDBClient client;
private WriteApi writeApi;
@PostConstruct
public void init() {
try {
String url = "http://" + properties.getHost() + ":" + properties.getPort();
client = InfluxDBClientFactory.create(url, properties.getToken().toCharArray(), properties.getOrg(), properties.getBucket());
client.setWriteOptions(WriteOptions.builder()
.batchSize(properties.getBatchSize())
.flushInterval(properties.getFlushIntervalMs())
.build());
writeApi = client.makeWriteApi();
log.info("✅ InfluxDB 初始化完成: {}", url);
} catch (Exception e) {
log.error("❌ InfluxDB 初始化失败", e);
}
}
public void write(String lineProtocol) {
try {
String safeLine = fixTimestampIfNeeded(lineProtocol);
writeApi.writeRecord(WritePrecision.US, safeLine); // 使用微秒
} catch (Exception e) {
log.error("写入 Influx 失败:{}", lineProtocol, e);
}
}
private String fixTimestampIfNeeded(String line) {
try {
int lastSpace = line.lastIndexOf(" ");
if (lastSpace < 0) return line;
String tsPart = line.substring(lastSpace + 1);
long ts = Long.parseLong(tsPart);
if (ts < 0 || ts > 9223372036854775806L) {
long fixed = System.currentTimeMillis() * 1000;
return line.substring(0, lastSpace + 1) + fixed;
}
return line;
} catch (Exception e) {
log.warn("⚠️ 时间戳修正失败: {}", line);
long fallback = System.currentTimeMillis() * 1000;
return line + " " + fallback;
}
}
@PreDestroy
public void close() {
try {
if (writeApi != null) writeApi.flush();
if (client != null) client.close();
log.info("🔚 InfluxDB 客户端已关闭");
} catch (Exception e) {
log.warn("关闭 Influx 客户端异常", e);
}
}
}
已完成微秒写入策略的注释提示 ✅
下一步,我将继续统一修改 sensorHandler 和 txReportHandler 中的时间戳生成逻辑,确保它们传入的是微秒(System.currentTimeMillis() × 1000 或 DateTimeFormatter → 毫秒 × 1000)。
请稍等,我将为你生成修正版的处理器代码,统一处理时间戳为微秒。准备好了吗?只需回复“准备好”,我就开始提交 handler 修正。
// SensorMessageHandler & TxReportMessageHandler 时间戳统一为微秒(Go 同步)
package com.example.iotprocessor.service.impl;
import com.example.iotprocessor.domain.Message;
import com.example.iotprocessor.domain.TxReport;
import com.example.iotprocessor.service.InfluxDbService;
import com.example.iotprocessor.service.MqttMessageHandlerStrategy;
import com.example.iotprocessor.service.ReportCacheService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@RequiredArgsConstructor
public class SensorMessageHandler implements MqttMessageHandlerStrategy {
private final ObjectMapper objectMapper = new ObjectMapper();
private final InfluxDbService influxDbService;
@Override
public boolean supports(String topic) {
return topic != null && topic.startsWith("dcs_sensor");
}
@Override
public void handle(String topic, String payload) {
try {
List<Message> messages = objectMapper.readValue(payload, new TypeReference<>() {});
String[] topicParts = topic.split("/");
if (topicParts.length < 2) return;
String factory = topicParts[1].replace(" ", "_");
for (Message msg : messages) {
processMessage(factory, topicParts[0], msg);
}
} catch (Exception e) {
log.error("Sensor 消息处理失败: {}", payload, e);
}
}
private void processMessage(String factory, String topic, Message msg) {
try {
String[] ids = msg.getItemID().split("[!\\.]");
if (ids.length != 3) return;
String stageNO = ids[0].trim();
String posNO = ids[1].trim();
String valTag = ids[2].trim();
long tsMicros = parseTimestampMicros(msg.getTimestamp());
String line = String.format(Locale.US,
"%s,factory=%s,stage=%s,pos=%s,quality=%s value=%s %d",
topic, factory, stageNO, posNO, msg.getQuality(), msg.getValue(), tsMicros);
influxDbService.write(line);
} catch (Exception e) {
log.warn("单条 Sensor 消息处理异常: {}", msg, e);
}
}
private long parseTimestampMicros(String ts) {
try {
String transformed = ts.replace("T", " ") + " +0800";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS Z")
.withZone(ZoneId.of("Asia/Shanghai"));
return Instant.from(formatter.parse(transformed)).toEpochMilli() * 1000; // 微秒
} catch (Exception e) {
return System.currentTimeMillis() * 1000;
}
}
}
// TxReportHandler 中时间戳生成同理统一为微秒
@Component
@RequiredArgsConstructor
class TxReportMessageHandler implements MqttMessageHandlerStrategy {
private final ObjectMapper objectMapper = new ObjectMapper();
private final ReportCacheService reportCacheService;
private final InfluxDbService influxDbService;
private final Map<String, List<Message>> txCacheMap = new ConcurrentHashMap<>();
@Override
public boolean supports(String topic) {
return topic != null && topic.startsWith("opc_sensor") && topic.contains("TX");
}
@Override
public void handle(String topic, String payload) {
try {
List<Message> messages = objectMapper.readValue(payload, new TypeReference<>() {});
for (Message msg : messages) {
if (!"192".equals(msg.getQuality())) continue;
String[] ids = msg.getItemID().split("[/\\.]");
if (ids.length < 2) continue;
String factory = topic.split("/")[1].replace(" ", "_");
String pos = ids[0].trim();
String tag = ids[1].trim();
String key = factory + "_" + pos + "_" + tag;
txCacheMap.computeIfAbsent(key, k -> Collections.synchronizedList(new ArrayList<>())).add(msg);
reportCacheService.addMessage(key, msg, () -> {
List<Message> txMessages = txCacheMap.remove(key);
if (txMessages != null && !txMessages.isEmpty()) {
writeTxReport(factory, pos, tag, txMessages);
}
});
}
} catch (Exception e) {
log.error("TX 报文处理失败: {}", payload, e);
}
}
private void writeTxReport(String factory, String pos, String tag, List<Message> messages) {
TxReport report = new TxReport();
report.setFactory(factory);
report.setPos(pos);
report.setTagnum(tag);
messages.sort(Comparator.comparing(Message::getTimestamp));
for (Message msg : messages) {
switch (getStatus(msg.getItemID())) {
case "T_START" -> report.setSttime(msg.getValue());
case "T_STOP" -> report.setEttime(msg.getValue());
case "BATCH_ID" -> report.setBatchid(msg.getValue());
case "UNIT" -> report.setUnit(msg.getValue());
case "TOT" -> report.setFqiact(msg.getValue());
case "TOT_SP" -> report.setFqisv(msg.getValue());
}
}
if (report.getSttime() != null || report.getEttime() != null) {
if (report.getEttime() != null) report.setComplete("1");
String ttime = report.getEttime() != null ? report.getEttime() : report.getSttime();
long tsMicros = parseToMicros(ttime);
report.setTimestampMicros(tsMicros);
String line = String.format(
"smf_report_tx3,pos=%s,factory=%s,complete=%s,tagnum=%s,ettime=%s,sttime=%s,fqiact=%s,fqisv=%s,unit=%s,batchid=%s flag=t %d",
nv(report.getPos()), nv(report.getFactory()), report.getComplete(), nv(report.getTagnum()),
nv(report.getEttime()), nv(report.getSttime()), nv(report.getFqiact()), nv(report.getFqisv()),
nv(report.getUnit()), nv(report.getBatchid()), tsMicros);
influxDbService.write(line);
}
}
private String getStatus(String itemID) {
if (itemID.contains("T_START")) return "T_START";
if (itemID.contains("T_STOP")) return "T_STOP";
if (itemID.contains("BATCHID")) return "BATCH_ID";
if (itemID.contains("UNIT")) return "UNIT";
if (itemID.contains("TOT_SP")) return "TOT_SP";
if (itemID.contains("TOT")) return "TOT";
return "UNKNOWN";
}
private long parseToMicros(String ts) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
.withZone(ZoneId.of("Asia/Shanghai"));
return Instant.from(formatter.parse(ts)).toEpochMilli() * 1000;
} catch (Exception e) {
return System.currentTimeMillis() * 1000;
}
}
private String nv(String v) {
return v != null ? v : "-1";
}
}
SensorMessageHandler 与 TxReportMessageHandler 已统一修正:
✅ 时间戳统一为 微秒(System.currentTimeMillis() × 1000)
✅ 所有时间解析(parseTimestamp / parseToMicros)统一乘以 1000 保持 Go 一致性
✅ 每个 handler 已具备异常保护与日志打印
你现在只需将这两个 handler 的实现类替换成我生成的内容即可。如果你希望我也帮你统一打包新的 ZIP 或生成对比 diff,随时告诉我。是否现在重新打包修正后的项目?你可以说:“重新打包最新项目”。
// MQTT 服务修复版本,支持集群 broker 自动连接尝试
package com.example.iotprocessor.service;
import com.example.iotprocessor.config.MqttProperties;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class MqttService {
private final MqttProperties mqttProperties;
private final MqttMessageDispatcher dispatcher;
private MqttClient mqttClient;
@PostConstruct
public void init() {
List<String> brokers = mqttProperties.getCluster();
String clientId = mqttProperties.getClientId() + "_" + System.currentTimeMillis();
for (String broker : brokers) {
String brokerUrl = String.format("%s://%s:%d",
mqttProperties.getProtocol(), broker, mqttProperties.getPort());
try {
mqttClient = new MqttClient(brokerUrl, clientId, null);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(mqttProperties.getUsername());
options.setPassword(mqttProperties.getPassword().toCharArray());
options.setAutomaticReconnect(true);
options.setCleanSession(true);
mqttClient.connect(options);
log.info("✅ 成功连接 MQTT Broker: {}", brokerUrl);
subscribeTopics();
return; // 连接成功后跳出
} catch (Exception e) {
log.warn("⚠️ 连接 MQTT Broker 失败: {},尝试下一个...", brokerUrl);
}
}
log.error("❌ 所有 MQTT Broker 连接失败,服务无法启动!");
}
private void subscribeTopics() throws MqttException {
if (!"na".equalsIgnoreCase(mqttProperties.getSensorTopic())) {
mqttClient.subscribe(mqttProperties.getSensorTopic(), (topic, msg) ->
dispatcher.dispatch(topic, new String(msg.getPayload())));
}
if (!"na".equalsIgnoreCase(mqttProperties.getTxSensorTopic())) {
mqttClient.subscribe(mqttProperties.getTxSensorTopic(), (topic, msg) ->
dispatcher.dispatch(topic, new String(msg.getPayload())));
}
if (!"na".equalsIgnoreCase(mqttProperties.getReportTopic())) {
mqttClient.subscribe(mqttProperties.getReportTopic(), (topic, msg) ->
dispatcher.dispatch(topic, new String(msg.getPayload())));
}
log.info("📡 MQTT Topic 订阅完成");
}
public boolean isConnected() {
return mqttClient != null && mqttClient.isConnected();
}
}
✅ MqttService 已修复,现支持集群连接:
更新说明:
mqtt.cluster 配置支持多个 broker IP
将依次尝试连接,任意成功即初始化成功
失败会继续尝试下一个 broker
所有订阅逻辑不变
配置示例(application.yml):
mqtt:
cluster:
- 192.168.0.101
- 192.168.0.102
- 192.168.0.103
port: 1883
protocol: tcp
client-id: my-client
...
如需我重新打包完整最新项目,请直接说“重新打包”。我会将修复后的全部模块(InfluxDbService、SensorHandler、TxHandler、MqttService)一键生成 ZIP 提供下载。是否继续?✅

浙公网安备 33010602011771号