tom 5 місяців тому
батько
коміт
8840946163

+ 14 - 14
pla-sim/01_SQL/02_table/sim_real_exam.sql

@@ -1,17 +1,17 @@
 /*
  Navicat Premium Dump SQL
 
- Source Server         : qdhome.iot321.top-dev
+ Source Server         : 47.104.188.84-sim
  Source Server Type    : MySQL
- Source Server Version : 50740 (5.7.40-log)
- Source Host           : qdhome.iot321.top:33103
+ Source Server Version : 80020 (8.0.20)
+ Source Host           : 47.104.188.84:65006
  Source Schema         : pla-chem-sim-dev-1
 
  Target Server Type    : MySQL
- Target Server Version : 50740 (5.7.40-log)
+ Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 15/12/2024 19:22:48
+ Date: 23/12/2024 19:14:28
 */
 
 SET NAMES utf8mb4;
@@ -22,14 +22,14 @@ SET FOREIGN_KEY_CHECKS = 0;
 -- ----------------------------
 DROP TABLE IF EXISTS `sim_real_exam`;
 CREATE TABLE `sim_real_exam`  (
-  `exam_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '考试ID',
-  `exam_collection_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '考试集合ID',
-  `user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '学员ID/用户ID',
-  `seat_id` bigint(20) NOT NULL COMMENT '座ID',
-  `sim_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '模拟器ID',
-  `exam_status` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '考试状\r\n0:未登录\r\n1:已登录\r\n2:模拟器检查并下发ok\r\n3:答题中\r\n4:已交卷\r\n5:获取模拟成绩分析\r\n80:教师标记缺考\r\n81:登录未开始答题\r\n90:模拟器异常结束\r\n',
-  `total_score` int(3) NULL DEFAULT NULL COMMENT '总分:累加扣分和计算出总分',
-  `deduction_total_score` int(3) NULL DEFAULT NULL COMMENT '扣分总计,不计超时扣分',
+  `exam_id` bigint NOT NULL DEFAULT 0 AUTO_INCREMENT COMMENT '考试ID',
+  `exam_collection_id` bigint NOT NULL DEFAULT 0 COMMENT '考试集合ID',
+  `user_id` bigint NOT NULL DEFAULT 0 COMMENT '学员ID/用户ID',
+  `seat_id` bigint NOT NULL COMMENT '座ID',
+  `sim_id` bigint NOT NULL DEFAULT 0 COMMENT '模拟器ID',
+  `exam_status` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '考试状态:[0]-未登录,[1]-已登录,[2]-模拟器检查并下发故障中,[3]:模拟器检查OK可开考,[4]-答题中,[5]-已交卷,[6]-计算成绩中,[7]-获取到成绩报告,[80]-教师标记缺考,[81]-登录未开始答题,[90]-模拟器异常结束',
+  `total_score` int NULL DEFAULT NULL COMMENT '总分:累加扣分和计算出总分',
+  `deduction_total_score` int NULL DEFAULT NULL COMMENT '扣分总计,不计超时扣分',
   `start_time` datetime NULL DEFAULT NULL COMMENT '考试实际开始时间(毫秒)',
   `end_time` datetime NULL DEFAULT NULL COMMENT '考试实际结束时间(毫秒)',
   `login_time` datetime NULL DEFAULT NULL COMMENT '登录时间',
@@ -40,6 +40,6 @@ CREATE TABLE `sim_real_exam`  (
   `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
   `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
   PRIMARY KEY (`exam_id`) USING BTREE
-) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'sim-考试表' ROW_FORMAT = DYNAMIC;
+) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'sim-考试表' ROW_FORMAT = DYNAMIC;
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 7 - 7
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -1,15 +1,15 @@
 package com.ruoyi.common.constant;
 
 import java.util.Locale;
+
 import io.jsonwebtoken.Claims;
 
 /**
  * 通用常量信息
- * 
+ *
  * @author ruoyi
  */
-public class Constants
-{
+public class Constants {
     /**
      * UTF-8 字符集
      */
@@ -158,16 +158,16 @@ public class Constants
     /**
      * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
      */
-    public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
+    public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.ruoyi"};
 
     /**
      * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
      */
-    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
+    public static final String[] JOB_WHITELIST_STR = {"com.ruoyi.quartz.task", "com.ruoyi.sim"};
 
     /**
      * 定时任务违规的字符
      */
-    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
-            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
+    public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator"};
 }

+ 39 - 14
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/RealExamController.java

@@ -38,7 +38,6 @@ public class RealExamController extends BaseController {
     /**
      * 查询考试列表
      */
-    // @PreAuthorize("@ss.hasPermi('sim:real-exam:list')")
     // @GetMapping("/teacher/list")
     // @ApiOperation("[老师]查询学生考试列表")
     public TableDataInfo list(RealExam realExam) {
@@ -47,26 +46,52 @@ public class RealExamController extends BaseController {
         return getDataTable(list);
     }
 
-    @GetMapping("/student/listByUserId/{userId}")
+    @GetMapping("/student/exam/listByUserId/{userId}")
     @ApiOperation("[学生]查询userId学生考试列表")
     public TableDataInfo listByUserId(@PathVariable("userId") Long userId) {
         // todo:
-        RealExam realExam = new RealExam();
+        RealExam q = new RealExam();
+        q.setUserId(userId);
         startPage();
-        List<RealExam> list = realExamService.selectRealExamList(realExam);
+        List<RealExam> list = realExamService.selectRealExamList(q);
+        // todo:
         return getDataTable(list);
     }
 
-    /**
-     * 导出考试列表
-     */
-    // @PreAuthorize("@ss.hasPermi('sim:real-exam:export')")
-    @Log(title = "考试", businessType = BusinessType.EXPORT)
-    // @PostMapping("/export")
-    public void export(HttpServletResponse response, RealExam realExam) {
-        List<RealExam> list = realExamService.selectRealExamList(realExam);
-        ExcelUtil<RealExam> util = new ExcelUtil<RealExam>(RealExam.class);
-        util.exportExcel(response, list, "考试数据");
+    @GetMapping("/student/exam/enter/{examId}")
+    @ApiOperation("[学生]进入考试")
+    public AjaxResult studentEnterRealExam(@PathVariable("examId") Long examId) {
+        return realExamService.studentEnterRealExam(examId);
+    }
+
+    @GetMapping("/student/exam/prepare/{examId}")
+    @ApiOperation("[轮询][学生]准备考试界面")
+    public AjaxResult studentLoopPrepareRealExam(@PathVariable("examId") Long examId) {
+        return realExamService.studentLoopPrepareRealExam(examId);
+    }
+
+    @GetMapping("/student/exam/start/{examId}")
+    @ApiOperation("[学生]开始考试")
+    public AjaxResult studentStartRealExam(@PathVariable("examId") Long examId) {
+        return realExamService.studentStartRealExam(examId);
+    }
+
+    @GetMapping("/student/exam/answering/{examId}")
+    @ApiOperation("[轮询][学生]正在考试界面")
+    public AjaxResult studentLoopAnsweringRealExam(@PathVariable("examId") Long examId) {
+        return realExamService.studentLoopAnsweringRealExam(examId);
+    }
+
+    @GetMapping("/student/exam/submit/{examId}")
+    @ApiOperation("[学生]交卷")
+    public AjaxResult studentSubmitRealExam(@PathVariable("examId") Long examId) {
+        return realExamService.studentSubmitRealExam(examId);
+    }
+
+    @GetMapping("/student/exam/report/{examId}")
+    @ApiOperation("[轮询][学生]结束考试界面")
+    public AjaxResult studentLoopPostRealExam(@PathVariable("examId") Long examId) {
+        return realExamService.studentLoopPostRealExam(examId);
     }
 
     // @PreAuthorize("@ss.hasPermi('sim:real-exam:query')")

+ 34 - 30
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/RealExam.java

@@ -48,17 +48,18 @@ public class RealExam extends BaseEntity {
     private Long simId;
 
     /**
-     * 考试状态
-     * 0:未登录
-     * 1:已登录
-     * 2:故障下发中
-     * 2:模拟器检查并下发ok
-     * 3:答题中
-     * 4:已交卷
-     * 5:已经获取模拟成绩分析
-     * 80:教师标记缺考
-     * 81:登录未开始答题
-     * 90:模拟器异常结束
+     * 考试状态:
+     * [0]-未登录,
+     * [1]-已登录,
+     * [2]-模拟器检查并下发故障中,
+     * [3]:模拟器检查OK可开考,
+     * [4]-答题中,
+     * [5]-已交卷,
+     * [6]-计算成绩中,
+     * [7]-获取到成绩报告,
+     * [80]-教师标记缺考,
+     * [81]-登录未开始答题,
+     * [90]-模拟器异常结束
      */
     @Excel(name = "考试状态")
     private String examStatus;
@@ -67,13 +68,13 @@ public class RealExam extends BaseEntity {
      * 总分:累加扣分和计算出总分
      */
     @Excel(name = "总分:累加扣分和计算出总分")
-    private Integer totalScore;
+    private Long totalScore;
 
     /**
      * 扣分总计,不计超时扣分
      */
     @Excel(name = "扣分总计,不计超时扣分")
-    private Integer deductionTotalScore;
+    private Long deductionTotalScore;
 
     /**
      * 考试实际开始时间(毫秒)
@@ -151,19 +152,19 @@ public class RealExam extends BaseEntity {
         return examStatus;
     }
 
-    public void setTotalScore(Integer totalScore) {
+    public void setTotalScore(Long totalScore) {
         this.totalScore = totalScore;
     }
 
-    public Integer getTotalScore() {
+    public Long getTotalScore() {
         return totalScore;
     }
 
-    public void setDeductionTotalScore(Integer deductionTotalScore) {
+    public void setDeductionTotalScore(Long deductionTotalScore) {
         this.deductionTotalScore = deductionTotalScore;
     }
 
-    public Integer getDeductionTotalScore() {
+    public Long getDeductionTotalScore() {
         return deductionTotalScore;
     }
 
@@ -223,17 +224,20 @@ public class RealExam extends BaseEntity {
     }
 
     // -------------------------------- tom add  --------------------------------
-
-    public static final String STATE_NOT_LOGGED_IN = "0";
-
-    public static final String STATE_LOGGED_IN = "1";
-    public static final String STATE_SIM_WRITING = "2????";// todo:??
-    public static final String STATE_SIM_PREPARE_OK = "2";
-    public static final String STATE_ANSWERING = "3";
-    public static final String STATE_SUBMITTED = "4";
-    public static final String STATE_CALCULATING_SCORE = "4????";// todo:??
-    public static final String STATE_GOT_REPORT = "5";
-    public static final String STATE_ABSENCE_BY_TEACHER = "80";
-    public static final String STATE_LOGIN_NOT_STARTED_ANSWERING_QUESTIONS = "81";
-    public static final String STATE_SIMULATOR_ERROR = "90";
+    public static long EXAM_TIMEOUT_LIMIT = 1000 * 60 * 5;
+
+    public interface State {
+        String NOT_LOGGED_IN = "0";
+
+        String LOGGED_IN = "1";
+        String SIM_WRITING = "2";
+        String SIM_PREPARE_OK = "3";
+        String ANSWERING = "4";
+        String SUBMITTED = "5";
+        String CALCULATING_SCORE = "6";
+        String GOT_REPORT = "7";
+        String ABSENCE_BY_TEACHER = "80";
+        String LOGIN_NOT_STARTED_ANSWERING_QUESTIONS = "81";
+        String SIMULATOR_ERROR = "90";
+    }
 }

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

@@ -198,16 +198,22 @@ public class RealExamFault extends BaseEntity {
     }
 
     // -------------------------------- tom add  --------------------------------
-    public static final String REF_TYPE_1 = "1";
-    public static final String REF_TYPE_2 = "2";
-
-    public static final String REF_STATE_INIT = "0";
-    public static final String REF_STATE_CLEARED = "1";
-    public static final String REF_STATE_WRITTEN = "2";
-    public static final String REF_STATE_LOOP_READ = "3";
-    public static final String REF_STATE_FINISH = "4";
-
-    public static final String FLAG_YES = "1";
-    public static final String FLAG_NO = "0";
-    public static final String FLAG_UNKNOWN = "7";
+    public interface Type {
+        String TYPE_1 = "1";
+        String TYPE_2 = "2";
+    }
+
+    public interface State {
+        String INIT = "0";
+        String CLEARED = "1";
+        String WRITTEN = "2";
+        String LOOP_READ = "3";
+        String FINISH = "4";
+    }
+
+    public interface Flag {
+        String YES = "1";
+        String NO = "0";
+        String UNKNOWN = "7";
+    }
 }

+ 14 - 12
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Sim.java

@@ -176,19 +176,21 @@ public class Sim extends BaseEntity {
             Map.entry(TYPE_0003, TYPE_0003_NAME)
     );
 
-    public static String STATE_ENABLE_INIT = "0";
-    public static String STATE_ONLINE = "1";
-    public static String STATE_SIM_OFFLINE = "2";
-    public static String STATE_GATEWAY_OFFLINE = "3";
-    public static String STATE_SIM_ERROR = "4";
-    public static String STATE_DISABLE = "5";
+    public interface State {
+        String ENABLE_INIT = "0";
+        String ONLINE = "1";
+        String SIM_OFFLINE = "2";
+        String GATEWAY_OFFLINE = "3";
+        String SIM_ERROR = "4";
+        String DISABLE = "5";
+    }
 
     public static final Set<String> STATE_SET = new HashSet<>(Arrays.asList(
-            STATE_ENABLE_INIT,
-            STATE_ONLINE,
-            STATE_SIM_OFFLINE,
-            STATE_GATEWAY_OFFLINE,
-            STATE_SIM_ERROR,
-            STATE_DISABLE
+            State.ENABLE_INIT,
+            State.ONLINE,
+            State.SIM_OFFLINE,
+            State.GATEWAY_OFFLINE,
+            State.SIM_ERROR,
+            State.DISABLE
     ));
 }

+ 27 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/RealExamFaultVo.java

@@ -0,0 +1,27 @@
+package com.ruoyi.sim.domain.vo;
+
+import com.ruoyi.sim.domain.Fault;
+import com.ruoyi.sim.domain.RealExamFault;
+
+public class RealExamFaultVo extends RealExamFault {
+
+    private boolean answerRight = false;
+
+    private Fault fault;
+
+    public boolean isAnswerRight() {
+        return answerRight;
+    }
+
+    public void setAnswerRight(boolean answerRight) {
+        this.answerRight = answerRight;
+    }
+
+    public Fault getFault() {
+        return fault;
+    }
+
+    public void setFault(Fault fault) {
+        this.fault = fault;
+    }
+}

+ 21 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/StudentRealExamIngVo.java

@@ -1,12 +1,25 @@
 package com.ruoyi.sim.domain.vo;
 
+import com.ruoyi.sim.domain.RealExam;
+
 /**
  * [学生]正在考试页面Vo。
  */
 public class StudentRealExamIngVo {
 
+    private RealExam realExam;
+
     private Long remainingMilliseconds;
 
+    private boolean compulsiveSubmit = false;
+
+    public RealExam getRealExam() {
+        return realExam;
+    }
+
+    public void setRealExam(RealExam realExam) {
+        this.realExam = realExam;
+    }
 
     public Long getRemainingMilliseconds() {
         return remainingMilliseconds;
@@ -15,4 +28,12 @@ public class StudentRealExamIngVo {
     public void setRemainingMilliseconds(Long remainingMilliseconds) {
         this.remainingMilliseconds = remainingMilliseconds;
     }
+
+    public boolean isCompulsiveSubmit() {
+        return compulsiveSubmit;
+    }
+
+    public void setCompulsiveSubmit(boolean compulsiveSubmit) {
+        this.compulsiveSubmit = compulsiveSubmit;
+    }
 }

+ 24 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/StudentRealExamPostVo.java

@@ -1,4 +1,28 @@
 package com.ruoyi.sim.domain.vo;
 
+import com.ruoyi.sim.domain.RealExam;
+
+import java.util.List;
+
 public class StudentRealExamPostVo {
+
+    private RealExam realExam;
+
+    private List<RealExamFaultVo> listPart1;
+
+    public RealExam getRealExam() {
+        return realExam;
+    }
+
+    public void setRealExam(RealExam realExam) {
+        this.realExam = realExam;
+    }
+
+    public List<RealExamFaultVo> getListPart1() {
+        return listPart1;
+    }
+
+    public void setListPart1(List<RealExamFaultVo> listPart1) {
+        this.listPart1 = listPart1;
+    }
 }

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

@@ -38,7 +38,7 @@ public class CommReceiveService {
         }
         if (!StringUtils.isEmpty(sm.getReceiveMsg())) {
             Sim f = simService.selectSimBySimId(s.getSimId());
-            simService.updateSimStateBySimId(s.getSimId(), Sim.STATE_ONLINE);
+            simService.updateSimStateBySimId(s.getSimId(), Sim.State.ONLINE);
         }
     }
 
@@ -47,7 +47,7 @@ public class CommReceiveService {
 
         //
         if (reF != null) {
-            reF.setRefState(RealExamFault.REF_STATE_CLEARED);
+            reF.setRefState(RealExamFault.State.CLEARED);
             realExamFaultService.updateRealExamFault(reF);
         }
     }
@@ -66,7 +66,7 @@ public class CommReceiveService {
         //
         String faultQuestionValue = parseGetData(sm.getReceiveMsg());
         l.info("faultQuestionValue = {}", faultQuestionValue);
-        reF.setRefState(RealExamFault.REF_STATE_WRITTEN);
+        reF.setRefState(RealExamFault.State.WRITTEN);
         reF.setSimFaultQuestionValue(faultQuestionValue);
         realExamFaultService.updateRealExamFault(reF);
     }
@@ -88,7 +88,7 @@ public class CommReceiveService {
         //
         String faultAnswerValue = parseGetData(sm.getReceiveMsg());
         l.info("faultAnswerValue = {}", faultAnswerValue);
-        reF.setRefState(RealExamFault.REF_STATE_LOOP_READ);
+        reF.setRefState(RealExamFault.State.LOOP_READ);
         reF.setSimFaultAnswerValue(faultAnswerValue);
         realExamFaultService.updateRealExamFault(reF);
     }

+ 135 - 45
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java

@@ -1,5 +1,6 @@
 package com.ruoyi.sim.service.impl;
 
+import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.sim.config.SimConfig;
 import com.ruoyi.sim.domain.*;
 import org.apache.commons.lang3.StringUtils;
@@ -14,7 +15,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.Socket;
-import java.net.SocketTimeoutException;
 import java.util.List;
 import java.util.Objects;
 
@@ -24,12 +24,12 @@ import static com.ruoyi.sim.service.impl.CommSendService.Const.*;
  * 硬件通信
  * send service.
  */
-@Service
+@Service("commSendService")
 public class CommSendService {
 
     interface Const {
 
-        String ROUTER_IP = "127.0.0.1";
+        String ROUTER_IP = "192.168.1.1";
         String IP = "123.112.16.165";
         int PORT = 8899;
         /**
@@ -95,23 +95,10 @@ public class CommSendService {
     private SimConfig sConfig;
 
     /**
-     * 初始化方法,项目启动后自动运行。
+     *
      */
     public void init() {
-        //
-        try {
-            if (!isReachable(ROUTER_IP)) {
-                // todo:ping 不通。
-            }
-            if (!isReachable(IP)) {
-                // todo:ping 不通。
-            }
-            openSocket();
-            //
-            checkAllSimState();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+
     }
 
     /**
@@ -122,7 +109,8 @@ public class CommSendService {
     }
 
     public void readAll() {
-        List<RealExam> listRE = realExamService.listAllStatus(RealExam.STATE_ANSWERING);
+        l.info("readAll");
+        List<RealExam> listRE = realExamService.listAllByStatus(RealExam.State.ANSWERING);
         listRE.forEach(e -> {
             if (e == null) {
                 return;
@@ -132,10 +120,11 @@ public class CommSendService {
                 Sim s = simService.selectSimBySimId(e.getSimId());
                 Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
                 if (f != null &&
-                        f.getFaultType().equals(Fault.TYPE_3) &&
-                        f.getFaultState().equals(Fault.State.ENABLE)
-                )
+                        Fault.TYPE_3.equals(f.getFaultType()) &&
+                        Fault.State.ENABLE.equals(f.getFaultState())
+                ) {
                     readOneFaultResistance(s, ref, f);
+                }
             });
         });
     }
@@ -155,14 +144,53 @@ public class CommSendService {
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
             readOneFaultResistance(s, ref, f);
         });
-        realExamService.updateOneState(re, RealExam.STATE_SUBMITTED);
+
+        // realExamService.updateOneState(re.getExamId(), RealExam.State.SUBMITTED);
+        {
+            RealExam re1 = realExamService.selectRealExamByExamId(re.getExamId());
+            re.setExamStatus(RealExam.State.SUBMITTED);
+            re.setEndTime(DateUtils.getNowDate());
+            realExamService.updateRealExam(re1);
+        }
     }
 
     /**
+     * todo:????
      * 定时任务。
      */
-    public void scheduledCheckAllSimState() {
-        checkAllSimState();
+    public void scheduledReadSim() {
+        l.info("scheduledReadSim");
+        if (isSocketOk()) {
+            readAll();
+        } else {
+            scheduledConnect();
+        }
+    }
+
+    /**
+     * todo:????
+     * 定时任务。
+     */
+    public void scheduledConnect() {
+        l.info("scheduledConnect");
+        try {
+            if (!isReachable(ROUTER_IP)) {
+                // ping 不通。
+                l.warn("ping not ok");
+                simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
+            }
+            if (!isReachable(IP)) {
+                // todo:ping 不通。
+                l.warn("ping not ok");
+                simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
+            }
+            openSocket();
+        } catch (IOException e) {
+            catchException(e);
+        }
+        if (isSocketOk()) {
+            checkAllSimStateAsync();
+        }
     }
 
     /**
@@ -175,9 +203,9 @@ public class CommSendService {
         });
     }
 
-    @Async()
+    @Async("tp-comm")
     public void checkAllSimStateAsync() {
-
+        checkAllSimState();
     }
 
     public void checkOneSimState(Sim s) {
@@ -186,6 +214,10 @@ public class CommSendService {
         if (Objects.isNull(s)) {
             return;
         }
+        if (Sim.State.DISABLE.equals(s.getSimState())) {
+            l.warn("sim DISABLE getSimId = {}", s.getSimId());
+            return;
+        }
         //
         try {
             SimMsg sm = new SimMsg();
@@ -195,11 +227,17 @@ public class CommSendService {
             sm.setReceiveMsg(receiveMsg);
             simReceiveService.checkOneSimState(sm, s);
         } catch (IOException e) {
-            throw new RuntimeException(e);
+            catchException(e);
         }
     }
 
     /**
+     * static lock
+     * todo:
+     */
+    public static boolean clearListFaultLocked = false;
+
+    /**
      * 清除一个考试的,对应的某型号一台模拟器的,所有设备故障。
      *
      * @param re
@@ -209,6 +247,16 @@ public class CommSendService {
         if (Objects.isNull(re)) {
 
         }
+        // about lock
+        if (clearListFaultLocked) {
+            l.warn("clearListFaultLocked = true");
+            return;
+        }
+        //
+        clearListFaultLocked = true;
+        {
+            realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING);
+        }
         //
         List<RealExamFault> list = realExamFaultService.listAllType2InitStateByExamId(re.getExamId());
         list.forEach(ref -> {
@@ -217,15 +265,17 @@ public class CommSendService {
                 l.warn("故障{}-Disable", ref.getFaultId());
                 return;
             }
-            l.info("f.toString() = " + f.toString());
+            l.info("f.toString() = {}", f);
             Sim s = simService.selectSimBySimId(re.getSimId());
-            l.info("s.toString() = " + s.toString());
+            l.info("s.toString() = {}", s);
             // check
             if (Objects.isNull(f)) {
 
             }
             clearOneFault(s, ref, f);
         });
+        //
+        clearListFaultLocked = false;
     }
 
     /**
@@ -267,19 +317,19 @@ public class CommSendService {
             sm1.setReceiveMsg(receiveMsg1);
             simReceiveService.clearOneFault(sm1, s, reF, f);
             // step2
-            if (reF != null && realExamFaultService.isState(reF.getRefId(), RealExamFault.REF_STATE_CLEARED)) {
+            if (reF != null && realExamFaultService.isState(reF.getRefId(), RealExamFault.State.CLEARED)) {
                 writeOneFault(s, reF, f);
             }
-        } catch (SocketTimeoutException e) {
-            throw new RuntimeException(e);
         } catch (IOException e) {
-            throw new RuntimeException(e);
+            catchException(e);
         }
     }
 
 
     public void writeOneFault(Sim s, RealExamFault ref, Fault f) {
         try {
+            // todo:ref is null.
+
             // 下发故障
             SimMsg sm1 = new SimMsg();
             String sendMsg1 = buildSendMsgWriteFault(s.getSimNum(), f.getBindHardwareMsg());
@@ -295,19 +345,32 @@ public class CommSendService {
             String receiveMsg2 = send(sendMsg2, s);
             sm2.setReceiveMsg(receiveMsg2);
             simReceiveService.setFaultQuestionValue(sm2, s, ref, f);
-            // 修改关联状态。
-            {
+            if (ref != null) {
+                // 修改关联状态。
                 RealExamFault f1 = realExamFaultService.selectRealExamFaultByRefId(ref.getRefId());
-                f1.setRefState(RealExamFault.REF_STATE_LOOP_READ);
+                f1.setRefState(RealExamFault.State.LOOP_READ);
                 realExamFaultService.updateRealExamFault(f1);
+
+                //
+                {
+                    boolean allWritten = realExamFaultService.isAllType2YesStateEqualWritten(f1.getExamId());
+                    if (allWritten) {
+                        // 如果全部下发完毕 修改RealExam状态
+                        l.info("allWritten id = {}", f1.getExamId());
+                        realExamService.updateOneState(f1.getExamId(), RealExam.State.SIM_PREPARE_OK);
+                    }
+                }
+            } else {
+                l.info("ref is null");
             }
         } catch (IOException e) {
-            throw new RuntimeException(e);
+            catchException(e);
         }
     }
 
 
     public void readOneFaultResistance(Sim s, RealExamFault reF, Fault f) {
+        l.info("readOneFaultResistance");
         try {
             SimMsg sm = new SimMsg();
             String sendMsg = buildSendMsgReadFaultResistance(s.getSimNum(), "03");
@@ -316,7 +379,7 @@ public class CommSendService {
             sm.setReceiveMsg(receiveMsg);
             simReceiveService.setFaultAnswerValue(sm, s, reF, f);
         } catch (IOException e) {
-            throw new RuntimeException(e);
+            catchException(e);
         }
     }
 
@@ -335,7 +398,7 @@ public class CommSendService {
                 send(sendMsg, null);
             }
         } catch (IOException e) {
-            throw new RuntimeException(e);
+            catchException(e);
         }
     }
 
@@ -423,8 +486,10 @@ public class CommSendService {
     public synchronized String send(final String sendMsg, final Sim s) throws IOException {
         l.info("sendMsg = " + sendMsg);
         String receiveMsg = null;
-        if (cachedSocket == null) {
+        if (!isSocketOk()) {
             openSocket();
+            l.warn("socket not open!");
+            return "";
         }
         InputStream is = cachedSocket.getInputStream();
         OutputStream os = cachedSocket.getOutputStream();
@@ -452,12 +517,22 @@ public class CommSendService {
     }
 
     public void openSocket() throws IOException {
-
-        if (cachedSocket == null) {
+        if (!isSocketOk()) {
+            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
+            l.info("openSocket cachedSocket is null");
             cachedSocket = new Socket(IP, PORT);
-            // setSoTimeout
-            // cachedSocket.setSoTimeout(1000);
+            // setSoTimeout todo:????
+            // cachedSocket.setSoTimeout(2000);
+        } else {
+            l.info("openSocket cachedSocket is ok");
+        }
+    }
+
+    public boolean isSocketOk() {
+        if (cachedSocket != null && cachedSocket.isConnected()) {
+            return true;
         }
+        return false;
     }
 
     /**
@@ -527,4 +602,19 @@ public class CommSendService {
         }
         return true;
     }
+
+    private void catchException(IOException e) {
+        l.warn("catchException", e);
+
+        // e.printStackTrace();
+        // throw new RuntimeException(e);
+        {
+            clearListFaultLocked = false;
+        }
+        try {
+            closeSocket();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
 }

+ 43 - 11
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamFaultService.java

@@ -1,10 +1,14 @@
 package com.ruoyi.sim.service.impl;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.sim.domain.vo.RealExamFaultVo;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.ruoyi.sim.mapper.RealExamFaultMapper;
@@ -20,6 +24,8 @@ import com.ruoyi.sim.domain.RealExamFault;
 public class RealExamFaultService {
     @Autowired
     private RealExamFaultMapper realExamFaultMapper;
+    @Autowired
+    private FaultService faultService;
 
     /**
      * 查询考试故障关联
@@ -96,32 +102,32 @@ public class RealExamFaultService {
     public List<RealExamFault> listAllType2InitStateByExamId(Long examId) {
         RealExamFault q = new RealExamFault();
         q.setExamId(examId);
-        q.setRefType(RealExamFault.REF_TYPE_2);
-        q.setRefState(RealExamFault.REF_STATE_INIT);
+        q.setRefType(RealExamFault.Type.TYPE_2);
+        q.setRefState(RealExamFault.State.INIT);
         return realExamFaultMapper.selectRealExamFaultList(q);
     }
 
     public List<RealExamFault> listAllType2YesLoopReadState(Long examId) {
         RealExamFault q = new RealExamFault();
         q.setExamId(examId);
-        q.setRefType(RealExamFault.REF_TYPE_2);
-        q.setRefState(RealExamFault.REF_STATE_LOOP_READ);
-        q.setFlag(RealExamFault.FLAG_YES);
+        q.setRefType(RealExamFault.Type.TYPE_2);
+        q.setRefState(RealExamFault.State.LOOP_READ);
+        q.setFlag(RealExamFault.Flag.YES);
         return realExamFaultMapper.selectRealExamFaultList(q);
     }
 
     public List<RealExamFault> listAllType2Yes(Long examId) {
         RealExamFault q = new RealExamFault();
         q.setExamId(examId);
-        q.setRefType(RealExamFault.REF_TYPE_2);
-        q.setFlag(RealExamFault.FLAG_YES);
+        q.setRefType(RealExamFault.Type.TYPE_2);
+        q.setFlag(RealExamFault.Flag.YES);
         return realExamFaultMapper.selectRealExamFaultList(q);
     }
 
     public boolean isAllType2YesStateEqualWritten(long examId) {
         List<RealExamFault> list = listAllType2Yes(examId);
         for (RealExamFault ref : list) {
-            if (ref.getRefState() != RealExamFault.REF_STATE_WRITTEN) {
+            if (ref.getRefState() != RealExamFault.State.WRITTEN) {
                 return false;
             }
         }
@@ -136,6 +142,14 @@ public class RealExamFaultService {
         return state.equals(f.getRefState());
     }
 
+    public void calculateMinusByRealExamId(long realExamId) {
+        RealExamFault q = new RealExamFault();
+        q.setExamId(realExamId);
+        selectRealExamFaultList(q).forEach(ref -> {
+            calculateMinus(ref.getRefId());
+        });
+    }
+
     /**
      * 计算减分。
      *
@@ -146,13 +160,13 @@ public class RealExamFaultService {
         if (f == null) {
             throw new RuntimeException("calculateMinus");
         }
-        if (!RealExamFault.FLAG_YES.equals(f.getFlag())) {
+        if (!RealExamFault.Flag.YES.equals(f.getFlag())) {
             throw new RuntimeException("calculateMinus");
         }
         int minus = 0;
-        if (RealExamFault.REF_TYPE_1.equals(f.getRefType())) {
+        if (RealExamFault.Type.TYPE_1.equals(f.getRefType())) {
             // todo:选择题的算减分
-        } else if (RealExamFault.REF_TYPE_2.equals(f.getRefType())) {
+        } else if (RealExamFault.Type.TYPE_2.equals(f.getRefType())) {
             // 模拟器故障的算减分
             if (!f.getChoiceQuestionValue().equals(f.getChoiceAnswerValue())) {
                 // 扣0分
@@ -166,4 +180,22 @@ public class RealExamFaultService {
         // 更新减分数据。
         updateRealExamFault(f);
     }
+
+    public List<RealExamFaultVo> getReportListPart1(Long examId) {
+        RealExamFault q = new RealExamFault();
+        q.setExamId(examId);
+        q.setRefType(RealExamFault.Type.TYPE_2);
+        List<RealExamFault> list1 = selectRealExamFaultList(q);
+        List<RealExamFaultVo> list2 = new ArrayList<>();
+        list1.forEach(ref -> {
+            RealExamFaultVo vo = new RealExamFaultVo();
+            BeanUtils.copyProperties(ref, vo);
+            vo.setAnswerRight(StringUtils.equals(ref.getSimFaultQuestionValue(), ref.getSimFaultAnswerValue()));
+            vo.setFault(faultService.selectFaultByFaultId(ref.getFaultId()));
+            list2.add(vo);
+        });
+        return list2;
+    }
+
+
 }

+ 92 - 38
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamService.java

@@ -1,14 +1,17 @@
 package com.ruoyi.sim.service.impl;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.sim.domain.*;
+import com.ruoyi.sim.domain.vo.StudentRealExamIngVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamPostVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamPreVo;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import com.ruoyi.sim.mapper.RealExamMapper;
 import org.springframework.transaction.annotation.Transactional;
@@ -23,6 +26,8 @@ import org.springframework.transaction.annotation.Transactional;
 public class RealExamService {
     @Autowired
     private RealExamMapper realExamMapper;
+    @Autowired
+    private CommReceiveService commReceiveService;
 
     /**
      * 查询考试
@@ -100,20 +105,27 @@ public class RealExamService {
     @Autowired
     private CommSendService commSendService;
 
-    public List<RealExam> listAllStatus(String examStatus) {
+    public List<RealExam> listAllByStatus(String state) {
         RealExam q = new RealExam();
-        q.setExamStatus(examStatus);
+        q.setExamStatus(state);
         return selectRealExamList(q);
     }
 
+    /**
+     * 交卷自动修改关联状态
+     *
+     * @param id
+     * @param state
+     * @return
+     */
     @Transactional
-    public int updateOneState(RealExam re, final String state) {
-        RealExam q = selectRealExamByExamId(re.getExamId());
-        if (state.equals(RealExam.STATE_SUBMITTED)) {
+    public int updateOneState(long id, final String state) {
+        RealExam q = selectRealExamByExamId(id);
+        if (RealExam.State.SUBMITTED.equals(state)) {
             // 关联故障list同步锁死。
             realExamFaultService.listAllType2YesLoopReadState(q.getExamId())
                     .forEach(ref -> {
-                        ref.setRefState(RealExamFault.REF_STATE_FINISH);
+                        ref.setRefState(RealExamFault.State.FINISH);
                         realExamFaultService.updateRealExamFault(ref);
                     });
         }
@@ -126,17 +138,21 @@ public class RealExamService {
      *
      * @param realExamId
      */
-    public void calculate(Long realExamId) {
+    public void calculateScore(Long realExamId) {
+        updateOneState(realExamId, RealExam.State.CALCULATING_SCORE);
         int minus = 0;
-        RealExam re = realExamMapper.selectRealExamByExamId(realExamId);
-
-        // 排除故障部分
-
-
-        // 维修报告选择部分
-
+        // 排除故障部分 + 维修报告选择部分
+        realExamFaultService.calculateMinusByRealExamId(realExamId);
         // 超时部分
         int timeoutMinute = 0;
+        //
+        updateOneState(realExamId, RealExam.State.GOT_REPORT);
+    }
+
+    // todo:
+    // @Async
+    public void calculateScoreAsync(Long realExamId) {
+        calculateScore(realExamId);
     }
 
     /**
@@ -144,8 +160,13 @@ public class RealExamService {
      *
      * @return
      */
-    public AjaxResult studentEnterRealExam() {
-        RealExam re = null;
+    public AjaxResult studentEnterRealExam(Long realExamId) {
+        RealExam re = selectRealExamByExamId(realExamId);
+        if (re == null) {
+            AjaxResult.error("realExamId error!");
+        }
+        // todo:应该在登录位置实现
+        updateOneState(realExamId, RealExam.State.LOGGED_IN);
         return AjaxResult.success(re);
     }
 
@@ -155,7 +176,7 @@ public class RealExamService {
      * @param realExamId
      * @return
      */
-    public StudentRealExamPreVo studentPrepareRealExam(Long realExamId) {
+    public AjaxResult studentLoopPrepareRealExam(Long realExamId) {
         // check
         if (realExamId == null || realExamId == 0) {
             // todo:
@@ -178,20 +199,21 @@ public class RealExamService {
         // check student
         Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
         // check seat
-        StudentRealExamPreVo v = new StudentRealExamPreVo();
-        v.setRealExam(re);
-        v.setRealExamCollection(collection);
-        v.setSim(sim);
-        v.setStudent(student);
-        v.setSeat(seat);
-        boolean next = studentPrepareRealExamCheck(v);
-        v.setNext(next);
+        StudentRealExamPreVo vo = new StudentRealExamPreVo();
+        vo.setRealExam(re);
+        vo.setRealExamCollection(collection);
+        vo.setSim(sim);
+        vo.setStudent(student);
+        vo.setSeat(seat);
+        // todo:多人请求同时进入的问题
+        boolean next = studentPrepareRealExamCheck(vo);
+        vo.setNext(next);
         if (!next) {
             // 执行模拟器通信,让模拟器准备好。
-            // async execute.
+            // 异步执行
             commSendService.clearListFaultByRealExamAsync(re);
         }
-        return v;
+        return AjaxResult.success(vo);
     }
 
     public boolean studentPrepareRealExamCheck(StudentRealExamPreVo v) {
@@ -208,9 +230,11 @@ public class RealExamService {
         //
         // todo:??
         // 学生答题中可以再次进入。
-        if (v.getRealExam().getExamStatus() == RealExam.STATE_SIM_PREPARE_OK ||
-                v.getRealExam().getExamStatus() == RealExam.STATE_ANSWERING ||
-                v.getSim().getSimState() == Sim.STATE_ONLINE
+        String examStatus = v.getRealExam().getExamStatus();
+        String simStatus = v.getSim().getSimState();
+        if (RealExam.State.SIM_PREPARE_OK.equals(examStatus) ||
+                RealExam.State.ANSWERING.equals(examStatus) ||
+                Sim.State.ONLINE.equals(simStatus)
         ) {
             return true;
         }
@@ -218,36 +242,66 @@ public class RealExamService {
     }
 
     /**
+     * [学生]开始考试
+     *
+     * @param realExamId
+     * @return
+     */
+    public AjaxResult studentStartRealExam(Long realExamId) {
+        RealExam re = selectRealExamByExamId(realExamId);
+        re.setExamStatus(RealExam.State.ANSWERING);
+        re.setStartTime(DateUtils.getNowDate());
+        updateRealExam(re);
+        return AjaxResult.success(re);
+    }
+
+    /**
      * [轮询][学生]正在考试界面。
      *
      * @param realExamId
      * @return
      */
-    public StudentRealExamPreVo studentIngRealExam(Long realExamId) {
-        return null;
+    public AjaxResult studentLoopAnsweringRealExam(Long realExamId) {
+        RealExam re = selectRealExamByExamId(realExamId);
+        StudentRealExamIngVo vo = new StudentRealExamIngVo();
+        vo.setRealExam(re);
+        long remaining = DateUtils.getNowDate().getTime() - re.getStartTime().getTime();
+        vo.setRemainingMilliseconds(remaining);
+        vo.setCompulsiveSubmit(remaining >= RealExam.EXAM_TIMEOUT_LIMIT);
+        return AjaxResult.success(vo);
     }
 
     /**
      * [学生]交卷
      *
-     * @param realExamId
+     * @param examId
      * @return
      */
-    public AjaxResult studentSubmitRealExam(Long realExamId) {
+    public AjaxResult studentSubmitRealExam(Long examId) {
         // 最后检查一下模拟器状态。
-        
+
         // 最后读取一下模拟器电阻值。
-        return AjaxResult.success();
+        // todo:
+        RealExam re1 = selectRealExamByExamId(examId);
+        commSendService.readOneExamAtLast(re1);
+        calculateScoreAsync(examId);
+        return AjaxResult.success(re1);
     }
 
     /**
      * [轮询][学生]结束考试界面。
      *
-     * @param realExamId
+     * @param examId
      * @return
      */
-    public AjaxResult studentPostRealExam(Long realExamId) {
+    public AjaxResult studentLoopPostRealExam(Long examId) {
+        RealExam re = selectRealExamByExamId(examId);
         StudentRealExamPostVo vo = new StudentRealExamPostVo();
+        {
+
+        }
+        vo.setRealExam(re);
+        vo.setListPart1(realExamFaultService.getReportListPart1(examId));
         return AjaxResult.success(vo);
     }
 }

+ 21 - 9
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SimService.java

@@ -88,17 +88,29 @@ public class SimService {
 
     // -------------------------------- tom add  --------------------------------
 
+    public List<Sim> listAll() {
+        return selectSimList(new Sim());
+    }
+
     public List<Sim> listAllEnable() {
-        List<Sim> list = selectSimList(new Sim());
         List<Sim> listR = new ArrayList<Sim>();
-        for (Sim o : list) {
-            if (!Sim.STATE_DISABLE.equals(o.getSimState())) {
-                listR.add(o);
-            }
-        }
+        listAll()
+                .stream()
+                .filter(Objects::nonNull)
+                .filter(s -> !Sim.State.DISABLE.equals(s.getSimState()))
+                .forEach(listR::add);
         return listR;
     }
 
+    public int updateAllEnableState(String simState) {
+        List<Sim> list = listAllEnable();
+        for (Sim sim : list) {
+            sim.setSimState(simState);
+            simMapper.updateSim(sim);
+        }
+        return list.size();
+    }
+
     public List<String> listSimTypes() {
         return Sim.TYPE_SET.stream().toList();
     }
@@ -138,7 +150,7 @@ public class SimService {
     }
 
     public boolean isSimDisable(Long simId) {
-        return isSimStateBySimId(simId, Sim.STATE_DISABLE);
+        return isSimStateBySimId(simId, Sim.State.DISABLE);
     }
 
     public int updateSimSnBySimId(Long simId, String simSn) {
@@ -163,8 +175,8 @@ public class SimService {
             if (o == null) {
                 continue;
             }
-            if (!Objects.equals(o.getSimState(), Sim.STATE_DISABLE)) {
-                o.setSimState(Sim.STATE_ENABLE_INIT);
+            if (!Objects.equals(o.getSimState(), Sim.State.DISABLE)) {
+                o.setSimState(Sim.State.ENABLE_INIT);
             }
             updateSim(o);
             count = count + 1;