Procházet zdrojové kódy

checkReceiveMsg完善。

tom před 2 měsíci
rodič
revize
a6d986d95f

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

@@ -2,8 +2,6 @@ package com.ruoyi.sim.constant;
 
 public interface CommConst {
 
-    String name = "lishuo";
-
     String PREFIX = "AA";
 
     String SUFFIX = "55";
@@ -46,6 +44,8 @@ public interface CommConst {
 
     String CMD_ID_GET_SN = "B1";
 
+    String PREFIX_ERROR_0 = "0";
+
     String ANSWER_RIGHT = "00000000";
     String ANSWER_WRONG = "00000001";
 
@@ -98,4 +98,11 @@ public interface CommConst {
     int RETRY_COUNT_0 = 0;
 
     int OFFLINE_LIMIT = 6;
+
+    String TYPE_0001_SN_PREFIX = "01";
+    String TYPE_0002_SN_PREFIX = "02";
+    String TYPE_0003_SN_PREFIX = "03";
+
+    Integer PORT_MIN = 1;
+    Integer PORT_MAX = 65535;
 }

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

@@ -165,10 +165,6 @@ public class Sim extends BaseEntity {
      */
     public static final String TYPE_0003 = "0003";
 
-    public static final String TYPE_0001_SN = "01";
-    public static final String TYPE_0002_SN = "02";
-    public static final String TYPE_0003_SN = "03";
-
     public static final Set<String> TYPE_SET = new HashSet<>(Arrays.asList(TYPE_0001, TYPE_0002, TYPE_0003));
 
     public static final String TYPE_0001_NAME = "FZD04B";

+ 37 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SimSocketVo.java

@@ -0,0 +1,37 @@
+package com.ruoyi.sim.domain.vo;
+
+public class SimSocketVo {
+
+    /**
+     * ip v4.
+     */
+    private String ip;
+    /**
+     * port.
+     */
+    private Integer port;
+
+    public SimSocketVo() {
+    }
+
+    public SimSocketVo(String ip, Integer port) {
+        this.ip = ip;
+        this.port = port;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public Integer getPort() {
+        return port;
+    }
+
+    public void setPort(Integer port) {
+        this.port = port;
+    }
+}

+ 62 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SocketWrapValue.java

@@ -0,0 +1,62 @@
+package com.ruoyi.sim.domain.vo;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+import java.net.Socket;
+
+public class SocketWrapValue {
+
+    /**
+     * ip v4.
+     */
+    private String ip;
+
+    /**
+     * port.
+     */
+    private Integer port;
+
+    private Socket socket;
+
+    private Long connectedTimeMillis;
+
+    public SocketWrapValue(String ip, Integer port, Socket socket, Long connectedTimeMillis) {
+        this.ip = ip;
+        this.port = port;
+        this.socket = socket;
+        this.connectedTimeMillis = connectedTimeMillis;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public Socket getSocket() {
+        return socket;
+    }
+
+    public void setSocket(Socket socket) {
+        this.socket = socket;
+    }
+
+    public Long getConnectedTimeMillis() {
+        return connectedTimeMillis;
+    }
+
+    public void setConnectedTimeMillis(Long connectedTimeMillis) {
+        this.connectedTimeMillis = connectedTimeMillis;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("ip", ip)
+                .append("socket", socket)
+                .append("connectedTimeMillis", connectedTimeMillis)
+                .toString();
+    }
+}

+ 0 - 46
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommBuildService.java

@@ -131,50 +131,4 @@ public class CommBuildService {
         final SimMsg sm = buildSendMsg(simNum, orn, cmd, cmdId, data);
         return AjaxResult.success(sm);
     }
-
-    /**
-     * check receiveMsg
-     *
-     * @param receiveMsg
-     * @return
-     */
-    public boolean checkReceiveMsg(String receiveMsg) {
-        // 不能是empty
-        if (StringUtils.isEmpty(receiveMsg)) {
-            return false;
-        }
-        // 长度
-        if (receiveMsg.length() != LENGTH_24) {
-            return false;
-        }
-        // 数据方向
-        final String orn = StringUtils.substring(receiveMsg, 4, 6);
-        if (!ORN_RECEIVE.equals(orn)) {
-            return false;
-        }
-        // 前缀
-        if (!StringUtils.startsWith(receiveMsg, PREFIX)) {
-            return false;
-        }
-        // 后缀
-        if (!StringUtils.endsWith(receiveMsg, SUFFIX)) {
-            return false;
-        }
-        // 计算CRC16
-        // todo: receive报文检验错误。
-        if (false) {
-            String crcContent = receiveMsg.substring(0, 18);
-            l.debug("crcContent: {}", crcContent.toUpperCase());
-            byte[] receiveByteContent = CommSendService.hexStrToByteArrs(crcContent);
-            byte[] receiveByteCrc = CRC16Modbus.calculateCRC(receiveByteContent);
-            String crc = CommSendService.bytesToHexV2(receiveByteCrc);
-            l.debug("crc: {}", crc.toUpperCase());
-//            if (!receiveMsg.substring(19, 22).equals(crc.toUpperCase())) {
-//                throw new IllegalArgumentException("checkReceiveMsg length error");
-//            }
-
-            // todo: 比对校验值,不正确。
-        }
-        return true;
-    }
 }

+ 63 - 3
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommReceiveService.java

@@ -1,8 +1,10 @@
 package com.ruoyi.sim.service.impl;
 
 import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.sim.constant.CommConst;
 import com.ruoyi.sim.constant.FaultConst;
 import com.ruoyi.sim.domain.*;
+import com.ruoyi.sim.util.CRC16Modbus;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -62,21 +64,21 @@ public class CommReceiveService {
         final String msgError = "连接模拟器类型不正确!";
         switch (simType) {
             case Sim.TYPE_0001 -> {
-                if (c.startsWith(Sim.TYPE_0001_SN)) {
+                if (c.startsWith(CommConst.TYPE_0001_SN_PREFIX)) {
                     return AjaxResult.success();
                 } else {
                     return AjaxResult.error(msgError);
                 }
             }
             case Sim.TYPE_0002 -> {
-                if (c.startsWith(Sim.TYPE_0002_SN)) {
+                if (c.startsWith(CommConst.TYPE_0002_SN_PREFIX)) {
                     return AjaxResult.success();
                 } else {
                     return AjaxResult.error(msgError);
                 }
             }
             case Sim.TYPE_0003 -> {
-                if (c.startsWith(Sim.TYPE_0003_SN)) {
+                if (c.startsWith(CommConst.TYPE_0003_SN_PREFIX)) {
                     return AjaxResult.success();
                 } else {
                     return AjaxResult.error(msgError);
@@ -252,4 +254,62 @@ public class CommReceiveService {
             return AjaxResult.success(f);
         }
     }
+
+    /**
+     * check receiveMsg
+     *
+     * @param receiveMsg
+     * @return
+     */
+    public AjaxResult checkReceiveMsg(String receiveMsg) {
+        l.info("####checkReceiveMsg#### = [{}]", receiveMsg);
+        String start = "ReceiveMsg ";
+        // check:不能是empty
+        if (StringUtils.isBlank(receiveMsg)) {
+            return AjaxResult.error(start + "isBlank");
+        }
+        // check:处理报文前端加00的情况,最多可能加5组00
+        {
+            int count = 0;
+            while (StringUtils.startsWith(receiveMsg, CommConst.PREFIX_ERROR_0)) {
+                receiveMsg = StringUtils.removeStartIgnoreCase(receiveMsg, CommConst.PREFIX_ERROR_0);
+                count = count + 1;
+            }
+            l.info("####remove count#### = [{}]", count);
+        }
+        // check:长度
+        if (receiveMsg.length() != LENGTH_24) {
+            return AjaxResult.error(start + "length error.length not 24.");
+        }
+        // check:数据方向
+        final String orn = StringUtils.substring(receiveMsg, 4, 6);
+        if (!ORN_RECEIVE.equals(orn)) {
+            return AjaxResult.error(start + "orn error.");
+        }
+        // check:前缀
+        if (!StringUtils.startsWith(receiveMsg, PREFIX)) {
+            return AjaxResult.error(start + "not start with AA.");
+        }
+        // check:后缀
+        if (!StringUtils.endsWith(receiveMsg, SUFFIX)) {
+            return AjaxResult.error(start + "not end with 55.");
+        }
+        // 计算CRC16
+        // todo:
+        // todo: receive报文检验错误。
+        if (false) {
+            String crcContent = receiveMsg.substring(0, 18);
+            l.debug("crcContent: {}", crcContent.toUpperCase());
+            byte[] receiveByteContent = CommSendService.hexStrToByteArrs(crcContent);
+            byte[] receiveByteCrc = CRC16Modbus.calculateCRC(receiveByteContent);
+            String crc = CommSendService.bytesToHexV2(receiveByteCrc);
+            l.debug("crc: {}", crc.toUpperCase());
+//            if (!receiveMsg.substring(19, 22).equals(crc.toUpperCase())) {
+//                throw new IllegalArgumentException("checkReceiveMsg length error");
+//            }
+
+            // todo: 比对校验值,不正确。
+        }
+        return AjaxResult.success("接收报文检查正确!");
+    }
 }

+ 32 - 119
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java

@@ -31,10 +31,7 @@ import static com.ruoyi.sim.constant.CommConst.*;
 public class CommSendService {
 
     private static final Logger l = LoggerFactory.getLogger(CommSendService.class);
-    /**
-     * 缓存的Socket连接。
-     */
-    private Socket cachedSocket = null;
+
     @Autowired
     private CommReceiveService simReceiveService;
     @Autowired
@@ -48,11 +45,15 @@ public class CommSendService {
     @Autowired
     private RealExamCollectionService realExamCollectionService;
     @Autowired
-    private SimConfig config;
-    @Autowired
     private CommBuildService commBuildService;
     @Autowired
     private DebugFaultService debugFaultService;
+    @Autowired
+    private SocketOldService socketOldService;
+    @Autowired
+    CommReceiveService commReceiveService;
+    @Autowired
+    private SimConfig config;
 
     /**
      * 定时任务。
@@ -120,7 +121,7 @@ public class CommSendService {
         //
         // 打开socket
         {
-            AjaxResult ar1 = openSocket();
+            AjaxResult ar1 = socketOldService.openSocket();
             if (ar1.isError()) {
                 return ar1;
             }
@@ -196,12 +197,14 @@ public class CommSendService {
         // debugReadSimType(simNum);
         // l.info("bRea:" + bRea);
 
-        l.info("cachedSocket.isConnected():" + cachedSocket.isConnected());
-        l.info("cachedSocket.isBound():" + cachedSocket.isBound());
-        l.info("cachedSocket.isClosed():" + cachedSocket.isClosed());
-        l.info("cachedSocket.isInputShutdown():" + cachedSocket.isInputShutdown());
-        l.info("cachedSocket.isOutputShutdown():" + cachedSocket.isOutputShutdown());
-
+        {
+            Socket s = socketOldService.getCachedSocket();
+            l.info("cachedSocket.isConnected():" + s.isConnected());
+            l.info("cachedSocket.isBound():" + s.isBound());
+            l.info("cachedSocket.isClosed():" + s.isClosed());
+            l.info("cachedSocket.isInputShutdown():" + s.isInputShutdown());
+            l.info("cachedSocket.isOutputShutdown():" + s.isOutputShutdown());
+        }
 
 //            debugReadAllFaultResistance(simNum, simType);
 
@@ -243,8 +246,8 @@ public class CommSendService {
             return;
         }
 
-        openSocket();
-        if (isCachedSocketOk()) {
+        socketOldService.openSocket();
+        if (socketOldService.isCachedSocketOk()) {
             checkAllSimState();
         }
     }
@@ -302,7 +305,7 @@ public class CommSendService {
         SimMsg smR = send(smS, s, retryCount, sleep);
         if (StringUtils.isNotBlank(smR.getReceiveMsg())) {
             l.info("isNotBlank");
-            commFailCountClearOne(s.getSimId());
+            socketOldService.commFailCountClearOne(s.getSimId());
         }
         simReceiveService.checkOneSimState(smR, s);
         {
@@ -553,7 +556,7 @@ public class CommSendService {
         // check sim
         // 打开socket
         {
-            AjaxResult ar1 = openSocket();
+            AjaxResult ar1 = socketOldService.openSocket();
             l.info("ar1 = {}", ar1);
             if (ar1.isError()) {
                 return ar1;
@@ -818,7 +821,7 @@ public class CommSendService {
                 l.info("####SendMsg#### == [{}]", sm);
             }
             // 如果没有打开socket,顺道打开。正好后面要sleep
-            openSocket();
+            socketOldService.openSocket();
             {
                 // sleep ,追求顺序请求。
                 if (sleep > 0 && previousSendSleep != 0L) {
@@ -826,8 +829,8 @@ public class CommSendService {
                 }
             }
             previousSendSleep = sleep;
-            InputStream is = cachedSocket.getInputStream();
-            OutputStream os = cachedSocket.getOutputStream();
+            InputStream is = socketOldService.getCachedSocket().getInputStream();
+            OutputStream os = socketOldService.getCachedSocket().getOutputStream();
             os.write(hexStrToByteArrs(sm.getSendMsg()));
             sm.setSendTime(DateUtils.getNowDate());
             if (s != null) {
@@ -845,10 +848,13 @@ public class CommSendService {
             {
                 l.info("####ReceiveMsg#### = [{}]", sm);
             }
-            if (!commBuildService.checkReceiveMsg(sm.getReceiveMsg())) {
-                // todo:
-                l.warn("####Fail#### = {}", sm);
-                return sm;
+            {
+                AjaxResult ar =    commReceiveService.checkReceiveMsg(sm.getReceiveMsg());
+                if (ar.isError()) {
+                    // todo:
+                    l.warn("####Fail#### = {}", sm);
+                    return sm;
+                }
             }
             if (s != null) {
                 simService.updateLastReceivedTime(s);
@@ -857,10 +863,10 @@ public class CommSendService {
             e.printStackTrace();
             // 失败计数
             l.info("fail sim data = {}", s);
-            boolean limit = commFailCountAdd1(Objects.requireNonNull(s).getSimId());
+            boolean limit = socketOldService.commFailCountAdd1(Objects.requireNonNull(s).getSimId());
             if (limit) {
                 simService.updateSimStateBySimId(s.getSimId(), Sim.State.OFFLINE);
-                commFailCountClearOne(s.getSimId());
+                socketOldService.commFailCountClearOne(s.getSimId());
             }
             // 先考虑一台模拟器演示。
             // 进行重试 start
@@ -893,75 +899,6 @@ public class CommSendService {
         return null;
     }
 
-    /**
-     * cachedSocket is ok
-     *
-     * @return
-     */
-    public boolean isCachedSocketOk() {
-        return (cachedSocket != null &&
-                cachedSocket.isConnected() &&
-                cachedSocket.isBound() &&
-                !cachedSocket.isClosed());
-    }
-
-    /**
-     * openSocket
-     *
-     * @return
-     */
-    public AjaxResult openSocket() {
-        // check
-        if (!config.isCommGlobal()) {
-            l.warn("isCommGlobal == false [模拟器通信被禁用!]");
-            return AjaxResult.error("模拟器通信被禁用!");
-        }
-        try {
-            if (!isCachedSocketOk()) {
-                l.info("openSocket cachedSocket is not ok!new socket!");
-                cachedSocket = new Socket(config.getRs485Ip(), config.getRs485Port());
-                cachedSocket.setSoTimeout(SOCKET_TIME_OUT);
-                commFailCountClearAll();
-            } else {
-                l.info("openSocket cachedSocket is ok!");
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-            return AjaxResult.error("开启连接失败!请检查物联网网关连接或配置。");
-        } finally {
-
-        }
-        return AjaxResult.success("开启连接成功!");
-    }
-
-    /**
-     * closeSocket
-     *
-     * @return
-     */
-    public AjaxResult closeSocket() {
-        if (!config.isCommGlobal()) {
-            l.warn("isCommGlobal == false [模拟器通信被禁用!]");
-            return AjaxResult.error("模拟器通信被禁用!");
-        }
-        try {
-            if (cachedSocket != null) {
-                // todo:判断shutdown
-                cachedSocket.getInputStream().close();
-                cachedSocket.getOutputStream().close();
-                cachedSocket.close();
-            } else {
-                l.info("cachedSocket == null!");
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-            return AjaxResult.success("关闭连接失败!请检查物联网网关连接或配置。");
-        } finally {
-            cachedSocket = null;
-            commFailCountClearAll();
-        }
-        return AjaxResult.success("关闭连接成功!");
-    }
 
     /**
      * 等同于ping命令。
@@ -984,30 +921,6 @@ public class CommSendService {
         }
     }
 
-    private HashMap<Long, Integer> map = new HashMap<>();
-
-    /**
-     * @param simId
-     * @return true超过通信失败次数限制
-     */
-    private boolean commFailCountAdd1(long simId) {
-        l.info("map.containsKey(simId) = {}", map.containsKey(simId));
-        if (map.containsKey(simId)) {
-            map.put(simId, map.get(simId) + 1);
-        } else {
-            map.put(simId, 1);
-        }
-        return (map.get(simId) >= OFFLINE_LIMIT);
-    }
-
-    private void commFailCountClearOne(long simId) {
-        map.remove(simId);
-    }
-
-    private void commFailCountClearAll() {
-        map.clear();
-    }
-
     /**
      * https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j/3.0.3
      */

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

@@ -17,30 +17,30 @@ public class FailedCountService {
     private static final int INIT_SIZE = 16;
     private static HashMap<String, AtomicInteger> failedMap = new HashMap<>(INIT_SIZE);
 
-    public int plus1(final String ip) {
-        if (!failedMap.containsKey(ip)) {
-            failedMap.put(ip, new AtomicInteger(COUNT_ADD_1));
+    public int plus1(final String key) {
+        if (!failedMap.containsKey(key)) {
+            failedMap.put(key, new AtomicInteger(COUNT_ADD_1));
         } else {
-            failedMap.get(ip).addAndGet(COUNT_ADD_1);
+            failedMap.get(key).addAndGet(COUNT_ADD_1);
         }
-        return failedMap.get(ip).get();
+        return failedMap.get(key).get();
     }
 
-    public int get(final String ip) {
-        if (failedMap.containsKey(ip)) {
-            return failedMap.get(ip).get();
+    public int get(final String key) {
+        if (failedMap.containsKey(key)) {
+            return failedMap.get(key).get();
         } else {
             return COUNT_0;
         }
     }
 
-    public void reset0(final String ip) {
-        failedMap.get(ip).set(COUNT_0);
+    public void reset0(final String key) {
+        failedMap.get(key).set(COUNT_0);
     }
 
     public void resetAll() {
-        for (String ip : failedMap.keySet()) {
-            failedMap.remove(ip);
+        for (String key : failedMap.keySet()) {
+            failedMap.remove(key);
         }
     }
 }

+ 0 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/IpService.java

@@ -6,7 +6,6 @@ import java.net.UnknownHostException;
 
 public class IpService {
 
-
     /**
      * 等同于ping命令。
      *

+ 69 - 28
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamCollectionService.java

@@ -35,6 +35,8 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     @Autowired
     @Lazy
     private CommSendService commSendService;
+    @Autowired
+    private SocketOldService socketOldService;
 
     /**
      * 查询考试集合
@@ -286,44 +288,53 @@ public class RealExamCollectionService extends Ele6RYBaseService {
      */
     @Transactional
     public AjaxResult open(final Long examCollectionId, final String examCollectionType) {
-        // check
-
-        // todo:暂时不做唯一性检查。
+        // check 1:检查id有效。
+        RealExamCollection ref = selectRealExamCollectionByExamCollectionId(examCollectionId);
+        if (ref == null) {
+            return AjaxResult.error("考试集合不存在!");
+        }
+        // check 2:检查考试集合类型对应。
+        {
+            final String dbType = ref.getExamCollectionType();
+            if (!StringUtils.equals(dbType, examCollectionType)) {
+                return AjaxResult.error("考试集合类型不对应!");
+            }
+        }
+        // check 3:唯一open检查。
 //        if (existOpened()) {
 //            return AjaxResult.error("已经有打开的考试或者练习!");
 //        }
 
-
         // check datetime todo:时间到了
         // do.
 
+        // Step 1:强制停掉所有的自主练习
+        {
+            closeAllType(RealExamCollection.Type.SELF_EXERCISE);
+        }
+        // Step 2:强制停掉所有的练习
+        {
+            closeAllType(RealExamCollection.Type.EXERCISE);
+        }
+        // Step 3:强制停掉 除本考试外的 所有考试
+        {
+            closeAllExamExcludeId(ref.getExamCollectionId());
+        }
+        // Step 4:尝试打开所有Socket,提前准备,允许有打开失败的
 
-        // 打开socket
-        AjaxResult ar1 = commSendService.openSocket();
+        AjaxResult ar1 = socketOldService.openSocket();
         if (ar1.isError()) {
             return ar1;
         }
         // 更新相关数据
 
-        // todo:强制停掉所有的自主练习 所有自主练习CLOSED
-        {
-            List<RealExamCollection> list = listAllByType(RealExamCollection.Type.SELF_EXERCISE);
-            for (RealExamCollection ec : list) {
-                ec.setExamCollectionState(RealExamCollection.State.CLOSED);
-                updateRealExamCollection(ec);
-            }
-        }
-        //
-        RealExamCollection ref = selectRealExamCollectionByExamCollectionId(examCollectionId);
-        if (ref == null) {
-            return AjaxResult.error("考试集合不存在!");
-        }
-        // 修改考试集合状态。
+
+        // Step :修改考试集合状态。
         {
             ref.setExamCollectionState(RealExamCollection.State.OPENED);
             updateRealExamCollection(ref);
         }
-        return AjaxResult.success("开启连接成功!");
+        return AjaxResult.success("开启成功!");
     }
 
     public List<RealExamCollection> listAllByType(String examCollectionType) {
@@ -374,21 +385,29 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     }
 
     public AjaxResult closeAll() {
-        return commSendService.closeSocket();
+        return socketOldService.closeSocket();
     }
 
-    public void closeAllType(String type) {
+    private void closeAllType(final String type) {
         RealExamCollection q = new RealExamCollection();
         q.setExamCollectionType(type);
         selectRealExamCollectionList(q)
-                .forEach((RealExamCollection c) -> {
-                    c.setExamCollectionState(RealExamCollection.State.CLOSED);
+                .forEach((RealExamCollection ec) -> {
+                    ec.setExamCollectionState(RealExamCollection.State.CLOSED);
+                    updateRealExamCollection(ec);
                 });
     }
 
-    public void closeAllTpye2and3() {
-        closeAllType(RealExamCollection.Type.EXAM);
-        closeAllType(RealExamCollection.Type.SELF_EXERCISE);
+    private void closeAllExamExcludeId(final Long excludeRecId) {
+        List<RealExamCollection> list = listAllByType(RealExamCollection.Type.EXAM);
+        for (RealExamCollection ec : list) {
+            if (ec.getExamCollectionId().equals(excludeRecId)) {
+                // 跳过本考试。
+                continue;
+            }
+            ec.setExamCollectionState(RealExamCollection.State.CLOSED);
+            updateRealExamCollection(ec);
+        }
     }
 
     /**
@@ -403,4 +422,26 @@ public class RealExamCollectionService extends Ele6RYBaseService {
         }
         return list.get(0);
     }
+
+
+    public AjaxResult resetAllSuperMan() {
+        // Step:关闭所有Socket连接
+
+        // Step:ping路由器,返回在线情况。
+
+        // Step:ping教员端主机,返回在线情况。
+
+        // Step:ping所有学员端主机,返回在线情况列表。
+
+        // Step:ping所有RS485,返回在线情况列表。
+
+        // Step:建立所有模拟器Socket,返回情况列表。
+
+        // Step:有Socket的前提下,尝试查询所有模拟器连接情况,得到连接的模拟器 型号、序列号信息
+
+        // Step:所有连接的模拟器,清除每一个模拟器的各个故障
+
+        // Step:所有连接的模拟器,读取每一个模拟器的各个故障,是否处于考试准备ok状态
+        return null;
+    }
 }

+ 19 - 5
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SimService.java

@@ -106,17 +106,24 @@ public class SimService {
         return true;
     }
 
+    /**
+     * @param simNum
+     * @return true:存在,false:不存在
+     */
     public boolean existBySimNum(final String simNum) {
         if (StringUtils.isEmpty(simNum)) {
             return false;
         }
         Sim s = uniqueBySimNum(simNum);
-        if (s == null) {
-            return false;
-        }
-        return true;
+        return s != null;
     }
 
+    /**
+     * 通过sim_num获取唯一模拟器Sim对象。
+     *
+     * @param simNum
+     * @return
+     */
     public Sim uniqueBySimNum(final String simNum) {
         Sim q = new Sim();
         q.setSimNum(simNum);
@@ -180,7 +187,7 @@ public class SimService {
         return list;
     }
 
-    public int updateAllEnableState(String simState) {
+    public int updateAllEnableState(final String simState) {
         List<Sim> list = listAllEnable();
         for (Sim sim : list) {
             sim.setSimState(simState);
@@ -234,6 +241,13 @@ public class SimService {
         return isSimStateBySimId(simId, Sim.State.DISABLE);
     }
 
+    /**
+     * 更新模拟器序列号。
+     *
+     * @param simId
+     * @param simSn
+     * @return
+     */
     public int updateSimSnBySimId(Long simId, String simSn) {
         // check
         if (StringUtils.isEmpty(simSn)) {

+ 131 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketOldService.java

@@ -0,0 +1,131 @@
+package com.ruoyi.sim.service.impl;
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.sim.config.SimConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.HashMap;
+
+import static com.ruoyi.sim.constant.CommConst.OFFLINE_LIMIT;
+import static com.ruoyi.sim.constant.CommConst.SOCKET_TIME_OUT;
+
+@Service
+public class SocketOldService {
+
+    private static final Logger l = LoggerFactory.getLogger(SocketOldService.class);
+
+    /**
+     * 缓存的Socket连接。
+     */
+    private Socket cachedSocket = null;
+
+    @Autowired
+    private SimConfig config;
+
+    public Socket getCachedSocket() {
+        return cachedSocket;
+    }
+
+    public void setCachedSocket(Socket cachedSocket) {
+        this.cachedSocket = cachedSocket;
+    }
+
+    /**
+     * openSocket
+     *
+     * @return
+     */
+    public AjaxResult openSocket() {
+        // check
+        if (!config.isCommGlobal()) {
+            l.warn("isCommGlobal == false [模拟器通信被禁用!]");
+            return AjaxResult.error("模拟器通信被禁用!");
+        }
+        try {
+            if (!isCachedSocketOk()) {
+                l.info("openSocket cachedSocket is not ok!new socket!");
+                cachedSocket = new Socket(config.getRs485Ip(), config.getRs485Port());
+                cachedSocket.setSoTimeout(SOCKET_TIME_OUT);
+                commFailCountClearAll();
+            } else {
+                l.info("openSocket cachedSocket is ok!");
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return AjaxResult.error("开启连接失败!请检查物联网网关连接或配置。");
+        } finally {
+
+        }
+        return AjaxResult.success("开启连接成功!");
+    }
+
+    /**
+     * closeSocket
+     *
+     * @return
+     */
+    public AjaxResult closeSocket() {
+        if (!config.isCommGlobal()) {
+            l.warn("isCommGlobal == false [模拟器通信被禁用!]");
+            return AjaxResult.error("模拟器通信被禁用!");
+        }
+        try {
+            if (cachedSocket != null) {
+                // todo:判断shutdown
+                cachedSocket.getInputStream().close();
+                cachedSocket.getOutputStream().close();
+                cachedSocket.close();
+            } else {
+                l.info("cachedSocket == null!");
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return AjaxResult.success("关闭连接失败!请检查物联网网关连接或配置。");
+        } finally {
+            cachedSocket = null;
+            commFailCountClearAll();
+        }
+        return AjaxResult.success("关闭连接成功!");
+    }
+
+    /**
+     * cachedSocket is ok
+     *
+     * @return
+     */
+    public boolean isCachedSocketOk() {
+        return (cachedSocket != null &&
+                cachedSocket.isConnected() &&
+                cachedSocket.isBound() &&
+                !cachedSocket.isClosed());
+    }
+
+    private HashMap<Long, Integer> map = new HashMap<>();
+
+    /**
+     * @param simId
+     * @return true超过通信失败次数限制
+     */
+    public boolean commFailCountAdd1(long simId) {
+        l.info("map.containsKey(simId) = {}", map.containsKey(simId));
+        if (map.containsKey(simId)) {
+            map.put(simId, map.get(simId) + 1);
+        } else {
+            map.put(simId, 1);
+        }
+        return (map.get(simId) >= OFFLINE_LIMIT);
+    }
+
+    public void commFailCountClearOne(long simId) {
+        map.remove(simId);
+    }
+
+    public void commFailCountClearAll() {
+        map.clear();
+    }
+}

+ 71 - 24
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketService.java

@@ -2,19 +2,25 @@ package com.ruoyi.sim.service.impl;
 
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.sim.config.SimConfig;
+import com.ruoyi.sim.constant.CommConst;
+import com.ruoyi.sim.domain.vo.SimSocketVo;
+import com.ruoyi.sim.domain.vo.SocketWrapValue;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.io.IOException;
-import java.net.InetAddress;
 import java.net.Socket;
-import java.net.UnknownHostException;
 import java.util.HashMap;
 
 import static com.ruoyi.sim.constant.CommConst.SOCKET_TIME_OUT;
 
+/**
+ * 1.所有Socket常开。
+ * todo: connectedTimeMillis 时间。
+ */
 @Service
 public class SocketService {
 
@@ -22,11 +28,15 @@ public class SocketService {
 
     private static final int INIT_SIZE = 16;
     /**
-     * key:
-     * value:
+     * 6 hours
      */
-    private static HashMap<String, Socket> cachedMap = new HashMap<>(INIT_SIZE);
+    private static final long LIMIT = 1000L * 60 * 60 * 6;
 
+    /**
+     * key: ip:port
+     * value:
+     */
+    private static HashMap<String, SocketWrapValue> cachedMap = new HashMap<>(INIT_SIZE);
 
     @Autowired
     private SimConfig config;
@@ -34,12 +44,13 @@ public class SocketService {
     private FailedCountService failedCountService;
 
     /**
-     * @param ip
+     * @param ip   ip v4.
+     * @param port
      * @return true:socket ok!
      */
-    public boolean isOk(final String ip) {
+    public boolean isOk(final String ip, final Integer port) {
         if (cachedMap.containsKey(ip)) {
-            Socket s = cachedMap.get(ip);
+            Socket s = cachedMap.get(buildKey(ip, port)).getSocket();
             if (s != null) {
                 return (s.isConnected() && s.isBound() && !s.isClosed());
             }
@@ -52,60 +63,97 @@ public class SocketService {
      * @param port
      * @return
      */
-    public AjaxResult openOne(final String ip, final int port) {
+    public String buildKey(final String ip, final Integer port) {
+        if (StringUtils.isBlank(ip)) {
+            throw new IllegalArgumentException("ip error");
+        }
+        if (port == null || port <= CommConst.PORT_MIN || port >= CommConst.PORT_MAX) {
+            throw new IllegalArgumentException("port error");
+        }
+        return ip + ":" + port;
+    }
+
+    /**
+     * @param ssv
+     * @return
+     */
+    public AjaxResult openOne(final SimSocketVo ssv) {
         // check.
         if (!config.isCommGlobal()) {
-            l.warn("isCommGlobal == false [模拟器通信被禁用!]");
+            l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
             return AjaxResult.error("模拟器通信被禁用!");
         }
         //
         try {
-            if (!isOk(ip)) {
+            final String ip = ssv.getIp();
+            final Integer port = ssv.getPort();
+            final String key = buildKey(ip, port);
+            if (!isOk(ip, port)) {
                 l.info("openSocket cachedSocket is not ok!new socket ip = {}!", ip);
-                closeOne(ip);
+                closeOne(ssv);
                 Socket s = new Socket(ip, port);
                 s.setSoTimeout(SOCKET_TIME_OUT);
-                cachedMap.put(ip, s);
+                SocketWrapValue value = new SocketWrapValue(ip, port, s, System.currentTimeMillis());
+                cachedMap.put(key, value);
                 // failed count reset.
-                failedCountService.reset0(ip);
+                failedCountService.reset0(key);
             }
         } catch (IOException e) {
             throw new RuntimeException(e);
         } finally {
 
         }
-        return null;
+        return AjaxResult.success("openOneSocket Success!");
+    }
+
+    /**
+     * todo:部分返回Aj结果。
+     *
+     * @param ssvs
+     * @return
+     */
+    public AjaxResult openAll(final SimSocketVo[] ssvs) {
+        if (!config.isCommGlobal()) {
+            l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
+            return AjaxResult.error("模拟器通信被禁用!");
+        }
+        for (SimSocketVo ssv : ssvs) {
+            openOne(ssv);
+        }
+        return AjaxResult.success("openAllSocket Success!");
     }
 
-    public AjaxResult closeOne(final String ip) {
+    public AjaxResult closeOne(final SimSocketVo ssv) {
         String msgOk = "关闭Socket成功!";
         if (!config.isCommGlobal()) {
             l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
             return AjaxResult.error("模拟器通信被禁用!");
         }
-        if (!cachedMap.containsKey(ip)) {
+        final String key = buildKey(ssv.getIp(), ssv.getPort());
+        if (!cachedMap.containsKey(key)) {
+            cachedMap.remove(key);
             return AjaxResult.success(msgOk);
         } else {
             try {
-                Socket s = cachedMap.get(ip);
+                Socket s = cachedMap.get(key).getSocket();
                 s.getInputStream().close();
                 s.getOutputStream().close();
                 s.close();
             } catch (IOException e) {
                 e.printStackTrace();
             } finally {
-                cachedMap.remove(ip);
+                cachedMap.remove(key);
                 // failed count reset.
-                failedCountService.reset0(ip);
+                failedCountService.reset0(key);
                 return AjaxResult.success(msgOk);
             }
         }
     }
 
-    public AjaxResult closeAll(final String[] ipArray) {
+    public AjaxResult closeAll(final SimSocketVo[] ssvs) {
         String msgOk = "关闭所有Socket成功!";
-        for (String ip : ipArray) {
-            AjaxResult ar = closeOne(ip);
+        for (SimSocketVo ssv : ssvs) {
+            AjaxResult ar = closeOne(ssv);
             if (ar != null && !ar.isSuccess()) {
                 return ar;
             }
@@ -119,5 +167,4 @@ public class SocketService {
     public void resetAll() {
 
     }
-
 }

+ 11 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/util/CRC16Modbus.java

@@ -24,10 +24,20 @@ public class CRC16Modbus {
 
     /**
      * main test.
+     *
      * @param args
      */
     public static void main(String[] args) {
-        byte[] data = {0x01, 0x03, 0x00, 0x20, 0x00, 0x01}; // 示例数据
+        byte[] data = {0x0A, 0x0A,
+                0x0A, 0x7,
+                0x00, 0x01,
+                0x00, 0x03,
+                0x00, 0x08,
+                0x00, 0x00,
+                0x00, 0x00,
+                0x00, 0x00,
+                0x00, 0x00}; // 示例数据
+        // byte[] data = {0xAA, 0xA7, 0x00, 0x20, 0x00, 0x01}; // 示例数据
         byte[] crc = calculateCRC(data);
         System.out.println("CRC-16/MODBUS: " + String.format("%02X %02X", crc[0], crc[1]));
     }