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 提供下载。是否继续?✅

posted @ 2025-06-24 07:45  九局下半的胖虎  阅读(57)  评论(0)    收藏  举报