Przeglądaj źródła

1.大屏添加折线图和图例接口

wangxiaofei 2 miesięcy temu
rodzic
commit
53baef774a

+ 6 - 0
taphole-iron/src/main/java/com/sckj/iron/constant/ParamsConstants.java

@@ -20,5 +20,11 @@ public class ParamsConstants {
 
     public static final String iron_weight = "iron_weight";
 
+    //开口机旋转位置
+    public static final String open_machine_value = "open_machine_value";
+
+    //泥炮旋转油压
+    public static final String mud_machine_value = "mud_machine_value";
+
 
 }

+ 9 - 0
taphole-iron/src/main/java/com/sckj/iron/constant/StandardConstans.java

@@ -21,4 +21,13 @@ public class StandardConstans {
 
     //标注出铁量,范围值
     public static String STANDARD_IRON_WEIGHT = "";
+
+
+    //标准开口机旋转位置
+    public static double STANDARD_OPEN_MACHINE_LOCATION = 0;
+
+    //标准泥炮旋转油压
+    public static double STANDARD_MUD_MACHINE_PRESSURE = 0;
+
+
 }

+ 14 - 14
taphole-iron/src/main/java/com/sckj/iron/constant/SubscribeTagConstants.java

@@ -11,7 +11,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_1_IRONNOTCH_TAPPING";
         }
-        return "AOD25606.PV";
+        return "BF4_1_IRONNOTCH_TAPPING";
     }
 
     //2号出铁状态标记
@@ -19,7 +19,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_2_IRONNOTCH_TAPPING";
         }
-        return "AOD25622.PV";
+        return "BF4_2_IRONNOTCH_TAPPING";
     }
 
     //3号出铁状态标记
@@ -27,7 +27,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_3_IRONNOTCH_TAPPING";
         }
-        return "AOD25706.PV";
+        return "BF4_3_IRONNOTCH_TAPPING";
     }
 
     //4号出铁状态标记
@@ -35,7 +35,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_4_IRONNOTCH_TAPPING";
         }
-        return "AOD25722.PV";
+        return "BF4_4_IRONNOTCH_TAPPING";
     }
 
     //铁水温度
@@ -43,7 +43,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_1_IRONNOTCH_MIR_TEMP";
         }
-        return "CL510104.CPV";
+        return "BF4_1_IRONNOTCH_MIR_TEMP";
     }
 
     //1TH-1号车受铁速度
@@ -51,7 +51,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1";
         }
-        return "WZ540101.PV";
+        return "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1";
     }
 
     //1TH-2号车受铁速度
@@ -67,7 +67,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_1TH_1_MIR_NWT";
         }
-        return "WI5401.PV";//这个是OPC采集点是用来模拟的
+        return "BF4_1TH_1_MIR_NWT";//这个是OPC采集点是用来模拟的
     }
 
     //四高炉1TH-2号铁水净重
@@ -75,7 +75,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_1TH_2_MIR_NWT";
         }
-        return "WI5402.PV";//这个是OPC采集点是用来模拟的
+        return "BF4_1TH_2_MIR_NWT";//这个是OPC采集点是用来模拟的
     }
 
     //出渣状态
@@ -83,7 +83,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4_1TH_SLAG_TAPPING_SIGNAL";
         }
-        return "AOD25607.PV";
+        return "BF4_1TH_SLAG_TAPPING_SIGNAL";
     }
 
     //水渣泵(粒化给水泵)是否开启
@@ -91,7 +91,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4SSZ_I_GRANUL_PUMP_RNG";
         }
-        return "AOD25607.PV";
+        return "BF4SSZ_I_GRANUL_PUMP_RNG";
     }
 
     //冲渣阀(吹制阀)是否开启
@@ -99,7 +99,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4SSZ_1_SLAGFLV_OIP";
         }
-        return "AOD25607.PV";
+        return "BF4SSZ_1_SLAGFLV_OIP";
     }
 
     //开口机旋转位置
@@ -107,7 +107,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4LQSDJ_1_SCH_IRONNOTCH_OPENER_ROTATE_LOC";
         }
-        return "AOD25607.PV";
+        return "BF4LQSDJ_1_SCH_IRONNOTCH_OPENER_ROTATE_LOC";
     }
 
     //泥炮旋转油压
@@ -115,7 +115,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4LQSDJ_1_SCH_CLAYCAN_ROTATE_PT";
         }
-        return "AOD25607.PV";
+        return "BF4LQSDJ_1_SCH_CLAYCAN_ROTATE_PT";
     }
 
     //四高炉南水渣粒化泵供水总管温度
@@ -123,7 +123,7 @@ public class SubscribeTagConstants {
         if (Objects.equals(type, "3")) {
             return "BF4SSZ_GRANUL_PUMP_WATSUPL_MTUBE_TEMP";
         }
-        return "AOD25607.PV";
+        return "BF4SSZ_GRANUL_PUMP_WATSUPL_MTUBE_TEMP";
     }
 
 

+ 7 - 0
taphole-iron/src/main/java/com/sckj/iron/constant/TaskNameConstants.java

@@ -29,4 +29,11 @@ public class TaskNameConstants {
 
     public static final String TASKNAME_CLOSURE_PREDICT = "closure_predict";
 
+    public static final String TASKNAME_EXCEPTION_LIST = "exception_list";
+
+    public static final String TASKNAME_IRON_CHART = "iron_chart";
+
+    public static final String TASKNAME_IRON_LEGEND = "iron_legend";
+
+
 }

+ 28 - 302
taphole-iron/src/main/java/com/sckj/iron/controller/TIronVisualScreenController.java

@@ -15,12 +15,9 @@ import com.sckj.iron.validate.IronLoginValidate;
 import com.sckj.iron.validate.TIronDataSearchScreenValidate;
 import com.sckj.iron.vo.IronLoginVo;
 import com.sckj.l2.dto.L2Properties;
-import com.sckj.l2.dto.TL2DataDTO;
-import com.sckj.l2.dto.TrendDTO;
 import com.sckj.l2.dto.TrendRequest;
 import com.sckj.l2.entity.TL2Data;
 import com.sckj.l2.service.impl.TL2DataServiceImpl;
-import com.sckj.opc.entity.OPCData;
 import com.sckj.opc.service.OPCDataServiceImpl;
 import com.sckj.warn.dto.WarnDTO;
 import com.sckj.warn.service.impl.TExceptionLogServiceImpl;
@@ -40,10 +37,9 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -82,6 +78,10 @@ public class TIronVisualScreenController {
     @Resource
     TIronTestServiceImpl iTIronTestService;
 
+    @Resource
+    TIronVisualScreenServiceImpl tIronVisualScreenService;
+
+
     private static final int DATA_COUNT = 6;
     @Autowired
     private L2Properties l2Properties;
@@ -103,68 +103,7 @@ public class TIronVisualScreenController {
     }
 
 
-    /***
-     * 从L2获取铁水成分数据
-     * @return
-     */
-    @NotPower
-    @PostMapping("/getIronElement")
-    @ApiOperation(value = "获取铁水成分数据")
-    public AjaxResult<TrendDTO> getIronElement(@RequestBody TrendRequest queryDateType) {
-        List<TL2DataDTO> tl2DataList = tl2DataService.getTrendDataByDiffDays(queryDateType);
-        TrendDTO trendDTO = new TrendDTO();
-        trendDTO.setList(tl2DataList);
-        trendDTO.setUnitName("100%");
-        return AjaxResult.success(trendDTO);
-    }
 
-    /***
-     * 获取铁水流速数据
-     * @return
-     */
-    @NotPower
-    @PostMapping("/getIronSpeed")
-    @ApiOperation(value = "获取铁水流速数据")
-    public AjaxResult<TrendDTO> getIronSpeed(@RequestBody TrendRequest queryDateType) {
-        List<TL2DataDTO> tl2DataList = tl2DataService.getTrendDataByDiffDays(queryDateType);
-        TrendDTO trendDTO = new TrendDTO();
-        trendDTO.setList(tl2DataList);
-        trendDTO.setUnitName("t/min");
-        trendDTO.setUnitDesc("出铁流速");
-        return AjaxResult.success(trendDTO);
-    }
-
-    /***
-     * 获取铁流量数据
-     * @return
-     */
-    @NotPower
-    @PostMapping("/getIronWeight")
-    @ApiOperation(value = "获取铁流量数据")
-    public AjaxResult<TrendDTO> getIronWeight(@RequestBody TrendRequest queryDateType) {
-        List<TL2DataDTO> tl2DataList = tl2DataService.getTrendDataByDiffDays(queryDateType);
-        TrendDTO trendDTO = new TrendDTO();
-        trendDTO.setList(tl2DataList);
-        trendDTO.setUnitName("t");
-        trendDTO.setUnitDesc("出铁量");
-        return AjaxResult.success(trendDTO);
-    }
-
-    /***
-     * 获取铁水温度数据
-     * @return
-     */
-    @NotPower
-    @PostMapping("/getIronTemp")
-    @ApiOperation(value = "获取铁水温度数据")
-    public AjaxResult<TrendDTO> getIronTemp(@RequestBody TrendRequest queryDateType) {
-        List<TL2DataDTO> tl2DataList = tl2DataService.getTrendDataByDiffDays(queryDateType);
-        TrendDTO trendDTO = new TrendDTO();
-        trendDTO.setList(tl2DataList);
-        trendDTO.setUnitName("℃");
-        trendDTO.setUnitDesc("铁水温度");
-        return AjaxResult.success(trendDTO);
-    }
 
     @NotPower
     @PostMapping("/getIronData")
@@ -289,243 +228,30 @@ public class TIronVisualScreenController {
     }
 
     @NotPower
-    @PostMapping("/getIronTrends")
-    @ApiOperation(value = "获取趋势所有数据")
-    public AjaxResult getIronTrends(@RequestBody TrendRequest queryDateType) {
-        // 1. 计算起止时间
-        int hours = queryDateType.getQueryDateType();
-        //给固定时间,24小时之内
-        hours = 24;
-        java.util.Date endTime = new java.util.Date();
-        java.util.Calendar cal = java.util.Calendar.getInstance();
-        cal.setTime(endTime);
-        cal.add(java.util.Calendar.HOUR_OF_DAY, -hours);
-        java.util.Date startTime = cal.getTime();
-
-        // 2. 异步查询所有数据
-        CompletableFuture<List<OPCData>> opcDataFuture = CompletableFuture.supplyAsync(() ->
-            opcDataService.lambdaQuery()
-                .in(OPCData::getPointName, Arrays.asList(
-                    "BF4_1_IRONNOTCH_MIR_TEMP",           // 温度
-                    "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1", // 1车流速
-                    "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED2", // 2车流速
-                    "BF4_1TH_2_TPC_TWT",                  // 1车铁水重量
-                    "BF4_1TH_1_TPC_TWT",                   // 2车铁水重量
-                    "BF4_1_IRONNOTCH_TAPPING",               // 1号出铁状态
-                    "BF4_1TH_2_TPC_CARNO",                  // 1TH-2号车混铁车车号
-                    "BF4_1TH_1_TPC_CARNO"                  // 1TH-1号车混铁车车号
-                ))
-                .between(OPCData::getServerTime, startTime, endTime)
-                .orderByAsc(OPCData::getServerTime)
-                .list()
-        );
-
-        CompletableFuture<List<TL2Data>> l2DataFuture = CompletableFuture.supplyAsync(() ->
-            tl2DataService.lambdaQuery()
-                .between(TL2Data::getIronStarttime, 
-                    new SimpleDateFormat("yyyyMMddHHmmss").format(startTime),
-                    new SimpleDateFormat("yyyyMMddHHmmss").format(endTime))
-                .orderByAsc(TL2Data::getIronStarttime)
-                .list()
-        );
-
-        // 3. 等待所有查询完成并转换数据
-        try {
-            CompletableFuture.allOf(opcDataFuture, l2DataFuture).join();
-
-            // 4. 按pointName分类OPC数据并转换为DTO
-            try {
-                Map<String, List<OPCData>> opcDataMap = opcDataFuture.get().stream()
-                    .collect(Collectors.groupingBy(OPCData::getPointName));
-                
-                List<TL2Data> l2DataList = l2DataFuture.get();
-
-                // 并行转换所有数据
-
-                //铁水温度
-                CompletableFuture<List<IronTrendL1DTO>> tempListFuture = CompletableFuture.supplyAsync(() ->
-                    opcDataMap.getOrDefault("BF4_1_IRONNOTCH_MIR_TEMP", new ArrayList<>())
-                        .stream().map(item -> {
-                            IronTrendL1DTO dto = new IronTrendL1DTO();
-                            dto.setData(item.getData());
-                            dto.setCreateTime(item.getSourceTime());
-                            return dto;
-                        }).collect(Collectors.toList())
-                );
-
-                //1TH-1号车受铁速度
-                CompletableFuture<List<IronTrendL1DTO>> speed1ListFuture = CompletableFuture.supplyAsync(() ->
-                    opcDataMap.getOrDefault("BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1", new ArrayList<>())
-                        .stream().map(item -> {
-                            IronTrendL1DTO dto = new IronTrendL1DTO();
-                            dto.setData(item.getData());
-                            dto.setCreateTime(item.getSourceTime());
-                            return dto;
-                        }).collect(Collectors.toList())
-                );
-
-                //1TH-2号车受铁速度
-                CompletableFuture<List<IronTrendL1DTO>> speed2ListFuture = CompletableFuture.supplyAsync(() ->
-                    opcDataMap.getOrDefault("BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED2", new ArrayList<>())
-                        .stream().map(item -> {
-                            IronTrendL1DTO dto = new IronTrendL1DTO();
-                            dto.setData(item.getData());
-                            dto.setCreateTime(item.getSourceTime());
-                            return dto;
-                        }).collect(Collectors.toList())
-                );
-
-                //1TH-2号车TPC总重
-                CompletableFuture<List<IronTrendL1DTO>> weight1ListFuture = CompletableFuture.supplyAsync(() ->
-                    opcDataMap.getOrDefault("BF4_1TH_2_TPC_TWT", new ArrayList<>())
-                        .stream().map(item -> {
-                            IronTrendL1DTO dto = new IronTrendL1DTO();
-                            dto.setData(item.getData());
-                            dto.setCreateTime(item.getSourceTime());
-                            return dto;
-                        }).collect(Collectors.toList())
-                );
-
-                //1TH-1号车TPC总重
-                CompletableFuture<List<IronTrendL1DTO>> weight2ListFuture = CompletableFuture.supplyAsync(() ->
-                    opcDataMap.getOrDefault("BF4_1TH_1_TPC_TWT", new ArrayList<>())
-                        .stream().map(item -> {
-                            IronTrendL1DTO dto = new IronTrendL1DTO();
-                            dto.setData(item.getData());
-                            dto.setCreateTime(item.getSourceTime());
-                            return dto;
-                        }).collect(Collectors.toList())
-                );
-
-                //L2
-                CompletableFuture<List<IronTrendL2DTO>> elemenListFuture = CompletableFuture.supplyAsync(() ->
-                    l2DataList.stream().map(item -> {
-                        IronTrendL2DTO dto = new IronTrendL2DTO();
-                        try {
-                            dto.setElementS(item.getElementS() != null ? Double.parseDouble(item.getElementS()) : null);
-                        } catch (NumberFormatException e) {
-                            dto.setElementS(null);
-                        }
-                        try {
-                            dto.setElementSi(item.getElementSi() != null ? Double.parseDouble(item.getElementSi()) : null);
-                        } catch (NumberFormatException e) {
-                            dto.setElementSi(null);
-                        }
-                        dto.setIronStarttime(item.getIronStarttime());
-                        dto.setIronEndtime(item.getIronEndtime());
-                        dto.setPollMm(item.getPollMm());
-                        dto.setOpenDepth(item.getOpenDepth());
-                        dto.setMudWeight(item.getMudWeight());
-                        dto.setIronCosttime(item.getIronCosttime());
-                        return dto;
-                    }).collect(Collectors.toList())
-                );
-
-                // 等待所有转换完成
-                CompletableFuture.allOf(
-                    tempListFuture,
-                    speed1ListFuture,
-                    speed2ListFuture,
-                    weight1ListFuture,
-                    weight2ListFuture,
-                    elemenListFuture
-                ).join();
-
-                // 5. 封装返回
-                try {
-                    List<IronTrendL1DTO> tempList = tempListFuture.get();
-                    List<IronTrendL1DTO> speed1List = speed1ListFuture.get();
-                    List<IronTrendL1DTO> speed2List = speed2ListFuture.get();
-                    List<IronTrendL1DTO> weight1List = weight1ListFuture.get();
-                    List<IronTrendL1DTO> weight2List = weight2ListFuture.get();
-                    List<IronTrendL2DTO> elemenList = elemenListFuture.get();
-
-                    // 1. 收集所有时间点
-                    java.util.Set<String> allTimes = new java.util.TreeSet<>();
-                    java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-                    tempList.forEach(e -> allTimes.add(e.getCreateTime() != null ? sdf.format(e.getCreateTime()) : null));
-                    speed1List.forEach(e -> allTimes.add(e.getCreateTime() != null ? sdf.format(e.getCreateTime()) : null));
-                    speed2List.forEach(e -> allTimes.add(e.getCreateTime() != null ? sdf.format(e.getCreateTime()) : null));
-                    weight1List.forEach(e -> allTimes.add(e.getCreateTime() != null ? sdf.format(e.getCreateTime()) : null));
-                    weight2List.forEach(e -> allTimes.add(e.getCreateTime() != null ? sdf.format(e.getCreateTime()) : null));
-                    elemenList.forEach(e -> allTimes.add(e.getIronStarttime()));
-                    allTimes.removeIf(Objects::isNull);
-                    java.util.List<String> xAxis = new java.util.ArrayList<>(allTimes);
-
-                    // 2. 构建series
-                    java.util.List<java.util.Map<String, Object>> series = new java.util.ArrayList<>();
-                    series.add(buildSeries("温度", tempList, xAxis));
-                    series.add(buildSeries("1车流速", speed1List, xAxis));
-                    series.add(buildSeries("2车流速", speed2List, xAxis));
-                    series.add(buildSeries("1车铁水重量", weight1List, xAxis));
-                    series.add(buildSeries("2车铁水重量", weight2List, xAxis));
-                    series.add(buildSeries("硫", elemenList, xAxis, "elementS"));
-                    series.add(buildSeries("硅", elemenList, xAxis, "elementSi"));
-                    series.add(buildSeries("泥炮量", elemenList, xAxis, "mudWeight"));
-                    series.add(buildSeries("钻杆直径", elemenList, xAxis, "pollMm"));
-                    series.add(buildSeries("开口深度", elemenList, xAxis, "openDepth"));
-                    series.add(buildSeries("出铁时间", elemenList, xAxis, "ironCosttime"));
-
-                    java.util.Map<String, Object> result = new java.util.HashMap<>();
-                    result.put("xAxis", xAxis);
-                    result.put("series", series);
-                    return AjaxResult.success(result);
-                } catch (InterruptedException | ExecutionException e) {
-                    log.error("获取或转换趋势数据异常", e);
-                    return AjaxResult.failed(500, "获取或转换趋势数据异常: " + e.getMessage(), null);
-                }
-
-            } catch (InterruptedException | ExecutionException e) {
-                log.error("获取或转换趋势数据异常", e);
-                return AjaxResult.failed(500, "获取或转换趋势数据异常: " + e.getMessage(), null);
-            }
-
-        } catch (Exception e) {
-            log.error("获取趋势数据异常", e);
-            return AjaxResult.failed(500, "获取趋势数据异常: " + e.getMessage(), null);
-        }
+    @PostMapping("/getIronLegend")
+    @ApiOperation(value = "实时数据图图例")
+    public AjaxResult getIronLegend() {
+        Map<String, Object> result = tIronVisualScreenService.getIronLegend(new HashMap<>());
+        //生产随机数据
+        result.put("ironCosttime",  RealtimeData.builder().desc("出铁时间").value((Math.random() * 100) + 200).unit("min").build());
+        result.put("elementS", RealtimeData.builder().desc("铁水成分-硫").value((Math.random() * 1)).build());
+        result.put("elementSi", RealtimeData.builder().desc("铁水成分-硅").value((Math.random() * 1) + 1).build());
+        result.put("mudWeight",  RealtimeData.builder().desc("泥炮量").value((Math.random() * 60) + 1).unit("L").build());
+        result.put("pollMm",RealtimeData.builder().desc("钻杆直径").value((Math.random() * 60) + 1).unit("mm").build());
+        result.put("openDepth", RealtimeData.builder().desc("开口深度").value((Math.random() * 100) + 1).unit("mm").build());
+        result.put("ironTemp", RealtimeData.builder().desc("铁水温度").value((Math.random() * 100) + 1400).unit("℃").build());
+        result.put("ironSpeed", RealtimeData.builder().desc("铁水流速").value((Math.random() * 10) + 10).unit("t/s").build());
+        result.put("ironWeight", RealtimeData.builder().desc("铁水流量").value((Math.random() * 100) + 1100).unit("t").build());
+        return AjaxResult.success(result);
     }
 
-    // 工具方法
-    private java.util.Map<String, Object> buildSeries(String name, java.util.List<IronTrendL1DTO> list, java.util.List<String> xAxis) {
-        java.util.Map<String, Object> map = new java.util.HashMap<>();
-        map.put("name", name);
-        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-        java.util.Map<String, Object> timeValueMap = list.stream()
-            .filter(e -> e.getCreateTime() != null)
-            .collect(java.util.stream.Collectors.toMap(
-                e -> sdf.format(e.getCreateTime()),
-                IronTrendL1DTO::getData,
-                (v1, v2) -> v1
-            ));
-        java.util.List<Object> data = xAxis.stream().map(timeValueMap::get).collect(java.util.stream.Collectors.toList());
-        map.put("data", data);
-        return map;
-    }
 
-    private java.util.Map<String, Object> buildSeries(String name, java.util.List<IronTrendL2DTO> list, java.util.List<String> xAxis, String field) {
-        java.util.Map<String, Object> map = new java.util.HashMap<>();
-        map.put("name", name);
-        java.util.Map<String, Object> timeValueMap = list.stream()
-            .filter(e -> e.getIronStarttime() != null)
-            .collect(java.util.stream.Collectors.toMap(
-                IronTrendL2DTO::getIronStarttime,
-                e -> {
-                    switch (field) {
-                        case "elementS": return e.getElementS();
-                        case "elementSi": return e.getElementSi();
-                        case "mudWeight": return e.getMudWeight();
-                        case "pollMm": return e.getPollMm();
-                        case "openDepth": return e.getOpenDepth();
-                        case "ironCosttime": return e.getIronCosttime();
-                        default: return null;
-                    }
-                },
-                (v1, v2) -> v1
-            ));
-        java.util.List<Object> data = xAxis.stream().map(timeValueMap::get).collect(java.util.stream.Collectors.toList());
-        map.put("data", data);
-        return map;
+    @NotPower
+    @PostMapping("/getIronChart")
+    @ApiOperation(value = "实时折线图数据")
+    public AjaxResult getIronChart(@RequestBody TrendRequest queryDateType) {
+        return AjaxResult.success(tIronVisualScreenService.getIronChart(queryDateType));
     }
 
 }
+

+ 6 - 0
taphole-iron/src/main/java/com/sckj/iron/dto/RealtimeData.java

@@ -2,7 +2,10 @@ package com.sckj.iron.dto;
 
 import io.swagger.annotations.ApiModelProperty;
 import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.springframework.stereotype.Component;
 
 /***
@@ -10,6 +13,9 @@ import org.springframework.stereotype.Component;
  * 铁水温度、铁水流速、铁水成分、铁水流量
  *
  */
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
 @Data
 @Component
 @ApiOperation("实时数据信息")

+ 648 - 0
taphole-iron/src/main/java/com/sckj/iron/service/impl/TIronVisualScreenServiceImpl.java

@@ -0,0 +1,648 @@
+package com.sckj.iron.service.impl;
+
+import com.sckj.common.exception.OperateException;
+import com.sckj.iron.dto.IronTrendL1DTO;
+import com.sckj.iron.dto.IronTrendL2DTO;
+import com.sckj.iron.dto.RealtimeData;
+import com.sckj.l2.dto.TrendRequest;
+import com.sckj.l2.entity.TL2Data;
+import com.sckj.l2.service.impl.TL2DataServiceImpl;
+import com.sckj.opc.entity.OPCData;
+import com.sckj.opc.service.OPCDataServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class TIronVisualScreenServiceImpl {
+    @Resource
+    OPCDataServiceImpl opcDataService;
+
+
+    @Resource
+    TL2DataServiceImpl tl2DataService;
+
+
+    /***
+     * 实时数据图图例
+     * @param result
+     * @return
+     */
+    public Map<String, Object> getIronLegend(Map<String, Object> result) {
+        java.util.Date startTime = new java.util.Date();
+        // 查询区间前的最近一条历史数据(前置值)
+        // OPC类
+//        OPCData preTemp = opcDataService.lambdaQuery()
+//                .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_MIR_TEMP")
+//                .isNotNull(OPCData::getData)
+//                .le(OPCData::getServerTime, startTime)
+//                .orderByDesc(OPCData::getServerTime)
+//                .last("limit 1").one();
+//        OPCData preSpeed1 = opcDataService.lambdaQuery()
+//                .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1")
+//                .isNotNull(OPCData::getData)
+//                .le(OPCData::getServerTime, startTime)
+//                .orderByDesc(OPCData::getServerTime)
+//                .last("limit 1").one();
+//        OPCData preSpeed2 = opcDataService.lambdaQuery()
+//                .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED2")
+//                .isNotNull(OPCData::getData)
+//                .le(OPCData::getServerTime, startTime)
+//                .orderByDesc(OPCData::getServerTime)
+//                .last("limit 1").one();
+//        OPCData preWeight1 = opcDataService.lambdaQuery()
+//                .eq(OPCData::getPointName, "BF4_1TH_1_MIR_NWT")
+//                .isNotNull(OPCData::getData)
+//                .le(OPCData::getServerTime, startTime)
+//                .orderByDesc(OPCData::getServerTime)
+//                .last("limit 1").one();
+//        OPCData preWeight2 = opcDataService.lambdaQuery()
+//                .eq(OPCData::getPointName, "BF4_1TH_2_MIR_NWT")
+//                .isNotNull(OPCData::getData)
+//                .le(OPCData::getServerTime, startTime)
+//                .orderByDesc(OPCData::getServerTime)
+//                .last("limit 1").one();
+
+        // tappingMap补齐每一分钟,前面无数据用前置值补齐
+        // 先查区间前最近一条tapping
+//        Integer preTappingVal = 0;
+//        OPCData preTapping = opcDataService.lambdaQuery()
+//                .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_TAPPING")
+//                .isNotNull(OPCData::getData)
+//                .lt(OPCData::getServerTime, startTime)
+//                .orderByDesc(OPCData::getServerTime)
+//                .last("limit 1").one();
+
+        // L2类
+        TL2Data preL2 = tl2DataService.lambdaQuery()
+                .le(TL2Data::getIronStarttime, new SimpleDateFormat("yyyyMMddHHmmss").format(startTime))
+                .orderByDesc(TL2Data::getIronStarttime)
+                .last("limit 1").one();
+
+//        result.put("ironTemp", preTemp.getData());
+//        result.put("ironSpeed", Double.parseDouble(preSpeed1.getData().toString()) + Double.parseDouble(preSpeed2.getData().toString()));
+        result.put("elementS", RealtimeData.builder().desc("铁水成分-硫").value(preL2.getElementS()).build());
+        result.put("elementSi", RealtimeData.builder().desc("铁水成分-硅").value(preL2.getElementSi()).build());
+        result.put("mudWeight",  RealtimeData.builder().desc("泥炮量").value(preL2.getMudWeight()).unit("L").build());
+        result.put("pollMm",RealtimeData.builder().desc("钻杆直径").value(preL2.getPollMm()).unit("mm").build());
+        result.put("openDepth", RealtimeData.builder().desc("开口深度").value(preL2.getOpenDepth()).unit("mm").build());
+//        result.put("ironCosttime", preL2.getIronCosttime());
+//        result.put("ironWeight",  Double.parseDouble(preWeight1.getData().toString()) + Double.parseDouble(preWeight2.getData().toString()));
+
+        return result;
+    }
+
+    /***
+     * 实时折线图数据,
+     * BF4_1_IRONNOTCH_TAPPING表示出铁状态,数值只有0和1,1表示正在出铁,0表示未出铁
+     * BF4_1TH_1_MIR_NWT表示四高炉1TH-1号车铁水净重,BF4_1TH_2_MIR_NWT表示四高炉1TH-2号车铁水净重,这2车一开始的重量为0,然后装入铁水重量不断增加,直到满载,然后减为0,然后继续增加,以此往复
+     *
+     * @param queryDateType
+     * @return
+     */
+    public Map<String, Object> getIronChart(TrendRequest queryDateType) {
+        // 1. 计算起止时间
+        int hours = Objects.isNull(queryDateType.getQueryHourBefore()) ? 8 : queryDateType.getQueryHourBefore();
+        //给固定时间,24小时之内
+        java.util.Date endTime = new java.util.Date();
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.setTime(endTime);
+        cal.add(java.util.Calendar.HOUR_OF_DAY, -hours);
+        java.util.Date startTime = cal.getTime();
+
+        // 2. 异步查询所有数据
+        CompletableFuture<List<OPCData>> opcDataFuture = CompletableFuture.supplyAsync(() ->
+                opcDataService.lambdaQuery()
+                        .in(OPCData::getPointName, Arrays.asList(
+                                "BF4_1_IRONNOTCH_MIR_TEMP",                 // 温度
+                                "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1",  // 1车流速
+                                "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED2",  // 2车流速
+                                "BF4_1TH_1_MIR_NWT",                        // 四高炉1TH-1号车铁水净重
+                                "BF4_1TH_2_MIR_NWT",                        // 四高炉1TH-2号车铁水净重
+                                "BF4_1_IRONNOTCH_TAPPING",                  // 1号铁口出铁状态
+                                "BF4_1TH_2_TPC_CARNO",                      // 1TH-2号车混铁车车号
+                                "BF4_1TH_1_TPC_CARNO"                       // 1TH-1号车混铁车车号
+                        ))
+                        .between(OPCData::getServerTime, startTime, endTime)
+                        .orderByAsc(OPCData::getServerTime)
+                        .list()
+        );
+
+        CompletableFuture<List<TL2Data>> l2DataFuture = CompletableFuture.supplyAsync(() ->
+                tl2DataService.lambdaQuery()
+                        .between(TL2Data::getIronStarttime,
+                                new SimpleDateFormat("yyyyMMddHHmmss").format(startTime),
+                                new SimpleDateFormat("yyyyMMddHHmmss").format(endTime))
+                        .orderByAsc(TL2Data::getIronStarttime)
+                        .list()
+        );
+
+        // 3. 等待所有查询完成并转换数据
+        try {
+            CompletableFuture.allOf(opcDataFuture, l2DataFuture).join();
+
+            // 4. 按pointName分类OPC数据并转换为DTO
+            try {
+                Map<String, List<OPCData>> opcDataMap = opcDataFuture.get().stream()
+                        .collect(Collectors.groupingBy(OPCData::getPointName));
+
+                List<TL2Data> l2DataList = l2DataFuture.get();
+
+                // 并行转换所有数据
+                //铁水温度
+                CompletableFuture<List<IronTrendL1DTO>> tempListFuture = CompletableFuture.supplyAsync(() ->
+                        opcDataMap.getOrDefault("BF4_1_IRONNOTCH_MIR_TEMP", new ArrayList<>())
+                                .stream().map(item -> {
+                                    IronTrendL1DTO dto = new IronTrendL1DTO();
+                                    dto.setData(item.getData());
+                                    dto.setCreateTime(item.getSourceTime());
+                                    return dto;
+                                }).collect(Collectors.toList())
+                );
+
+                //1TH-1号车受铁速度
+                CompletableFuture<List<IronTrendL1DTO>> speed1ListFuture = CompletableFuture.supplyAsync(() ->
+                        opcDataMap.getOrDefault("BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1", new ArrayList<>())
+                                .stream().map(item -> {
+                                    IronTrendL1DTO dto = new IronTrendL1DTO();
+                                    dto.setData(item.getData());
+                                    dto.setCreateTime(item.getSourceTime());
+                                    return dto;
+                                }).collect(Collectors.toList())
+                );
+
+                //1TH-2号车受铁速度
+                CompletableFuture<List<IronTrendL1DTO>> speed2ListFuture = CompletableFuture.supplyAsync(() ->
+                        opcDataMap.getOrDefault("BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED2", new ArrayList<>())
+                                .stream().map(item -> {
+                                    IronTrendL1DTO dto = new IronTrendL1DTO();
+                                    dto.setData(item.getData());
+                                    dto.setCreateTime(item.getSourceTime());
+                                    return dto;
+                                }).collect(Collectors.toList())
+                );
+
+                //BF4_1TH_1_MIR_NWT 1号车净重
+                CompletableFuture<List<IronTrendL1DTO>> weight1ListFuture = CompletableFuture.supplyAsync(() ->
+                        opcDataMap.getOrDefault("BF4_1TH_1_MIR_NWT", new ArrayList<>())
+                                .stream().map(item -> {
+                                    IronTrendL1DTO dto = new IronTrendL1DTO();
+                                    dto.setData(item.getData());
+                                    dto.setCreateTime(item.getSourceTime());
+                                    return dto;
+                                }).collect(Collectors.toList())
+                );
+
+                //BF4_1TH_2_MIR_NWT 2号车净重
+                CompletableFuture<List<IronTrendL1DTO>> weight2ListFuture = CompletableFuture.supplyAsync(() ->
+                        opcDataMap.getOrDefault("BF4_1TH_2_MIR_NWT", new ArrayList<>())
+                                .stream().map(item -> {
+                                    IronTrendL1DTO dto = new IronTrendL1DTO();
+                                    dto.setData(item.getData());
+                                    dto.setCreateTime(item.getSourceTime());
+                                    return dto;
+                                }).collect(Collectors.toList())
+                );
+
+                //L2
+                CompletableFuture<List<IronTrendL2DTO>> elemenListFuture = CompletableFuture.supplyAsync(() ->
+                        l2DataList.stream().map(item -> {
+                            IronTrendL2DTO dto = new IronTrendL2DTO();
+                            try {
+                                dto.setElementS(item.getElementS() != null ? Double.parseDouble(item.getElementS()) : null);
+                            } catch (NumberFormatException e) {
+                                dto.setElementS(null);
+                            }
+                            try {
+                                dto.setElementSi(item.getElementSi() != null ? Double.parseDouble(item.getElementSi()) : null);
+                            } catch (NumberFormatException e) {
+                                dto.setElementSi(null);
+                            }
+                            dto.setIronStarttime(item.getIronStarttime());
+                            dto.setIronEndtime(item.getIronEndtime());
+                            dto.setPollMm(item.getPollMm());
+                            dto.setOpenDepth(item.getOpenDepth());
+                            dto.setMudWeight(item.getMudWeight());
+                            dto.setIronCosttime(item.getIronCosttime());
+                            return dto;
+                        }).collect(Collectors.toList())
+                );
+
+                // 等待所有转换完成
+                CompletableFuture.allOf(
+                        tempListFuture,
+                        speed1ListFuture,
+                        speed2ListFuture,
+                        weight1ListFuture,
+                        weight2ListFuture,
+                        elemenListFuture
+                ).join();
+
+                // 5. 封装返回
+                try {
+                    List<IronTrendL1DTO> tempList = tempListFuture.get();
+                    List<IronTrendL1DTO> speed1List = speed1ListFuture.get();
+                    List<IronTrendL1DTO> speed2List = speed2ListFuture.get();
+                    List<IronTrendL1DTO> weight1List = weight1ListFuture.get();
+                    List<IronTrendL1DTO> weight2List = weight2ListFuture.get();
+                    List<IronTrendL2DTO> elemenList = elemenListFuture.get();
+
+                    // 1. 生成完整的每一分钟时间点
+                    java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:00");
+                    java.util.List<String> xAxis = new java.util.ArrayList<>();
+                    java.util.Calendar cursor = java.util.Calendar.getInstance();
+                    cursor.setTime(startTime);
+                    while (!cursor.getTime().after(endTime)) {
+                        xAxis.add(sdf.format(cursor.getTime()));
+                        cursor.add(java.util.Calendar.MINUTE, 1);
+                    }
+
+                    // 查询区间前的最近一条历史数据(前置值)
+                    // OPC类
+                    OPCData preTemp = opcDataService.lambdaQuery()
+                            .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_MIR_TEMP")
+                            .isNotNull(OPCData::getData)
+                            .lt(OPCData::getServerTime, startTime)
+                            .orderByDesc(OPCData::getServerTime)
+                            .last("limit 1").one();
+                    OPCData preSpeed1 = opcDataService.lambdaQuery()
+                            .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED1")
+                            .isNotNull(OPCData::getData)
+                            .lt(OPCData::getServerTime, startTime)
+                            .orderByDesc(OPCData::getServerTime)
+                            .last("limit 1").one();
+                    OPCData preSpeed2 = opcDataService.lambdaQuery()
+                            .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_RAILLINE_ACPIRON_SPEED2")
+                            .isNotNull(OPCData::getData)
+                            .lt(OPCData::getServerTime, startTime)
+                            .orderByDesc(OPCData::getServerTime)
+                            .last("limit 1").one();
+                    OPCData preWeight1 = opcDataService.lambdaQuery()
+                            .eq(OPCData::getPointName, "BF4_1TH_1_MIR_NWT")
+                            .isNotNull(OPCData::getData)
+                            .lt(OPCData::getServerTime, startTime)
+                            .orderByDesc(OPCData::getServerTime)
+                            .last("limit 1").one();
+                    OPCData preWeight2 = opcDataService.lambdaQuery()
+                            .eq(OPCData::getPointName, "BF4_1TH_2_MIR_NWT")
+                            .isNotNull(OPCData::getData)
+                            .lt(OPCData::getServerTime, startTime)
+                            .orderByDesc(OPCData::getServerTime)
+                            .last("limit 1").one();
+
+                    // tappingMap补齐每一分钟,前面无数据用前置值补齐
+                    // 先查区间前最近一条tapping
+                    Integer preTappingVal = 0;
+                    OPCData preTapping = opcDataService.lambdaQuery()
+                            .eq(OPCData::getPointName, "BF4_1_IRONNOTCH_TAPPING")
+                            .isNotNull(OPCData::getData)
+                            .lt(OPCData::getServerTime, startTime)
+                            .orderByDesc(OPCData::getServerTime)
+                            .last("limit 1").one();
+
+                    // L2类
+                    TL2Data preL2 = tl2DataService.lambdaQuery()
+                            .lt(TL2Data::getIronStarttime, new SimpleDateFormat("yyyyMMddHHmmss").format(startTime))
+                            .orderByDesc(TL2Data::getIronStarttime)
+                            .last("limit 1").one();
+
+                    // 2. 构建每分钟补全的二维数组
+                    java.util.Map<String, Object> result = new java.util.LinkedHashMap<>();
+//                    result.put("ironTemp", buildMinuteArray(tempList, xAxis, preTemp != null ? preTemp.getData() : null));
+//                    result.put("ironSpeed", buildMinuteArrayForSpeed(xAxis, speed1List, speed2List, preSpeed1 != null ? preSpeed1.getData() : null, preSpeed2 != null ? preSpeed2.getData() : null));
+//                    result.put("elementS", buildMinuteArray(elemenList, xAxis, "elementS", preL2 != null ? preL2.getElementS() : null));
+//                    result.put("elementSi", buildMinuteArray(elemenList, xAxis, "elementSi", preL2 != null ? preL2.getElementSi() : null));
+//                    result.put("mudWeight", buildMinuteArray(elemenList, xAxis, "mudWeight", preL2 != null ? preL2.getMudWeight() : null));
+//                    result.put("pollMm", buildMinuteArray(elemenList, xAxis, "pollMm", preL2 != null ? preL2.getPollMm() : null));
+//                    result.put("openDepth", buildMinuteArray(elemenList, xAxis, "openDepth", preL2 != null ? preL2.getOpenDepth() : null));
+//                    result.put("ironCosttime", buildMinuteArray(elemenList, xAxis, "ironCosttime", preL2 != null ? preL2.getIronCosttime() : null));
+
+                    // 统计出铁累计重量递增曲线
+                    if (preTapping != null && preTapping.getData() != null) {
+                        Object v = preTapping.getData();
+                        if (v instanceof Number) preTappingVal = ((Number) v).intValue();
+                        else if (v instanceof String) {
+                            String str = ((String) v).trim();
+                            if (!str.isEmpty()) {
+                                try {
+                                    preTappingVal = Integer.parseInt(str);
+                                } catch (Exception ignore) {
+                                }
+                            }
+                        }
+                    }
+                    // 构建每分钟tappingMap
+                    Map<String, Integer> tappingMap = new LinkedHashMap<>();
+                    List<OPCData> tappingList = opcDataMap.getOrDefault("BF4_1_IRONNOTCH_TAPPING", new ArrayList<>());
+                    Map<String, Integer> rawTappingMap = new LinkedHashMap<>();
+                    for (OPCData item : tappingList) {
+                        if (item.getServerTime() != null && item.getData() != null) {
+                            String min = sdf.format(item.getServerTime());
+                            Integer val = null;
+                            Object v = item.getData();
+                            if (v instanceof Number) val = ((Number) v).intValue();
+                            else if (v instanceof String) {
+                                String str = ((String) v).trim();
+                                if (!str.isEmpty()) {
+                                    try {
+                                        val = Integer.parseInt(str);
+                                    } catch (Exception ignore) {
+                                    }
+                                }
+                            }
+                            if (val != null) rawTappingMap.put(min, val);
+                        }
+                    }
+                    Integer last = preTappingVal;
+                    for (String t : xAxis) {
+                        Integer v = rawTappingMap.getOrDefault(t, last);
+                        tappingMap.put(t, v);
+                        if (v != null) last = v;
+                    }
+                    // weight1Map/weight2Map
+                    Map<String, Double> weight1Map = new LinkedHashMap<>();
+                    Map<String, Double> weight2Map = new LinkedHashMap<>();
+                    for (IronTrendL1DTO e : weight1List) {
+                        if (e.getCreateTime() != null) {
+                            String min = sdf.format(e.getCreateTime());
+                            Object v = e.getData();
+                            Double value = null;
+                            if (v instanceof Number) value = ((Number) v).doubleValue();
+                            else if (v instanceof String) {
+                                String str = ((String) v).trim();
+                                if (!str.isEmpty()) {
+                                    try {
+                                        value = Double.parseDouble(str);
+                                    } catch (Exception ignore) {
+                                    }
+                                }
+                            }
+                            if (value != null) weight1Map.put(min, value);
+                        }
+                    }
+                    for (IronTrendL1DTO e : weight2List) {
+                        if (e.getCreateTime() != null) {
+                            String min = sdf.format(e.getCreateTime());
+                            Object v = e.getData();
+                            Double value = null;
+                            if (v instanceof Number) value = ((Number) v).doubleValue();
+                            else if (v instanceof String) {
+                                String str = ((String) v).trim();
+                                if (!str.isEmpty()) {
+                                    try {
+                                        value = Double.parseDouble(str);
+                                    } catch (Exception ignore) {
+                                    }
+                                }
+                            }
+                            if (value != null) weight2Map.put(min, value);
+                        }
+                    }
+                    // 计算铁水流量总和
+//                    double totalIronFlow = calculateTotalIronFlow(xAxis, weight1Map, weight2Map);
+//                    result.put("铁水流量", totalIronFlow);
+
+                    // 铁水流量分钟累计显示,分别处理weight1Map和weight2Map,不合并为totalWeightMap
+                    int ironWeightScale = 2; // 可调整为0、1、2等
+                    List<List<Object>> ironFlowArr = buildIronFlowMinuteArrayV3(xAxis, tappingMap, weight1Map, weight2Map, ironWeightScale);
+                    result.put("ironWeight", ironFlowArr);
+
+                    return result;
+                } catch (InterruptedException | ExecutionException e) {
+                    e.printStackTrace();
+                    throw new OperateException("获取或转换趋势数据异常: " + e.getMessage());
+                }
+
+            } catch (InterruptedException | ExecutionException e) {
+                e.printStackTrace();
+                throw new OperateException("获取或转换趋势数据异常: " + e.getMessage());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new OperateException("获取趋势数据异常: " + e.getMessage());
+        }
+    }
+
+    // 新增工具方法:L1类型
+    private java.util.List<java.util.List<Object>> buildMinuteArray(java.util.List<IronTrendL1DTO> list, java.util.List<String> xAxis, Object preValue) {
+        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:00");
+        java.util.Map<String, Object> timeValueMap = new java.util.LinkedHashMap<>();
+        for (IronTrendL1DTO e : list) {
+            if (e.getCreateTime() != null) {
+                String min = sdf.format(e.getCreateTime());
+                timeValueMap.put(min, e.getData());
+            }
+        }
+        java.util.List<java.util.List<Object>> arr = new java.util.ArrayList<>();
+        Object last = preValue;
+        for (String t : xAxis) {
+            Object v = timeValueMap.getOrDefault(t, last);
+            arr.add(java.util.Arrays.asList(t, v));
+            if (v != null) last = v;
+        }
+        return arr;
+    }
+
+    // 合并铁水流速专用
+    private java.util.List<java.util.List<Object>> buildMinuteArrayForSpeed(java.util.List<String> xAxis, java.util.List<IronTrendL1DTO> speed1List, java.util.List<IronTrendL1DTO> speed2List, Object pre1, Object pre2) {
+        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:00");
+        java.util.Map<String, Double> speed1Map = new java.util.LinkedHashMap<>();
+        java.util.Map<String, Double> speed2Map = new java.util.LinkedHashMap<>();
+        for (IronTrendL1DTO e : speed1List) {
+            if (e.getCreateTime() != null) {
+                String min = sdf.format(e.getCreateTime());
+                Object v = e.getData();
+                Double value = null;
+                if (v instanceof Number) value = ((Number) v).doubleValue();
+                else if (v instanceof String) {
+                    try {
+                        value = Double.parseDouble((String) v);
+                    } catch (Exception ignore) {
+                    }
+                }
+                if (value != null) speed1Map.put(min, value);
+            }
+        }
+        for (IronTrendL1DTO e : speed2List) {
+            if (e.getCreateTime() != null) {
+                String min = sdf.format(e.getCreateTime());
+                Object v = e.getData();
+                Double value = null;
+                if (v instanceof Number) value = ((Number) v).doubleValue();
+                else if (v instanceof String) {
+                    try {
+                        value = Double.parseDouble((String) v);
+                    } catch (Exception ignore) {
+                    }
+                }
+                if (value != null) speed2Map.put(min, value);
+            }
+        }
+        java.util.List<java.util.List<Object>> arr = new java.util.ArrayList<>();
+        Double last = null;
+        Double pre1Val = null, pre2Val = null;
+        if (pre1 != null) {
+            if (pre1 instanceof Number) pre1Val = ((Number) pre1).doubleValue();
+            else if (pre1 instanceof String) {
+                String str = ((String) pre1).trim();
+                if (!str.isEmpty()) {
+                    try {
+                        pre1Val = Double.parseDouble(str);
+                    } catch (Exception ignore) {
+                    }
+                }
+            }
+        }
+        if (pre2 != null) {
+            if (pre2 instanceof Number) pre2Val = ((Number) pre2).doubleValue();
+            else if (pre2 instanceof String) {
+                String str = ((String) pre2).trim();
+                if (!str.isEmpty()) {
+                    try {
+                        pre2Val = Double.parseDouble(str);
+                    } catch (Exception ignore) {
+                    }
+                }
+            }
+        }
+        if (pre1Val != null && pre2Val != null) last = pre1Val + pre2Val;
+        else if (pre1Val != null) last = pre1Val;
+        else if (pre2Val != null) last = pre2Val;
+        for (String t : xAxis) {
+            Double cur1 = speed1Map.getOrDefault(t, null);
+            Double cur2 = speed2Map.getOrDefault(t, null);
+            Double sum = null;
+            if (cur1 != null && cur2 != null) sum = cur1 + cur2;
+            else if (cur1 != null) sum = cur1;
+            else if (cur2 != null) sum = cur2;
+            else sum = last;
+            arr.add(java.util.Arrays.asList(t, sum));
+            if (sum != null) last = sum;
+        }
+        return arr;
+    }
+
+    // L2类型,增加preValue参数
+    private java.util.List<java.util.List<Object>> buildMinuteArray(java.util.List<IronTrendL2DTO> list, java.util.List<String> xAxis, String field, Object preValue) {
+        java.util.Map<String, Object> timeValueMap = new java.util.LinkedHashMap<>();
+        for (IronTrendL2DTO e : list) {
+            String min = e.getIronStarttime();
+            if (min != null) {
+                Object v = null;
+                switch (field) {
+                    case "elementS":
+                        v = e.getElementS();
+                        break;
+                    case "elementSi":
+                        v = e.getElementSi();
+                        break;
+                    case "mudWeight":
+                        v = e.getMudWeight();
+                        break;
+                    case "pollMm":
+                        v = e.getPollMm();
+                        break;
+                    case "openDepth":
+                        v = e.getOpenDepth();
+                        break;
+                    case "ironCosttime":
+                        v = e.getIronCosttime();
+                        break;
+                }
+                timeValueMap.put(min, v);
+            }
+        }
+        java.util.List<java.util.List<Object>> arr = new java.util.ArrayList<>();
+        Object last = preValue;
+        for (String t : xAxis) {
+            Object v = timeValueMap.getOrDefault(t, last);
+            arr.add(java.util.Arrays.asList(t, v));
+            if (v != null) last = v;
+        }
+        return arr;
+    }
+
+
+    // 新增:铁水流量分钟累计显示方法V3,分别处理weight1Map和weight2Map
+    private List<List<Object>> buildIronFlowMinuteArrayV3(List<String> xAxis, Map<String, Integer> tappingMap, Map<String, Double> weight1Map, Map<String, Double> weight2Map, int scale) {
+        List<List<Object>> arr = new ArrayList<>();
+        double accWeight = 0;
+        // 1号车状态
+        double max1 = 0, last1 = 0;
+        boolean inCycle1 = false;
+        // 2号车状态
+        double max2 = 0, last2 = 0;
+        boolean inCycle2 = false;
+        for (int i = 0; i < xAxis.size(); i++) {
+            String t = xAxis.get(i);
+            Integer tapping = tappingMap.getOrDefault(t, 0);
+            double w1 = weight1Map.getOrDefault(t, 0d);
+            double w2 = weight2Map.getOrDefault(t, 0d);
+
+            // 1号车逻辑
+            if (w1 > 0) {
+                if (!inCycle1) {
+                    inCycle1 = true;
+                    max1 = w1;
+                } else {
+                    if (w1 > max1) max1 = w1;
+                }
+            }
+            // 2号车逻辑
+            if (w2 > 0) {
+                if (!inCycle2) {
+                    inCycle2 = true;
+                    max2 = w2;
+                } else {
+                    if (w2 > max2) max2 = w2;
+                }
+            }
+
+            // 检查1号车归零
+            boolean add1 = false;
+            if (inCycle1 && w1 == 0 && last1 > 0) {
+                add1 = true;
+                inCycle1 = false;
+            }
+            // 检查2号车归零
+            boolean add2 = false;
+            if (inCycle2 && w2 == 0 && last2 > 0) {
+                add2 = true;
+                inCycle2 = false;
+            }
+
+            // 归零时把最大值加到累计值
+            if (add1 && max1 > 0) {
+                accWeight += max1;
+                max1 = 0;
+            }
+            if (add2 && max2 > 0) {
+                accWeight += max2;
+                max2 = 0;
+            }
+
+            // tapping=1时,显示accWeight+max(max1,max2);tapping=0时,显示0
+            double value;
+            if (tapping == 1) {
+                double curMax = Math.max(max1, max2);
+                value = accWeight + curMax;
+            } else {
+                value = 0;
+            }
+            // 按scale参数四舍五入
+            double factor = Math.pow(10, scale);
+            value = Math.round(value * factor) / factor;
+            arr.add(Arrays.asList(t, value));
+            last1 = w1;
+            last2 = w2;
+        }
+        return arr;
+    }
+}

+ 51 - 13
taphole-iron/src/main/java/com/sckj/iron/socketio/DeviceEventListener.java

@@ -19,6 +19,7 @@ import com.sckj.iron.service.impl.*;
 import com.sckj.iron.util.LocalDateUtils;
 import com.sckj.iron.util.RangeUtils;
 import com.sckj.iron.vo.IronStepVO;
+import com.sckj.l2.dto.TrendRequest;
 import com.sckj.l2.entity.TL2Data;
 import com.sckj.l2.service.impl.TL2DataServiceImpl;
 import com.sckj.opc.dataservice.HDServiceImpl;
@@ -120,6 +121,9 @@ public class DeviceEventListener extends EventListener { //
     @Resource
     OPCDAServiceImpl opcuaService;
 
+    @Resource
+    TIronVisualScreenServiceImpl tIronVisualScreenService;
+
     //铁水成分
     private static final String IRON_ELEMENT = "ironElement";
     //铁水温度
@@ -191,10 +195,13 @@ public class DeviceEventListener extends EventListener { //
     //实时数据
     private Map<String, Object> mRealtimeData = new ConcurrentHashMap<>();
 
+    //实时数据-折线图图例
+    private Map<String, Object> mRealtimeLegend = new ConcurrentHashMap<>();
+
+
     //实时状态
     //开口机开口状态
     //泥炮堵口状态
-    //
     private Map<String, Object> mRealtimeStatus = new ConcurrentHashMap<>();
 
     private AtomicDouble speed1 = new AtomicDouble(0);
@@ -265,8 +272,11 @@ public class DeviceEventListener extends EventListener { //
             getModels();
         });
 
+
+
         taskExecutor.submit(() -> {
             getSchedules();
+
             TIronSchedule opcdasubscribe = scheduleMap.get(TaskNameConstants.TASKNAME_OPCDASUBSCRIBE);
             if (opcdasubscribe != null && "1".equals(opcdasubscribe.getStatus())) {
                 scheduledTaskManager.addTask(opcdasubscribe.getName(), opcdasubscribe.getDelay(), opcdasubscribe.getPeriod(), TimeUnit.SECONDS, () -> {
@@ -280,11 +290,32 @@ public class DeviceEventListener extends EventListener { //
                     scheduledTaskManager.cancelTask(TaskNameConstants.TASKNAME_OPCDASUBSCRIBE);
                 });
             }
-        });
 
+            //预警列表
+            TIronSchedule getExceptionList = scheduleMap.get(TaskNameConstants.TASKNAME_EXCEPTION_LIST);
+            if (getExceptionList != null && "1".equals(getExceptionList.getStatus())) {
+                scheduledTaskManager.addTask(getExceptionList.getName(), getExceptionList.getDelay(), getExceptionList.getPeriod(), TimeUnit.SECONDS, () -> {
+                    getExceptionList();
+                });
+            }
+
+            //折线图数据
+            TIronSchedule getIronChart = scheduleMap.get(TaskNameConstants.TASKNAME_IRON_CHART);
+            if (getIronChart != null && "1".equals(getIronChart.getStatus())) {
+                scheduledTaskManager.addTask(getIronChart.getName(), getIronChart.getDelay(), getIronChart.getPeriod(), TimeUnit.SECONDS, () -> {
+                    PushData.send2Chart(tIronVisualScreenService.getIronChart(new TrendRequest()));
+                });
+            }
+
+            //图例数据
+            TIronSchedule getIronLegend = scheduleMap.get(TaskNameConstants.TASKNAME_IRON_LEGEND);
+            if (getIronLegend != null && "1".equals(getIronLegend.getStatus())) {
+                scheduledTaskManager.addTask(getIronLegend.getName(), getIronLegend.getDelay(), getIronLegend.getPeriod(), TimeUnit.SECONDS, () -> {
+                    mRealtimeLegend.put("ironCosttime",  RealtimeData.builder().desc("出铁时间").value(getIronElapsedMinute()).unit("min").build());
+                    PushData.send2Legend(tIronVisualScreenService.getIronLegend(mRealtimeLegend));
+                });
+            }
 
-        scheduledTaskManager.addTask("warnList", 0, 60, TimeUnit.SECONDS, () -> {
-             getExceptionList();
         });
 
     }
@@ -311,7 +342,7 @@ public class DeviceEventListener extends EventListener { //
                     tappingAlramUrl = SERVER_URL + "/" + GlobalConfig.publicPrefix + File.separator + tAudio.getPath();
                 } else if (Objects.equals(tAudio.getExceptionType(), "300")) {
                     tappingTimeoutAlramUrl = SERVER_URL + "/" + GlobalConfig.publicPrefix + File.separator + tAudio.getPath();
-                }else if (Objects.equals(tAudio.getExceptionType(), "300")) {
+                } else if (Objects.equals(tAudio.getExceptionType(), "300")) {
                     openAlarmUrl = SERVER_URL + "/" + GlobalConfig.publicPrefix + File.separator + tAudio.getPath();
                 }
             }
@@ -344,6 +375,10 @@ public class DeviceEventListener extends EventListener { //
                     StandardConstans.STANDARD_OPEN_HOUR = Integer.parseInt(mIronParam.getParamValue());
                 } else if (Objects.equals(mIronParam.getParamName(), ParamsConstants.iron_weight)) {
                     StandardConstans.STANDARD_IRON_WEIGHT = mIronParam.getParamValue();
+                } else if (Objects.equals(mIronParam.getParamName(), ParamsConstants.open_machine_value)) {
+                    StandardConstans.STANDARD_OPEN_MACHINE_LOCATION = Double.parseDouble(mIronParam.getParamValue());
+                } else if (Objects.equals(mIronParam.getParamName(), ParamsConstants.mud_machine_value)) {
+                    StandardConstans.STANDARD_MUD_MACHINE_PRESSURE = Double.parseDouble(mIronParam.getParamValue());
                 }
 //                mContext.setVariable(ExpressionConstants.stdSpeed, STANDARD_SPEED.get());
                 mContext.setVariable(ExpressionConstants.stdSpeedMin, Double.parseDouble(StandardConstans.STANDARD_SPEED.split("-")[0]));
@@ -361,7 +396,7 @@ public class DeviceEventListener extends EventListener { //
                 mContext.setVariable(ExpressionConstants.stdIronWeightMin, Double.parseDouble(StandardConstans.STANDARD_IRON_WEIGHT.split("-")[0]));
                 mContext.setVariable(ExpressionConstants.stdIronWeightMax, Double.parseDouble(StandardConstans.STANDARD_IRON_WEIGHT.split("-")[1]));
             }
-            log.info("STANDARD_SPEED: {},PRESSURE_DIFF_VALUE:{}", StandardConstans.STANDARD_SPEED,  StandardConstans.STANDARD_PRESSURE_DIFF);
+            log.info("STANDARD_SPEED: {},PRESSURE_DIFF_VALUE:{}", StandardConstans.STANDARD_SPEED, StandardConstans.STANDARD_PRESSURE_DIFF);
             log.info("IRON_TIME: {},SERVER_URL:{}", StandardConstans.STANDARD_IRON_TIME, SERVER_URL);
         }
     }
@@ -393,7 +428,7 @@ public class DeviceEventListener extends EventListener { //
 
         //异步保存OPC数据
         taskExecutor.submit(() -> {
-            if(!"1".equals(opcData.getSaveDb())){
+            if (!"1".equals(opcData.getSaveDb())) {
                 return;
             }
             if (opcData.getPointName().contains(SubscribeTagConstants.TAG_TAPHOLE1_STATUS(opcData.getServerType()))) {
@@ -553,7 +588,7 @@ public class DeviceEventListener extends EventListener { //
         PushData.send2CloseTime(realtimeData);
     }
 
-    private void getExceptionList(){
+    private void getExceptionList() {
         List<TExceptionLogBigScreenVo> list = exceptionLogService.getExceptionLogList();
         PushData.send2Exception(list);
     }
@@ -895,6 +930,7 @@ public class DeviceEventListener extends EventListener { //
                 realtimeData.setDesc("铁水温度");
                 realtimeData.setTime(LocalDateUtils.formatDate(opcData.getServerTime()));
                 mRealtimeData.put(IRON_TEMP, realtimeData);
+                mRealtimeLegend.put(IRON_TEMP, realtimeData);
 
                 double tempNow = Double.parseDouble(opcData.getData().toString());
 
@@ -913,7 +949,7 @@ public class DeviceEventListener extends EventListener { //
                 //1TH-1号车受铁速度、1TH-2号车受铁速度、
                 RealtimeData realtimeData = new RealtimeData();
                 realtimeData.setValue(opcData.getData());
-                realtimeData.setUnit("t/s");
+                realtimeData.setUnit("t/min");
 
                 RealtimeData ironSpeed = (RealtimeData) mRealtimeData.get(IRON_SPEED);
                 List<RealtimeData> speeds = null;
@@ -929,6 +965,7 @@ public class DeviceEventListener extends EventListener { //
 
                     ironSpeed.setValue(speeds);
                     mRealtimeData.put(IRON_SPEED, ironSpeed);
+                    mRealtimeLegend.put(IRON_SPEED, ironSpeed);
                 } else {
                     speeds = (List<RealtimeData>) ironSpeed.getValue();
                 }
@@ -1081,16 +1118,16 @@ public class DeviceEventListener extends EventListener { //
                 realtimeData.setValue(Boolean.TRUE.equals(value) ? 1 : 0);
                 realtimeData.setDesc("冲渣状态");
                 mRealtimeStatus.put(CHONGZ_STATUS, realtimeData);
-            }else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_KKJ_STATUS(opcData.getServerType()))) {
+            } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_KKJ_STATUS(opcData.getServerType()))) {
                 //开口机状态
                 RealtimeData realtimeData = new RealtimeData();
-                realtimeData.setValue(opcData.getData());
+                realtimeData.setValue(Double.parseDouble(opcData.getData().toString()) >= StandardConstans.STANDARD_OPEN_MACHINE_LOCATION ? 1 : 0);
                 realtimeData.setDesc("开口机状态");
                 mRealtimeStatus.put(KKJ_STATUS, realtimeData);
-            }else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_NPXZYY(opcData.getServerType()))) {
+            } else if (opcData.getPointName().contains(SubscribeTagConstants.TAG_NPXZYY(opcData.getServerType()))) {
                 //泥炮堵口状态
                 RealtimeData realtimeData = new RealtimeData();
-                realtimeData.setValue(opcData.getData());
+                realtimeData.setValue(Double.parseDouble(opcData.getData().toString()) >= StandardConstans.STANDARD_MUD_MACHINE_PRESSURE ? 1 : 0);
                 realtimeData.setDesc("泥炮堵口状态");
                 mRealtimeStatus.put(NPDK_STATUS, realtimeData);
             }
@@ -1143,6 +1180,7 @@ public class DeviceEventListener extends EventListener { //
         ironWeight.setValue(mTotalWeight);
         ironWeight.setTime(LocalDateUtils.formatDate(opcData.getServerTime()));
         mRealtimeData.put(IRON_WEIGHT, ironWeight);
+        mRealtimeLegend.put(IRON_WEIGHT, ironWeight);
         mContext.setVariable(ExpressionConstants.rtIronWeight, mTotalWeight.doubleValue());
 
         //推送实时数据

+ 40 - 0
taphole-iron/src/main/java/com/sckj/iron/socketio/PushData.java

@@ -88,6 +88,16 @@ public class PushData {
      */
     public static final String IRON_EXCEPTION = "IRON_EXCEPTION";
 
+    /***
+     *实时数据图图例
+     */
+    public static final String IRON_REALTIME_LEGEND = "IRON_REALTIME_LEGEND";
+
+    /**
+     * 折线图数据
+     */
+    public static final String IRON_REALTIME_CHART = "IRON_REALTIME_CHART";
+
 
     /**
      * 出铁操作
@@ -288,6 +298,36 @@ public class PushData {
             entry.getValue().sendEvent(PushData.IRON_EXCEPTION, AjaxResult.success(message));
         }
     }
+    /**
+     * 告警列表
+     *
+     * @return
+     * @Param
+     **/
+    public static void send2Legend(Object message) {
+        if (SocketUtil.connectMap.isEmpty()) {
+            return;
+        }
+        //
+        for (Map.Entry<String, SocketIOClient> entry : SocketUtil.connectMap.entrySet()) {
+            entry.getValue().sendEvent(PushData.IRON_REALTIME_LEGEND, AjaxResult.success(message));
+        }
+    }
+    /**
+     * 告警列表
+     *
+     * @return
+     * @Param
+     **/
+    public static void send2Chart(Object message) {
+        if (SocketUtil.connectMap.isEmpty()) {
+            return;
+        }
+        //
+        for (Map.Entry<String, SocketIOClient> entry : SocketUtil.connectMap.entrySet()) {
+            entry.getValue().sendEvent(PushData.IRON_REALTIME_CHART, AjaxResult.success(message));
+        }
+    }
 
 
 }

+ 4 - 3
taphole-l2-start/src/main/java/com/sckj/l2start/listener/L2EventListener.java

@@ -44,7 +44,7 @@ public class L2EventListener extends EventListener {
 //        log.info("onL2DataMessageEvent");
         log.info("{}",dataList);
 //        log.info("onL2DataMessageEvent");
-        if (dataList.size() == 22) {
+        if (dataList.size() >= 23) {
             TL2Material tl2Material = new TL2Material();
             tl2Material.setChargeNo(dataList.get(0));
             tl2Material.setTheoryWeight(dataList.get(1));
@@ -82,10 +82,11 @@ public class L2EventListener extends EventListener {
             taskExecutor.submit(() -> {
                 RedisUtils.addFixedElement("ironElement", tl2Data, 10);
             });
+            taskExecutor.submit(() -> {
+                RedisUtils.addFixedElement("materialSpeed", dataList.get(22), 10);
+            });
             //boolean dataResult = tl2DataService.saveOrUpdate(tl2Data);
-
             //log.info("tl2Material:{},tl2Data:{}", materialResult, dataResult);
-
         }
     }
 

+ 4 - 4
taphole-l2-start/src/main/resources/application-prod.yml

@@ -14,10 +14,10 @@ spring:
     password: root # 数据库密码
   # Redis配置
   redis:
-    host: redis   # Redis服务地址
-    port: 6379        # Redis端口
-#    password: sckj@1234        # Redis密码
-#    database: 5       # 数据库索引
+    host: 168.15.15.38   # Redis服务地址
+    port: 16379        # Redis端口
+    password:         # Redis密码
+    database: 5       # 数据库索引
 
 # Mybatis-plus配置 【是否开启SQL日志输出】
 #mybatis-plus:

+ 2 - 2
taphole-l2/src/main/java/com/sckj/l2/dto/TrendRequest.java

@@ -15,8 +15,8 @@ import org.springframework.stereotype.Component;
 @ApiOperation("趋势请求信息")
 public class TrendRequest {
 
-    @ApiModelProperty(value = "查询日期类型",required = true)
-    private Integer queryDateType;
+    @ApiModelProperty(value = "多少小时之前",required = true)
+    private Integer queryHourBefore;
 
 
 }

+ 0 - 13
taphole-l2/src/main/java/com/sckj/l2/service/impl/TL2DataServiceImpl.java

@@ -10,8 +10,6 @@ import com.sckj.common.exception.OperateException;
 import com.sckj.common.util.ExcelUtils;
 import com.sckj.common.util.StringUtils;
 import com.sckj.common.validate.commons.PageValidate;
-import com.sckj.l2.dto.TL2DataDTO;
-import com.sckj.l2.dto.TrendRequest;
 import com.sckj.l2.entity.TL2Data;
 import com.sckj.l2.mapper.TL2DataMapper;
 import com.sckj.l2.validate.TL2DataSearchValidate;
@@ -208,17 +206,6 @@ public class TL2DataServiceImpl extends ServiceImpl<TL2DataMapper, TL2Data> {
     }
 
 
-    public List<TL2DataDTO> getTrendDataByDiffDays(TrendRequest diffDays) {
-        switch (diffDays.getQueryDateType()) {
-            case -1:
-            case 0:
-                List<TL2DataDTO> trendDataByDiffDayList = tL2DataMapper.getTrendDataByDiffDay(diffDays.getQueryDateType());
-                return trendDataByDiffDayList;
-            default:
-                List<TL2DataDTO> trendDataByDiffDays = tL2DataMapper.getTrendDataByDiffDays(diffDays.getQueryDateType());
-                return trendDataByDiffDays;
-        }
-    }
 
     /**
      * 数据统计

+ 112 - 18
taphole-opc/src/main/java/com/sckj/opc/dataservice/OPCDAServiceImpl.java

@@ -18,6 +18,9 @@ import org.openscada.opc.lib.common.ConnectionInformation;
 import org.openscada.opc.lib.da.*;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 
@@ -30,6 +33,7 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.regex.Pattern;
 
 @Service
 @Slf4j
@@ -58,6 +62,10 @@ public class OPCDAServiceImpl {
     private ConcurrentHashMap<String, ItemState> mOPCDaPointsMap = new ConcurrentHashMap<>();
 
 
+    // 存储上一次的数据值,用于避免推送重复数据
+    private ConcurrentHashMap<String, Object> lastDataMap = new ConcurrentHashMap<>();
+
+
     /**
      * 获得基础的连接信息
      */
@@ -106,10 +114,10 @@ public class OPCDAServiceImpl {
                             int errorCode = itemstate.getErrorCode();
                             String pointName = item.getId();
                             Calendar calendar = itemstate.getTimestamp();
-                            Object object = null;
+                            Object data = null;
                             try {
-                                object = itemstate.getValue().getObject();
-                                if (NumberUtils.isCreatable(object.toString())) {
+                                data = itemstate.getValue().getObject();
+                                if (NumberUtils.isCreatable(data.toString())) {
                                     // 如果 digits 为 null,默认设为 0
                                     int decimalDigits = (opcPoint.getDigits() != null) ? opcPoint.getDigits() : 0;
                                     // 构建格式化模式
@@ -121,26 +129,112 @@ public class OPCDAServiceImpl {
                                         }
                                     }
                                     DecimalFormat decimalFormat = new DecimalFormat(pattern);
-                                    String formatString = decimalFormat.format(object);
+                                    String formatString = decimalFormat.format(data);
                                     if (formatString.contains(".")) {
-                                        object = Double.parseDouble(formatString);
+                                        data = Double.parseDouble(formatString);
                                     } else {
-                                        object = Integer.parseInt(formatString);
+                                        data = Integer.parseInt(formatString);
+                                    }
+                                }
+
+                                // 判断data是否在有效范围内(支持区间、枚举、比较符号、正则、复杂逻辑等多种格式)
+                                String dataRange = opcPoint.getDataRangeExp();
+                                boolean valid = true;
+                                if (ObjectUtils.isNotEmpty(dataRange) && !dataRange.trim().isEmpty() && ObjectUtils.isNotEmpty(data)) {
+                                    String valueStr = data.toString();
+                                    String[] conditions = dataRange.split(",");
+                                    for (String cond : conditions) {
+                                        cond = cond.trim();
+                                        if (cond.isEmpty()) continue;
+                                        boolean condValid = true;
+                                        try {
+                                            if (cond.startsWith("regex:")) {
+                                                // 正则表达式判断
+                                                String regex = cond.substring(6);
+                                                condValid = Pattern.matches(regex, valueStr);
+                                            } else if (cond.contains("-")) {
+                                                // 区间判断
+                                                String[] parts = cond.split("-");
+                                                if (parts.length == 2) {
+                                                    double min = Double.parseDouble(parts[0].trim());
+                                                    double max = Double.parseDouble(parts[1].trim());
+                                                    double val = Double.parseDouble(valueStr);
+                                                    condValid = val >= min && val <= max;
+                                                } else {
+                                                    condValid = false;
+                                                }
+                                            } else if (cond.startsWith(">=")) {
+                                                double min = Double.parseDouble(cond.substring(2).trim());
+                                                double val = Double.parseDouble(valueStr);
+                                                condValid = val >= min;
+                                            } else if (cond.startsWith("<=")) {
+                                                double max = Double.parseDouble(cond.substring(2).trim());
+                                                double val = Double.parseDouble(valueStr);
+                                                condValid = val <= max;
+                                            } else if (cond.startsWith(">")) {
+                                                double min = Double.parseDouble(cond.substring(1).trim());
+                                                double val = Double.parseDouble(valueStr);
+                                                condValid = val > min;
+                                            } else if (cond.startsWith("<")) {
+                                                double max = Double.parseDouble(cond.substring(1).trim());
+                                                double val = Double.parseDouble(valueStr);
+                                                condValid = val < max;
+                                            } else if (cond.startsWith("!=")) {
+                                                double notVal = Double.parseDouble(cond.substring(2).trim());
+                                                double val = Double.parseDouble(valueStr);
+                                                condValid = val != notVal;
+                                            } else if (cond.contains("val") || cond.contains("&&") || cond.contains("||")) {
+                                                // 复杂逻辑表达式,使用Spring SpEL
+                                                try {
+                                                    StandardEvaluationContext context = new StandardEvaluationContext();
+                                                    context.setVariable("val", data instanceof Number ? ((Number) data).doubleValue() : data);
+                                                    ExpressionParser parser = new SpelExpressionParser();
+                                                    condValid = parser.parseExpression(cond).getValue(context, Boolean.class);
+                                                } catch (Exception e) {
+                                                    condValid = false;
+                                                }
+                                            } else {
+                                                // 枚举判断(字符串或数字)
+                                                condValid = cond.equals(valueStr);
+                                            }
+                                        } catch (Exception e) {
+                                            condValid = false;
+                                        }
+                                        if (!condValid) {
+                                            valid = false;
+                                            break;
+                                        }
                                     }
                                 }
+                                if (!valid) {
+                                    log.info("{}({})数据{}不在有效范围{}内,不推送", opcPoint.getPointName(), opcPoint.getPointDesc(), data, dataRange);
+                                    return;
+                                }
 
-                                OPCData opcData = OPCData.builder()
-                                        .data(object)
-                                        .serverTime(calendar.getTime())
-                                        .sourceTime(calendar.getTime())
-                                        .statusCode((long) errorCode)
-                                        .pointName(pointName)
-                                        .dataType(opcPoint.getDataType())
-                                        .identifier(opcPoint.getIdentifier())
-                                        .build();
-                                //post给其他模块使用
-                                asyncEventBus.post(opcData);
-                                log.debug("DA,{},{}", item.getId(), itemstate);
+                                // 获取上一次的数据值
+                                Object lastData = lastDataMap.get(opcPoint.getPointName());
+
+                                // 判断是否推送数据(1-允许重复,0-不允许重复)
+                                boolean allowDuplicate = "1".equals(opcPoint.getAllowDuplicate());
+
+                                if (allowDuplicate || !ObjectUtils.equals(data, lastData)) {
+                                    OPCData opcData = OPCData.builder()
+                                            .data(data)
+                                            .serverTime(calendar.getTime())
+                                            .sourceTime(calendar.getTime())
+                                            .statusCode((long) errorCode)
+                                            .pointName(pointName)
+                                            .dataType(opcPoint.getDataType())
+                                            .identifier(opcPoint.getIdentifier())
+                                            .build();
+                                    //post给其他模块使用
+                                    asyncEventBus.post(opcData);
+                                    // 仅在不允许重复时更新缓存
+                                    if (!allowDuplicate) {
+                                        lastDataMap.put(opcPoint.getPointName(), data);
+                                    }
+                                    log.debug("DA,{},{}", item.getId(), itemstate);
+                                }
                             } catch (JIException e) {
                                 e.printStackTrace();
                             }

+ 9 - 0
taphole-opc/src/main/java/com/sckj/opc/entity/OPCPoint.java

@@ -63,4 +63,13 @@ public class OPCPoint {
     @ApiModelProperty(value = "唯一名称")
     private String identifier;
 
+    @ApiModelProperty(value = "是否允许推送重复数据(0-否,1-是)")
+    private String allowDuplicate;
+
+    @ApiModelProperty(value = "数据有效性范围(支持区间:10-100,枚举:A,B,C,比较符号:>10,<100,>=5,<=20,!=0,正则:regex:^[A-Z]{2}\\d+$,复杂逻辑:val>10&&val<100||val==200 等格式)")
+    private String dataRangeExp;
+
+    @ApiModelProperty(value = "是否入库(0-否,1-是)")
+    private String saveDb;
+
 }

+ 2 - 0
taphole-opc/src/main/java/com/sckj/opc/service/THdTagServiceImpl.java

@@ -57,6 +57,8 @@ public class THdTagServiceImpl  extends ServiceImpl<THdTagMapper, THdTag> {
             "=:tagType@tag_type:str",
             "like:tagDesc@tag_desc:str",
             "like:remark:str",
+            "=:allowDuplicate@allow_duplicate:str",
+            "=:saveDb@save_db:str",
         });
 
         IPage<THdTag> iPage = tHdTagMapper.selectPage(new Page<>(page, limit), queryWrapper);