瀏覽代碼

Merge remote-tracking branch 'origin/master'

黄渊昊 1 周之前
父節點
當前提交
2606b7043a
共有 26 個文件被更改,包括 1146 次插入44 次删除
  1. 7 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/event/SensorDataEventListener.java
  2. 56 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/ModBusTcpServer.java
  3. 45 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/ModbusTcpServerChannelInitializer.java
  4. 134 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/adapter/ModbusDataHandlerAdapter.java
  5. 56 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/coder/MyDecoder.java
  6. 36 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/coder/MyEncoder.java
  7. 51 3
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/renke/config/JfcloudColdChainServerAutoConfiguration.java
  8. 6 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/renke/config/JfcloudColdChainServerProperties.java
  9. 5 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/dataclean/MonitorDataProcessor.java
  10. 236 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/dataclean/impl/AbsMeilingMonitorDataProcessor.java
  11. 208 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/dataclean/impl/ModbusMeilingTempProcessor.java
  12. 100 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/handler/impl/MeilingColdChainDataHandler.java
  13. 26 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/model/MeilingColdChainMessageData.java
  14. 28 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/controller/SimsController.java
  15. 2 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitordevice/enums/DeviceModelEnum.java
  16. 45 37
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitorscreen/service/impl/MonitorServiceImpl.java
  17. 11 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/controller/MonitorTargetController.java
  18. 9 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/entity/MonitorTarget.java
  19. 3 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/enums/MonitorStatusEnum.java
  20. 6 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/param/MonitorTargetAddParam.java
  21. 6 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/param/MonitorTargetAddWithRoomParam.java
  22. 7 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/param/MonitorTargetEditWithRoomParam.java
  23. 8 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/MonitorTargetService.java
  24. 43 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/impl/MonitorTargetServiceImpl.java
  25. 6 0
      snowy-web-app/src/main/resources/_sql/zhangjian_20251212_update.sql
  26. 6 1
      snowy-web-app/src/main/resources/application-master.properties

+ 7 - 1
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/event/SensorDataEventListener.java

@@ -17,6 +17,8 @@ import org.springframework.stereotype.Component;
 import vip.xiaonuo.coldchain.core.alarm.service.check.SensorAlarmChecker;
 import vip.xiaonuo.coldchain.core.bean.influxdb.DataType;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
+import vip.xiaonuo.coldchain.modular.monitortarget.enums.MonitorStatusEnum;
+import vip.xiaonuo.coldchain.modular.monitortarget.service.MonitorTargetService;
 
 import java.util.Objects;
 
@@ -26,6 +28,7 @@ import java.util.Objects;
 public class SensorDataEventListener {
     private final JfcloudInfluxDBService jfcloudInfluxDBService;
     private final SensorAlarmChecker sensorAlarmChecker;
+    private final MonitorTargetService monitorTargetService;
 
     @Async
     @EventListener
@@ -34,7 +37,10 @@ public class SensorDataEventListener {
         try {
             // 实时数据才预警提醒
             if (Objects.nonNull(sensorData.getType()) && sensorData.getType() == DataType.NEW.getCode()) {
-                sensorAlarmChecker.checkAlarm(sensorData);
+                //如果触发报警则修改对象为异常状态
+                if(sensorAlarmChecker.checkAlarm(sensorData)){
+//                    monitorTargetService.updateStatusByDeviceCode(sensorData.getDeviceId(),sensorData.getRoads(), MonitorStatusEnum.ERR);
+                }
             }
             jfcloudInfluxDBService.writePojo(sensorData);
             log.info("成功写入数据到 InfluxDB: {}", sensorData);

+ 56 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/ModBusTcpServer.java

@@ -0,0 +1,56 @@
+package vip.xiaonuo.coldchain.core.meiling;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import vip.xiaonuo.coldchain.core.service.dataprocess.handler.impl.MeilingColdChainDataHandler;
+import vip.xiaonuo.coldchain.modular.monitordevice.mapper.MonitorDeviceMapper;
+import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
+
+import java.net.InetSocketAddress;
+
+/**
+ * 基于netty开启监听modbus端口
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+@Slf4j
+public class ModBusTcpServer {
+    private final MeilingColdChainDataHandler meilingColdChainDataHandler;
+    private final MonitorDeviceService monitorDeviceService;
+
+    public ModBusTcpServer(MeilingColdChainDataHandler meilingColdChainDataHandler, MonitorDeviceService monitorDeviceService) {
+        this.meilingColdChainDataHandler = meilingColdChainDataHandler;
+        this.monitorDeviceService = monitorDeviceService;
+    }
+
+    public void start(InetSocketAddress address) {
+        EventLoopGroup bossGroup = new NioEventLoopGroup();
+        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
+
+        try {
+            ServerBootstrap bootstrap = (new ServerBootstrap()).group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
+                    .localAddress(address)
+                    .childHandler(new ModbusTcpServerChannelInitializer(meilingColdChainDataHandler,monitorDeviceService))
+                    .option(ChannelOption.SO_BACKLOG, 1024).childOption(ChannelOption.SO_KEEPALIVE, true).childOption(ChannelOption.TCP_NODELAY, true);
+            ChannelFuture future = bootstrap.bind(address).sync();
+            if (future.isSuccess()) {
+                log.info("modbus服务端开始监听端口:{}", address.getPort());
+            }
+            future.channel().closeFuture().sync();
+        } catch (Exception var7) {
+            var7.printStackTrace();
+            bossGroup.shutdownGracefully();
+            workerGroup.shutdownGracefully();
+        }
+
+    }
+
+}

+ 45 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/ModbusTcpServerChannelInitializer.java

@@ -0,0 +1,45 @@
+package vip.xiaonuo.coldchain.core.meiling;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.util.HashedWheelTimer;
+import vip.xiaonuo.coldchain.core.meiling.adapter.ModbusDataHandlerAdapter;
+import vip.xiaonuo.coldchain.core.meiling.coder.MyDecoder;
+import vip.xiaonuo.coldchain.core.meiling.coder.MyEncoder;
+import vip.xiaonuo.coldchain.core.service.dataprocess.handler.impl.MeilingColdChainDataHandler;
+import vip.xiaonuo.coldchain.modular.monitordevice.mapper.MonitorDeviceMapper;
+import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * modbus tcp服务器通道初始化器
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+public class ModbusTcpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
+      private MeilingColdChainDataHandler meilingColdChainDataHandler;
+      private MonitorDeviceService monitorDeviceService;
+
+    // 定时任务调度器(全局单例,高效处理大量定时任务) 默认五分钟
+    private final HashedWheelTimer timer = new HashedWheelTimer(300, TimeUnit.SECONDS);
+
+    public ModbusTcpServerChannelInitializer(MeilingColdChainDataHandler meilingColdChainDataHandler, MonitorDeviceService monitorDeviceMapper) {
+        this.meilingColdChainDataHandler = meilingColdChainDataHandler;
+        this.monitorDeviceService=monitorDeviceMapper;
+    }
+
+    @Override
+    protected void initChannel(SocketChannel socketChannel) throws Exception {
+        ChannelPipeline pipeline = socketChannel.pipeline();
+        pipeline.addLast("decoder", new MyDecoder());
+        pipeline.addLast("encoder", new MyEncoder());
+        if(this.meilingColdChainDataHandler instanceof MeilingColdChainDataHandler)
+        pipeline.addLast(new ChannelHandler[]{new ModbusDataHandlerAdapter(timer,meilingColdChainDataHandler,monitorDeviceService)});
+
+    }
+}

+ 134 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/adapter/ModbusDataHandlerAdapter.java

@@ -0,0 +1,134 @@
+package vip.xiaonuo.coldchain.core.meiling.adapter;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.Timeout;
+import lombok.extern.slf4j.Slf4j;
+import vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.MonitorDataProcessor;
+import vip.xiaonuo.coldchain.core.service.dataprocess.handler.ColdChainDataHandler;
+import vip.xiaonuo.coldchain.core.service.dataprocess.handler.impl.MeilingColdChainDataHandler;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.MeilingColdChainMessageData;
+import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDevice;
+import vip.xiaonuo.coldchain.modular.monitordevice.mapper.MonitorDeviceMapper;
+import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
+
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * modbus数据处理程序适配器
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+@Slf4j
+public class ModbusDataHandlerAdapter extends ChannelInboundHandlerAdapter {
+    private final HashedWheelTimer timer;
+    private Timeout queryTimeout; // 询问超时任务
+    private static final int QUERY_INTERVAL = 5; // 询问间隔(秒)
+    private static final int RESPONSE_TIMEOUT = 15; // 响应超时时间(秒)
+    private final MeilingColdChainDataHandler meilingColdChainDataHandler;
+    private final MonitorDeviceService monitorDeviceService;
+
+    public ModbusDataHandlerAdapter(HashedWheelTimer timer, MeilingColdChainDataHandler meilingColdChainDataHandler, MonitorDeviceService monitorDeviceService) {
+        this.timer = timer;
+        this.meilingColdChainDataHandler= meilingColdChainDataHandler;
+        this.monitorDeviceService=monitorDeviceService;
+    }
+
+    // 客户端连接成功时触发
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        Channel channel = ctx.channel();
+        log.info("客户端连接:" + channel.remoteAddress());
+        // 启动定时询问任务(首次延迟1秒,之后每隔QUERY_INTERVAL秒执行)
+        startQueryTask(channel);
+        //设备上线
+        MonitorDevice device = monitorDeviceService.getOne(new LambdaQueryWrapper<MonitorDevice>()
+                .eq(MonitorDevice::getIp, getIP(ctx)),false);
+        if(device!=null){
+            meilingColdChainDataHandler.login(new MeilingColdChainMessageData(device.getDeviceCode()));
+        }
+    }
+
+
+    // 收到客户端消息时触发
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        Channel channel = ctx.channel();
+        // 只处理客户端的响应指令(0x02)
+        log.info("收到客户端[" + channel.remoteAddress() + "]响应:" + msg);
+        // 取消当前超时任务(响应正常,无需断开连接)
+        if (queryTimeout != null && !queryTimeout.isCancelled()) {
+            queryTimeout.cancel();
+        }
+        MeilingColdChainMessageData meilingColdChainMessageData = new MeilingColdChainMessageData();
+        meilingColdChainMessageData.setHost(getIP(ctx));
+        String data = String.valueOf(msg);
+        MonitorDevice device = monitorDeviceService.getOne(new LambdaQueryWrapper<MonitorDevice>()
+                .eq(MonitorDevice::getIp, meilingColdChainMessageData.getHost()),false);
+        if(device!=null){
+            meilingColdChainMessageData.setHexData(data);
+            meilingColdChainMessageData.setDeviceId(device.getDeviceCode());
+            meilingColdChainMessageData.setModelName(MonitorDataProcessor.ML_WD_MODBUS);
+            meilingColdChainDataHandler.handleRealTimeData(meilingColdChainMessageData);
+        }
+    }
+
+    /**
+     * 获取IP地址
+     */
+    private String getIP(ChannelHandlerContext ctx) {
+        String socketString = ctx.channel().remoteAddress().toString();
+        int index = socketString.indexOf(":");
+        return socketString.substring(1, index);
+    }
+
+
+    // 客户端连接关闭时触发
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) {
+        Channel channel = ctx.channel();
+        log.warn("客户端断开连接:" + channel.remoteAddress());
+        // 取消定时任务,避免内存泄漏
+        if (queryTimeout != null && !queryTimeout.isCancelled()) {
+            queryTimeout.cancel();
+        }
+    }
+
+    // 启动定时询问任务
+    private void startQueryTask(Channel channel) {
+        timer.newTimeout(timeout -> {
+            if (channel.isActive()) { // 连接仍活跃时发送询问
+                // 美菱冰箱固定 发送固定询问指令(0x01)
+                channel.writeAndFlush("01 03 00 00 00 04 44 09");
+                log.info("向客户端[" + channel.remoteAddress() + "]发送询问");
+
+                // 启动响应超时任务:若RESPONSE_TIMEOUT秒内未收到响应,断开连接
+//                    queryTimeout = timer.newTimeout(timeout1 -> {
+//                        if (channel.isActive()) {
+//                            System.out.println("客户端[" + channel.remoteAddress() + "]响应超时,断开连接");
+//                            channel.close(); // 超时断开
+//                        }
+//                    }, RESPONSE_TIMEOUT, TimeUnit.SECONDS);
+
+                // 递归调度下一次询问(间隔QUERY_INTERVAL秒)
+                startQueryTask(channel);
+            }
+        }, QUERY_INTERVAL, TimeUnit.SECONDS); // 首次延迟1秒发送
+    }
+
+    // 异常处理
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        log.error("服务端异常:" + cause.getMessage());
+        ctx.close(); // 异常断开连接
+    }
+
+
+}

+ 56 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/coder/MyDecoder.java

@@ -0,0 +1,56 @@
+package vip.xiaonuo.coldchain.core.meiling.coder;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+import java.util.List;
+
+/**
+ * @author zj
+ * @date 2023-7-24
+ */
+public class MyDecoder extends ByteToMessageDecoder {
+    public MyDecoder() {
+    }
+
+    @Override
+    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
+        // 确保ByteBuf容量足够
+//        byteBuf.ensureWritable(expectedSize);
+        byte[] b = new byte[byteBuf.readableBytes()];
+        byteBuf.readBytes(b);
+        list.add(this.bytesToHexString(b));
+    }
+
+    public String bytesToHexString(byte[] bArray) {
+        StringBuffer sb = new StringBuffer(bArray.length);
+
+        for(int i = 0; i < bArray.length; ++i) {
+            String sTemp = Integer.toHexString(255 & bArray[i]);
+            if (sTemp.length() < 2) {
+                sb.append(0);
+            }
+
+            sb.append(sTemp.toUpperCase());
+            sb.append(" ");
+        }
+
+        return sb.toString();
+    }
+
+    public static String toHexString1(byte[] b) {
+        StringBuffer buffer = new StringBuffer();
+
+        for(int i = 0; i < b.length; ++i) {
+            buffer.append(toHexString1(b[i]));
+        }
+
+        return buffer.toString();
+    }
+
+    public static String toHexString1(byte b) {
+        String s = Integer.toHexString(b & 255);
+        return s.length() == 1 ? "0" + s : s;
+    }
+}

+ 36 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/meiling/coder/MyEncoder.java

@@ -0,0 +1,36 @@
+package vip.xiaonuo.coldchain.core.meiling.coder;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+
+/**
+ * @author zj
+ * @date 2023-7-24
+ */
+public class MyEncoder extends MessageToByteEncoder<String> {
+    public MyEncoder() {
+    }
+
+    @Override
+    protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception {
+        byteBuf.writeBytes(hexString2Bytes(s));
+    }
+
+    public static byte[] hexString2Bytes(String src) {
+        String[] split = src.split(" ");
+        int l = split.length;
+        byte[] ret = new byte[l];
+        int i = 0;
+        String[] var5 = split;
+        int var6 = split.length;
+
+        for(int var7 = 0; var7 < var6; ++var7) {
+            String s = var5[var7];
+            ret[i] = Integer.valueOf(s, 16).byteValue();
+            ++i;
+        }
+
+        return ret;
+    }
+}

+ 51 - 3
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/renke/config/JfcloudColdChainServerAutoConfiguration.java

@@ -3,24 +3,24 @@ package vip.xiaonuo.coldchain.core.renke.config;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.EnableAsync;
 import rk.netDevice.sdk.p2.IDataListener;
 import rk.netDevice.sdk.p2.RSServer;
+import vip.xiaonuo.coldchain.core.meiling.ModBusTcpServer;
 import vip.xiaonuo.coldchain.core.renke.listener.JfcloudColdChainRenKeDefaultDataListener;
 import vip.xiaonuo.coldchain.core.renke.util.IPUtil;
 import vip.xiaonuo.coldchain.core.renke.util.ParamFileUtil;
+import vip.xiaonuo.coldchain.core.service.dataprocess.handler.impl.MeilingColdChainDataHandler;
 import vip.xiaonuo.coldchain.core.service.dataprocess.handler.impl.RenKeColdChainDataHandler;
 import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
 
 import javax.annotation.PreDestroy;
+import java.net.InetSocketAddress;
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -96,6 +96,54 @@ public class JfcloudColdChainServerAutoConfiguration {
         });
     }
 
+    /**
+     * ModbusTcp Bean 配置,使用异步方式启动,以避免阻塞应用启动。
+     *
+     * @param properties   RSServer 配置属性,包含端口等配置信息
+     * @param meilingColdChainDataHandler modbus数据处理
+     * @return ModBusTcpServer 实例
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    @SneakyThrows
+    public ModBusTcpServer jfcloudColdChainModbusServer(JfcloudColdChainServerProperties properties, MeilingColdChainDataHandler meilingColdChainDataHandler, @Autowired @Lazy MonitorDeviceService monitorDeviceService) {
+        if ((Objects.nonNull(properties.getModbusPort()) && properties.getModbusPort() > 0)) {
+            // 初始化 ModbusServer,并注入数据监听器
+            log.info("""
+                    ----------------------------------------------------------
+                    冷链通信服务(JfcloudColdChainModbusServer)正在启动, Access URLs:
+                    服务端地址:    Modbustcp://{}:{}
+                    ----------------------------------------------------------
+                    """, IPUtil.getIp(), properties.getModbusPort());
+            // 异步启动 ModbusServer,避免阻塞主线程
+            ModBusTcpServer modBusTcpServer =new ModBusTcpServer(meilingColdChainDataHandler,monitorDeviceService);
+            startJfcloudColdChainModbusServerAsync(properties,modBusTcpServer);
+            return modBusTcpServer;
+        }
+        return null;
+    }
+
+    /**
+     * 异步启动 ModBusTcpServer,确保不会阻塞主线程。
+     *
+     * @param modBusTcpServer 要启动的 modbus 实例
+     */
+    @Async("coldChainAsyncTask")
+    public void startJfcloudColdChainModbusServerAsync(JfcloudColdChainServerProperties properties,@Autowired(required = false)ModBusTcpServer modBusTcpServer) {
+        executorService.submit(() -> {
+            try {
+                if (modBusTcpServer != null) {
+                    modBusTcpServer.start(new InetSocketAddress(properties.getModbusPort()));
+                    log.info("ModBusTcpServer 已成功启动");
+                } else {
+                    log.error("ModBusTcpServer 未启动");
+                }
+            } catch (Exception e) {
+                log.error("ModBusTcpServer 启动失败", e);
+            }
+        });
+    }
+
     /**
      * 关闭 RSServer,在 Spring 容器销毁时调用。
      */

+ 6 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/renke/config/JfcloudColdChainServerProperties.java

@@ -35,4 +35,10 @@ public class JfcloudColdChainServerProperties {
     private Long deviceOfflineInterval = JfcloudColdChainConstants.OFFLINE_THRESHOLD_SECONDS;
 
 
+    /**
+     * modbusTCP端口监听
+     */
+    private Integer modbusPort = 0;
+
+
 }

+ 5 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/dataclean/MonitorDataProcessor.java

@@ -67,6 +67,11 @@ public interface MonitorDataProcessor<T> {
      */
     String RS_N2_WIFI_2 = "String RS_N2_WIFI_2";
 
+    /**
+     * 美菱冰箱ModbusRTU协议设备
+     */
+    String ML_WD_MODBUS = "ML-WD-MODBUS";
+
     Boolean processData(T data);
 
     /**

+ 236 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/dataclean/impl/AbsMeilingMonitorDataProcessor.java

@@ -0,0 +1,236 @@
+package vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.github.jfcloud.influxdb.util.InfluxdbTypeConverter;
+import com.google.common.collect.Lists;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationEventPublisher;
+import rk.netDevice.sdk.p2.StoreData;
+import vip.xiaonuo.coldchain.core.bean.influxdb.DataType;
+import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
+import vip.xiaonuo.coldchain.core.event.MonitorDeviceHeartBeatEvent;
+import vip.xiaonuo.coldchain.core.event.MonitorDeviceLoginEvent;
+import vip.xiaonuo.coldchain.core.event.SensorDataEvent;
+import vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.MonitorDataProcessor;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.MeilingColdChainMessageData;
+import vip.xiaonuo.coldchain.modular.monitordevice.enums.DeviceModelEnum;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 美菱冰箱基于ModbusRTU协议设备监控数据处理器
+ * 该类用于处理来自Renke设备的实时数据,将其转换为标准的传感器数据并进行后续处理(如发布事件)。
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+@Slf4j
+@RequiredArgsConstructor
+public abstract class AbsMeilingMonitorDataProcessor implements MonitorDataProcessor<MeilingColdChainMessageData> {
+    // 设备型号
+    protected String modelName;
+    // 事件发布器
+    private final ApplicationEventPublisher applicationEventPublisher;
+
+
+    /**
+     * 处理实时数据
+     * 该方法是数据处理的入口,执行以下步骤:
+     * 1. 校验数据是否有效
+     * 2. 获取设备信息(如设备型号)
+     * 3. 转换数据格式为传感器数据
+     * 4. 发布传感器数据事件
+     *
+     * @param meilingColdChainMessageData 实时数据
+     * @return 处理是否成功
+     */
+    @Override
+    public Boolean processData(MeilingColdChainMessageData meilingColdChainMessageData) {
+        // Step 1: 检查输入数据是否为 null
+        if (Objects.isNull(meilingColdChainMessageData)) {
+            log.error("接收到的处理数据为空.");
+            return false;
+        }
+        // Step 2: 初始化数据和设备型号
+        // 保存数据集合
+        List<SensorData> sensorDataList = Lists.newArrayList();
+        this.modelName = meilingColdChainMessageData.getModelName();
+        // Step 3: 进行数据前处理
+        preProcess(meilingColdChainMessageData);
+        final String deviceId = String.valueOf(meilingColdChainMessageData.getDeviceId());
+        // Step 4: 处理实时数据
+        if (isValidRealTimeData(meilingColdChainMessageData)) {
+            List<SensorData> realTimeSensorData = transRealTimeData2SensorDatas(meilingColdChainMessageData);
+            if (realTimeSensorData != null && !realTimeSensorData.isEmpty()) {
+                sensorDataList.addAll(realTimeSensorData);
+            } else {
+                log.warn("设备ID: {} 的实时数据中没有有效的传感器数据", deviceId);
+            }
+        }
+        boolean result = false;
+        if (!sensorDataList.isEmpty()) {
+            // Step 6: 保存传感器数据(实时数据 + 缓存数据)
+            result = writeSensorDatas(sensorDataList);
+            // Step 7: 输出处理结果日志,并在保存成功后进行后置处理
+            if (result) {
+                postProcess(sensorDataList);
+            } else {
+                log.error("设备ID: {}, 型号: {} 的数据处理失败", deviceId, modelName);
+            }
+        }
+        return result;
+    }
+
+    // 校验实时数据是否有效
+    private boolean isValidRealTimeData(MeilingColdChainMessageData data) {
+        return Objects.nonNull(data) && StrUtil.isNotBlank(data.getHexData());
+    }
+
+    // 校验缓存数据是否有效
+    private boolean isValidStoreData(StoreData data) {
+        return Objects.nonNull(data) && Objects.nonNull(data.getNodeList()) && !data.getNodeList().isEmpty();
+    }
+
+
+    /**
+     * 数据前处理
+     * 子类可以重写此方法执行特定的预处理操作,如数据校验、转换等。
+     *
+     * @param data 实时数据
+     */
+    protected void preProcess(MeilingColdChainMessageData data) {
+        // 默认不做处理,子类可根据需求重写
+    }
+
+    /**
+     * 数据后处理
+     *
+     * @param sensorDataList 实时数据
+     */
+    protected void postProcess(List<SensorData> sensorDataList) {
+        // 默认不做处理,子类可根据需求重写
+    }
+
+    /**
+     * 将实时数据转换为传感器数据列表
+     * 该方法是抽象的,子类需要实现具体的转换逻辑。
+     *
+     * @param data 实时数据
+     * @return 转换后的传感器数据列表
+     */
+    abstract List<SensorData> transRealTimeData2SensorDatas(MeilingColdChainMessageData data);
+
+    /**
+     * 发布单个传感器数据事件
+     *
+     * @param sensorData 需要发布的传感器数据
+     * @return 是否发布成功
+     */
+    protected boolean writeSensorData(SensorData sensorData) {
+        try {
+            applicationEventPublisher.publishEvent(new SensorDataEvent(this, sensorData));
+//            log.info("成功发布传感器数据事件: {}", sensorData);
+            return true;
+        } catch (Exception e) {
+            log.error("保存传感器数据失败: {}", sensorData, e);
+            return false;
+        }
+    }
+
+    /**
+     * 发布传感器数据事件列表
+     * 待优化的功能  jackzhou-2024-1127
+     *
+     * @param sensorDataList 传感器数据列表
+     * @return 是否成功发布所有事件
+     */
+    protected boolean writeSensorDatas(List<SensorData> sensorDataList) {
+        if (sensorDataList.isEmpty()) {
+            return false;
+        }
+        // 遍历传感器数据列表并发布每个数据的事件
+        for (SensorData sensorData : sensorDataList) {
+            boolean b = writeSensorData(sensorData);
+            if (b) {
+                MeilingColdChainMessageData meilingColdChainMessageData = new MeilingColdChainMessageData();
+                meilingColdChainMessageData.setDeviceId(sensorData.getDeviceId());
+//                log.info("发送心跳。。。。。。。。。。。。。。。。。。。。。。。。");
+                heartbeat(meilingColdChainMessageData);
+            } else {
+                log.error("发布传感器数据事件失败: {}", sensorData);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 获取设备型号
+     * 该方法是抽象的,子类需要实现返回设备模型的逻辑。
+     *
+     * @return 设备型号
+     */
+    abstract DeviceModelEnum deviceModel();
+
+    public String getModelName() {
+        return deviceModel().getDeviceCode();
+    }
+
+    /**
+     * 基础的SensorData
+     *
+     * @param data
+     * @return
+     */
+    protected SensorData defaultSensorData(MeilingColdChainMessageData data) {
+        final String deviceId = data.getDeviceId();
+        SensorData sensorData = new SensorData();
+        // 如果记录时间为空,使用当前时间
+        Instant defaultTime = Instant.now();
+        sensorData.setTime(defaultTime);
+        sensorData.setCreateTime(InfluxdbTypeConverter.instantStr(defaultTime));
+        //设置模型型号
+        sensorData.setModelName(modelName);
+        sensorData.setDeviceId(deviceId);
+        //定义为实时数据
+        sensorData.setType(DataType.NEW.getCode());
+        return sensorData;
+    }
+
+    // 公共的事件发布方法
+    @SneakyThrows
+    private void publishDeviceEvent(Integer deviceId, Date timestamp, Class<?> eventType) {
+        if (timestamp == null) {
+            // 默认使用当前时间
+            timestamp = new Date();
+        }
+        applicationEventPublisher.publishEvent(eventType.getConstructor(Object.class, Integer.class, String.class, Date.class).newInstance(this, deviceId, null, timestamp));
+    }
+
+    @Override
+    public void login(MeilingColdChainMessageData data) {
+        Integer deviceId = Integer.parseInt(data.getDeviceId());
+        Date timestamp = new Date();
+        publishDeviceEvent(deviceId, timestamp, MonitorDeviceLoginEvent.class);
+    }
+
+    @Override
+    public void heartbeat(MeilingColdChainMessageData data) {
+        Integer deviceId = Integer.parseInt(data.getDeviceId());
+        Date timestamp = new Date();
+        publishDeviceEvent(deviceId, timestamp, MonitorDeviceHeartBeatEvent.class);
+    }
+
+    protected Float floatValue(float value) {
+        if (value != 0.0) {
+            return value;
+        } else {
+            return null;
+        }
+    }
+}

+ 208 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/dataclean/impl/ModbusMeilingTempProcessor.java

@@ -0,0 +1,208 @@
+package vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.impl;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
+import vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.MonitorDataProcessor;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.MeilingColdChainMessageData;
+import vip.xiaonuo.coldchain.modular.monitordevice.enums.DeviceModelEnum;
+
+import java.util.List;
+
+/**
+ * 美菱冰箱ModbusRTU协议温度处理
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+@Slf4j
+@Component(MonitorDataProcessor.ML_WD_MODBUS)
+public class ModbusMeilingTempProcessor extends AbsMeilingMonitorDataProcessor {
+
+    public ModbusMeilingTempProcessor(ApplicationEventPublisher applicationEventPublisher) {
+        super(applicationEventPublisher);
+    }
+
+    @Override
+    List<SensorData> transRealTimeData2SensorDatas(MeilingColdChainMessageData meilingColdChainMessageData) {
+        String data = meilingColdChainMessageData.getHexData();
+        //解析数据
+        //查询设备id
+        String[] split = data.split(" ");
+        //长度
+        //前两位温度
+        String valueHex = split[3] + split[4];
+        int tempure = Integer.parseInt(valueHex, 16);
+        if(tempure==0){
+            tempure = Integer.parseInt("FFFF", 16);
+        }
+
+        String valueHex2 = split[5] + split[6];
+        int tempure2 = Integer.parseInt(valueHex2, 16);
+        if(tempure2==0){
+            tempure2 = Integer.parseInt("FFFF", 16);
+        }
+
+//        //后两位故障码
+//        String valueHex3 = split[7]+split[8];
+//        BigInteger a1 = new BigInteger(valueHex3,16);
+//        log.info("故障1: "+ transFault(new StringBuffer(a1.toString(2)).reverse().toString()));
+//
+//        String valueHex4 = split[9]+split[10];
+//        BigInteger a2 = new BigInteger(valueHex4,16);
+//        log.info("故障2: "+ transFault2(new StringBuffer(a2.toString(2)).reverse().toString()));
+
+        //温度1
+        SensorData sensorData = defaultSensorData(meilingColdChainMessageData);
+        String formatted = String.format("%.2f", NumberUtil.div((tempure -Integer.parseInt("FFFF", 16) - 100), 10, 2));
+        float temperatureFloat = Float.parseFloat(formatted);
+        sensorData.setTemperature(temperatureFloat);
+        //设置路数
+        sensorData.setRoads(1);
+        //温度2
+        SensorData sensorData2 = defaultSensorData(meilingColdChainMessageData);
+        String formatted2 = String.format("%.2f", NumberUtil.div((tempure2 - Integer.parseInt("FFFF", 16) - 100), 10, 2));
+        float temperatureFloat2 = Float.parseFloat(formatted2);
+        sensorData2.setTemperature(temperatureFloat2);
+        //设置路数
+        sensorData2.setRoads(2);
+        //判断数据有效性
+        if(sensorData.getTemperature()>30 || sensorData.getTemperature()<-86
+                || sensorData2.getTemperature()>30 || sensorData2.getTemperature()<-86){
+            log.error("无效数据 sensorData1={} sensorData2 ={}", JSONObject.toJSONString(sensorData),JSONObject.toJSONString(sensorData2));
+            return ListUtil.empty();
+        }
+        return ListUtil.of(sensorData, sensorData2);
+    }
+
+
+    @Override
+    DeviceModelEnum deviceModel() {
+        return DeviceModelEnum.ML_WD_MODBUS;
+    }
+
+    /**
+     * 故障码1转换
+     *
+     * @param code 代码
+     * @return {@link String }
+     */
+    private String transFault(String code) {
+        StringBuffer stricodengBuffer = new StringBuffer("");
+        if (StrUtil.equalsCharAt(code, 0, '1')) {
+            stricodengBuffer.append("柜温高温报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 1, '1')) {
+            stricodengBuffer.append("柜温低温报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 2, '1')) {
+            stricodengBuffer.append("电池电量低报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 3, '1')) {
+            stricodengBuffer.append("电压过低报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 4, '1')) {
+            stricodengBuffer.append("电压过高报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 5, '1')) {
+            stricodengBuffer.append("断电报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 6, '1')) {
+            stricodengBuffer.append("PT100探头报警(Z06-RT1)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 7, '1')) {
+            stricodengBuffer.append("环温探头报警(Z06-RT2)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 8, '1')) {
+            stricodengBuffer.append("冷凝器探头报警(Z06-RT3)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 9, '1')) {
+            stricodengBuffer.append("加热探头报警(Z06-RT4)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 10, '1')) {
+            stricodengBuffer.append("0(预留)(Z06-RT5)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 11, '1')) {
+            stricodengBuffer.append("0(预留)(Z06-RT6)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 12, '1')) {
+            stricodengBuffer.append("0(预留)(Z06-湿度探头)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 13, '1')) {
+            stricodengBuffer.append("冷凝温度过高报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 14, '1')) {
+            stricodengBuffer.append("环温低报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 15, '1')) {
+            stricodengBuffer.append("环温高报警").append("+");
+        }
+        return stricodengBuffer.toString();
+
+    }
+
+    /**
+     * 故障码2转换
+     *
+     * @param code 代码
+     * @return {@link String }
+     */
+    private String transFault2(String code) {
+        StringBuffer stricodengBuffer = new StringBuffer("");
+        if (StrUtil.equalsCharAt(code, 0, '1')) {
+            stricodengBuffer.append("开门报警").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 1, '1')) {
+            stricodengBuffer.append("0(预留)(高湿报警)(下室高温报警)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 2, '1')) {
+            stricodengBuffer.append("0(预留)(低湿报警)(下室低温报警)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 3, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 4, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 5, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 6, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 7, '1')) {
+            stricodengBuffer.append("0(预留)(Z06-RT2)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 8, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 9, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 10, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 11, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 12, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 13, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 14, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        if (StrUtil.equalsCharAt(code, 15, '1')) {
+            stricodengBuffer.append("0(预留)").append("+");
+        }
+        return stricodengBuffer.toString();
+
+    }
+}

+ 100 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/handler/impl/MeilingColdChainDataHandler.java

@@ -0,0 +1,100 @@
+package vip.xiaonuo.coldchain.core.service.dataprocess.handler.impl;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import rk.netDevice.sdk.p2.RealTimeData;
+import vip.xiaonuo.coldchain.core.cache.monitordevice.MonitorDeviceCache;
+import vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.MonitorDataProcessor;
+import vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.impl.AbsMeilingMonitorDataProcessor;
+import vip.xiaonuo.coldchain.core.service.dataprocess.dataclean.impl.AbsRenkeMonitorDataProcessor;
+import vip.xiaonuo.coldchain.core.service.dataprocess.handler.AbstractColdChainDataHandler;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.HaierColdChainMessageData;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.MeilingColdChainMessageData;
+import vip.xiaonuo.coldchain.core.service.dataprocess.model.RenKeColdChainMessageData;
+
+import java.util.Map;
+
+/**
+ * 美菱冰箱ModbusRTU协议数据处理器
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class MeilingColdChainDataHandler extends AbstractColdChainDataHandler<MeilingColdChainMessageData> {
+    private final Map<String, MonitorDataProcessor<?>> monitorDataProcessorMap;
+    private final MonitorDeviceCache monitorDeviceCache;
+
+    @Override
+    public boolean handleRealTimeData(MeilingColdChainMessageData data) {
+        return handleData(data.getDeviceId(), data);
+    }
+
+    @Override
+    public boolean handleStoreData(RenKeColdChainMessageData renKeColdChainMessageData) {
+        return false;
+    }
+
+
+    private boolean handleData(String deviceIdStr, MeilingColdChainMessageData meilingColdChainMessageData) {
+        // 从缓存中获取设备模型名称
+        final String modelName = monitorDeviceCache.getDeviceModel(deviceIdStr);
+        // 默认处理器
+        AbsMeilingMonitorDataProcessor monitorDataProcessor =
+                (AbsMeilingMonitorDataProcessor) monitorDataProcessorMap.get(MonitorDataProcessor.ML_WD_MODBUS);
+        // 如果模型名称非空,则尝试获取对应处理器
+        if (StrUtil.isNotBlank(modelName)) {
+            meilingColdChainMessageData.setModelName(modelName);
+            AbsMeilingMonitorDataProcessor specificProcessor =
+                    (AbsMeilingMonitorDataProcessor) monitorDataProcessorMap.get(modelName);
+            if (specificProcessor != null) {
+                monitorDataProcessor = specificProcessor;
+            } else {
+                log.warn("No processor found for modelName: {}, using default processor.", modelName);
+            }
+        } else {
+            log.warn("Model name not found for deviceId: {}, using default processor.", deviceIdStr);
+        }
+        // 如果处理器存在,则处理数据,否则记录警告
+        if (monitorDataProcessor != null) {
+//            log.info("Using processor [{}] to process data for deviceId: {}",monitorDataProcessor.getModelName(), deviceIdStr);
+            monitorDataProcessor.processData(meilingColdChainMessageData);
+            return Boolean.TRUE;
+        } else {
+            log.error("No valid data processor found for deviceId: {}", deviceIdStr);
+            return Boolean.FALSE;
+        }
+    }
+
+    @Override
+    public void login(MeilingColdChainMessageData meilingColdChainMessageData) {
+        // 从缓存中获取设备模型名称
+        final String modelName = monitorDeviceCache.getDeviceModel(meilingColdChainMessageData.getDeviceId());
+        // 默认处理器
+        AbsMeilingMonitorDataProcessor monitorDataProcessor =
+                (AbsMeilingMonitorDataProcessor) monitorDataProcessorMap.get(MonitorDataProcessor.ML_WD_MODBUS);
+        // 如果模型名称非空,则尝试获取对应处理器
+        if (StrUtil.isNotBlank(modelName)) {
+            AbsMeilingMonitorDataProcessor specificProcessor =
+                    (AbsMeilingMonitorDataProcessor) monitorDataProcessorMap.get(modelName);
+            if (specificProcessor != null) {
+                monitorDataProcessor = specificProcessor;
+            } else {
+                log.warn("No processor found for modelName: {}, using default processor.", modelName);
+            }
+        } else {
+            log.warn("Model name not found for deviceId: {}, using default processor.", meilingColdChainMessageData.getDeviceId());
+        }
+        // 如果处理器存在,则处理数据,否则记录警告
+        if (monitorDataProcessor != null) {
+            log.info("Using processor [{}] to process data for deviceId: {}",monitorDataProcessor.getModelName(), meilingColdChainMessageData.getDeviceId());
+            monitorDataProcessor.login(meilingColdChainMessageData);
+        }
+    }
+
+
+}

+ 26 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/dataprocess/model/MeilingColdChainMessageData.java

@@ -0,0 +1,26 @@
+package vip.xiaonuo.coldchain.core.service.dataprocess.model;
+
+import lombok.Data;
+
+/**
+ * 美菱ModbusTCP协议解析后数据
+ *
+ * @author zj
+ * @date 2025/12/04
+ */
+@Data
+public class MeilingColdChainMessageData implements ColdChainMessageData {
+    private String host;
+    private String deviceId;
+    private String modelName;
+    private Float temperature;
+    private Float temperature1;
+    private String hexData;
+
+    public MeilingColdChainMessageData() {
+    }
+
+    public MeilingColdChainMessageData(String deviceId) {
+        this.deviceId = deviceId;
+    }
+}

+ 28 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/controller/SimsController.java

@@ -1,16 +1,27 @@
 package vip.xiaonuo.coldchain.modular.app.controller;
 
+import cn.hutool.core.bean.BeanUtil;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import vip.xiaonuo.coldchain.core.alarm.service.SensorAlarmService;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.modular.app.dto.DeviceDataDto;
 import vip.xiaonuo.coldchain.modular.app.param.AppDeviceQueryParams;
 import vip.xiaonuo.coldchain.modular.app.param.SensorEchartDataResult;
+import vip.xiaonuo.coldchain.modular.app.param.export.AppTrendParam;
+import vip.xiaonuo.coldchain.modular.app.service.AppDeviceService;
 import vip.xiaonuo.coldchain.modular.app.service.SimsService;
+import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDevice;
+import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
+import vip.xiaonuo.coldchain.modular.monitornotice.param.TrendParam;
+import vip.xiaonuo.coldchain.modular.monitornotice.service.MonitorNoticeService;
 import vip.xiaonuo.common.pojo.CommonResult;
 
 import java.util.List;
@@ -23,6 +34,11 @@ public class SimsController {
     @Autowired
     private SimsService simsService;
 
+    @Resource
+    private MonitorDeviceService monitorDeviceService;
+    @Resource
+    private AppDeviceService appDeviceService;
+
     @Operation(summary = "获取设备的最新的温湿度")
     @GetMapping("/{deviceCode}/{roads}")
     public CommonResult<SensorData> status(@PathVariable String deviceCode, @PathVariable Integer roads) {
@@ -42,5 +58,17 @@ public class SimsController {
         return CommonResult.data(simsService.queryDeviceDataList(appDeviceQueryParams));
     }
 
+    /**
+     * 导出传感器数据
+     */
+    @Operation(summary = "导出传感器数据")
+    @PostMapping("/monitornotice/export")
+    public void export(HttpServletResponse response, TrendParam trendParam, MultipartFile file) {
+        MonitorDevice monitorDevice = monitorDeviceService.queryEntity(trendParam.getDeviceId());
+        AppTrendParam appTrendParam = BeanUtil.copyProperties(trendParam, AppTrendParam.class);
+        appTrendParam.setSensorCode(monitorDevice.getDeviceCode());
+        appDeviceService.export(response, appTrendParam, file);
+    }
+
 
 }

+ 2 - 1
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitordevice/enums/DeviceModelEnum.java

@@ -26,7 +26,8 @@ public enum DeviceModelEnum {
     RS_MG111_WIFI_1(MonitorDataProcessor.RS_MG111_WIFI_1, "WIFI环境七合一变送记录仪"),
     RS_WS_WIFI5_6J(MonitorDataProcessor.RS_WS_WIFI5_6J, "WIFI单温度变iete记录仪"),
     RS_N2_WIFI_2(MonitorDataProcessor.RS_N2_WIFI_2, "氮气变送器"),
-    DEFAULT(MonitorDataProcessor.RS_WS_DEFAULT, "默认温湿度处理");
+    DEFAULT(MonitorDataProcessor.RS_WS_DEFAULT, "默认温湿度处理"),
+    ML_WD_MODBUS(MonitorDataProcessor.ML_WD_MODBUS, "美菱冰箱ModbusRTU协议设备");
 
 
     private final String deviceCode;  // 设备型号

+ 45 - 37
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitorscreen/service/impl/MonitorServiceImpl.java

@@ -3,6 +3,7 @@ package vip.xiaonuo.coldchain.modular.monitorscreen.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import jakarta.annotation.Resource;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -157,50 +158,57 @@ public class MonitorServiceImpl implements MonitorService {
 
     @Override
     public List<SensorAlarm> getWarningInfo(String s) {
-        List<SensorAlarm> sensorAlarmList = sensorAlarmService.list(getSensorAlarmQueryWrapper(s));
-        // 1. 按deviceId分组
-        Map<String, List<SensorAlarm>> groupedByDevice = sensorAlarmList.stream()
-                .filter(a -> a.getDeviceId() != null && a.getAlarmTime() != null)
-                .collect(Collectors.groupingBy(SensorAlarm::getDeviceId));
+//        List<SensorAlarm> sensorAlarmList = sensorAlarmService.list(getSensorAlarmQueryWrapper(s));
+//        // 1. 按deviceId分组
+//        Map<String, List<SensorAlarm>> groupedByDevice = sensorAlarmList.stream()
+//                .filter(a -> a.getDeviceId() != null && a.getAlarmTime() != null)
+//                .collect(Collectors.groupingBy(SensorAlarm::getDeviceId));
+//
+//        List<SensorAlarm> result = new ArrayList<>();
+//        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+//
+//        groupedByDevice.forEach((deviceId, group) -> {
+//            // 2. 按时间降序排序(最新数据在前)
+//            group.sort((a1, a2) -> {
+//                LocalDateTime t1 = LocalDateTime.parse(a1.getAlarmTime(), formatter);
+//                LocalDateTime t2 = LocalDateTime.parse(a2.getAlarmTime(), formatter);
+//                return t2.compareTo(t1); // 降序
+//            });
+//
+//            // 3. 遍历去重逻辑
+//            List<SensorAlarm> filteredGroup = new ArrayList<>();
+//            LocalDateTime lastTime = null;
+//            for (SensorAlarm alarm : group) {
+//                LocalDateTime currentTime = LocalDateTime.parse(alarm.getAlarmTime(), formatter);
+//                if (lastTime == null) {
+//                    filteredGroup.add(alarm);
+//                    lastTime = currentTime;
+//                } else {
+//                    // 计算时间间隔
+//                    Duration duration = Duration.between(currentTime, lastTime);
+//                    if (duration.toMinutes() >= 5) { // 间隔≥5分钟则保留
+//                        filteredGroup.add(alarm);
+//                        lastTime = currentTime;
+//                    }
+//                }
+//            }
+//            result.addAll(filteredGroup);
+//        });
 
-        List<SensorAlarm> result = new ArrayList<>();
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        Page<SensorAlarm> sensorAlarmPage = sensorAlarmService.page(new Page<>(1, 10),
+                getSensorAlarmQueryWrapper(s).select(SensorAlarm::getAlarmTime,SensorAlarm::getAlarmTime,SensorAlarm::getPriority,
+                        SensorAlarm::getSource,SensorAlarm::getMessage));
 
-        groupedByDevice.forEach((deviceId, group) -> {
-            // 2. 按时间降序排序(最新数据在前)
-            group.sort((a1, a2) -> {
-                LocalDateTime t1 = LocalDateTime.parse(a1.getAlarmTime(), formatter);
-                LocalDateTime t2 = LocalDateTime.parse(a2.getAlarmTime(), formatter);
-                return t2.compareTo(t1); // 降序
-            });
-
-            // 3. 遍历去重逻辑
-            List<SensorAlarm> filteredGroup = new ArrayList<>();
-            LocalDateTime lastTime = null;
-            for (SensorAlarm alarm : group) {
-                LocalDateTime currentTime = LocalDateTime.parse(alarm.getAlarmTime(), formatter);
-                if (lastTime == null) {
-                    filteredGroup.add(alarm);
-                    lastTime = currentTime;
-                } else {
-                    // 计算时间间隔
-                    Duration duration = Duration.between(currentTime, lastTime);
-                    if (duration.toMinutes() >= 5) { // 间隔≥5分钟则保留
-                        filteredGroup.add(alarm);
-                        lastTime = currentTime;
-                    }
-                }
-            }
-            result.addAll(filteredGroup);
-        });
-
-        return result;
+        return sensorAlarmPage.getRecords();
     }
 
     @Override
     public List<SensorAlarmDto> getWarningInfoBar(String s) {
         LambdaQueryWrapper<SensorAlarm> queryWrapper = getSensorAlarmQueryWrapper(s);
-        List<SensorAlarm> sensorAlarmList = sensorAlarmService.list(queryWrapper);
+//        List<SensorAlarm> sensorAlarmList = sensorAlarmService.list(queryWrapper);
+        List<SensorAlarm> sensorAlarmList = sensorAlarmService.page(new Page<>(1,10000),
+                queryWrapper.select(SensorAlarm::getType,SensorAlarm::getDeviceId,SensorAlarm::getDeviceName)).getRecords();
+
 
         //按 sensorCode 分组,并统计不同 type 的告警次数
         Map<String, Map<String, Long>> stats = sensorAlarmList.stream()

+ 11 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/controller/MonitorTargetController.java

@@ -31,6 +31,7 @@ import vip.xiaonuo.common.annotation.CommonLog;
 import vip.xiaonuo.common.pojo.CommonResult;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 监控对象管理控制器
@@ -230,4 +231,14 @@ public class MonitorTargetController {
         boolean result = monitorTargetService.flushAll(id);
         return result ? CommonResult.data(true) : CommonResult.fair(false);
     }
+
+
+    /**
+     * 获取监控对象分类统计
+     */
+    @Operation(summary = "获取监控对象分类统计")
+    @GetMapping("/coldchain/monitortarget/countType")
+    public CommonResult<Map> list() {
+        return CommonResult.data(monitorTargetService.countTargetByType());
+    }
 }

+ 9 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/entity/MonitorTarget.java

@@ -136,4 +136,13 @@ public class MonitorTarget extends OrgEntity {
     @TableField(value = "leaders", typeHandler = SensorAlarmUserTypeHandler.class)
     @Schema(description = "负责人")
     private List<SensorAlarmUser> leaders = Lists.newArrayList();
+
+
+
+    /**
+     * 对象类型
+     */
+    @TableField(value = "target_type")
+    @Schema(description = "对象类型")
+    private String targetType;
 }

+ 3 - 1
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/enums/MonitorStatusEnum.java

@@ -26,7 +26,9 @@ public enum MonitorStatusEnum {
      * 设备正常
      */
     ONLINE("1", "设备启用"),
-    OFF("2", "设备停用");
+    OFF("2", "设备停用"),
+    ERR("3", "设备异常"),
+    ;
 
     @Getter
     private final String code;

+ 6 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/param/MonitorTargetAddParam.java

@@ -88,4 +88,10 @@ public class MonitorTargetAddParam {
     @Schema(description = "负责人")
     private List<SensorAlarmUser> leaders = Lists.newArrayList();
 
+    /**
+     * 对象类型
+     */
+    @Schema(description = "对象类型")
+    private String targetType;
+
 }

+ 6 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/param/MonitorTargetAddWithRoomParam.java

@@ -95,4 +95,10 @@ public class MonitorTargetAddWithRoomParam {
     @Schema(description = "负责人")
     private List<SensorAlarmUser> leaders = Lists.newArrayList();
 
+    /**
+     * 对象类型
+     */
+    @Schema(description = "对象类型")
+    private String targetType;
+
 }

+ 7 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/param/MonitorTargetEditWithRoomParam.java

@@ -115,4 +115,11 @@ public class MonitorTargetEditWithRoomParam {
     @TableField(value = "leaders", typeHandler = SensorAlarmUserTypeHandler.class)
     @Schema(description = "负责人")
     private List<SensorAlarmUser> leaders = Lists.newArrayList();
+
+    /**
+     * 对象类型
+     */
+    @Schema(description = "对象类型")
+    private String targetType;
+
 }

+ 8 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/MonitorTargetService.java

@@ -23,6 +23,7 @@ import vip.xiaonuo.coldchain.modular.targetroom.entity.TargetRoom;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -179,4 +180,11 @@ public interface MonitorTargetService extends IService<MonitorTarget> {
      * @return
      */
     boolean flushAll(String id);
+
+    /**
+     * 按类型统计对象上下线数量
+     *
+     * @return {@link Map }
+     */
+    Map countTargetByType();
 }

+ 43 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/impl/MonitorTargetServiceImpl.java

@@ -15,6 +15,7 @@ package vip.xiaonuo.coldchain.modular.monitortarget.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollStreamUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.excel.EasyExcel;
@@ -27,6 +28,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
@@ -52,6 +54,8 @@ import vip.xiaonuo.common.enums.CommonDeleteFlagEnum;
 import vip.xiaonuo.common.enums.CommonSortOrderEnum;
 import vip.xiaonuo.common.exception.CommonException;
 import vip.xiaonuo.common.page.CommonPageRequest;
+import vip.xiaonuo.dev.modular.dict.entity.DevDict;
+import vip.xiaonuo.dev.modular.dict.service.DevDictService;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -535,6 +539,45 @@ public class MonitorTargetServiceImpl extends ServiceImpl<MonitorTargetMapper, M
         }
         return monitorTargetRegionService.removeBatchByIds(monitorTargetRegions);
     }
+    @Resource
+     DevDictService devDictService;
+
+    @Override
+    public Map countTargetByType() {
+        Map<String,Map> result= new HashMap<>();
+        DevDict parentType = devDictService.getOne(new LambdaQueryWrapper<DevDict>()
+                .eq(DevDict::getDictValue, "DXLX"), false);
+        if(parentType!=null){
+            List<DevDict> devDicts = devDictService.list(new LambdaQueryWrapper<DevDict>()
+                    .eq(DevDict::getParentId, parentType.getId())
+                    .orderByAsc(DevDict::getSortCode));
+            SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
+            devDicts.forEach(d->{
+                //在线
+                long online = this.count(new LambdaQueryWrapper<MonitorTarget>()
+                        .eq(MonitorTarget::getTargetType, d.getDictValue())
+                        .eq(MonitorTarget::getCreateOrg, loginUser.getOrgId()).eq(MonitorTarget::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE)
+                        .eq(MonitorTarget::getStatus, MonitorStatusEnum.ONLINE.getCode()));
+                //异常
+                long err = this.count(new LambdaQueryWrapper<MonitorTarget>()
+                        .eq(MonitorTarget::getTargetType, d.getDictValue())
+                        .eq(MonitorTarget::getCreateOrg, loginUser.getOrgId()).eq(MonitorTarget::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE)
+                        .eq(MonitorTarget::getStatus,MonitorStatusEnum.ERR.getCode()));
+                //离线
+                long offline =this.count(new LambdaQueryWrapper<MonitorTarget>()
+                        .eq(MonitorTarget::getTargetType,d.getDictValue())
+                        .eq(MonitorTarget::getCreateOrg, loginUser.getOrgId()).eq(MonitorTarget::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE)
+                        .eq(MonitorTarget::getStatus,MonitorStatusEnum.OFF.getCode()));
+                Map<String, Long> map = MapUtil.builder(new TreeMap<String, Long>())
+                        .put("all", online+offline+err)
+                        .put("online", online)
+                        .put("offline", offline)
+                        .put("err",err).build();
+                result.put(d.getDictValue(),map);
+            });
+        }
+        return result;
+    }
 
 
 }

+ 6 - 0
snowy-web-app/src/main/resources/_sql/zhangjian_20251212_update.sql

@@ -0,0 +1,6 @@
+ALTER TABLE `coldchain`.`monitor_target`
+    ADD COLUMN `target_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型' AFTER `leaders`;
+INSERT INTO `coldchain`.`dev_dict` (`ID`, `PARENT_ID`, `DICT_LABEL`, `DICT_VALUE`, `CATEGORY`, `SORT_CODE`, `EXT_JSON`, `DELETE_FLAG`, `CREATE_TIME`, `CREATE_USER`, `UPDATE_TIME`, `UPDATE_USER`) VALUES ('1997965325061758977', '0', '对象类型', 'DXLX', 'BIZ', 99, NULL, 'NOT_DELETE', '2025-12-08 17:43:49', '1543837863788879871', NULL, NULL);
+INSERT INTO `coldchain`.`dev_dict` (`ID`, `PARENT_ID`, `DICT_LABEL`, `DICT_VALUE`, `CATEGORY`, `SORT_CODE`, `EXT_JSON`, `DELETE_FLAG`, `CREATE_TIME`, `CREATE_USER`, `UPDATE_TIME`, `UPDATE_USER`) VALUES ('1997965431848738817', '1997965325061758977', '冰箱', 'BX', 'BIZ', 99, NULL, 'NOT_DELETE', '2025-12-08 17:44:14', '1543837863788879871', NULL, NULL);
+INSERT INTO `coldchain`.`dev_dict` (`ID`, `PARENT_ID`, `DICT_LABEL`, `DICT_VALUE`, `CATEGORY`, `SORT_CODE`, `EXT_JSON`, `DELETE_FLAG`, `CREATE_TIME`, `CREATE_USER`, `UPDATE_TIME`, `UPDATE_USER`) VALUES ('1997965465541582849', '1997965325061758977', '环境', 'HJ', 'BIZ', 99, NULL, 'NOT_DELETE', '2025-12-08 17:44:22', '1543837863788879871', NULL, NULL);
+INSERT INTO `coldchain`.`dev_dict` (`ID`, `PARENT_ID`, `DICT_LABEL`, `DICT_VALUE`, `CATEGORY`, `SORT_CODE`, `EXT_JSON`, `DELETE_FLAG`, `CREATE_TIME`, `CREATE_USER`, `UPDATE_TIME`, `UPDATE_USER`) VALUES ('1998939392224989186', '1997965325061758977', '液氮罐', 'YDG', 'BIZ', 99, NULL, 'NOT_DELETE', '2025-12-11 10:14:24', '1543837863788879871', NULL, NULL);

+ 6 - 1
snowy-web-app/src/main/resources/application-master.properties

@@ -1,2 +1,7 @@
 server.port=58888
-coldchain.port=0
+#\u516C\u7F51
+coldchain.port=0
+#modbusTCP\u76D1\u542C\u63A5\u53E3
+coldchain.modbus-port=0
+#\u4E2D\u5357\u533B\u9662
+#coldchain.port=40404