浏览代码

Merge remote-tracking branch 'origin/master'

“lqy 1 月之前
父节点
当前提交
4368816ca5

+ 20 - 4
mjava-huagao/src/main/java/com/malk/huagao/service/impl/HuaGaoServiceImpl.java

@@ -5,6 +5,8 @@ import cn.hutool.core.util.NumberUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.malk.huagao.entity.YdNonProdPrice;
 import com.malk.huagao.entity.YdProdWorkHoursPrice;
 import com.malk.huagao.entity.YdUserInput;
@@ -78,7 +80,6 @@ public class HuaGaoServiceImpl implements HuaGaoService {
             String price=UtilMap.getString(formData,"numberField_m8e4zajw_value");
             List<String> users=ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(),Long.parseLong(deptId));
             for (String user : users) {
-//                if (!user.equals("17399270929001990") && !user.equals("17102075948191646") && !user.equals("16137991608422361") && !user.equals("16992517127785448")) continue;//刘多富、白涛、陈辉、吕明权
                 List<Map> valList=ddClientAttendance.getAttColumnVal(ddClient.getAccessToken(),user, Arrays.asList("82772106","89349019","82772125"),stTime,edTime);
                 if(valList!=null&&valList.size()==3){
                     Map<String,Map<String,String>> dateMap=toDateMap(valList);
@@ -88,9 +89,24 @@ public class HuaGaoServiceImpl implements HuaGaoService {
                                 dataMap.get("89349019"): NumberUtil.add(dataMap.get("89349019"),dataMap.get("82772106")).toString();
 
                         //查询用户详情
-                        DDR_New ddrNew1 = (DDR_New) UtilHttp.doPost("https://oapi.dingtalk.com/topapi/v2/user/get", null, ddClient.initTokenParams(), UtilMap.map("userid", user), DDR_New.class);
-                        Map result1 = (Map) ddrNew1.getResult();
-                        String userName = UtilMap.getString(result1, "name");
+                        String userName = "";
+
+                        LambdaQueryWrapper<YdUserInput> ydUserInputLambdaQueryWrapper2 = new LambdaQueryWrapper<>();
+                        ydUserInputLambdaQueryWrapper2.eq(YdUserInput::getUserId,user)
+                                .isNotNull(YdUserInput::getUserName);
+
+                        Page<YdUserInput> page = new Page<>(1, 1);
+                        IPage<YdUserInput> result = ydUserInputMapper.selectPage(page, ydUserInputLambdaQueryWrapper2);
+
+                        YdUserInput ydUserInput2 = result.getRecords().isEmpty() ? null : result.getRecords().get(0);
+
+                        if (Objects.nonNull(ydUserInput2)){
+                            userName = ydUserInput2.getUserName();
+                        }else {
+                            DDR_New ddrNew1 = (DDR_New) UtilHttp.doPost("https://oapi.dingtalk.com/topapi/v2/user/get", null, ddClient.initTokenParams(), UtilMap.map("userid", user), DDR_New.class);
+                            Map result1 = (Map) ddrNew1.getResult();
+                            userName = UtilMap.getString(result1, "name");
+                        }
 
                         log.info("user:{},userName:{},date:{},workTime:{},price:{}",user,userName,date,workTime,price);
 

+ 22 - 5
mjava-huagao/src/test/java/com/malk/huagao/YyYdTest.java

@@ -4,6 +4,8 @@ import cn.hutool.core.util.NumberUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.malk.huagao.entity.YdUserInput;
 import com.malk.huagao.mapper.YdUserInputMapper;
 import com.malk.huagao.service.HuaGaoService;
@@ -129,8 +131,8 @@ public class YyYdTest {
 
     @Test
     public void test5(){
-        String stTime="2025-10-11 00:00:00";
-        String edTime="2025-10-16 00:00:00";
+        String stTime="2025-10-23 00:00:00";
+        String edTime="2025-10-24 00:00:00";
         List<Map> list=ydService.queryAllFormData(YDParam.builder().formUuid("FORM-5061F7AE543B429C8241EC730A6F31653NNK").build());
         for(Map map:list){
             Map formData= UtilMap.getMap(map,"formData");
@@ -147,9 +149,24 @@ public class YyYdTest {
                                 dataMap.get("89349019"): NumberUtil.add(dataMap.get("89349019"),dataMap.get("82772106")).toString();
 
                         //查询用户详情
-                        DDR_New ddrNew1 = (DDR_New) UtilHttp.doPost("https://oapi.dingtalk.com/topapi/v2/user/get", null, ddClient.initTokenParams(), UtilMap.map("userid", user), DDR_New.class);
-                        Map result1 = (Map) ddrNew1.getResult();
-                        String userName = UtilMap.getString(result1, "name");
+                        String userName = "";
+
+                        LambdaQueryWrapper<YdUserInput> ydUserInputLambdaQueryWrapper2 = new LambdaQueryWrapper<>();
+                        ydUserInputLambdaQueryWrapper2.eq(YdUserInput::getUserId,user)
+                                .isNotNull(YdUserInput::getUserName);
+
+                        Page<YdUserInput> page = new Page<>(1, 1);
+                        IPage<YdUserInput> result = ydUserInputMapper.selectPage(page, ydUserInputLambdaQueryWrapper2);
+
+                        YdUserInput ydUserInput2 = result.getRecords().isEmpty() ? null : result.getRecords().get(0);
+
+                        if (Objects.nonNull(ydUserInput2)){
+                            userName = ydUserInput2.getUserName();
+                        }else {
+                            DDR_New ddrNew1 = (DDR_New) UtilHttp.doPost("https://oapi.dingtalk.com/topapi/v2/user/get", null, ddClient.initTokenParams(), UtilMap.map("userid", user), DDR_New.class);
+                            Map result1 = (Map) ddrNew1.getResult();
+                            userName = UtilMap.getString(result1, "name");
+                        }
 
                         log.info("user:{},userName:{},date:{},workTime:{},price:{}",user,userName,date,workTime,price);
 

+ 10 - 10
mjava-jianhui/pom.xml

@@ -26,16 +26,16 @@
             <artifactId>base</artifactId>
             <version>1.1-SNAPSHOT</version>
         </dependency>
-<!--        <dependency>-->
-<!--            <groupId>com.example</groupId>-->
-<!--            <artifactId>k3cloud</artifactId>-->
-<!--            <version>8.0.6</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>com.example</groupId>-->
-<!--            <artifactId>gson</artifactId>-->
-<!--            <version>2.8.0</version>-->
-<!--        </dependency>-->
+        <dependency>
+            <groupId>com.example</groupId>
+            <artifactId>k3cloud</artifactId>
+            <version>8.0.6</version>
+        </dependency>
+        <dependency>
+            <groupId>com.example</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.0</version>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>

+ 4 - 0
mjava-tonglibo/pom.xml

@@ -18,6 +18,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.projectlombok</groupId>

+ 6 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/Mapper/MachineDataMapper.java

@@ -3,7 +3,13 @@ package com.malk.tonglibo.Mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.malk.tonglibo.entity.MachineData;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
 
 @Mapper
 public interface MachineDataMapper extends BaseMapper<MachineData> {
+    List<MachineData> selectLatestRuntimeByParamId();
+    List<MachineData> selectDailyZcnAtEight();
 }

+ 4 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/MjavaTongliboApplication.java

@@ -1,9 +1,13 @@
 package com.malk.tonglibo;
 
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication(scanBasePackages = {"com.malk"})
+@MapperScan("com.malk.tonglibo.Mapper")
+@EnableScheduling
 public class MjavaTongliboApplication {
 
     public static void main(String[] args) {

+ 7 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/Service/IMachineDataService.java

@@ -0,0 +1,7 @@
+package com.malk.tonglibo.Service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.malk.tonglibo.entity.MachineData;
+
+public interface IMachineDataService extends IService<MachineData> {
+}

+ 16 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/Service/impl/MachineDataServiceImpl.java

@@ -0,0 +1,16 @@
+package com.malk.tonglibo.Service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.malk.tonglibo.Mapper.MachineDataMapper;
+import com.malk.tonglibo.Service.IMachineDataService;
+import com.malk.tonglibo.entity.MachineData;
+import org.springframework.stereotype.Service;
+
+/**
+ * 功能:
+ * 作者:hanxue
+ * 日期:2025/10/18 1:23
+ */
+@Service
+public class MachineDataServiceImpl extends ServiceImpl<MachineDataMapper, MachineData> implements IMachineDataService {
+}

+ 103 - 67
mjava-tonglibo/src/main/java/com/malk/tonglibo/controller/machineController.java

@@ -2,19 +2,20 @@ package com.malk.tonglibo.controller;
 
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.malk.server.common.McR;
 import com.malk.tonglibo.Mapper.MachineDataMapper;
 import com.malk.tonglibo.entity.MachineData;
+import com.malk.tonglibo.entity.RawDeviceData;
+import com.malk.tonglibo.utils.DataBuffer;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.catalina.User;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -29,96 +30,131 @@ import java.util.regex.Pattern;
 @Slf4j
 public class machineController {
 
+    @Autowired
+    private DataBuffer dataBuffer;
     @Autowired
     private MachineDataMapper machineDataMapper;
     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
     /**
      * 同步设备数据
-     * @param params
+     * @param
      */
 
     @PostMapping("/device/data")
-    public void deviceData(@RequestBody String paramsStr){
+    public McR deviceData(@RequestBody String paramsStr){
         try {
-            log.info("设备数据paramsStr:{}",paramsStr);
-            JSONObject params = JSONObject.parseObject(paramsStr);
-            log.info("设备数据JSON:{}",params);
-            // 创建请求记录对象
-            LocalDateTime time = LocalDateTime.parse((String) params.get("time"), FORMATTER);
+            log.info("设备数据paramsStr:{}", paramsStr);
+            String fixedStr = repairMalformedKFields(paramsStr);
+            JSONObject params = JSONObject.parseObject(fixedStr);
+
+            log.info("修复后设备数据JSON:{}", params);
 
-            // 正则:匹配 k 后跟数字的字符串
+            // 解析时间
+            LocalDateTime time;
+            try {
+                time = LocalDateTime.parse((String) params.get("time"), FORMATTER);
+            } catch (Exception e) {
+                log.warn("时间解析失败,使用当前时间。原值:{}", params.get("time"));
+                time = LocalDateTime.now();
+            }
+            String timeStr = time.format(OUTPUT_FORMATTER);
+            String deviceId = params.getString("id");
             Pattern kPattern = Pattern.compile("^k\\d+$");
+            boolean hasValidData = false;
+            // 遍历所有 k* 字段,放入缓冲队列
             for (Map.Entry<String, Object> entry : params.entrySet()) {
                 String key = entry.getKey();
-                // 判断是否是 k 字段:以 'k' 开头,后面是数字
                 if (kPattern.matcher(key).matches()) {
-                    Object dataObj = entry.getValue();
-                    if (dataObj instanceof List) {
-                        List<String> data = new ArrayList<>();
-                        MachineData machineData = new MachineData();
-                        for (Object item : (List<?>) dataObj) {
-                            String strValue;
-                            if (item instanceof Number) {
-                                // 所有数字类型(Integer, Double, Float, Long)直接转字符串
-                                strValue = item.toString();
-                            } else if (item == null) {
-                                strValue = "null"; // 或者跳过、用 "" 代替
+                    try {
+                        Object dataObj = entry.getValue();
+                        if (dataObj instanceof List) {
+                            List<String> data = new ArrayList<>();
+                            for (Object item : (List<?>) dataObj) {
+                                if (item instanceof Number) {
+                                    data.add(item.toString());
+                                } else if (item == null) {
+                                    data.add("null");
+                                } else {
+                                    data.add(item.toString());
+                                }
+                            }
+                            // 创建原始数据对象,放入缓冲队列
+                            RawDeviceData raw = new RawDeviceData(deviceId, time, timeStr, key, data);
+                            if (dataBuffer.offer(raw)) {
+                                hasValidData = true;
                             } else {
-                                // 兜底:调用 toString()
-                                strValue = item.toString();
+                                log.warn("缓冲队列已满,丢弃数据 key={}, deviceId={}", key, deviceId);
                             }
-                            data.add(strValue); // 存入字符串
                         }
-
-                        System.out.println("数据:" + data);
-                        machineData.setParamId(params.get("id").toString());
-                        machineData.setTime(time);
-                        machineData.setMachineNo(key);
-                        machineData.setJqyxsh(data.get(0));
-                        machineData.setZcn(data.get(2));
-                        machineData.setZqsj(data.get(4));
-                        machineData.setSgdyl(data.get(6));
-                        machineData.setXgdyl(data.get(8));
-                        machineData.setYskqyl1(data.get(10));
-                        machineData.setYskqyl2(data.get(12));
-                        machineData.setZymwd1(data.get(14));
-                        machineData.setZymwd2(data.get(16));
-                        machineData.setZymwd3(data.get(18));
-                        machineData.setZymwd4(data.get(20));
-                        machineData.setSmwd1(data.get(22));
-                        machineData.setSmwd2(data.get(24));
-                        machineData.setSmwd3(data.get(26));
-                        machineData.setSmwd4(data.get(28));
-                        machineData.setXmwd1(data.get(30));
-                        machineData.setXmwd2(data.get(32));
-                        machineData.setXmwd3(data.get(34));
-                        machineData.setXmwd4(data.get(36));
-                        machineData.setXjsj(data.get(38));
-                        machineData.setTssj(data.get(40));
-                        machineData.setZysj(data.get(42));
-                        machineDataMapper.insert(machineData);
+                    } catch (Exception fieldEx) {
+                        log.error("处理字段 {} 时发生异常,已跳过:{}", key, fieldEx.getMessage());
                     }
                 }
             }
-        }catch (Exception e){
-            log.error("设备数据同步异常:{}",e);
+            // 立即返回成功,不等待数据库写入
+            if (hasValidData) {
+                return McR.success("设备数据接收成功");
+            } else {
+                return McR.errorParam("未解析到有效数据");
+            }
+        } catch (Exception e) {
+            log.error("设备数据同步异常:", e);
+            return McR.errorParam("设备数据同步异常");
         }
-
     }
 
-    /**
-     * 宜搭获取设备数据
-     * @param params
-     */
-    @PostMapping("/device/getData")
-    public List<MachineData> deviceHeartbeat(@RequestBody Map params){
-        QueryWrapper<MachineData> queryWrapper = new QueryWrapper<>();
 
-        List<MachineData> machineDatas = machineDataMapper.selectList(queryWrapper);
-        return null;
+    @GetMapping("/device/getRunTimeData")
+    public List<MachineData> deviceHeartbeat(){
+        List machineData = machineDataMapper.selectLatestRuntimeByParamId();
+        log.info("设备数据:{}", machineData);
+        List result = new ArrayList();
+        for(int i = 0; i < machineData.size(); i++){
+            MachineData machinedata = (MachineData) machineData.get(i);
+            Map map =new HashMap();
+            map.put("content", machinedata.getMachineNo());
+            map.put("value", machinedata.getJqyxsh());
+            result.add(map);
+        }
 
+        return result;
+    }
+    @GetMapping("/device/getZcnData")
+    public List<MachineData> getDailyZcnAtEight() {
+        List data = machineDataMapper.selectDailyZcnAtEight();
+        List result = new ArrayList();
+        for(int i = 0; i < data.size(); i++){
+            MachineData machinedata = (MachineData) data.get(i);
+            Map map =new HashMap();
+            map.put("s", machinedata.getMachineNo());
+            map.put("y", machinedata.getZcn());
+            map.put("x", machinedata.getTimestr());
+            result.add(map);
+        }
+        return result;
     }
 
+    private String safeGet(List<String> list, int index) {
+        if (list == null) {
+            return "";
+        }
+        if (index >= 0 && index < list.size()) {
+            String value = list.get(index);
+            return value != null ? value : "";
+        }
+        return "";
+    }
 
+    private String repairMalformedKFields(String input) {
+        String result = input;
+        // 1. 修复 "k1":] → "k1":[]
+        result = result.replaceAll("(\"k\\d+\"\\s*:\\s*)\\]", "$1[]");
+        // 2. 修复 "k1":  → "k1":[]
+        //    (即冒号后直接跟逗号或其他字符,中间无内容)
+        //    先找这种结构:"k1": ,  或 "k1": }
+        result = result.replaceAll("(\"k\\d+\"\\s*:\\s*)([\\,\\}])", "$1[]$2");
+        return result;
+    }
 
 }

+ 9 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/entity/MachineData.java

@@ -42,5 +42,14 @@ public class MachineData {
     @TableField("machineNo")
     private String machineNo;
 
+    private String timestr;
+
+    /**
+     * 数据指纹(MD5)
+     * 用于去重判断
+     */
+    @TableField(exist = false) // 不映射数据库字段
+    private String dataFingerprint;
+
 
 }

+ 22 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/entity/RawDeviceData.java

@@ -0,0 +1,22 @@
+package com.malk.tonglibo.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 功能:
+ * 作者:hanxue
+ * 日期:2025/10/18 0:56
+ */
+@Data
+@AllArgsConstructor
+public class RawDeviceData {
+    private String deviceId;
+    private LocalDateTime time;
+    private String timeStr;
+    private String key;        // k0, k1, k2...
+    private List<String> data; // 已转为字符串的数组
+}

+ 77 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/utils/ChangeDetector.java

@@ -0,0 +1,77 @@
+package com.malk.tonglibo.utils;
+
+import com.malk.tonglibo.entity.MachineData;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 功能:
+ * 作者:hanxue
+ * 日期:2025/10/20 9:34
+ */
+@Component
+public class ChangeDetector {
+    // 存储每个设备最新的数据指纹
+    private final Map<String, String> lastFingerprintMap = new ConcurrentHashMap<>();
+
+    /**
+     * 判断当前数据是否为新状态(MD5 指纹不同)
+     */
+    public boolean isChanged(MachineData data) {
+        String paramId = data.getParamId();
+        String fingerprint = generateFingerprint(data);
+
+        String lastFingerprint = lastFingerprintMap.get(paramId);
+        if (lastFingerprint == null) {
+            return true; // 第一次上报,算变化
+        }
+
+        return !lastFingerprint.equals(fingerprint);
+    }
+
+    /**
+     * 记录当前指纹为最新状态
+     */
+    public void recordAsCurrent(String paramId, String fingerprint) {
+        lastFingerprintMap.put(paramId, fingerprint);
+    }
+
+    String concatFields(MachineData data) {
+        return new StringBuilder()
+                .append(defaultString(data.getJqyxsh()))
+                .append(defaultString(data.getZcn()))
+                .append(defaultString(data.getZqsj()))
+                .append(defaultString(data.getSgdyl()))
+                .append(defaultString(data.getXgdyl()))
+                .append(defaultString(data.getYskqyl1()))
+                .append(defaultString(data.getYskqyl2()))
+                .append(defaultString(data.getZymwd1()))
+                .append(defaultString(data.getZymwd2()))
+                .append(defaultString(data.getZymwd3()))
+                .append(defaultString(data.getZymwd4()))
+                .append(defaultString(data.getSmwd1()))
+                .append(defaultString(data.getSmwd2()))
+                .append(defaultString(data.getSmwd3()))
+                .append(defaultString(data.getSmwd4()))
+                .append(defaultString(data.getXmwd1()))
+                .append(defaultString(data.getXmwd2()))
+                .append(defaultString(data.getXmwd3()))
+                .append(defaultString(data.getXmwd4()))
+                .append(defaultString(data.getXjsj()))
+                .append(defaultString(data.getTssj()))
+                .append(defaultString(data.getZysj()))
+                .toString();
+    }
+
+    private String generateFingerprint(MachineData data) {
+        String content = concatFields(data);
+        return MD5Util.md5(content);
+    }
+
+    private String defaultString(String str) {
+        return str == null ? "" : str;
+    }
+
+}

+ 34 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/utils/DataBuffer.java

@@ -0,0 +1,34 @@
+package com.malk.tonglibo.utils;
+
+import com.malk.tonglibo.entity.RawDeviceData;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * 功能:
+ * 作者:hanxue
+ * 日期:2025/10/18 0:50
+ */
+@Component
+public class DataBuffer {
+    // 内存队列,缓冲原始数据
+    private final BlockingQueue<RawDeviceData> queue = new LinkedBlockingQueue<>(10000);
+
+    public boolean offer(RawDeviceData data) {
+        return queue.offer(data);
+    }
+
+    public List<RawDeviceData> drain(int maxSize) {
+        List<RawDeviceData> list = new ArrayList<>();
+        queue.drainTo(list, maxSize);
+        return list;
+    }
+
+    public int size() {
+        return queue.size();
+    }
+}

+ 121 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/utils/DeviceDataPersistTask.java

@@ -0,0 +1,121 @@
+package com.malk.tonglibo.utils;
+
+import com.malk.tonglibo.Mapper.MachineDataMapper;
+import com.malk.tonglibo.Service.IMachineDataService;
+import com.malk.tonglibo.entity.RawDeviceData;
+import com.malk.tonglibo.entity.MachineData;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 功能:
+ * 作者:hanxue
+ * 日期:2025/10/18 1:00
+ */
+@Slf4j
+@Component
+public class DeviceDataPersistTask {
+    @Autowired
+    private MachineDataMapper machineDataMapper;
+    @Autowired
+    private IMachineDataService machineDataService;
+
+    @Autowired
+    private DataBuffer dataBuffer;
+
+    @Autowired
+    private ChangeDetector changeDetector;
+
+
+    @Scheduled(fixedDelay = 1000) // 每秒执行一次
+    public void persist() {
+        List<RawDeviceData> dataList = dataBuffer.drain(500);
+        if (dataList.isEmpty()) return;
+
+        List<MachineData> toInsert = new ArrayList<>();
+        int unchangedCount = 0;
+
+        for (RawDeviceData item : dataList) {
+            MachineData data = convertToMachineData(item);
+            if (data == null) continue;
+
+            //使用 MD5 判断是否变化
+            if (changeDetector.isChanged(data)) {
+                String fingerprint = MD5Util.md5(changeDetector.concatFields(data)); // 或在 convert 时生成
+                data.setDataFingerprint(fingerprint);
+                toInsert.add(data);
+                changeDetector.recordAsCurrent(data.getParamId(), fingerprint);
+            } else {
+                unchangedCount++;
+            }
+        }
+
+        if (!toInsert.isEmpty()) {
+            machineDataService.saveBatch(toInsert, 100);
+            log.info("入库 {} 条(变化数据),跳过 {} 条未变数据", toInsert.size(), unchangedCount);
+        } else if (unchangedCount > 0) {
+            log.debug("跳过 {} 条未变化数据", unchangedCount);
+        }
+    }
+
+
+
+    private MachineData convertToMachineData(RawDeviceData item) {
+        try {
+            String deviceId = item.getDeviceId();
+            String key = item.getKey();
+            List<String> data = item.getData();
+
+            MachineData machineData = new MachineData();
+            machineData.setParamId(deviceId); // 唯一设备标识
+            machineData.setMachineNo(key);
+            machineData.setTime(item.getTime());
+            machineData.setTimestr(item.getTimeStr());
+
+            // 填充字段(根据索引取值)
+            machineData.setJqyxsh(safeGet(data, 0));
+            machineData.setZcn(safeGet(data, 2));
+            machineData.setZqsj(safeGet(data, 4));
+            machineData.setSgdyl(safeGet(data, 6));
+            machineData.setXgdyl(safeGet(data, 8));
+            machineData.setYskqyl1(safeGet(data, 10));
+            machineData.setYskqyl2(safeGet(data, 12));
+            machineData.setZymwd1(safeGet(data, 14));
+            machineData.setZymwd2(safeGet(data, 16));
+            machineData.setZymwd3(safeGet(data, 18));
+            machineData.setZymwd4(safeGet(data, 20));
+            machineData.setSmwd1(safeGet(data, 22));
+            machineData.setSmwd2(safeGet(data, 24));
+            machineData.setSmwd3(safeGet(data, 26));
+            machineData.setSmwd4(safeGet(data, 28));
+            machineData.setXmwd1(safeGet(data, 30));
+            machineData.setXmwd2(safeGet(data, 32));
+            machineData.setXmwd3(safeGet(data, 34));
+            machineData.setXmwd4(safeGet(data, 36));
+            machineData.setXjsj(safeGet(data, 38));
+            machineData.setTssj(safeGet(data, 40));
+            machineData.setZysj(safeGet(data, 42));
+
+            return machineData;
+
+        } catch (Exception e) {
+            log.error("转换数据失败: {}", item, e);
+            return null;
+        }
+    }
+
+    // 安全获取索引值
+    private String safeGet(List<String> list, int index) {
+        if (index >= 0 && index < list.size()) {
+            return list.get(index);
+        }
+        return "";
+    }
+}

+ 23 - 0
mjava-tonglibo/src/main/java/com/malk/tonglibo/utils/MD5Util.java

@@ -0,0 +1,23 @@
+package com.malk.tonglibo.utils;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 功能:
+ * 作者:hanxue
+ * 日期:2025/10/20 9:28
+ */
+public class MD5Util {
+    public static String md5(String input) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
+            return String.format("%032x", new BigInteger(1, digest));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("MD5 not available", e);
+        }
+    }
+}

+ 9 - 1
mjava-tonglibo/src/main/resources/application-dev.yml

@@ -10,11 +10,19 @@ spring:
     driver-class-name: com.mysql.cj.jdbc.Driver
 #    driver-class-name: com.mysql.cj.jdbc.Driver
 
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true
+    default-executor-type: batch
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  mapper-locations: classpath*:mapper/*.xml
+
+  type-aliases-package: com.example.demo.entity
 
 
 
 enable:
-  scheduling: false
+  scheduling: enable
 logging:
   config: classpath:logback-spring.xml
   path: /home/server/tonglibo/log/

+ 10 - 1
mjava-tonglibo/src/main/resources/application-prod.yml

@@ -9,13 +9,22 @@ spring:
     password: cloudPoc@2025++
     driver-class-name: com.mysql.cj.jdbc.Driver
 enable:
-  scheduling: false
+  scheduling: enabled
 logging:
   config: classpath:logback-spring.xml
   path: /home/server/tonglibo/log/
   level:
     com.malk.*: info
 
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true
+    default-executor-type: batch
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  mapper-locations: classpath*:mapper/*.xml
+  type-aliases-package: com.example.demo.entity
+
+
 # dingtalk
 dingtalk:
   agentId: 3914874648

+ 109 - 0
mjava-tonglibo/src/main/resources/mapper/MachineDataMapper.xml

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.malk.tonglibo.Mapper.MachineDataMapper">
+
+    <!-- 查询每个 paramId 最新一条数据 -->
+    <select id="selectLatestRuntimeByParamId" resultType="com.malk.tonglibo.entity.MachineData">
+        SELECT
+            paramId,
+            machineNo,
+            jqyxsh,
+            zcn,
+            timeStr,
+            updateTime
+        FROM (
+                 SELECT
+                     paramId,
+                     machineNo,
+                     zcn,
+                     timeStr,
+                     jqyxsh,
+                     time AS updateTime,
+                     ROW_NUMBER() OVER (PARTITION BY machineNo ORDER BY time DESC) AS rn
+                 FROM T_TLB_MACHINEDATA
+             ) t
+        WHERE rn = 1
+        ORDER BY updateTime DESC
+    </select>
+
+    <!-- 支持条件过滤的版本 -->
+    <select id="selectLatestRuntimeByParamIdWithFilter" resultType="com.malk.tonglibo.entity.MachineData">
+        SELECT
+        t1.paramId,
+        t1.machineNo,
+        t1.jqyxsh,
+        t1.time AS updateTime
+        FROM
+        T_TLB_MACHINEDATA t1
+        WHERE
+        t1.time = (
+        SELECT MAX(t2.time)
+        FROM T_TLB_MACHINEDATA t2
+        WHERE t2.paramId = t1.paramId
+        )
+        <!-- 动态条件 -->
+        <if test="paramId != null and paramId != ''">
+            AND t1.paramId = #{paramId}
+        </if>
+        <if test="machineNo != null and machineNo != ''">
+            AND t1.machineNo = #{machineNo}
+        </if>
+        ORDER BY t1.time DESC
+    </select>
+
+    <select id="selectDailyZcnAtEight" resultType="com.malk.tonglibo.entity.MachineData">
+        SELECT
+            date,
+            machineNo,
+            zcn,
+            timeStr
+        FROM (
+            SELECT
+            DATE(time) AS date,
+            machineNo AS machineNo,
+            zcn,
+            timeStr,
+            ROW_NUMBER() OVER (PARTITION BY DATE(time), machineNo ORDER BY time ASC) AS rn
+            FROM T_TLB_MACHINEDATA
+            WHERE HOUR(time) = 8
+            AND zcn IS NOT NULL
+            AND TRIM(zcn) != ''
+            ) t
+        WHERE rn = 1
+        ORDER BY date, machineNo
+    </select>
+
+    <select id="selectDailyShiftOutput" resultType="map">
+        SELECT
+        CASE
+        WHEN HOUR(time) < 8 THEN DATE_SUB(DATE(time), INTERVAL 1 DAY)
+        ELSE DATE(time)
+        END AS date,
+
+        CASE
+        WHEN HOUR(time) >= 8 AND HOUR(time) < 16 THEN '早班'
+        WHEN HOUR(time) >= 16 THEN '中班'
+        ELSE '晚班'
+        END AS shift,
+
+        SUM(CAST(zcn AS DECIMAL(10,2))) AS output
+
+        FROM T_TLB_MACHINEDATA
+        WHERE zcn REGEXP '^[0-9]+(\\.[0-9]+)?$'
+        AND time >= #{startDate}
+        AND time &lt; #{endDate}
+
+        GROUP BY
+        CASE WHEN HOUR(time) < 8 THEN DATE_SUB(DATE(time), INTERVAL 1 DAY) ELSE DATE(time) END,
+        CASE
+        WHEN HOUR(time) >= 8 AND HOUR(time) < 16 THEN '早班'
+        WHEN HOUR(time) >= 16 THEN '中班'
+        ELSE '晚班'
+        END
+
+        ORDER BY date, FIELD(shift, '早班', '中班', '晚班')
+    </select>
+
+</mapper>