Parcourir la source

提交 考试中间读取 功能。

tom il y a 1 mois
Parent
commit
807c7d1d65

+ 2 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/constant/CommConst.java

@@ -89,7 +89,8 @@ public interface CommConst {
     int RETRY_COUNT_CHECK_ONE_FAULT = 1;
     int RETRY_COUNT_CLEAR_ONE_FAULT = 2;
     int RETRY_COUNT_WRITE_ONE_FAULT = 2;
-    int RETRY_COUNT_READ_ONE_RESISTANCE = 4;
+    int RETRY_COUNT_READ_ONE_RESISTANCE_MIDDLE = 1;
+    int RETRY_COUNT_READ_ONE_RESISTANCE_FINAL = 4;
     int RETRY_COUNT_QUERY_SN_IMPORTANT = 1;
     int RETRY_COUNT_WHICH_SIM_IMPORTANT = 3;
     int RETRY_COUNT_0 = 0;

+ 3 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/TestIotController.java

@@ -116,6 +116,9 @@ public class TestIotController extends BaseController {
                 String ip = "192.168.1.201";
                 return commCheckService.checkPingRs485State(ip);
             }
+            case 201: {
+                realExamService.studentMiddleReadRealExam();
+            }
         }
         return AjaxResult.success("ZZZZZZZZZZZZZZZZZZZZ");
     }

+ 2 - 2
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/RealExamFault.java

@@ -217,7 +217,7 @@ public class RealExamFault extends BaseEntity {
      * <p>
      * <p>
      * 选中Yes的故障:0 1 2 3 4
-     * 未选中No:0 1 2 3 4
+     * 未选中No:0 1 4
      * 故障ID关联状态:[0]-初始化,[1]-已经清除故障,[2]-故障已经下发~出题值填充,[3]-轮询读取~中间答题值填充,[4]-交卷~最终答题值填充
      */
     public interface State {
@@ -234,7 +234,7 @@ public class RealExamFault extends BaseEntity {
          */
         String WRITTEN = "2";
         /**
-         * [3]-轮询读取~中间答题值填充
+         * [3]-中间轮询读取~中间答题值填充
          */
         String LOOP_READ = "3";
         /**

+ 4 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/SimMsg.java

@@ -305,6 +305,10 @@ public class SimMsg extends BaseEntity {
          * 接收报文格式不匹配。
          */
         Integer RECEIVE_NOT_MATCH = 530;
+        /**
+         * 重要报文在运行,低优先级的跳过
+         */
+        Integer SKIP = 600;
     }
 
     public SimMsg() {

+ 0 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SocketWrapCacheVo.java

@@ -4,7 +4,6 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
 import java.net.Socket;
-import java.util.concurrent.atomic.AtomicInteger;
 
 public class SocketWrapCacheVo {
 

+ 5 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommReceiveService.java

@@ -118,6 +118,11 @@ public class CommReceiveService {
             if (StringUtils.isNotBlank(refState)) {
                 reF.setRefState(refState);
             }
+            // LOOP_READ 没有成功的结果,跳过数据库更新。
+            if (RealExamFault.State.LOOP_READ.equals(refState) && sm.isResultNotOk()) {
+                l.info("nothing change.");
+                return;
+            }
             reF.setSimFaultAnswerValue(faultAnswerValue);
             realExamFaultService.updateRealExamFault(reF);
         } else {

+ 0 - 22
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommRunningService.java

@@ -1,22 +0,0 @@
-package com.ruoyi.sim.service.impl;
-
-import org.springframework.stereotype.Service;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@Service
-public class CommRunningService {
-
-    /**
-     * 是否有关键指令运行。
-     */
-    private AtomicBoolean keyIsRunning = new AtomicBoolean(false);
-
-    public boolean getKeyIsRunning() {
-        return keyIsRunning.get();
-    }
-
-    public void setKeyIsRunning(Boolean keyIsRunning) {
-        this.keyIsRunning.set(keyIsRunning);
-    }
-}

+ 90 - 31
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java

@@ -98,8 +98,39 @@ public class CommSendService {
         readAll();
     }
 
+    /**
+     * 开始考试以后,交卷之前,定时任务中间读取值,作为答题值。有优先级高的任务可能跳过执行。
+     *
+     * @param re
+     */
+    public void readOneExamAtMiddle(RealExam re) {
+        l.info("readOneExamAtMiddle getExamId = {}", re.getExamId());
+        List<RealExamFault> list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId());
+        for (RealExamFault ref : list) {
+            {
+                RealExamFault refQ = realExamFaultService.selectRealExamFaultByRefId(ref.getRefId());
+                if (refQ == null || refQ.getExamId() == 0L) {
+                    continue;
+                }
+                if (!RealExamFault.State.WRITTEN.equals(ref.getRefState()) &&
+                        !RealExamFault.State.LOOP_READ.equals(ref.getRefState())) {
+                    continue;
+                }
+            }
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
+            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
+            readOneSimOneFaultResistance(seat, sim, ref, f, RealExamFault.State.LOOP_READ);
+        }
+    }
+
+    /**
+     * 交卷最后读取值,作为答题值。
+     *
+     * @param re
+     */
     public void readOneExamAtLast(RealExam re) {
-        l.info("readOneExamAtLast");
+        l.info("readOneExamAtLast getExamId = {}", re.getExamId());
         List<RealExamFault> list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId());
         for (RealExamFault ref : list) {
             Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
@@ -823,7 +854,7 @@ public class CommSendService {
      * @param sim
      * @param reF
      * @param f
-     * @param refState 中间轮询是null,交卷最后一次读取为finish状态。用来修改状态。debug模式下执行为null。
+     * @param refState 修改的目标状态。debug模式下执行为null。
      */
     public void readOneSimOneFaultResistance(Seat seat, Sim sim, RealExamFault reF, Fault f, String refState) {
         l.info("readOneSimOneFaultResistance");
@@ -831,16 +862,26 @@ public class CommSendService {
         SimMsg sm2 = null;
         if (reF != null && refState != null) {
             if (RealExamFault.State.FINISH.equals(refState)) { // 是否最后一次读取。
-                sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE, commStrategy.getSleepShort());
+                sm2 = send(sm1, seat, sim,
+                        RETRY_COUNT_READ_ONE_RESISTANCE_FINAL, commStrategy.getSleepShort());
+            } else if (RealExamFault.State.LOOP_READ.equals(refState)) { // 是否是中间读取
+                sm2 = send(sm1, seat, sim,
+                        RETRY_COUNT_READ_ONE_RESISTANCE_MIDDLE, commStrategy.getSleepShort(), false);
             } else {
-                sm2 = send(sm1, seat, sim, RETRY_COUNT_0, commStrategy.getSleepShort());
+                sm2 = send(sm1, seat, sim,
+                        RETRY_COUNT_0, commStrategy.getSleepShort());
             }
         } else {
-            sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE, commStrategy.getSleepShort());
+            sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE_FINAL, commStrategy.getSleepShort());
         }
         simReceiveService.setFaultAnswerValue(sm2, sim, reF, f, refState);
     }
 
+    public SimMsg send(final SimMsg sm, final Seat seat, final Sim sim,
+                       final int retryTotalCount, final long sleep) {
+        return send(sm, seat, sim, retryTotalCount, sleep, true);
+    }
+
     /**
      * 最基本的通信方法。
      * send hex message
@@ -852,51 +893,66 @@ public class CommSendService {
      * @param sim             可以为空!更新最后发送/接收时间 用。
      * @param retryTotalCount 重试次数
      * @param sleep           不使用传入0,不进行挂起。
+     * @param importantTask
      * @return
      */
-    public SimMsg send(final SimMsg sm, final Seat seat, final Sim sim, final int retryTotalCount, final long sleep) {
+    public SimMsg send(final SimMsg sm, final Seat seat, final Sim sim,
+                       final int retryTotalCount, final long sleep, final boolean importantTask) {
+        if (!config.isCommGlobal()) {
+            l.warn("isCommGlobal == false [模拟器通信被禁用!]");
+            return sm;
+        }
+        if (sm == null || sm.getSendMsg() == null || StringUtils.isBlank(sm.getSendMsg())) {
+            throw new IllegalArgumentException("SimMsg IllegalArgument");
+        }
+        // sim
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        if (sleep < 0) {
+            throw new IllegalArgumentException("SimMsg sleep");
+        }
+        // log.
+        {
+            l.info("####发送#### == Seat[{}],SimMsg[{}]", seat, sm);
+        }
+        SimSocketParamVo sspv = seat.toSimSocketParamVo();
         try {
-            if (!config.isCommGlobal()) {
-                l.warn("isCommGlobal == false [模拟器通信被禁用!]");
-                return sm;
-            }
-            if (sm == null || sm.getSendMsg() == null || StringUtils.isBlank(sm.getSendMsg())) {
-                throw new IllegalArgumentException("SimMsg IllegalArgument");
-            }
-            // sim
-            if (seat == null) {
-                throw new IllegalArgumentException("seat is null");
-            }
-            if (sleep < 0) {
-                throw new IllegalArgumentException("SimMsg sleep");
-            }
-            // log.
-            {
-                l.info("####发送#### == Seat[{}],SimMsg[{}]", seat, sm);
-            }
             // 如果没有打开socket,顺道打开。正好后面要sleep。
             // 不强制重开Socket。
             // 先进行Socket相关处理。
-            SimSocketParamVo sspv = seat.toSimSocketParamVo();
+
+            // 优先级高的在运行,跳过
+            if (importantTask == false && socketService.getImportantTaskRunning(sspv)) {
+                sm.setResult(SimMsg.Result.SKIP);
+                socketService.setImportantTaskRunning(sspv, false);
+                l.warn("####跳过运行#### sm = {}", sm);
+                return sm;
+            }
+            if (importantTask) {
+                socketService.setImportantTaskRunning(sspv, true);
+            }
             socketService.openOne(sspv);
             // Socket情况不正确,直接返回。
             if (socketService.isNotOk(sspv)) {
                 sm.setResult(SimMsg.Result.SOCKET_CONNECT_EXCEPTION);
+                socketService.setImportantTaskRunning(sspv, false);
                 return sm;
             }
             {
                 // sleep挂起线程,追求顺序请求。
                 // 大于0才挂起。
-                if (sleep > 0 && socketService.get(sspv).getPreviousSendSleep() > 0L) {
+                if (sleep > 0 && socketService.getVo(sspv).getPreviousSendSleep() > 0L) {
                     // 时间间隔挂起。
-                    Thread.sleep(socketService.get(sspv).getPreviousSendSleep());
+                    Thread.sleep(socketService.getVo(sspv).getPreviousSendSleep());
                 }
             }
-            socketService.get(sspv).setPreviousSendSleep(sleep);
-            Socket socket = socketService.get(sspv).getSocket();
+            socketService.getVo(sspv).setPreviousSendSleep(sleep);
+            Socket socket = socketService.getVo(sspv).getSocket();
             InputStream is = socket.getInputStream();
             OutputStream os = socket.getOutputStream();
-            socket.setSoTimeout(commStrategy.getSoTimeout());
+            Sim simSeat = simService.selectSimBySimId(seat.getCurrentSimId());
+            socket.setSoTimeout(commStrategy.getSoTimeout(simSeat));
             os.write(hexStrToByteArrs(sm.getSendMsg()));
             sm.setSendTime(DateUtils.getNowDate());
             if (sim != null) {
@@ -920,6 +976,7 @@ public class CommSendService {
                     // todo:
                     l.warn("####接收错误@格式错误#### = sm = {},ar = {}", sm, ar);
                     sm.setResult(SimMsg.Result.RECEIVE_CHECK_FAIL);
+                    socketService.setImportantTaskRunning(sspv, false);
                     return sm;
                 }
             }
@@ -928,6 +985,7 @@ public class CommSendService {
                 if (ar.isError()) {
                     l.warn("####接收错误@匹配错误#### sm = {},ar = {}", sm, ar);
                     sm.setResult(SimMsg.Result.RECEIVE_NOT_MATCH);
+                    socketService.setImportantTaskRunning(sspv, false);
                     return sm;
                 }
             }
@@ -936,6 +994,7 @@ public class CommSendService {
                 simService.updateLastReceivedTime(sim);
             }
             sm.setResult(SimMsg.Result.SUCCESS);
+            socketService.setImportantTaskRunning(sspv, false);
             // 最后返回报文实体。
             return sm;
         } catch (InterruptedException | IOException e) {
@@ -943,10 +1002,10 @@ public class CommSendService {
             l.error("SocketTimeoutException");
             e.printStackTrace();
             sm.setResult(SimMsg.Result.READ_TIMEOUT_EXCEPTION);
+            socketService.setImportantTaskRunning(sspv, false);
             if (sim != null) {
                 l.info("fail sim.getSimId() = {}", sim.getSimId());
             }
-            SimSocketParamVo sspv = seat.toSimSocketParamVo();
             // Socket失败计数
             socketService.failedPlus1(sspv);
             if (socketService.failedIsReachedMax(sspv, SocketService.SOCKET_CONNECT_RETRY_COUNT_LIMIT)) {

+ 12 - 2
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommStrategy.java

@@ -1,5 +1,7 @@
 package com.ruoyi.sim.service.impl;
 
+import com.ruoyi.sim.domain.Sim;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -49,7 +51,15 @@ public class CommStrategy {
         }
     }
 
-    public int getSoTimeout() {
-        return 4000;
+    public int getSoTimeout(Sim sim) {
+        if (sim == null) {
+            return 4000;
+        }
+        return switch (sim.getSimType()) {
+            case Sim.TYPE_0001 -> 4000;
+            case Sim.TYPE_0002 -> 2000;
+            case Sim.TYPE_0003 -> 2000;
+            default -> 4000;
+        };
     }
 }

+ 30 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamService.java

@@ -1,6 +1,7 @@
 package com.ruoyi.sim.service.impl;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 
@@ -507,6 +508,35 @@ public class RealExamService {
         return AjaxResult.success(vo);
     }
 
+    public void studentMiddleReadRealExam() {
+        l.info("studentMiddleReadRealExam now = {}", new Date());
+        //
+        RealExamCollection rec = realExamCollectionService.selectRealExamCollectionOpened();
+        if (rec == null ||
+                rec.getExamCollectionId() == null ||
+                rec.getExamCollectionId() == 0L ||
+                !StringUtils.equals(rec.getExamCollectionType(), RealExamCollection.Type.EXAM)) {
+            l.info("考试集合不匹配。不需要中间读取,rec = {}", rec);
+            return;
+        }
+        {
+            RealExam reQ = new RealExam();
+            reQ.setExamCollectionId(rec.getExamCollectionId());
+            List<RealExam> reList = selectRealExamList(reQ);
+            for (RealExam re : reList) {
+                if (re != null && re.getExamId() != null && re.getExamId() != 0L
+                        && StringUtils.equals(re.getExamStatus(), RealExam.State.ANSWERING)) {
+                    commSendService.readOneExamAtMiddle(re);
+                } else {
+                    l.info("skip examId = {}", re != null ? re.getExamId() : null);
+                }
+            }
+        }
+    }
+
+    /**
+     * 10分钟延长时间。
+     */
     public static final Long DURATION_10_MIN = 1000L * 60 * 10;
 
     /**
@@ -635,7 +665,6 @@ public class RealExamService {
 
         //
 
-
         // Step:最后读取一下模拟器电阻值。
         commSendService.readOneExamAtLast(re);
         // Step:

+ 34 - 11
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketService.java

@@ -2,21 +2,19 @@ package com.ruoyi.sim.service.impl;
 
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.sim.config.SimConfig;
-import com.ruoyi.sim.config.SimDebugConfig;
 import com.ruoyi.sim.domain.Seat;
 import com.ruoyi.sim.domain.vo.SimSocketParamVo;
 import com.ruoyi.sim.domain.vo.SocketWrapCacheVo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.parameters.P;
 import org.springframework.stereotype.Service;
 
 import java.io.IOException;
-import java.net.InetAddress;
 import java.net.Socket;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static com.ruoyi.sim.constant.CommConst.SOCKET_TIME_OUT;
@@ -58,13 +56,19 @@ public class SocketService {
      * key: ip:port
      * value:
      */
-    private static HashMap<String, SocketWrapCacheVo> cachedMap = new HashMap<>(INIT_SIZE);
+    private static final HashMap<String, SocketWrapCacheVo> cachedMap = new HashMap<>(INIT_SIZE);
     /**
      * 每个Socket都有。
      * 重试次数。
      * default 0.
      */
-    private static HashMap<String, AtomicInteger> failedMap = new HashMap<>(INIT_SIZE);
+    private static final HashMap<String, AtomicInteger> failedMap = new HashMap<>(INIT_SIZE);
+
+    /**
+     * 是否有重要任务在运行。
+     * 除中间答题值读取外,都为重要任务。
+     */
+    private static final HashMap<String, AtomicBoolean> importantTaskRunningMap = new HashMap<>(INIT_SIZE);
 
     @Autowired
     private SimConfig config;
@@ -77,7 +81,7 @@ public class SocketService {
      */
     public boolean isOk(final SimSocketParamVo sspv) {
         final String key = sspv.toKey();
-        if (cachedMap.containsKey(key) && cachedMap.get(key) != null) {
+        if (cachedMap != null && cachedMap.containsKey(key) && cachedMap.get(key) != null) {
             Socket s = cachedMap.get(key).getSocket();
             if (s != null) {
                 return (s.isConnected() &&
@@ -135,8 +139,8 @@ public class SocketService {
                 // Socket s = new Socket(sspv.getIp(), sspv.getPort(), InetAddress.getLocalHost(), SimDebugConfig.TCP_LOCAL_PORT);
                 s.setSoTimeout(SOCKET_TIME_OUT);
                 SocketWrapCacheVo value = new SocketWrapCacheVo(sspv.getIp(), sspv.getPort(), s, System.currentTimeMillis());
-                // 新建Socket需要挂起 2s后再发指令。
-                value.setPreviousSendSleep(2000L);
+                // 新建Socket需要挂起 500ms后再发指令。
+                value.setPreviousSendSleep(500L);
                 cachedMap.put(key, value);
                 // socket failed count reset.
                 failedReset0(sspv);
@@ -233,18 +237,18 @@ public class SocketService {
      * @param seat
      * @return todo:null
      */
-    public SocketWrapCacheVo get(final Seat seat) {
+    public SocketWrapCacheVo getVo(final Seat seat) {
         if (seat == null) {
             throw new IllegalArgumentException("seat为空。");
         }
-        return get(new SimSocketParamVo(seat.getSeatRs485Ip(), seat.getSeatRs485Port()));
+        return getVo(new SimSocketParamVo(seat.getSeatRs485Ip(), seat.getSeatRs485Port()));
     }
 
     /**
      * @param sspv
      * @return
      */
-    public SocketWrapCacheVo get(final SimSocketParamVo sspv) {
+    public SocketWrapCacheVo getVo(final SimSocketParamVo sspv) {
         if (isNotOk(sspv)) {
             AjaxResult ar = openOne(sspv);
             if (ar.isError()) {
@@ -301,4 +305,23 @@ public class SocketService {
             l.debug("not containsKey SimSocketParamVo sspv:" + sspv);
         }
     }
+
+    public void setImportantTaskRunning(final SimSocketParamVo sspv, final boolean running) {
+        final String key = sspv.toKey();
+        if (!importantTaskRunningMap.containsKey(key)) {
+            importantTaskRunningMap.put(key, new AtomicBoolean(running));
+        } else {
+            importantTaskRunningMap.get(key).set(running);
+        }
+    }
+
+    public boolean getImportantTaskRunning(final SimSocketParamVo sspv) {
+        final String key = sspv.toKey();
+        if (!importantTaskRunningMap.containsKey(key)) {
+            importantTaskRunningMap.put(key, new AtomicBoolean(false));
+            return false;
+        } else {
+            return importantTaskRunningMap.get(key).get();
+        }
+    }
 }