6 Коміти 3ccb629559 ... a42d9f13d2

Автор SHA1 Опис Дата
  tom a42d9f13d2 提交新485布局变更。 10 місяців тому
  tom af2559d8f1 多socket连接方案改版。 10 місяців тому
  tom b920d62c46 保存old socket。 10 місяців тому
  tom 4ce4c5b98c 更改seat表和sim表数据。 10 місяців тому
  tom ce26747ef5 暂时设置间隔时间统一为300ms。 10 місяців тому
  tom 23182ec2e4 处理报文前端加00的情况,最多可能加5组00。调试模式下,两个清除按钮,都删除DebugFault表所有数据。 10 місяців тому
31 змінених файлів з 1388 додано та 716 видалено
  1. 16 15
      pla-sim/01_SQL/02_table/mx_seat.sql
  2. 32 32
      pla-sim/01_SQL/02_table/mx_sim.sql
  3. 16 15
      pla-sim/01_SQL/02_table/sim_seat.sql
  4. 2 2
      pla-sim/01_SQL/02_table/sim_sim.sql
  5. 2 1
      ruoyi-admin/src/main/resources/application-druid.yml
  6. 6 3
      ruoyi-admin/src/main/resources/application.yml
  7. 24 4
      ruoyi-sim/src/main/java/com/ruoyi/sim/constant/CommConst.java
  8. 41 26
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/HardwareCommDebugController.java
  9. 8 13
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/RealExamController.java
  10. 18 10
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/SeatController.java
  11. 36 37
      ruoyi-sim/src/main/java/com/ruoyi/sim/controller/TestIotController.java
  12. 17 13
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/RealExam.java
  13. 46 15
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Seat.java
  14. 4 0
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Sim.java
  15. 40 27
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/SimMsg.java
  16. 40 3
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SimSocketVo.java
  17. 8 8
      ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SocketWrapValue.java
  18. 23 9
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommBuildService.java
  19. 301 0
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommCheckService.java
  20. 28 0
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommParseUtils.java
  21. 27 88
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommReceiveService.java
  22. 277 291
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java
  23. 0 2
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/DebugFaultService.java
  24. 20 4
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/FailedCountService.java
  25. 12 6
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamCollectionService.java
  26. 110 23
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamService.java
  27. 111 1
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SeatService.java
  28. 18 8
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SimService.java
  29. 87 58
      ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketService.java
  30. 7 1
      ruoyi-sim/src/main/resources/mapper/sim/RealExamMapper.xml
  31. 11 1
      ruoyi-sim/src/main/resources/mapper/sim/SeatMapper.xml

+ 16 - 15
pla-sim/01_SQL/02_table/mx_seat.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:20
+ Date: 12/03/2025 16:30:24
 */
 
 SET NAMES utf8mb4;
@@ -24,11 +24,12 @@ DROP TABLE IF EXISTS `mx_seat`;
 CREATE TABLE `mx_seat`  (
   `seat_id` bigint NOT NULL AUTO_INCREMENT COMMENT '座ID',
   `seat_num` int NOT NULL DEFAULT 0 COMMENT '座号',
-  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[电脑]绑定的[IP地址]',
-  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[RS485]绑定的[IP地址]',
-  `seat_rs485_port` int NOT NULL COMMENT '学员端座次上[RS485]绑定的[端口]',
+  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[电脑]绑定的[IP地址]',
+  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[RS485]绑定的[IP地址]',
+  `seat_rs485_port` int NOT NULL COMMENT '[RS485]绑定的[端口]',
+  `seat_rs485_socket_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用',
   `current_user_id` bigint NOT NULL DEFAULT 0 COMMENT '当前座上学员/用户ID',
-  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '学员端座次上模拟器的ID:[0]没有连接任何模拟器',
+  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器',
   `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
   `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
   `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
@@ -40,15 +41,15 @@ CREATE TABLE `mx_seat`  (
 -- ----------------------------
 -- Records of mx_seat
 -- ----------------------------
-INSERT INTO `mx_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, 0, 0, NULL, NULL, NULL, NULL, '座号01');
-INSERT INTO `mx_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, 0, 0, NULL, NULL, NULL, NULL, '座号02');
-INSERT INTO `mx_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, 0, 0, NULL, NULL, NULL, NULL, '座号03');
-INSERT INTO `mx_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, 0, 0, NULL, NULL, NULL, NULL, '座号04');
-INSERT INTO `mx_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, 0, 0, NULL, NULL, NULL, NULL, '座号05');
-INSERT INTO `mx_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, 0, 0, NULL, NULL, NULL, NULL, '座号06');
-INSERT INTO `mx_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, 0, 0, NULL, NULL, NULL, NULL, '座号07');
-INSERT INTO `mx_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, 0, 0, NULL, NULL, NULL, NULL, '座号08');
-INSERT INTO `mx_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, 0, 0, NULL, NULL, NULL, NULL, '座号09');
-INSERT INTO `mx_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, 0, 0, NULL, NULL, NULL, NULL, '座号10');
+INSERT INTO `mx_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, '', 0, 0, NULL, NULL, NULL, NULL, '座号01');
+INSERT INTO `mx_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, '', 0, 0, NULL, NULL, NULL, NULL, '座号02');
+INSERT INTO `mx_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, '', 0, 0, NULL, NULL, NULL, NULL, '座号03');
+INSERT INTO `mx_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, '', 0, 0, NULL, NULL, NULL, NULL, '座号04');
+INSERT INTO `mx_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, '', 0, 0, NULL, NULL, NULL, NULL, '座号05');
+INSERT INTO `mx_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, '', 0, 0, NULL, NULL, NULL, NULL, '座号06');
+INSERT INTO `mx_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, '', 0, 0, NULL, NULL, NULL, NULL, '座号07');
+INSERT INTO `mx_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, '', 0, 0, NULL, NULL, NULL, NULL, '座号08');
+INSERT INTO `mx_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, '', 0, 0, NULL, NULL, NULL, NULL, '座号09');
+INSERT INTO `mx_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, '', 0, 0, NULL, NULL, NULL, NULL, '座号10');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 32 - 32
pla-sim/01_SQL/02_table/mx_sim.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:15
+ Date: 12/03/2025 16:30:14
 */
 
 SET NAMES utf8mb4;
@@ -25,7 +25,7 @@ CREATE TABLE `mx_sim`  (
   `sim_id` bigint NOT NULL AUTO_INCREMENT COMMENT '模拟器ID',
   `seat_id` bigint NOT NULL DEFAULT 0 COMMENT '[废弃]座ID',
   `sim_type` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器类型',
-  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:可用初始化,[1]:在线,[2]:模拟器离线,[3]:网关离线,[4]:硬件故障异常,[5]:手动禁用',
+  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:初始化,[1]:在线,[2]:离线,[3]:[废弃]网关离线,[4]:[废弃]硬件故障异常,[5]:手动禁用',
   `sim_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器序列号',
   `sim_num` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器设备通信编号-站ID',
   `last_sent_time` datetime NULL DEFAULT NULL COMMENT '最后一次成功发送报文时间',
@@ -41,35 +41,35 @@ CREATE TABLE `mx_sim`  (
 -- ----------------------------
 -- Records of mx_sim
 -- ----------------------------
-INSERT INTO `mx_sim` VALUES (1, 1, '0001', '2', '', '01', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (2, 2, '0001', '2', '', '02', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (3, 3, '0001', '2', '', '03', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (4, 4, '0001', '2', '', '04', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:50', '');
-INSERT INTO `mx_sim` VALUES (5, 5, '0001', '2', '', '05', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (6, 6, '0001', '2', '', '06', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (7, 7, '0001', '2', '', '07', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (8, 8, '0001', '2', '', '08', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:31', '');
-INSERT INTO `mx_sim` VALUES (9, 9, '0001', '2', '', '09', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (10, 10, '0001', '2', '', '10', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (81, 1, '0002', '2', '', '51', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:31', '');
-INSERT INTO `mx_sim` VALUES (82, 2, '0002', '2', '', '52', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (83, 3, '0002', '2', '', '53', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:51', '');
-INSERT INTO `mx_sim` VALUES (84, 4, '0002', '2', '', '54', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (85, 5, '0002', '2', '', '55', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (86, 6, '0002', '2', '', '56', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (87, 7, '0002', '2', '', '57', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (88, 8, '0002', '2', '', '58', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (89, 9, '0002', '2', '', '59', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (90, 10, '0002', '2', '', '5A', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (161, 1, '0003', '2', '', 'A1', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (162, 2, '0003', '2', '', 'A2', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (163, 3, '0003', '2', '', 'A3', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:52', '');
-INSERT INTO `mx_sim` VALUES (164, 4, '0003', '2', '', 'A4', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (165, 5, '0003', '2', '', 'A5', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (166, 6, '0003', '2', '', 'A6', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (167, 7, '0003', '0', '', 'A7', NULL, NULL, NULL, NULL, NULL, NULL, '');
-INSERT INTO `mx_sim` VALUES (168, 8, '0003', '2', '', 'A8', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (169, 9, '0003', '2', '', 'A9', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
-INSERT INTO `mx_sim` VALUES (170, 10, '0003', '2', '', 'AA', NULL, NULL, NULL, NULL, NULL, '2025-03-11 17:08:53', '');
+INSERT INTO `mx_sim` VALUES (1, 1, '0001', '2', '', '01', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (2, 2, '0001', '2', '', '02', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (3, 3, '0001', '2', '', '03', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (4, 4, '0001', '2', '', '04', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:50', '');
+INSERT INTO `mx_sim` VALUES (5, 5, '0001', '2', '', '05', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (6, 6, '0001', '2', '', '06', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (7, 7, '0001', '2', '', '07', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (8, 8, '0001', '2', '', '08', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:31', '');
+INSERT INTO `mx_sim` VALUES (9, 9, '0001', '2', '', '09', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (10, 10, '0001', '2', '', '10', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (81, 1, '0002', '2', '', '51', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:31', '');
+INSERT INTO `mx_sim` VALUES (82, 2, '0002', '2', '', '52', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (83, 3, '0002', '2', '', '53', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:51', '');
+INSERT INTO `mx_sim` VALUES (84, 4, '0002', '2', '', '54', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (85, 5, '0002', '2', '', '55', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (86, 6, '0002', '2', '', '56', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (87, 7, '0002', '2', '', '57', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (88, 8, '0002', '2', '', '58', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (89, 9, '0002', '2', '', '59', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (90, 10, '0002', '2', '', '5A', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (161, 1, '0003', '2', '', 'A1', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:22', '');
+INSERT INTO `mx_sim` VALUES (162, 2, '0003', '2', '', 'A2', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (163, 3, '0003', '2', '', 'A3', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:52', '');
+INSERT INTO `mx_sim` VALUES (164, 4, '0003', '2', '', 'A4', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (165, 5, '0003', '2', '', 'A5', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (166, 6, '0003', '2', '', 'A6', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (167, 7, '0003', '2', '', 'A7', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:23', '');
+INSERT INTO `mx_sim` VALUES (168, 8, '0003', '2', '', 'A8', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (169, 9, '0003', '2', '', 'A9', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
+INSERT INTO `mx_sim` VALUES (170, 10, '0003', '2', '', 'AA', NULL, NULL, NULL, NULL, NULL, '2025-03-12 16:29:53', '');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 16 - 15
pla-sim/01_SQL/02_table/sim_seat.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:06
+ Date: 12/03/2025 16:29:56
 */
 
 SET NAMES utf8mb4;
@@ -24,11 +24,12 @@ DROP TABLE IF EXISTS `sim_seat`;
 CREATE TABLE `sim_seat`  (
   `seat_id` bigint NOT NULL AUTO_INCREMENT COMMENT '座ID',
   `seat_num` int NOT NULL DEFAULT 0 COMMENT '座号',
-  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[电脑]绑定的[IP地址]',
-  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '学员端座次上[RS485]绑定的[IP地址]',
-  `seat_rs485_port` int NOT NULL COMMENT '学员端座次上[RS485]绑定的[端口]',
+  `seat_bind_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[电脑]绑定的[IP地址]',
+  `seat_rs485_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '[RS485]绑定的[IP地址]',
+  `seat_rs485_port` int NOT NULL COMMENT '[RS485]绑定的[端口]',
+  `seat_rs485_socket_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用',
   `current_user_id` bigint NOT NULL DEFAULT 0 COMMENT '当前座上学员/用户ID',
-  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '学员端座次上模拟器的ID:[0]没有连接任何模拟器',
+  `current_sim_id` bigint NOT NULL DEFAULT 0 COMMENT '模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器',
   `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
   `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
   `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者',
@@ -40,15 +41,15 @@ CREATE TABLE `sim_seat`  (
 -- ----------------------------
 -- Records of sim_seat
 -- ----------------------------
-INSERT INTO `sim_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, 0, 0, NULL, NULL, NULL, NULL, '座号01');
-INSERT INTO `sim_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, 0, 0, NULL, NULL, NULL, NULL, '座号02');
-INSERT INTO `sim_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, 0, 0, NULL, NULL, NULL, NULL, '座号03');
-INSERT INTO `sim_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, 0, 0, NULL, NULL, NULL, NULL, '座号04');
-INSERT INTO `sim_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, 0, 0, NULL, NULL, NULL, NULL, '座号05');
-INSERT INTO `sim_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, 0, 0, NULL, NULL, NULL, NULL, '座号06');
-INSERT INTO `sim_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, 0, 0, NULL, NULL, NULL, NULL, '座号07');
-INSERT INTO `sim_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, 0, 0, NULL, NULL, NULL, NULL, '座号08');
-INSERT INTO `sim_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, 0, 0, NULL, NULL, NULL, NULL, '座号09');
-INSERT INTO `sim_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, 0, 0, NULL, NULL, NULL, NULL, '座号10');
+INSERT INTO `sim_seat` VALUES (1, 1, '192.168.1.101', '192.168.1.201', 10001, '0', 0, 0, NULL, NULL, NULL, NULL, '座号01');
+INSERT INTO `sim_seat` VALUES (2, 2, '192.168.1.102', '192.168.1.201', 10002, '0', 0, 0, NULL, NULL, NULL, NULL, '座号02');
+INSERT INTO `sim_seat` VALUES (3, 3, '192.168.1.103', '192.168.1.201', 10003, '0', 0, 0, NULL, NULL, NULL, NULL, '座号03');
+INSERT INTO `sim_seat` VALUES (4, 4, '192.168.1.104', '192.168.1.201', 10004, '0', 0, 0, NULL, NULL, NULL, NULL, '座号04');
+INSERT INTO `sim_seat` VALUES (5, 5, '192.168.1.105', '192.168.1.201', 10008, '0', 0, 0, NULL, NULL, NULL, NULL, '座号05');
+INSERT INTO `sim_seat` VALUES (6, 6, '192.168.1.106', '192.168.1.202', 11001, '0', 0, 0, NULL, NULL, NULL, NULL, '座号06');
+INSERT INTO `sim_seat` VALUES (7, 7, '192.168.1.107', '192.168.1.202', 11002, '0', 0, 0, NULL, NULL, NULL, NULL, '座号07');
+INSERT INTO `sim_seat` VALUES (8, 8, '192.168.1.108', '192.168.1.202', 11003, '0', 0, 0, NULL, NULL, NULL, NULL, '座号08');
+INSERT INTO `sim_seat` VALUES (9, 9, '192.168.1.109', '192.168.1.202', 11004, '0', 0, 0, NULL, NULL, NULL, NULL, '座号09');
+INSERT INTO `sim_seat` VALUES (10, 10, '192.168.1.110', '192.168.1.202', 11008, '0', 0, 0, NULL, NULL, NULL, NULL, '座号10');
 
 SET FOREIGN_KEY_CHECKS = 1;

+ 2 - 2
pla-sim/01_SQL/02_table/sim_sim.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80020 (8.0.20)
  File Encoding         : 65001
 
- Date: 11/03/2025 17:09:00
+ Date: 12/03/2025 16:29:47
 */
 
 SET NAMES utf8mb4;
@@ -25,7 +25,7 @@ CREATE TABLE `sim_sim`  (
   `sim_id` bigint NOT NULL AUTO_INCREMENT COMMENT '模拟器ID',
   `seat_id` bigint NOT NULL DEFAULT 0 COMMENT '[废弃]座ID',
   `sim_type` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器类型',
-  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:可用初始化,[1]:在线,[2]:模拟器离线,[3]:网关离线,[4]:硬件故障异常,[5]:手动禁用',
+  `sim_state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '模拟器状态:[0]:初始化,[1]:在线,[2]:离线,[3]:[废弃]网关离线,[4]:[废弃]硬件故障异常,[5]:手动禁用',
   `sim_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器序列号',
   `sim_num` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模拟器设备通信编号-站ID',
   `last_sent_time` datetime NULL DEFAULT NULL COMMENT '最后一次成功发送报文时间',

+ 2 - 1
ruoyi-admin/src/main/resources/application-druid.yml

@@ -18,7 +18,8 @@ spring:
                 # password: 8M6ahN7BXsXXDccR
 
                 # server-现场实验室
-                url: jdbc:mysql://192.168.1.61:4886/pla-chem-sim-dev-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                # url: jdbc:mysql://192.168.1.61:4886/pla-chem-sim-dev-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                url: jdbc:mysql://192.168.1.61:4886/pla-chem-sim-dev-2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                 username: root
                 password: 7ZNo#9Arn3DFBN8N
 

+ 6 - 3
ruoyi-admin/src/main/resources/application.yml

@@ -86,7 +86,8 @@ spring:
     # server-阿里云47服务器内网
     # database: 2
     # server-其他
-    database: 0
+    # database: 0
+    database: 2
     # 密码
     # server-阿里云47
     # password: Z*eQ8xXK7ryYynFv
@@ -151,9 +152,11 @@ xss:
 # com.ruoyi.sim.config.SimConfig
 sim-module-config:
   # 123.112.16.165
-  routerIp: 192.168.1.199
+  # routerIp: 221.218.212.74
+  routerIp: 192.168.1.1
   # 123.112.16.165
-  rs485Ip: 192.168.1.199
+  # rs485Ip: 221.218.212.74
+  rs485Ip: 127.0.0.1
   #
   rs485Port: 8899
   #

+ 24 - 4
ruoyi-sim/src/main/java/com/ruoyi/sim/constant/CommConst.java

@@ -1,5 +1,10 @@
 package com.ruoyi.sim.constant;
 
+/**
+ * 雷电扩展坞MAC
+ * 192.168.1.153
+ * 00-E0-4C-68-01-AD
+ */
 public interface CommConst {
 
     String PREFIX = "AA";
@@ -37,6 +42,8 @@ public interface CommConst {
      * 设备类型读取
      */
     String CMD_READ_TYPE = "03";
+
+    String BLANK_SIM_NUM = "00";
     /**
      * 状态读取 RESISTANCE电阻值
      */
@@ -67,18 +74,23 @@ public interface CommConst {
 
     /**
      * 请求间隔睡眠时间-long
+     * default:200L
      */
-    Long SLEEP_LONG = 200L;
+    Long SLEEP_LONG = 1000L;
     /**
      * 请求间隔睡眠时间-mid
+     * default:100L
      */
-    Long SLEEP_MID = 100L;
+    Long SLEEP_MID = 1000L;
     /**
      * 请求间隔睡眠时间-short
+     * default:64L
      */
-    Long SLEEP_SHORT = 64L;
+    Long SLEEP_SHORT = 1000L;
+
+    int SOCKET_TIME_OUT = 50;
 
-    int SOCKET_TIME_OUT = 200;
+    int PING_TIME_OUT = 1000;
 
     String[] TYPE_1_BIND_MSG = new String[]{"01", "02", "03", "04", "05",
             "06", "07", "08", "09", "0A"};
@@ -95,6 +107,7 @@ public interface CommConst {
     int RETRY_COUNT_WRITE_ONE_FAULT = 2;
     int RETRY_COUNT_READ_ONE_RESISTANCE = 4;
     int RETRY_COUNT_QUERY_SN_IMPORTANT = 1;
+    int RETRY_COUNT_WHICH_SIM_IMPORTANT = 3;
     int RETRY_COUNT_0 = 0;
 
     int OFFLINE_LIMIT = 6;
@@ -105,4 +118,11 @@ public interface CommConst {
 
     Integer PORT_MIN = 1;
     Integer PORT_MAX = 65535;
+
+    public interface COMM_TYPE {
+        Integer SET = 1;
+        Integer CLEAR = 2;
+        Integer READ = 3;
+        Integer WHO = 4;
+    }
 }

+ 41 - 26
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/HardwareCommDebugController.java

@@ -9,17 +9,22 @@ import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
 @RequestMapping("/sim/debug")
 @Api("硬件通信DebugController")
 public class HardwareCommDebugController extends BaseController {
+
     @Autowired
+    @Lazy
     private CommSendService commSendService;
     @Autowired
+    @Lazy
     private CommBuildService commBuildService;
     @Autowired
+    @Lazy
     private ApplicationContext applicationContext;
 
     @GetMapping(value = "/spring-boot-close")
@@ -28,42 +33,42 @@ public class HardwareCommDebugController extends BaseController {
         ((ConfigurableApplicationContext) applicationContext).close();
     }
 
-    @GetMapping(value = "/debugReadSimType/{simNum}")
+    @GetMapping(value = "/debugReadSimType/{seatId}")
     @ApiOperation("debug读取模拟器类型序列号")
-    public AjaxResult debugReadSimType(@PathVariable("simNum") final String simNum) {
-        return success(commSendService.debugReadSimType(simNum));
+    public AjaxResult debugReadSimType(@PathVariable("seatId") final Long seatId) {
+        return success(commSendService.debugReadSimType(seatId));
     }
 
-    @GetMapping(value = "/debugReadOneFaultResistance/{simNum}/{bindHardwareMsg}")
+    @GetMapping(value = "/debugReadOneFaultResistance/{seatId}/{bindHardwareMsg}")
     @ApiOperation("debug读取一个故障位置数据")
-    public AjaxResult debugReadOneFaultResistance(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugReadOneFaultResistance(@PathVariable("seatId") final Long seatId,
                                                   @PathVariable("bindHardwareMsg") final String bindHardwareMsg) {
-        return success(commSendService.debugReadOneFaultResistance(simNum, bindHardwareMsg));
+        return success(commSendService.debugReadOneFaultResistance(seatId, bindHardwareMsg));
     }
 
-    @GetMapping(value = "/debugReadAllFaultResistance/{simNum}")
+    @GetMapping(value = "/debugReadAllFaultResistance/{seatId}")
     @ApiOperation("debug读取全部故障位置数据")
-    public AjaxResult debugReadAllFaultResistance(@PathVariable("simNum") final String simNum) {
-        return success(commSendService.debugReadAllFaultResistance(simNum));
+    public AjaxResult debugReadAllFaultResistance(@PathVariable("seatId") final Long seatId) {
+        return success(commSendService.debugReadAllFaultResistance(seatId));
     }
 
-    @GetMapping(value = "/debugReadAllFaultResistanceBySimNum/{simNum}")
+    @GetMapping(value = "/debugReadAllFaultResistanceBySimNum/{seatId}")
     @ApiOperation("debug通过simNum读取一台模拟器所有故障答题值,保存[debug_fault]表中,类似交卷")
-    public AjaxResult debugReadAllFaultResistanceBySimNum(@PathVariable("simNum") final String simNum) {
-        return commSendService.debugReadAllFaultResistanceBySimNum(simNum);
+    public AjaxResult debugReadAllFaultResistanceBySimNum(@PathVariable("seatId") final Long seatId) {
+        return commSendService.debugReadAllFaultResistanceBySimNum(seatId);
     }
 
-    @GetMapping(value = "/debugClearOneFault/{simNum}/{bindHardwareMsg}")
+    @GetMapping(value = "/debugClearOneFault/{seatId}/{bindHardwareMsg}")
     @ApiOperation("debug清除一个故障")
-    public AjaxResult debugClearOneFault(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugClearOneFault(@PathVariable("seatId") final Long seatId,
                                          @PathVariable("bindHardwareMsg") final String bindHardwareMsg) {
-        return success(commSendService.debugClearOneFault(simNum, bindHardwareMsg));
+        return success(commSendService.debugClearOneFault(seatId, bindHardwareMsg));
     }
 
-    @GetMapping(value = "/debugClearAllFaultBySimNum/{simNum}")
+    @GetMapping(value = "/debugClearAllFaultBySimNum/{seatId}")
     @ApiOperation("debug通过simNum清除一台模拟器所有故障")
-    public AjaxResult debugClearAllFaultBySimNum(@PathVariable("simNum") final String simNum) {
-        return commSendService.debugClearAllFaultBySimNum(simNum);
+    public AjaxResult debugClearAllFaultBySimNum(@PathVariable("seatId") final Long seatId) {
+        return commSendService.debugClearAllFaultBySeatId(seatId);
     }
 
     @GetMapping(value = "/debugClearAllOnlineSimAllFault/")
@@ -74,26 +79,26 @@ public class HardwareCommDebugController extends BaseController {
 
     @GetMapping(value = "/debugWriteOneFault/{simNum}/{bindHardwareMsg}")
     @ApiOperation("debug下发一个故障")
-    public AjaxResult debugWriteOneFault(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugWriteOneFault(@PathVariable("seatId") final Long seatId,
                                          @PathVariable("bindHardwareMsg") final String bindHardwareMsg) {
-        return success(commSendService.debugWriteOneFault(simNum, bindHardwareMsg));
+        return success(commSendService.debugWriteOneFault(seatId, bindHardwareMsg));
     }
 
-    @GetMapping(value = "/debugWriteAllFault/{simNum}")
+    @GetMapping(value = "/debugWriteAllFault/{seatId}")
     @ApiOperation("debug下发所有故障")
-    public AjaxResult debugWriteAllFault(@PathVariable("simNum") final String simNum) {
-        return success(commSendService.debugWriteAllFault(simNum));
+    public AjaxResult debugWriteAllFault(@PathVariable("seatId") final Long seatId) {
+        return success(commSendService.debugWriteAllFault(seatId));
     }
 
     @GetMapping(value = "/debugWriteSelectedFaultBySimNum/{simNum}/{faultIds}")
     @ApiOperation("debug下发所选故障,保存[debug_fault]表中,类似开始考试")
-    public AjaxResult debugWriteSelectedFaultBySimNum(@PathVariable("simNum") final String simNum,
+    public AjaxResult debugWriteSelectedFaultBySimNum(@PathVariable("seatId") final Long seatId,
                                                       @PathVariable("faultIds") final String[] faultIds,
                                                       @RequestParam final Boolean checkReplace) {
-        return commSendService.debugWriteSelectedFaultBySimNum(simNum, faultIds, checkReplace);
+        return commSendService.debugWriteSelectedFaultBySimNum(seatId, faultIds, checkReplace);
     }
-
     @GetMapping(value = "/buildMsg/")
+
     @ApiOperation("buildMsg")
     public AjaxResult buildSendMsg(@RequestParam final String simNum,
                                    @RequestParam final String orn,
@@ -102,4 +107,14 @@ public class HardwareCommDebugController extends BaseController {
                                    @RequestParam final String data) {
         return commBuildService.buildSendMsgAR(simNum, orn, cmd, cmdId, data);
     }
+
+    @ApiOperation("buildMsgAndSend")
+    public AjaxResult buildMsgAndSend(@RequestParam final String simNum,
+                                   @RequestParam final String orn,
+                                   @RequestParam final String cmd,
+                                   @RequestParam final String cmdId,
+                                   @RequestParam final String data) {
+        // todo:
+        return commBuildService.buildSendMsgAR(simNum, orn, cmd, cmdId, data);
+    }
 }

+ 8 - 13
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/RealExamController.java

@@ -3,6 +3,7 @@ package com.ruoyi.sim.controller;
 import java.util.List;
 
 import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.sim.domain.RealExamCollection;
 import com.ruoyi.sim.domain.vo.RealExamVo;
 import com.ruoyi.sim.service.impl.RealExamService;
 import io.swagger.annotations.Api;
@@ -10,13 +11,7 @@ import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
@@ -76,23 +71,23 @@ public class RealExamController extends BaseController {
 
     @GetMapping("/student/exam/start/{examId}")
     @ApiOperation("[学生][正式使用]开始考试")
-    public AjaxResult studentStartRealExam(@PathVariable("examId") Long examId) {
+    public AjaxResult studentStartRealExam(@PathVariable("examId") Long examId, @RequestParam final String ip) {
         l.info("[学生][正式使用]开始考试");
-        return realExamService.studentStartRealExam(examId);
+        return realExamService.studentStartRealExam(examId, ip, RealExamCollection.Type.EXAM);
     }
 
     @GetMapping("/student/exercise/start/{examId}")
     @ApiOperation("[学生][正式使用]开始练习")
-    public AjaxResult studentStartRealExercise(@PathVariable("examId") Long examId) {
+    public AjaxResult studentStartRealExercise(@PathVariable("examId") Long examId, @RequestParam final String ip) {
         l.info("[学生][正式使用]开始练习");
-        return realExamService.studentStartRealExam(examId);
+        return realExamService.studentStartRealExam(examId, ip, RealExamCollection.Type.EXERCISE);
     }
 
     @GetMapping("/student/self-exercise/start/{examId}")
     @ApiOperation("[学生][正式使用]开始自主练习")
-    public AjaxResult studentStartRealSelfExercise(@PathVariable("examId") Long examId) {
+    public AjaxResult studentStartRealSelfExercise(@PathVariable("examId") Long examId, @RequestParam final String ip) {
         l.info("[学生][正式使用]开始自主练习");
-        return realExamService.studentStartRealExam(examId);
+        return realExamService.studentStartRealExam(examId, ip, RealExamCollection.Type.SELF_EXERCISE);
     }
 
     @GetMapping("/student/exam/answering/{examId}")

+ 18 - 10
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/SeatController.java

@@ -53,7 +53,7 @@ public class SeatController extends BaseController {
      */
     // @PreAuthorize("@ss.hasPermi('sim:seat:list')")
     @GetMapping("/listAll")
-    @ApiOperation("[老师][轮询]查询全部座列表")
+    // @ApiOperation("[老师][轮询]查询全部座列表")
     public TableDataInfo listAll() {
         Seat seat = new Seat();
         startPage();
@@ -64,8 +64,8 @@ public class SeatController extends BaseController {
     /**
      * 导出座列表
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:export')")
-    @Log(title = "座", businessType = BusinessType.EXPORT)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:export')")
+    // @Log(title = "座", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(HttpServletResponse response, Seat seat) {
         List<Seat> list = seatService.selectSeatList(seat);
@@ -76,7 +76,7 @@ public class SeatController extends BaseController {
     /**
      * 获取座详细信息
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:query')")
+    // @PreAuthorize("@ss.hasPermi('sim:seat:query')")
     @GetMapping(value = "/{seatId}")
     public AjaxResult getInfo(@PathVariable("seatId") Long seatId) {
         return success(seatService.selectSeatBySeatId(seatId));
@@ -85,8 +85,8 @@ public class SeatController extends BaseController {
     /**
      * 新增座
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:add')")
-    @Log(title = "座", businessType = BusinessType.INSERT)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:add')")
+    // @Log(title = "座", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody Seat seat) {
         return toAjax(seatService.insertSeat(seat));
@@ -95,8 +95,8 @@ public class SeatController extends BaseController {
     /**
      * 修改座
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:edit')")
-    @Log(title = "座", businessType = BusinessType.UPDATE)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:edit')")
+    // @Log(title = "座", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody Seat seat) {
         return toAjax(seatService.updateSeat(seat));
@@ -105,10 +105,18 @@ public class SeatController extends BaseController {
     /**
      * 删除座
      */
-    @PreAuthorize("@ss.hasPermi('sim:seat:remove')")
-    @Log(title = "座", businessType = BusinessType.DELETE)
+    // @PreAuthorize("@ss.hasPermi('sim:seat:remove')")
+    // @Log(title = "座", businessType = BusinessType.DELETE)
     @DeleteMapping("/{seatIds}")
     public AjaxResult remove(@PathVariable Long[] seatIds) {
         return toAjax(seatService.deleteSeatBySeatIds(seatIds));
     }
+
+    // -------------------------------- tom add  --------------------------------
+
+    @GetMapping("/listAllEnable")
+    @ApiOperation("获取所有没有被禁用的座列表")
+    public AjaxResult listAllEnable() {
+        return seatService.listAllEnableAj();
+    }
 }

+ 36 - 37
ruoyi-sim/src/main/java/com/ruoyi/sim/controller/TestIotController.java

@@ -4,13 +4,13 @@ import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.sim.domain.Fault;
 import com.ruoyi.sim.domain.RealExam;
-import com.ruoyi.sim.service.impl.CommSendService;
-import com.ruoyi.sim.service.impl.FaultService;
-import com.ruoyi.sim.service.impl.MajorService;
-import com.ruoyi.sim.service.impl.RealExamService;
+import com.ruoyi.sim.domain.Seat;
+import com.ruoyi.sim.domain.vo.SimSocketVo;
+import com.ruoyi.sim.service.impl.*;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -24,49 +24,35 @@ import java.util.List;
 public class TestIotController extends BaseController {
 
     @Autowired
-    private MajorService majorService;
-    @Autowired
+    @Lazy
     private CommSendService commSendService;
     @Autowired
+    @Lazy
     private RealExamService realExamService;
     @Autowired
+    @Lazy
     private FaultService faultService;
+    @Autowired
+    @Lazy
+    private SocketService socketService;
+    @Autowired
+    @Lazy
+    private CommCheckService commCheckService;
+    @Autowired
+    @Lazy
+    private SeatService seatService;
 
-    @ApiOperation("testIot通信")
+    @ApiOperation("debug666")
     @GetMapping(value = "/{codeId}")
     public AjaxResult testIndex(@PathVariable("codeId") Integer codeId) {
-
         long examId = 2L;
         switch (codeId) {
-            case 1: {
-                commSendService.checkAllSimState();
-            }
-            break;
-            case 2: {
-                // commSnedService.clearOneFault(null, null, null);
-            }
-            break;
-            case 3: {
-                // commSnedService.readOneFaultResistance(null, null);
-            }
-            break;
-            case 4: {
-                // commSnedService.writeOneFault(null, null, null);
-            }
-            break;
-            case 5: {
-//                for (int i = 0; i < 100; i++) {
-//                    commSnedService.readOneFaultResistance(null, null);
-//                }
-            }
-            break;
             case 6: {
                 RealExam re = realExamService.selectRealExamByExamId(1L);
                 commSendService.clearOneSimAllFaultByExam(re);
             }
             break;
             case 10: {
-                commSendService.checkAllSimState();
                 commSendService.clearAll();
                 RealExam re = realExamService.selectRealExamByExamId(1L);
                 commSendService.clearOneSimAllFaultByExam(re);
@@ -82,7 +68,6 @@ public class TestIotController extends BaseController {
                 //
             }
             break;
-
             case 20: {
                 realExamService.studentEnterRealExam(examId);
                 //
@@ -94,7 +79,7 @@ public class TestIotController extends BaseController {
             }
             break;
             case 22: {
-                realExamService.studentStartRealExam(examId);
+                realExamService.studentStartRealExam(examId, null, null);
                 //
             }
             break;
@@ -112,12 +97,26 @@ public class TestIotController extends BaseController {
                 realExamService.studentLoopPostRealExam(examId);
                 //
             }
-            case 66: {
-
+            case 26: {
+                AjaxResult aj = socketService.tryOpenAll();
+                return aj;
+            }
+            case 27: {
+                socketService.closeOne(new SimSocketVo("192.168.1.202", 11001));
+                socketService.closeOne(new SimSocketVo("192.168.1.202", 11008));
+            }
+            case 100: {
+                return socketService.tryOpenAll();
+            }
+            case 101: {
+                Seat seat = seatService.selectSeatBySeatId(10L);
+                return commCheckService.checkOneSeatState(seat, true);
+            }
+            case 102: {
+                return commCheckService.checkAllSeatAndSimState();
             }
-            break;
         }
-        return AjaxResult.success();
+        return AjaxResult.success("ZZZZZZZZZZZZZZZZZZZZ");
     }
 
     @ApiOperation("testIot通信")

+ 17 - 13
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/RealExam.java

@@ -30,6 +30,12 @@ public class RealExam extends BaseEntity {
     private Long examCollectionId;
 
     /**
+     * 模拟器类型
+     */
+    @Excel(name = "模拟器类型")
+    private String simType;
+
+    /**
      * 学员ID/用户ID
      */
     @Excel(name = "学员ID/用户ID")
@@ -48,20 +54,9 @@ public class RealExam extends BaseEntity {
     private Long simId;
 
     /**
-     * 考试状态:
-     * [0]-未登录,
-     * [1]-已登录,
-     * [2]-模拟器检查并下发故障中,
-     * [3]:模拟器检查OK可开考,
-     * [4]-答题中,
-     * [5]-已交卷,
-     * [6]-计算成绩中,
-     * [7]-获取到成绩报告,
-     * [80]-教师标记缺考,
-     * [81]-登录未开始答题,
-     * [90]-模拟器异常结束
+     * 考试状态:[0]-未登录,[1]-已登录,[2]-模拟器检查并下发故障中,[3]:模拟器检查OK可开考,[4]-答题中,[5]-已交卷,[6]-计算成绩中,[7]-获取到成绩报告,[80]-教师标记缺考,[81]-登录未开始答题,[90]-模拟器异常结束
      */
-    @Excel(name = "考试状态")
+    @Excel(name = "考试状态:[0]-未登录,[1]-已登录,[2]-模拟器检查并下发故障中,[3]:模拟器检查OK可开考,[4]-答题中,[5]-已交卷,[6]-计算成绩中,[7]-获取到成绩报告,[80]-教师标记缺考,[81]-登录未开始答题,[90]-模拟器异常结束")
     private String examStatus;
 
     /**
@@ -120,6 +115,14 @@ public class RealExam extends BaseEntity {
         return examCollectionId;
     }
 
+    public void setSimType(String simType) {
+        this.simType = simType;
+    }
+
+    public String getSimType() {
+        return simType;
+    }
+
     public void setUserId(Long userId) {
         this.userId = userId;
     }
@@ -205,6 +208,7 @@ public class RealExam extends BaseEntity {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                 .append("examId", getExamId())
                 .append("examCollectionId", getExamCollectionId())
+                .append("simType", getSimType())
                 .append("userId", getUserId())
                 .append("seatId", getSeatId())
                 .append("simId", getSimId())

+ 46 - 15
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/Seat.java

@@ -1,5 +1,6 @@
 package com.ruoyi.sim.domain;
 
+import com.ruoyi.sim.domain.vo.SimSocketVo;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
@@ -24,36 +25,42 @@ public class Seat extends BaseEntity {
      * 座号
      */
     @Excel(name = "座号")
-    private Long seatNum;
+    private Integer seatNum;
 
     /**
-     * 学员端座次上[电脑]绑定的[IP地址]
+     * [电脑]绑定的[IP地址]
      */
-    // @Excel(name = "学员端座次上[电脑]绑定的[IP地址]")
+    @Excel(name = "[电脑]绑定的[IP地址]")
     private String seatBindIp;
 
     /**
-     * 学员端座次上[RS485]绑定的[IP地址]
+     * [RS485]绑定的[IP地址]
      */
-    // @Excel(name = "学员端座次上[RS485]绑定的[IP地址]")
+    @Excel(name = "[RS485]绑定的[IP地址]")
     private String seatRs485Ip;
 
     /**
-     * 学员端座次上[RS485]绑定的[端口]
+     * [RS485]绑定的[端口]
      */
-    // @Excel(name = "学员端座次上[RS485]绑定的[端口]")
-    private Long seatRs485Port;
+    @Excel(name = "[RS485]绑定的[端口]")
+    private Integer seatRs485Port;
+
+    /**
+     * Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用
+     */
+    @Excel(name = "Socket状态:[0]:初始化,[1]:打开,[2]:关闭,[5]:禁用")
+    private String seatRs485SocketState;
 
     /**
      * 当前座上学员/用户ID
      */
-    // @Excel(name = "当前座上学员/用户ID")
+    @Excel(name = "当前座上学员/用户ID")
     private Long currentUserId;
 
     /**
-     * 学员端座次上模拟器的ID:[0]没有连接任何模拟器
+     * 模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器
      */
-    // @Excel(name = "学员端座次上模拟器的ID:[0]没有连接任何模拟器")
+    @Excel(name = "模拟器的ID:[0]没有连接任何模拟器,[xx]:具体某台模拟器")
     private Long currentSimId;
 
     public void setSeatId(Long seatId) {
@@ -64,11 +71,11 @@ public class Seat extends BaseEntity {
         return seatId;
     }
 
-    public void setSeatNum(Long seatNum) {
+    public void setSeatNum(Integer seatNum) {
         this.seatNum = seatNum;
     }
 
-    public Long getSeatNum() {
+    public Integer getSeatNum() {
         return seatNum;
     }
 
@@ -88,14 +95,22 @@ public class Seat extends BaseEntity {
         return seatRs485Ip;
     }
 
-    public void setSeatRs485Port(Long seatRs485Port) {
+    public void setSeatRs485Port(Integer seatRs485Port) {
         this.seatRs485Port = seatRs485Port;
     }
 
-    public Long getSeatRs485Port() {
+    public Integer getSeatRs485Port() {
         return seatRs485Port;
     }
 
+    public void setSeatRs485SocketState(String seatRs485SocketState) {
+        this.seatRs485SocketState = seatRs485SocketState;
+    }
+
+    public String getSeatRs485SocketState() {
+        return seatRs485SocketState;
+    }
+
     public void setCurrentUserId(Long currentUserId) {
         this.currentUserId = currentUserId;
     }
@@ -120,6 +135,7 @@ public class Seat extends BaseEntity {
                 .append("seatBindIp", getSeatBindIp())
                 .append("seatRs485Ip", getSeatRs485Ip())
                 .append("seatRs485Port", getSeatRs485Port())
+                .append("seatRs485SocketState", getSeatRs485SocketState())
                 .append("currentUserId", getCurrentUserId())
                 .append("currentSimId", getCurrentSimId())
                 .append("createBy", getCreateBy())
@@ -129,4 +145,19 @@ public class Seat extends BaseEntity {
                 .append("remark", getRemark())
                 .toString();
     }
+
+    // -------------------------------- tom add  --------------------------------
+
+    public static final Long ID_0 = 0L;
+
+    public interface SocketState {
+        String ENABLE_INIT = "0";
+        String ONLINE = "1";
+        String OFFLINE = "2";
+        String DISABLE = "5";
+    }
+
+    public SimSocketVo toSimSocketVo() {
+        return new SimSocketVo(this.getSeatRs485Ip(), this.getSeatRs485Port());
+    }
 }

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

@@ -151,6 +151,8 @@ public class Sim extends BaseEntity {
     }
 
     // -------------------------------- tom add  --------------------------------
+
+    public static final Long ID_0 = 0L;
     /**
      * FZD04B
      */
@@ -180,7 +182,9 @@ public class Sim extends BaseEntity {
         String ENABLE_INIT = "0";
         String ONLINE = "1";
         String OFFLINE = "2";
+        @Deprecated
         String GATEWAY_OFFLINE = "3";
+        @Deprecated
         String SIM_ERROR = "4";
         String DISABLE = "5";
     }

+ 40 - 27
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/SimMsg.java

@@ -3,7 +3,6 @@ package com.ruoyi.sim.domain;
 import java.util.Date;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
@@ -82,6 +81,8 @@ public class SimMsg extends BaseEntity {
     @Excel(name = "接收报文")
     private String receiveMsg;
 
+    private String receiveOriginalMsg;
+
     /**
      * 接收时间
      */
@@ -96,9 +97,9 @@ public class SimMsg extends BaseEntity {
     private Integer retryCount = 0;
 
     /**
-     * default value false.
+     * default.
      */
-    private Boolean ok = false;
+    private Integer result = Result.DEFAULT_VALUE;
 
     /**
      * default ""
@@ -185,6 +186,14 @@ public class SimMsg extends BaseEntity {
         return receiveMsg;
     }
 
+    public String getReceiveOriginalMsg() {
+        return receiveOriginalMsg;
+    }
+
+    public void setReceiveOriginalMsg(String receiveOriginalMsg) {
+        this.receiveOriginalMsg = receiveOriginalMsg;
+    }
+
     public void setReceiveTime(Date receiveTime) {
         this.receiveTime = receiveTime;
     }
@@ -201,12 +210,12 @@ public class SimMsg extends BaseEntity {
         return retryCount;
     }
 
-    public Boolean getOk() {
-        return ok;
+    public Integer getResult() {
+        return result;
     }
 
-    public void setOk(Boolean ok) {
-        this.ok = ok;
+    public void setResult(Integer result) {
+        this.result = result;
     }
 
     public String getErrorMsg() {
@@ -220,28 +229,32 @@ public class SimMsg extends BaseEntity {
     @Override
     public String toString() {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-                .append("simMsgId", getSimMsgId())
-                .append("simId", getSimId())
-                .append("examFaultRefId", getExamFaultRefId())
-                .append("examCollectionType", getExamCollectionType())
-                .append("sendMsgState", getSendMsgState())
-                .append("priority", getPriority())
-                .append("sendMsgType", getSendMsgType())
-                .append("sendMsg", getSendMsg())
-                .append("sendTime", getSendTime())
-                .append("receiveMsg", getReceiveMsg())
-                .append("receiveTime", getReceiveTime())
-                .append("retryCount", getRetryCount())
-                .append("ok", getOk())
-                .append("errorMsg", getErrorMsg())
-                .append("createBy", getCreateBy())
-                .append("createTime", getCreateTime())
-                .append("updateBy", getUpdateBy())
-                .append("updateTime", getUpdateTime())
-                .append("remark", getRemark())
+                .append("simMsgId", simMsgId)
+                .append("simId", simId)
+                .append("examFaultRefId", examFaultRefId)
+                .append("examCollectionType", examCollectionType)
+                .append("sendMsgState", sendMsgState)
+                .append("priority", priority)
+                .append("sendMsgType", sendMsgType)
+                .append("sendMsg", sendMsg)
+                .append("sendTime", sendTime)
+                .append("receiveMsg", receiveMsg)
+                .append("receiveOriginalMsg", receiveOriginalMsg)
+                .append("receiveTime", receiveTime)
+                .append("retryCount", retryCount)
+                .append("result", result)
+                .append("errorMsg", errorMsg)
                 .toString();
     }
-    // -------------------------------- tom add  --------------------------------
+
+// -------------------------------- tom add  --------------------------------
+
+    public interface Result {
+        Integer DEFAULT_VALUE = 0;
+        Integer SUCCESS = 200;
+        Integer RECEIVE_CHECK_FAIL = 500;
+        Integer SOCKET_EXCEPTION = 501;
+    }
 
     public SimMsg() {
     }

+ 40 - 3
ruoyi-sim/src/main/java/com/ruoyi/sim/domain/vo/SimSocketVo.java

@@ -1,5 +1,11 @@
 package com.ruoyi.sim.domain.vo;
 
+import com.ruoyi.sim.constant.CommConst;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
 public class SimSocketVo {
 
     /**
@@ -11,9 +17,6 @@ public class SimSocketVo {
      */
     private Integer port;
 
-    public SimSocketVo() {
-    }
-
     public SimSocketVo(String ip, Integer port) {
         this.ip = ip;
         this.port = port;
@@ -34,4 +37,38 @@ public class SimSocketVo {
     public void setPort(Integer port) {
         this.port = port;
     }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("ip", ip)
+                .append("port", port)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || getClass() != o.getClass()) return false;
+
+        SimSocketVo that = (SimSocketVo) o;
+
+        return new EqualsBuilder().append(ip, that.ip).append(port, that.port).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(ip).append(port).toHashCode();
+    }
+
+    public String toKey() {
+        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;
+    }
 }

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

@@ -18,13 +18,13 @@ public class SocketWrapValue {
 
     private Socket socket;
 
-    private Long connectedTimeMillis;
+    private Long okTimeMillis;
 
-    public SocketWrapValue(String ip, Integer port, Socket socket, Long connectedTimeMillis) {
+    public SocketWrapValue(String ip, Integer port, Socket socket, Long okTimeMillis) {
         this.ip = ip;
         this.port = port;
         this.socket = socket;
-        this.connectedTimeMillis = connectedTimeMillis;
+        this.okTimeMillis = okTimeMillis;
     }
 
     public String getIp() {
@@ -43,12 +43,12 @@ public class SocketWrapValue {
         this.socket = socket;
     }
 
-    public Long getConnectedTimeMillis() {
-        return connectedTimeMillis;
+    public Long getOkTimeMillis() {
+        return okTimeMillis;
     }
 
-    public void setConnectedTimeMillis(Long connectedTimeMillis) {
-        this.connectedTimeMillis = connectedTimeMillis;
+    public void setOkTimeMillis(Long okTimeMillis) {
+        this.okTimeMillis = okTimeMillis;
     }
 
     @Override
@@ -56,7 +56,7 @@ public class SocketWrapValue {
         return new ToStringBuilder(this)
                 .append("ip", ip)
                 .append("socket", socket)
-                .append("connectedTimeMillis", connectedTimeMillis)
+                .append("okTimeMillis", okTimeMillis)
                 .toString();
     }
 }

+ 23 - 9
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommBuildService.java

@@ -27,7 +27,7 @@ public class CommBuildService {
     private SnowflakeIdService idService;
 
     /**
-     * 设备类型读取
+     * 读取设备序列号
      *
      * @param simNum sim.sim_num
      */
@@ -36,6 +36,15 @@ public class CommBuildService {
     }
 
     /**
+     * 询问设备类型和序列号
+     *
+     * @return
+     */
+    public SimMsg buildSendMsgWhichSim() {
+        return buildSendMsg(CommConst.BLANK_SIM_NUM, CMD_READ_TYPE, CMD_ID_GET_SN);
+    }
+
+    /**
      * 故障下发
      *
      * @param simNum          sim.sim_num
@@ -46,7 +55,7 @@ public class CommBuildService {
     }
 
     /**
-     * 状态读取
+     * 读取故障
      *
      * @param simNum          sim.sim_num
      * @param bindHardwareMsg fault.bind_hardware_msg
@@ -56,7 +65,7 @@ public class CommBuildService {
     }
 
     /**
-     * 故障清清除
+     * 清除故障
      *
      * @param simNum          sim.sim_num
      * @param bindHardwareMsg fault.bind_hardware_msg
@@ -65,11 +74,20 @@ public class CommBuildService {
         return buildSendMsg(simNum, CMD_CLEAR_FAULT, bindHardwareMsg);
     }
 
+    /**
+     * 填充内容为空。
+     *
+     * @param simNum
+     * @param cmd
+     * @param cmdId
+     * @return
+     */
     public SimMsg buildSendMsg(final String simNum, final String cmd, final String cmdId) {
-        return buildSendMsg(simNum, ORN_SEND, cmd, cmdId, CMD_DATA_PLACE_HOLDER);
+        return buildSendMsg(simNum, ORN_SEND, cmd, cmdId, CommConst.CMD_DATA_PLACE_HOLDER);
     }
 
     /**
+     * 生成发送指令基本方法。
      *
      * @param simNum
      * @param orn
@@ -123,11 +141,7 @@ public class CommBuildService {
         return smS;
     }
 
-    public AjaxResult buildSendMsgAR(final String simNum,
-                                     final String orn,
-                                     final String cmd,
-                                     final String cmdId,
-                                     final String data) {
+    public AjaxResult buildSendMsgAR(final String simNum, final String orn, final String cmd, final String cmdId, final String data) {
         final SimMsg sm = buildSendMsg(simNum, orn, cmd, cmdId, data);
         return AjaxResult.success(sm);
     }

+ 301 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommCheckService.java

@@ -0,0 +1,301 @@
+package com.ruoyi.sim.service.impl;
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.sim.constant.CommConst;
+import com.ruoyi.sim.domain.Seat;
+import com.ruoyi.sim.domain.Sim;
+import com.ruoyi.sim.domain.SimMsg;
+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 org.springframework.transaction.annotation.Transactional;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Objects;
+
+import static com.ruoyi.sim.constant.CommConst.RETRY_COUNT_0;
+import static com.ruoyi.sim.constant.CommConst.RETRY_COUNT_QUERY_SN_IMPORTANT;
+
+@Service
+public class CommCheckService {
+
+    private static final Logger l = LoggerFactory.getLogger(CommCheckService.class);
+
+    @Autowired
+    private SeatService seatService;
+    @Autowired
+    private SimService simService;
+    @Autowired
+    private CommBuildService commBuildService;
+    @Autowired
+    private CommSendService commSendService;
+    @Autowired
+    private FailedCountService failedCountService;
+    @Autowired
+    private SocketService socketService;
+
+    /**
+     * 等同于ping命令。
+     *
+     * @param ipV4
+     * @return
+     * @throws IOException
+     */
+    public boolean pingIsReachable(String ipV4) {
+        InetAddress ia = null;
+        try {
+            ia = InetAddress.getByName(ipV4);
+            return ia.isReachable(CommConst.PING_TIME_OUT);
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            return false;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * ping路由器得到状态
+     *
+     * @param routerIp
+     * @return
+     */
+    public AjaxResult checkRouterState(final String routerIp) {
+        if (routerIp == null || StringUtils.isBlank(routerIp)) {
+            throw new IllegalArgumentException("routerIp isBlank");
+        }
+        if (pingIsReachable(routerIp)) {
+            return AjaxResult.success();
+        } else {
+            simService.updateAllEnableState(Sim.State.OFFLINE);
+            seatService.updateAllEnableState(Seat.SocketState.OFFLINE);
+            return AjaxResult.error("路由器[" + routerIp + "]无法连接!");
+        }
+    }
+
+    /**
+     * pingRS485得到状态
+     *
+     * @param rs485Ip
+     * @return
+     */
+    public AjaxResult checkPingRs485State(final String rs485Ip) {
+        if (rs485Ip == null || StringUtils.isBlank(rs485Ip)) {
+            throw new IllegalArgumentException("rs485Ip isBlank");
+        }
+        if (pingIsReachable(rs485Ip)) {
+            return AjaxResult.success();
+        } else {
+            // 更新SocketState
+            seatService.updateSocketStateByRs485Ip(rs485Ip, Seat.SocketState.OFFLINE);
+            return AjaxResult.error("RS485物联网网关[" + rs485Ip + "]无法连接!");
+        }
+    }
+
+    public AjaxResult checkPingStudentPcState(final String studentIp) {
+        if (studentIp == null || StringUtils.isBlank(studentIp)) {
+            throw new IllegalArgumentException("studentIp isBlank");
+        }
+        if (pingIsReachable(studentIp)) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("学员操作端[" + studentIp + "]无法连接!");
+        }
+    }
+
+    /**
+     * 检查一个座次状况。
+     * 维护 seat表-socket_state字段;
+     * 维护 seat表-sim_id字段;
+     * 维护 sim表-Online、Offline状态;
+     *
+     * @param seat      座次
+     * @param important true:重要的场景 开始考试 重试次数不同,也会进行序列号检查。false:不重要场景 定时巡查。
+     * @return
+     */
+    @Transactional
+    public AjaxResult checkOneSeatState(final Seat seat, final boolean important) {
+        // check args.
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        //
+        socketService.tryOpenAll();
+        //
+        int retryTotalCount;
+        if (important) {
+            retryTotalCount = CommConst.RETRY_COUNT_WHICH_SIM_IMPORTANT;
+        } else {
+            retryTotalCount = CommConst.RETRY_COUNT_0;
+        }
+        SimMsg smS01 = commBuildService.buildSendMsgWhichSim();
+        SimMsg smR01 = commSendService.send(smS01, seat, null, retryTotalCount, CommConst.SLEEP_SHORT);
+        Integer result = smR01.getResult();
+        if (Objects.equals(result, SimMsg.Result.SUCCESS)) {
+            final String simNum = CommParseUtils.subSimNum(smR01);
+            Sim sim = simService.uniqueBySimNum(simNum);
+            if (sim == null) {
+                return AjaxResult.error("找不到模拟器[" + simNum + "]对应数据。");
+            } else {
+                l.info("在座次[{}]上发现模拟器[{}]", seat.getSeatNum(), sim.getSimNum());
+            }
+            // 更新SimId
+            seatService.updateSimIdBySeatNum(seat.getSeatNum(), sim.getSimId());
+            // 更新Sim状态
+            simService.updateSimStateBySimId(sim.getSimId(), Sim.State.ONLINE);
+            return AjaxResult.success("成功,检查一个座次[" + seat.getSeatNum() + "]OK!模拟器[" + sim.getSimNum() + "]在线。");
+        } else if (Objects.equals(result, SimMsg.Result.RECEIVE_CHECK_FAIL)) {
+            return AjaxResult.error("失败,报文回复异常。");
+        } else if (Objects.equals(result, SimMsg.Result.SOCKET_EXCEPTION)) {
+            // 更新SimId
+            seatService.updateSimIdBySeatNum(seat.getSeatNum(), Sim.ID_0);
+            return AjaxResult.success("成功,检查一个座次[" + seat.getSeatNum() + "]OK!未连接模拟器。");
+        }
+        return AjaxResult.error("失败");
+    }
+
+    /**
+     * 纯数据库查询,模拟器是否在线。
+     * 模拟器是否被禁用。
+     *
+     * @param simId
+     * @return
+     */
+    public AjaxResult checkOneSimOnlineState(final Long simId) {
+        Sim sim = simService.selectSimBySimId(simId);
+        if (sim != null) {
+            switch (sim.getSimState()) {
+                case Sim.State.ONLINE: {
+                    return AjaxResult.success("模拟器[" + sim.getSimNum() + "]在线!");
+                }
+                case Sim.State.OFFLINE: {
+                    return AjaxResult.success("模拟器[" + sim.getSimNum() + "]离线!");
+                }
+                case Sim.State.DISABLE: {
+                    return AjaxResult.error("模拟器[" + sim.getSimNum() + "]禁用!");
+                }
+            }
+        }
+        return AjaxResult.error("模拟器[" + Objects.requireNonNull(sim).getSimNum() + "]XXXX!");
+    }
+
+    /**
+     * 默认Seat中已经有CurrentSimId数据
+     * 检查回应报文模拟器类型是否正确。
+     *
+     * @param seat          座次
+     * @param important
+     * @param targetSimType 期望模拟器目标类型
+     * @return
+     */
+    public AjaxResult checkOneSimType(final Seat seat, final boolean important, final String targetSimType) {
+        // check args.
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        if (seat.getCurrentSimId() == null || Sim.ID_0.equals(seat.getCurrentSimId())) {
+            throw new IllegalArgumentException("sim id is 0");
+        }
+        if (!simService.existBySimId(seat.getCurrentSimId())) {
+            return AjaxResult.error("模拟器ID[" + seat.getCurrentSimId() + "]不存在!");
+        }
+        //
+        final String msgError = "连接模拟器类型或序列号不正确!应该连接型号:";
+        final String msgOk = "连接模拟器类型或序列号正确!";
+        int retryTotalCount;
+        if (important) {
+            retryTotalCount = RETRY_COUNT_QUERY_SN_IMPORTANT;
+        } else {
+            retryTotalCount = RETRY_COUNT_0;
+        }
+        Sim sim = simService.selectSimBySimId(seat.getCurrentSimId());
+        SimMsg smS = commBuildService.buildSendMsgReadSimType(sim.getSimNum());
+        SimMsg smR = commSendService.send(smS, seat, sim, retryTotalCount, CommConst.SLEEP_SHORT);
+        if (StringUtils.isNotBlank(smR.getReceiveMsg())) {
+            final String content = CommParseUtils.subContentData(smR);
+            switch (targetSimType) {
+                case Sim.TYPE_0001 -> {
+                    if (content.startsWith(CommConst.TYPE_0001_SN_PREFIX) && content.endsWith(sim.getSimNum())) {
+                        return AjaxResult.success(msgOk);
+                    } else {
+                        return AjaxResult.error(msgError + Sim.TYPE_NAME_MAP.get(targetSimType));
+                    }
+                }
+                case Sim.TYPE_0002 -> {
+                    if (content.startsWith(CommConst.TYPE_0002_SN_PREFIX) && content.endsWith(sim.getSimNum())) {
+                        return AjaxResult.success(msgOk);
+                    } else {
+                        return AjaxResult.error(msgError + Sim.TYPE_NAME_MAP.get(targetSimType));
+                    }
+                }
+                case Sim.TYPE_0003 -> {
+                    if (content.startsWith(CommConst.TYPE_0003_SN_PREFIX) && content.endsWith(sim.getSimNum())) {
+                        return AjaxResult.success(msgOk);
+                    } else {
+                        return AjaxResult.error(msgError + Sim.TYPE_NAME_MAP.get(targetSimType));
+                    }
+                }
+                default -> throw new IllegalStateException("Unexpected value: " + targetSimType);
+            }
+        }
+        return AjaxResult.error("失败,检查一个模拟器[" + sim.getSimNum() + "]型号或序列号执行错误!");
+    }
+
+    /**
+     * 默认Seat中已经有CurrentSimId数据
+     *
+     * @param seat
+     * @param important
+     * @return
+     */
+    @Transactional
+    public AjaxResult checkOneSimOnlineState(final Seat seat, final boolean important) {
+        // check args.
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        if (seat.getCurrentSimId() == null || Sim.ID_0.equals(seat.getCurrentSimId())) {
+            return AjaxResult.error("模拟器ID不存在!");
+        }
+        if (!simService.existBySimId(seat.getCurrentSimId())) {
+            return AjaxResult.error("模拟器ID[" + seat.getCurrentSimId() + "]不存在!");
+        }
+        //
+        int retryTotalCount;
+        if (important) {
+            retryTotalCount = RETRY_COUNT_QUERY_SN_IMPORTANT;
+        } else {
+            retryTotalCount = RETRY_COUNT_0;
+        }
+        Sim sim = simService.selectSimBySimId(seat.getCurrentSimId());
+        SimMsg smS02 = commBuildService.buildSendMsgReadSimType(sim.getSimNum());
+        SimMsg smR02 = commSendService.send(smS02, seat, sim, retryTotalCount, CommConst.SLEEP_SHORT);
+        if (StringUtils.isNotBlank(smR02.getReceiveMsg())) {
+            // 只要返回正确报文,即认为在线。
+            simService.updateSimStateBySimId(sim.getSimId(), Sim.State.ONLINE);
+            // SocketOldService实现。
+            // socketOldService.commFailCountClearOne(sim.getSimId());
+            failedCountService.reset0(seat.toSimSocketVo());
+            return AjaxResult.success("成功,检查一个模拟器[" + sim.getSimNum() + "]OK!");
+        }
+        return AjaxResult.error("失败,检查一个模拟器[" + sim.getSimNum() + "]在线状态执行错误!");
+    }
+
+    /**
+     * [定时执行]查找所有没有被手动禁用的座次 和 座次上的模拟器。
+     */
+    public AjaxResult checkAllSeatAndSimState() {
+        List<Seat> list = seatService.listAllEnable();
+        list.forEach(seat -> {
+            checkOneSeatState(seat, false);
+            // checkOneSimState(seat, false);
+        });
+        return AjaxResult.success("检查完毕,成功!");
+    }
+}

+ 28 - 0
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommParseUtils.java

@@ -0,0 +1,28 @@
+package com.ruoyi.sim.service.impl;
+
+
+import com.ruoyi.sim.domain.SimMsg;
+import org.apache.commons.lang3.StringUtils;
+
+public class CommParseUtils {
+
+    /**
+     * 截取 内容报文。
+     *
+     * @param
+     * @return "01 02 03 04"
+     */
+    public static String subContentData(SimMsg sm) {
+        if (StringUtils.isBlank(sm.getReceiveMsg())) {
+            throw new IllegalArgumentException("sm isBlank");
+        }
+        return StringUtils.substring(sm.getReceiveMsg(), 10, 18);
+    }
+
+    public static String subSimNum(SimMsg sm) {
+        if (StringUtils.isBlank(sm.getReceiveMsg())) {
+            throw new IllegalArgumentException("sm isBlank");
+        }
+        return StringUtils.substring(sm.getReceiveMsg(), 2, 4);
+    }
+}

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

@@ -32,62 +32,6 @@ public class CommReceiveService {
     @Autowired
     private FaultService faultService;
 
-    /**
-     * 只要返回信息,即认为在线。
-     *
-     * @param sm
-     * @param s
-     * @return
-     */
-    public AjaxResult checkOneSimState(SimMsg sm, Sim s) {
-        if (s == null) {
-            l.warn("s is null");
-            return AjaxResult.error("sim is null");
-        }
-        if (StringUtils.isNotBlank(sm.getReceiveMsg())) {
-            simService.updateSimStateBySimId(s.getSimId(), Sim.State.ONLINE);
-        }
-        return AjaxResult.success();
-    }
-
-    /**
-     * c.endsWith(s.getSimNum())
-     * 检查回应报文模拟器类型是否正确。
-     *
-     * @param sm
-     * @param s
-     * @return
-     */
-    public AjaxResult checkOneSimSn(SimMsg sm, Sim s) {
-        final String simType = s.getSimType();
-        final String c = subContentData(sm.getReceiveMsg());
-        final String msgError = "连接模拟器类型不正确!";
-        switch (simType) {
-            case Sim.TYPE_0001 -> {
-                if (c.startsWith(CommConst.TYPE_0001_SN_PREFIX)) {
-                    return AjaxResult.success();
-                } else {
-                    return AjaxResult.error(msgError);
-                }
-            }
-            case Sim.TYPE_0002 -> {
-                if (c.startsWith(CommConst.TYPE_0002_SN_PREFIX)) {
-                    return AjaxResult.success();
-                } else {
-                    return AjaxResult.error(msgError);
-                }
-            }
-            case Sim.TYPE_0003 -> {
-                if (c.startsWith(CommConst.TYPE_0003_SN_PREFIX)) {
-                    return AjaxResult.success();
-                } else {
-                    return AjaxResult.error(msgError);
-                }
-            }
-            default -> throw new IllegalStateException("Unexpected value: " + simType);
-        }
-    }
-
     public void clearOneFault(SimMsg sm, Sim s, RealExamFault reF, Fault f) {
         // check
 
@@ -111,7 +55,7 @@ public class CommReceiveService {
         // check
 
         //
-        String faultQuestionValue = subContentData(sm.getReceiveMsg());
+        String faultQuestionValue = CommParseUtils.subContentData(sm);
         // todo:
         if (StringUtils.isBlank(faultQuestionValue)) {
             l.warn("faultQuestionValue is empty!");
@@ -178,7 +122,7 @@ public class CommReceiveService {
     public void setFaultAnswerValue(SimMsg sm, Sim s, RealExamFault reF, Fault f, String refState) {
         // check
         //
-        String faultAnswerValue = subContentData(sm.getReceiveMsg());
+        String faultAnswerValue = CommParseUtils.subContentData(sm);
         // todo:
         if (StringUtils.isBlank(faultAnswerValue)) {
             l.warn("faultAnswerValue is empty!");
@@ -206,18 +150,6 @@ public class CommReceiveService {
         }
     }
 
-    /**
-     * 截取 内容报文。
-     *
-     * @param receiveMsg
-     * @return
-     */
-    public String subContentData(String receiveMsg) {
-        if (StringUtils.isEmpty(receiveMsg)) {
-            return "";
-        }
-        return StringUtils.substring(receiveMsg, 10, 18);
-    }
 
     /**
      * 开始考试 检查 故障部位 检查
@@ -228,7 +160,7 @@ public class CommReceiveService {
      * @return
      */
     public AjaxResult getOneFaultCheck(SimMsg sm, Sim s, Fault f) {
-        String checkValue = subContentData(sm.getReceiveMsg());
+        String checkValue = CommParseUtils.subContentData(sm);
         if (s == null) {
             return AjaxResult.error("没有对应模拟器!");
         }
@@ -256,43 +188,50 @@ public class CommReceiveService {
     }
 
     /**
-     * check receiveMsg
+     * 处理报文前端加00的情况,最多可能加5组00
      *
      * @param receiveMsg
      * @return
      */
-    public AjaxResult checkReceiveMsg(String receiveMsg) {
+    public String removeRrefix0(String receiveMsg) {
+        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);
+        return receiveMsg;
+    }
+
+    /**
+     * 有返回报文的情况下,检查Receive的报文。
+     *
+     * @param receiveMsg
+     * @return
+     */
+    public AjaxResult checkReceiveMsg(final String receiveMsg) {
         l.info("####checkReceiveMsg#### = [{}]", receiveMsg);
-        String start = "ReceiveMsg ";
+        String msgErr = "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);
+            return AjaxResult.error(msgErr + "isBlank");
         }
         // check:长度
         if (receiveMsg.length() != LENGTH_24) {
-            return AjaxResult.error(start + "length error.length not 24.");
+            return AjaxResult.error(msgErr + "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.");
+            return AjaxResult.error(msgErr + "orn error.");
         }
         // check:前缀
         if (!StringUtils.startsWith(receiveMsg, PREFIX)) {
-            return AjaxResult.error(start + "not start with AA.");
+            return AjaxResult.error(msgErr + "not start with AA.");
         }
         // check:后缀
         if (!StringUtils.endsWith(receiveMsg, SUFFIX)) {
-            return AjaxResult.error(start + "not end with 55.");
+            return AjaxResult.error(msgErr + "not end with 55.");
         }
         // 计算CRC16
         // todo:

+ 277 - 291
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/CommSendService.java

@@ -3,21 +3,21 @@ package com.ruoyi.sim.service.impl;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.sim.config.SimConfig;
-import com.ruoyi.sim.config.SimDebugConfig;
+import com.ruoyi.sim.constant.CommConst;
 import com.ruoyi.sim.domain.*;
+import com.ruoyi.sim.domain.vo.SimSocketVo;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.InetAddress;
 import java.net.Socket;
-import java.net.UnknownHostException;
 import java.util.*;
 
 import static com.ruoyi.sim.constant.CommConst.*;
@@ -35,6 +35,8 @@ public class CommSendService {
     @Autowired
     private CommReceiveService simReceiveService;
     @Autowired
+    private SeatService seatService;
+    @Autowired
     private SimService simService;
     @Autowired
     private FaultService faultService;
@@ -48,8 +50,13 @@ public class CommSendService {
     private CommBuildService commBuildService;
     @Autowired
     private DebugFaultService debugFaultService;
+    // private SocketOldService socketOldService;
+    @Autowired
+    private SocketService socketService;
+    @Autowired
+    private FailedCountService failedCountService;
     @Autowired
-    private SocketOldService socketOldService;
+    private CommCheckService commCheckService;
     @Autowired
     CommReceiveService commReceiveService;
     @Autowired
@@ -71,13 +78,12 @@ public class CommSendService {
             }
             List<RealExamFault> listRef = realExamFaultService.listAllType2State2and3ByExamId(e.getExamId());
             listRef.forEach(ref -> {
-                Sim s = simService.selectSimBySimId(e.getSimId());
+                RealExam re = realExamService.selectRealExamByExamId(ref.getExamId());
+                Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+                Sim sim = simService.selectSimBySimId(e.getSimId());
                 Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-                if (f != null &&
-                        Fault.Type.REAL_GZBW.equals(f.getFaultType()) &&
-                        Fault.State.ENABLE.equals(f.getFaultState())
-                ) {
-                    readOneSimOneFaultResistance(s, ref, f, null);
+                if (f != null && Fault.Type.REAL_GZBW.equals(f.getFaultType()) && Fault.State.ENABLE.equals(f.getFaultState())) {
+                    readOneSimOneFaultResistance(seat, sim, ref, f, null);
                 }
             });
         });
@@ -95,9 +101,10 @@ public class CommSendService {
         l.info("readOneExamAtLast");
         List<RealExamFault> list = realExamFaultService.listAllType2State2and3ByExamId(re.getExamId());
         for (RealExamFault ref : list) {
-            Sim s = simService.selectSimBySimId(re.getSimId());
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-            readOneSimOneFaultResistance(s, ref, f, RealExamFault.State.FINISH);
+            readOneSimOneFaultResistance(seat, sim, ref, f, RealExamFault.State.FINISH);
         }
         // 计算扣分。
         // 最后都读取到,才算扣分。
@@ -107,36 +114,34 @@ public class CommSendService {
         }
     }
 
-    public void readOneSimAtLastByDebug(Sim s) {
+    public void readOneSimAtLastByDebug(Seat seat, Sim sim) {
         l.info("readOneSimAtLastByDebug");
-        List<Fault> list = faultService.listType3(s.getSimType());
+        List<Fault> list = faultService.listType3(sim.getSimType());
         for (Fault f : list) {
-            readOneSimOneFaultResistance(s, null, f, null);
+            readOneSimOneFaultResistance(seat, sim, null, f, null);
         }
     }
 
-    public AjaxResult debugReadAllFaultResistanceBySimNum(String simNum) {
+    public AjaxResult debugReadAllFaultResistanceBySimNum(final Long seatId) {
         // check
 
         //
         // 打开socket
         {
-            AjaxResult ar1 = socketOldService.openSocket();
-            if (ar1.isError()) {
-                return ar1;
-            }
+            socketService.tryOpenAll();
         }
         //
-        Sim s = simService.uniqueBySimNum(simNum);
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
         {
-            AjaxResult arE3 = checkOneSimStateActive(s);
+            AjaxResult arE3 = checkOneSimStateActive(seat);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s = simService.uniqueBySimNum(simNum);
+        sim = simService.uniqueBySimNum(sim.getSimNum());
         // todo: aj改造
-        readOneSimAtLastByDebug(s);
+        readOneSimAtLastByDebug(seat, sim);
         return AjaxResult.success("成功读取!");
     }
 
@@ -168,7 +173,7 @@ public class CommSendService {
      */
     public void scheduledReadSim() {
         l.info("scheduledReadSim");
-        if (!realExamCollectionService.existOpened()) {
+        if (!realExamCollectionService.existAtLeastOneOpened()) {
             l.info("没有open的考试集合");
             return;
         }
@@ -197,15 +202,6 @@ public class CommSendService {
         // debugReadSimType(simNum);
         // l.info("bRea:" + bRea);
 
-        {
-            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);
 
 //            debugWriteAllFault(simNum, simType);
@@ -223,121 +219,78 @@ public class CommSendService {
 
     /**
      * 连接情况 的 定时任务。
-     * 执行频率: 3min
+     * 执行频率: 5min
      */
     public void scheduledConnect() {
         l.info("scheduled####Connect  连接情况 的 定时任务");
-        if (!SimDebugConfig.SCHEDULED_CONNECT) {
+//        if (!SimDebugConfig.SCHEDULED_CONNECT) {
+        if (true) {
             l.info("连接情况 的 定时任务被禁用!");
             return;
         }
-        if (!realExamCollectionService.existOpened()) {
-            l.info("没有open的考试集合");
-            return;
-        }
-        if (!isReachable(config.getRouterIp())) {
-            l.warn("ping RouterIp not ok");
-            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
-            return;
+
+        // 暂时注释
+//        if (!realExamCollectionService.existAtLeastOneOpened()) {
+//            l.info("没有open的任何集合");
+//            return;
+//        }
+
+        //
+        {
+            AjaxResult ar01 = commCheckService.checkRouterState(config.getRouterIp());
+            if (ar01.isError()) {
+                return;
+            }
         }
-        if (!isReachable(config.getRs485Ip())) {
-            l.warn("ping Rs485Ip not ok");
-            simService.updateAllEnableState(Sim.State.GATEWAY_OFFLINE);
-            return;
+        //
+        {
+            seatService.listAllRs485Ip().forEach(rs485Ip -> {
+                commCheckService.checkPingRs485State(rs485Ip);
+            });
         }
+        // SocketOldService实现
+        // socketOldService.openSocket();
 
-        socketOldService.openSocket();
-        if (socketOldService.isCachedSocketOk()) {
-            checkAllSimState();
-        }
+        // SocketOldService实现
+//        if (socketOldService.isCachedSocketOk()) {
+//            checkAllSeatAndSimState();
+//        }
+
+        socketService.tryOpenAll();
+        commCheckService.checkAllSeatAndSimState();
     }
 
     /**
      * 主动更新模拟器状态。
+     * <p>
+     * <p>
+     * 主动查询一次模拟器状态。更新模拟器在线/离线状态;否则返回对应错误。
+     * todo:需要重新考虑
      *
-     * @param s
+     * @param seat
      * @return
      */
-    public AjaxResult checkOneSimStateActive(Sim s) {
-        final long simId = s.getSimId();
+    @Deprecated
+    public AjaxResult checkOneSimStateActive(Seat seat) {
         // 这句可能会调整模拟器状态,后面需要重新查询。
-        {
-            AjaxResult ar1 = checkOneSimState(s, true);
-            if (ar1.isError()) {
-                return ar1;
-            }
+        commCheckService.checkOneSeatState(seat, true);
+        AjaxResult ar1 = commCheckService.checkOneSimOnlineState(seat, true);
+        if (ar1.isError()) {
+            return ar1;
         }
         // 重新最新模拟器。
-        s = simService.selectSimBySimId(simId);
+        Sim sim = gggSimBySeatId(seat.getSeatId());
         // 如果模拟器离线
-        if (s != null && Sim.State.ONLINE.equals(s.getSimState())) {
+        if (sim != null && Sim.State.ONLINE.equals(sim.getSimState())) {
             return AjaxResult.success();
         } else {
             return AjaxResult.error("未连接模拟器,请检查连接!");
         }
     }
 
-    /**
-     * @param s
-     * @param important true 重试次数不同,也会进行序列号检查。
-     * @return
-     */
-    public AjaxResult checkOneSimState(final Sim s, final boolean important) {
-        // check
-        if (s == null) {
-            return AjaxResult.error("sim is null");
-        }
-        if (Sim.State.DISABLE.equals(s.getSimState())) {
-            l.warn("sim DISABLE,模拟器被禁用,sim = {}", s);
-            return AjaxResult.error("模拟器被禁用");
-        }
-        if (StringUtils.isBlank(s.getSimType()) || StringUtils.isBlank(s.getSimNum())) {
-            l.warn("sim error data {}", s);
-            return AjaxResult.error("模拟器数据错误。");
-        }
-        //
-        SimMsg smS = commBuildService.buildSendMsgReadSimType(s.getSimNum());
-        int retryCount = RETRY_COUNT_0;
-        long sleep = SLEEP_SHORT;
-        if (important) {
-            retryCount = RETRY_COUNT_QUERY_SN_IMPORTANT;
-        }
-        SimMsg smR = send(smS, s, retryCount, sleep);
-        if (StringUtils.isNotBlank(smR.getReceiveMsg())) {
-            l.info("isNotBlank");
-            socketOldService.commFailCountClearOne(s.getSimId());
-        }
-        simReceiveService.checkOneSimState(smR, s);
-        {
-            if (important) {
-                AjaxResult ar1 = simReceiveService.checkOneSimSn(smR, s);
-                if (ar1.isError()) {
-                    return ar1;
-                }
-            }
-        }
-        return AjaxResult.success();
-    }
-
-    /**
-     * 查找所有没有被手动禁用,并order by sim_num的模拟器列表。检查所有模拟器状态。
-     */
-    public void checkAllSimState() {
-        // RealExamCollection ecF = realExamCollectionService.selectRealExamCollectionOpened();
-        // l.info("ecF.getSimType() = {}", ecF.getSimType());
-        // if (ecF != null) {
-
-        // }
-        List<Sim> list = simService.listAllEnable(); // ecF.getSimType()
-        l.info("checkAllSimState list.size() = {}", list.size());
-        list.forEach(s -> {
-            checkOneSimState(s, false);
-        });
-    }
-
     @Async("tp-comm")
     public void checkAllSimStateAsync() {
-        checkAllSimState();
+        commCheckService.checkAllSeatAndSimState();
     }
 
     /**
@@ -359,27 +312,26 @@ public class CommSendService {
             l.info("清除exam list = {}", list.size());
         }
         assert list != null;
-        list
-                .forEach(ref -> {
-                    Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-                    if (faultService.isDisable(f.getFaultId())) {
-                        l.warn("故障 {} -被禁用", f.getName());
-                        throw new IllegalArgumentException("故障被禁用");
-                    }
-                    Sim s = simService.selectSimBySimId(re.getSimId());
-                    // check
+        Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+        list.forEach(ref -> {
+            Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
+            if (faultService.isDisable(f.getFaultId())) {
+                l.warn("故障 {} -被禁用", f.getName());
+                throw new IllegalArgumentException("故障被禁用");
+            }
+            Sim sim = simService.selectSimBySimId(re.getSimId());
+            // check
 
-                    //
-                    clearOneSimOneFault(s, ref, f);
-                });
+            //
+            clearOneSimOneFault(seat, sim, ref, f);
+        });
     }
 
-    public void clearOneSimAllFaultBySim(Sim s) {
-        l.info("clearOneSimAllFaultBySim = {}", s);
-        faultService.listType3EnableBySimType(s.getSimType())
-                .forEach(f -> {
-                    clearOneSimOneFault(s, null, f);
-                });
+    public void clearOneSimAllFaultBySim(Seat seat, Sim sim) {
+        l.info("clearOneSimAllFaultBySim = {}", sim);
+        faultService.listType3EnableBySimType(sim.getSimType()).forEach(f -> {
+            clearOneSimOneFault(seat, sim, null, f);
+        });
     }
 
     /**
@@ -394,11 +346,13 @@ public class CommSendService {
 
     public void clearAll() {
         // todo:
-        simService.listAllEnable().forEach(s -> {
-            String simType = s.getSimType();
+        // 根据Seat数据遍历
+        seatService.listAllEnable().forEach(seat -> {
+            Sim sim = gggSimBySeatId(seat.getSeatId());
+            String simType = sim.getSimType();
             List<Fault> listF = faultService.listType3EnableBySimType(simType);
             listF.forEach(f -> {
-                clearOneSimOneFault(s, null, f);
+                clearOneSimOneFault(seat, sim, null, f);
             });
         });
     }
@@ -406,82 +360,89 @@ public class CommSendService {
     /**
      * debug读取模拟器类型序列号
      *
-     * @param simNum
+     * @param seatId
      * @return
      */
-    public SimMsg debugReadSimType(final String simNum) {
-        SimMsg sm = commBuildService.buildSendMsgReadSimType(simNum);
-        return send(sm, null, RETRY_COUNT_0, SLEEP_SHORT);
+    public SimMsg debugReadSimType(final Long seatId) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgReadSimType(sim.getSimNum());
+        return send(sm, seat, sim, CommConst.RETRY_COUNT_0, CommConst.SLEEP_SHORT);
     }
 
     /**
      * debug清除一个故障
      *
-     * @param simNum
+     * @param seatId
      * @param bindHardwareMsg
      * @return
      */
-    public SimMsg debugClearOneFault(final String simNum, final String bindHardwareMsg) {
-        SimMsg sm = commBuildService.buildSendMsgClearFault(simNum, bindHardwareMsg);
-        Sim s = simService.uniqueBySimNum(simNum);
-        return send(sm, s, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
+    public SimMsg debugClearOneFault(final Long seatId, final String bindHardwareMsg) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgClearFault(sim.getSimNum(), bindHardwareMsg);
+        return send(sm, seat, sim, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
     }
 
     public AjaxResult debugClearAllOnlineSimAllFault() {
         l.info("debugClearAllOnlineSimAllFault");
         simService.listAllOnline().forEach(s -> {
-            AjaxResult ar = debugClearAllFaultBySimNum(s.getSimNum());
+            // AjaxResult ar = debugClearAllFaultBySeatId(0);// todo:尚未实现
         });
+        debugFaultService.deleteAll();
         return AjaxResult.success("清除成功,清除所有在线模拟器所有故障!");
     }
 
     /**
      * debug清除所有故障
      *
-     * @param simNum
+     * @param seatId
      * @return
      */
-    public AjaxResult debugClearAllFaultBySimNum(final String simNum) {
-        Sim s1 = simService.uniqueBySimNum(simNum);
+    @Transactional
+    public AjaxResult debugClearAllFaultBySeatId(final Long seatId) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        // todo:
         {
-            AjaxResult arE3 = checkOneSimStateActive(s1);
+            AjaxResult arE3 = checkOneSimStateActive(seat);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s1 = simService.uniqueBySimNum(simNum);
-        if (s1 == null) {
+        sim = simService.uniqueBySimNum(sim.getSimNum());
+        if (sim == null) {
             return AjaxResult.error("清除失败,对应simNum模拟器不存在!");
         }
-        if (!Sim.State.ONLINE.equals(s1.getSimState())) {
+        if (!Sim.State.ONLINE.equals(sim.getSimState())) {
             return AjaxResult.error("清除失败,模拟器尚未在线!");
         }
         List<SimMsg> list = new ArrayList<>();
-        for (String b : getGZBWBySimType(s1.getSimType())) {
-            SimMsg sm = debugClearOneFault(simNum, b);
+        for (String b : getGZBWBySimType(sim.getSimType())) {
+            SimMsg sm = debugClearOneFault(seatId, b);
             list.add(sm);
-            if (sm != null && !sm.getOk()) {
-
-            }
+//            if (sm != null && !sm.isOk()) {
+//
+//            }
         }
+        debugFaultService.deleteAll();
         return AjaxResult.success("清除成功,清除当前模拟器所有故障!");
     }
 
     /**
-     * @param s
+     * @param sim
      * @param reF 可以为空,表示不关联考试的,单独执行的。调试模式下为空。
      * @param f
      */
-    public void clearOneSimOneFault(Sim s, RealExamFault reF, Fault f) {
-        l.info("clearOneSimOneFault 清除One故障:getSimNum = {},getBindHardwareMsg = {},fault.getName = {}",
-                s.getSimNum(), f.getBindHardwareMsg(), f.getName());
+    public void clearOneSimOneFault(Seat seat, Sim sim, RealExamFault reF, Fault f) {
+        l.info("clearOneSimOneFault 清除One故障:getSimNum = {},getBindHardwareMsg = {},fault.getName = {}", sim.getSimNum(), f.getBindHardwareMsg(), f.getName());
         // check todo:
 
         // step1
-        SimMsg smS = commBuildService.buildSendMsgClearFault(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg smR = send(smS, s, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
+        SimMsg smS = commBuildService.buildSendMsgClearFault(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg smR = send(smS, seat, sim, RETRY_COUNT_CLEAR_ONE_FAULT, SLEEP_LONG);
         if (reF != null) {
-            simReceiveService.clearOneFault(smR, s, reF, f);
+            simReceiveService.clearOneFault(smR, sim, reF, f);
         } else {
             l.info("reF == null");
         }
@@ -504,29 +465,31 @@ public class CommSendService {
     /**
      * debug下发一个故障
      *
-     * @param simNum
+     * @param seatId
      * @param bindHardwareMsg
      * @return
      * @throws IOException
      */
-    public SimMsg debugWriteOneFault(final String simNum, final String bindHardwareMsg) {
-        SimMsg sm = commBuildService.buildSendMsgWriteFault(simNum, bindHardwareMsg);
-        return send(sm, null, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
+    public SimMsg debugWriteOneFault(final Long seatId, final String bindHardwareMsg) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgWriteFault(sim.getSimNum(), bindHardwareMsg);
+        return send(sm, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
     }
 
     /**
      * debug下发所有故障
      *
-     * @param simNum
+     * @param seatId
      * @return
      * @throws IOException
      */
-    public List<SimMsg> debugWriteAllFault(final String simNum) {
+    public List<SimMsg> debugWriteAllFault(final Long seatId) {
         List<SimMsg> list = new ArrayList<>();
-        Sim s = simService.uniqueBySimNum(simNum);
-        String simType = s.getSimType();
-        for (String b : getGZBWBySimType(simType)) {
-            list.add(debugWriteOneFault(simNum, b));
+        Sim sim = gggSimBySeatId(seatId);
+        String simType = sim.getSimType();
+        for (String bind : getGZBWBySimType(simType)) {
+            list.add(debugWriteOneFault(seatId, bind));
         }
         return list;
     }
@@ -535,14 +498,13 @@ public class CommSendService {
      * todo:尚未实现
      * 实现方式类似 studentStartRealExam方法。
      *
-     * @param simNum
+     * @param seatId
      * @param faultIds
      * @param checkReplace 是否进行可换件检查
      * @return
      */
-    public AjaxResult debugWriteSelectedFaultBySimNum(final String simNum,
-                                                      final String[] faultIds,
-                                                      final Boolean checkReplace) {
+    @Transactional
+    public AjaxResult debugWriteSelectedFaultBySimNum(final Long seatId, final String[] faultIds, final Boolean checkReplace) {
         //
         l.info("faultIds.length = {}", faultIds.length);
         {
@@ -552,29 +514,26 @@ public class CommSendService {
         // check faultIds 有效性
 
         //
-        Sim s = simService.uniqueBySimNum(simNum);
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
         // check sim
         // 打开socket
         {
-            AjaxResult ar1 = socketOldService.openSocket();
-            l.info("ar1 = {}", ar1);
-            if (ar1.isError()) {
-                return ar1;
-            }
+            socketService.tryOpenAll();
         }
         // Step 1 主动查询一次模拟器状态。
         {
-            AjaxResult arE3 = checkOneSimStateActive(s);
+            AjaxResult arE3 = checkOneSimStateActive(seat);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s = simService.uniqueBySimNum(simNum);
+        sim = simService.uniqueBySimNum(sim.getSimNum());
         // Step 2
         // msg判断,是否含有"故障部位"字符串
         {
             if (checkReplace) {
-                AjaxResult arE2 = readOneSimAllFaultCheck(s);
+                AjaxResult arE2 = readOneSimAllFaultCheck(seat, sim);
                 if (arE2.isError()) {
                     return arE2;
                 }
@@ -582,7 +541,7 @@ public class CommSendService {
         }
         // Step 3 清除对应一台模拟器 所有故障部位故障。
         {
-            clearOneSimAllFaultBySim(s);
+            clearOneSimAllFaultBySim(seat, sim);
         }
         // Step 4 下发对应一台模拟器 出题选中的 故障位置故障。
         {
@@ -590,11 +549,11 @@ public class CommSendService {
             for (int i = 0; i < faultIds.length; i++) {
                 faults[i] = faultService.selectFaultByFaultId(faultIds[i]);
             }
-            writeOneSimAllSelectFaultByDebug(s, faults);
+            writeOneSimAllSelectFaultByDebug(seat, sim, faults);
         }
         // Step 5 读取
         {
-            readOneSimAllFaultFirstTimeBySim(s, faultIds);
+            readOneSimAllFaultFirstTimeBySim(seat, sim, faultIds);
         }
         return AjaxResult.success("下发故障流程执行成功!");
     }
@@ -629,41 +588,42 @@ public class CommSendService {
         realExamService.updateOneState(re.getExamId(), RealExam.State.SIM_WRITING);
         List<RealExamFault> list = realExamFaultService.listAllType2FlagYesClearedStateByExamId(re.getExamId());
         for (RealExamFault ref : list) {
-            Sim s = simService.selectSimBySimId(re.getSimId());
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
             // 选中的才下发。
-            writeOneSimOneFault(s, ref, f);
+            writeOneSimOneFault(seat, sim, ref, f);
         }
     }
 
-    public void writeOneSimAllSelectFaultByDebug(Sim s, Fault[] faults) {
+    public void writeOneSimAllSelectFaultByDebug(Seat seat, Sim sim, Fault[] faults) {
         for (Fault f : faults) {
-            writeOneSimOneFault(s, null, f);
+            writeOneSimOneFault(seat, sim, null, f);
         }
     }
 
-    public void writeOneSimOneFault(Sim s, RealExamFault ref, Fault f) {
-        l.info("下发故障:getSimId = {},fault.getName = {}", s.getSimId(), f.getName());
+    public void writeOneSimOneFault(Seat seat, Sim sim, RealExamFault ref, Fault f) {
+        l.info("下发故障:getSimId = {},fault.getName = {}", sim.getSimId(), f.getName());
         // todo:ref is null.
         // 下发故障
-        SimMsg smA1 = commBuildService.buildSendMsgWriteFault(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg smA2 = send(smA1, s, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
+        SimMsg smA1 = commBuildService.buildSendMsgWriteFault(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg smA2 = send(smA1, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
     }
 
     /**
      * 开始考试前检查读取。
      *
-     * @param s
+     * @param sim
      * @return
      */
-    public AjaxResult readOneSimAllFaultCheck(Sim s) {
+    public AjaxResult readOneSimAllFaultCheck(Seat seat, Sim sim) {
         Fault q = new Fault();
         q.setFaultType(Fault.Type.REAL_GZBW);
-        q.setSimType(s.getSimType());
+        q.setSimType(sim.getSimType());
         List<Fault> list = faultService.selectFaultList(q);
         List<Fault> listNG = new ArrayList<>();
         for (Fault f : list) {
-            AjaxResult ar = readOneSimOneFaultCheck(s, f);
+            AjaxResult ar = readOneSimOneFaultCheck(seat, sim, f);
             if (ar.isError()) {
                 listNG.add(f);
                 l.info("故障部位[" + f.getBindHardwareMsg() + "][" + f.getReplaceName() + "]未正确安装;");
@@ -686,15 +646,15 @@ public class CommSendService {
     /**
      * 检查读取。
      *
-     * @param s
+     * @param sim
      * @param f
      * @return
      */
-    public AjaxResult readOneSimOneFaultCheck(Sim s, Fault f) {
-        l.info("readOneSimOneFaultCheck s = {},f = {}", s, f);
-        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg sm2 = send(sm1, s, RETRY_COUNT_CHECK_ONE_FAULT, SLEEP_LONG);
-        return simReceiveService.getOneFaultCheck(sm2, s, f);
+    public AjaxResult readOneSimOneFaultCheck(Seat seat, Sim sim, Fault f) {
+        l.info("readOneSimOneFaultCheck sim = {},f = {}", sim, f);
+        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg sm2 = send(sm1, seat, sim, RETRY_COUNT_CHECK_ONE_FAULT, SLEEP_LONG);
+        return simReceiveService.getOneFaultCheck(sm2, sim, f);
     }
 
     /**
@@ -706,91 +666,93 @@ public class CommSendService {
         l.info("readOneSimAllFaultFirstTimeByExam re = {}", re);
         List<RealExamFault> list = realExamFaultService.listAllType2(re.getExamId());
         for (RealExamFault ref : list) {
-            Sim s = simService.selectSimBySimId(re.getSimId());
+            Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
+            Sim sim = simService.selectSimBySimId(re.getSimId());
             Fault f = faultService.selectFaultByFaultId(ref.getFaultId());
-            readOneSimOneFaultFirstTime(s, ref, f, null);
+            readOneSimOneFaultFirstTime(seat, sim, ref, f, null);
         }
     }
 
     /**
      * 第一次读取,作为出题值。debug模式。
      *
-     * @param s
+     * @param sim
      * @param faultIds
      */
-    public void readOneSimAllFaultFirstTimeBySim(Sim s, final String[] faultIds) {
-        l.info("readOneSimAllFaultFirstTimeBySim s = {}", s);
-        List<Fault> list = faultService.listType3(s.getSimType());
+    public void readOneSimAllFaultFirstTimeBySim(Seat seat, Sim sim, final String[] faultIds) {
+        l.info("readOneSimAllFaultFirstTimeBySim s = {}", sim);
+        List<Fault> list = faultService.listType3(sim.getSimType());
         for (Fault f : list) {
-            readOneSimOneFaultFirstTime(s, null, f, faultIds);
+            readOneSimOneFaultFirstTime(seat, sim, null, f, faultIds);
         }
     }
 
     /**
      * 第一次读取,作为出题值。
      *
-     * @param s
+     * @param sim
      * @param ref      debug调试模式为空。可以为空。
      * @param f
      * @param faultIds debug调试模式为空。
      */
-    public void readOneSimOneFaultFirstTime(Sim s, RealExamFault ref, Fault f, String[] faultIds) {
+    public void readOneSimOneFaultFirstTime(Seat seat, Sim sim, RealExamFault ref, Fault f, String[] faultIds) {
         l.info("readOneSimOneFaultFirstTime");
         // 读取一次当前电阻代表值作为出题值。
-        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
-        SimMsg sm2 = send(sm1, s, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
-        simReceiveService.setFaultQuestionValue(sm2, s, ref, f, faultIds);
+        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), f.getBindHardwareMsg());
+        SimMsg sm2 = send(sm1, seat, sim, RETRY_COUNT_WRITE_ONE_FAULT, SLEEP_LONG);
+        simReceiveService.setFaultQuestionValue(sm2, sim, ref, f, faultIds);
     }
 
     /**
      * debug读取一个故障位置数据
      *
-     * @param simNum
+     * @param seatId
      * @param bindHardwareMsg
      * @return
-     * @throws IOException
      */
-    public SimMsg debugReadOneFaultResistance(final String simNum, final String bindHardwareMsg) {
-        SimMsg sm = commBuildService.buildSendMsgReadFaultResistance(simNum, bindHardwareMsg);
-        return send(sm, null, RETRY_COUNT_0, SLEEP_SHORT);
+    public SimMsg debugReadOneFaultResistance(final Long seatId, final String bindHardwareMsg) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        Sim sim = gggSimBySeatId(seatId);
+        SimMsg sm = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), bindHardwareMsg);
+        return send(sm, seat, null, RETRY_COUNT_0, SLEEP_SHORT);
     }
 
     /**
      * debug读取全部故障位置数据
      *
-     * @param simNum
+     * @param seatId
      * @return
-     * @throws IOException
      */
-    public List<SimMsg> debugReadAllFaultResistance(final String simNum) {
+    public List<SimMsg> debugReadAllFaultResistance(final Long seatId) {
+        Sim sim = gggSimBySeatId(seatId);
         List<SimMsg> list = new ArrayList<>();
-        String simType = simService.uniqueBySimNum(simNum).getSimType();
+        String simType = simService.uniqueBySimNum(sim.getSimNum()).getSimType();
         for (String b : getGZBWBySimType(simType)) {
-            list.add(debugReadOneFaultResistance(simNum, b));
+            list.add(debugReadOneFaultResistance(seatId, b));
         }
         return list;
     }
 
     /**
-     * @param s
+     * @param sim
      * @param reF
      * @param f
      * @param refState 中间轮询是null,交卷最后一次读取为finish状态。用来修改状态的。debug模式下执行为null。
      */
-    public void readOneSimOneFaultResistance(Sim s, RealExamFault reF, Fault f, String refState) {
+    public void readOneSimOneFaultResistance(Seat seat, Sim sim, RealExamFault reF, Fault f, String refState) {
         l.info("readOneSimOneFaultResistance");
-        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(s.getSimNum(), f.getBindHardwareMsg());
+        SimMsg sm1 = commBuildService.buildSendMsgReadFaultResistance(sim.getSimNum(), f.getBindHardwareMsg());
         SimMsg sm2 = null;
         if (reF != null && refState != null) {
             if (RealExamFault.State.FINISH.equals(refState)) { // 是否最后一次读取。
-                sm2 = send(sm1, s, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
+                sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
             } else {
-                sm2 = send(sm1, s, RETRY_COUNT_0, SLEEP_SHORT);
+                sm2 = send(sm1, seat, sim, RETRY_COUNT_0, SLEEP_SHORT);
             }
         } else {
-            sm2 = send(sm1, s, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
+            sm2 = send(sm1, seat, sim, RETRY_COUNT_READ_ONE_RESISTANCE, SLEEP_SHORT);
         }
-        simReceiveService.setFaultAnswerValue(sm2, s, reF, f, refState);
+        simReceiveService.setFaultAnswerValue(sm2, sim, reF, f, refState);
     }
 
     private long previousSendSleep = 0;
@@ -799,12 +761,13 @@ public class CommSendService {
      * send hex message
      *
      * @param sm              发送
-     * @param s               可以为空,更新最后发送/接收时间 用。
+     * @param seat            不能为空
+     * @param sim             可以为空!更新最后发送/接收时间 用。
      * @param retryTotalCount 重试次数
      * @param sleep           不使用传入0,不进行挂起。
      * @return
      */
-    public synchronized SimMsg send(final SimMsg sm, final Sim s, final int retryTotalCount, final long sleep) {
+    public synchronized SimMsg send(final SimMsg sm, final Seat seat, final Sim sim, final int retryTotalCount, final long sleep) {
         try {
             if (!config.isCommGlobal()) {
                 l.warn("isCommGlobal == false [模拟器通信被禁用!]");
@@ -813,28 +776,37 @@ public class CommSendService {
             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("####SendMsg#### == [{}]", sm);
+                l.info("####发送#### == Seat[{}],SimMsg[{}]", seat, sm);
             }
             // 如果没有打开socket,顺道打开。正好后面要sleep
-            socketOldService.openSocket();
+            // SocketOldService实现
+            // socketOldService.openSocket();
+            socketService.openOne(seat.toSimSocketVo());
             {
-                // sleep ,追求顺序请求。
+                // sleep挂起线程,追求顺序请求。
                 if (sleep > 0 && previousSendSleep != 0L) {
                     Thread.sleep(previousSendSleep);
                 }
             }
             previousSendSleep = sleep;
-            InputStream is = socketOldService.getCachedSocket().getInputStream();
-            OutputStream os = socketOldService.getCachedSocket().getOutputStream();
+            // SocketOldService实现
+            // Socket socket = socketOldService.getCachedSocket();
+            Socket socket = socketService.get(seat);
+            InputStream is = socket.getInputStream();
+            OutputStream os = socket.getOutputStream();
             os.write(hexStrToByteArrs(sm.getSendMsg()));
             sm.setSendTime(DateUtils.getNowDate());
-            if (s != null) {
-                simService.updateLastSentTime(s);
+            if (sim != null) {
+                simService.updateLastSentTime(sim);
             }
             byte[] buffer = new byte[LENGTH_24];
             int length = is.read(buffer);
@@ -842,31 +814,49 @@ public class CommSendService {
             for (int i = 0; i < length; i++) {
                 sbHex.append(String.format("%02X", buffer[i]));
             }
-            sm.setReceiveMsg(sbHex.toString());
+            String receiveWith0 = sbHex.toString();
+            // 原始带0的收到报文。
+            sm.setReceiveOriginalMsg(receiveWith0);
+            sm.setReceiveMsg(commReceiveService.removeRrefix0(receiveWith0));
             sm.setReceiveTime(DateUtils.getNowDate());
             // log.
             {
-                l.info("####ReceiveMsg#### = [{}]", sm);
-            }
-            {
-                AjaxResult ar =    commReceiveService.checkReceiveMsg(sm.getReceiveMsg());
+                AjaxResult ar = commReceiveService.checkReceiveMsg(sm.getReceiveMsg());
                 if (ar.isError()) {
                     // todo:
-                    l.warn("####Fail#### = {}", sm);
+                    l.warn("####接收错误#### = {}", sm);
+                    sm.setResult(SimMsg.Result.RECEIVE_CHECK_FAIL);
                     return sm;
+                } else {
+                    l.info("####接收成功#### = {}", sm);
                 }
             }
-            if (s != null) {
-                simService.updateLastReceivedTime(s);
+            if (sim != null) {
+                simService.updateLastReceivedTime(sim);
             }
-        } catch (InterruptedException | IOException e) {   // SocketTimeoutException
+            sm.setResult(SimMsg.Result.SUCCESS);
+            // 最后返回报文实体。
+            return sm;
+        } catch (InterruptedException | IOException e) {
+            l.error("SocketTimeoutException");// SocketTimeoutException
             e.printStackTrace();
+            sm.setResult(SimMsg.Result.SOCKET_EXCEPTION);
+            if (sim != null) {
+                l.info("fail sim.getSimId() = {}", sim.getSimId());
+            }
+            // SocketOldService实现
+            // boolean limit = socketOldService.commFailCountAdd1(Objects.requireNonNull(sim).getSimId());
+            SimSocketVo ssv = seat.toSimSocketVo();
             // 失败计数
-            l.info("fail sim data = {}", s);
-            boolean limit = socketOldService.commFailCountAdd1(Objects.requireNonNull(s).getSimId());
-            if (limit) {
-                simService.updateSimStateBySimId(s.getSimId(), Sim.State.OFFLINE);
-                socketOldService.commFailCountClearOne(s.getSimId());
+            failedCountService.plus1(ssv);
+            if (failedCountService.isReachedMax(ssv, retryTotalCount)) {
+                // 达到重试次数上限,认为模拟器离线
+                if (sim != null) {
+                    simService.updateSimStateBySimId(sim.getSimId(), Sim.State.OFFLINE);
+                }
+                // SocketOldService实现
+                // socketOldService.commFailCountClearOne(sim.getSimId());
+                failedCountService.reset0(ssv);
             }
             // 先考虑一台模拟器演示。
             // 进行重试 start
@@ -876,14 +866,15 @@ public class CommSendService {
             }
             if (sm.getRetryCount() < retryTotalCount) {
                 sm.retryCountPlus1();
-                send(sm, s, retryTotalCount, sleep);
+                send(sm, seat, sim, retryTotalCount, sleep);
                 l.warn("####RetryTotalCount重试#### = {}", sm);
             } else {
                 l.warn("####RetryTotalCount达到重试上限#### = {}", sm);
             }
             // 进行重试 end
+            // 最后返回报文实体。
+            return sm;
         }
-        return sm;
     }
 
     /**
@@ -899,28 +890,6 @@ public class CommSendService {
         return null;
     }
 
-
-    /**
-     * 等同于ping命令。
-     *
-     * @param ipV4
-     * @return
-     * @throws IOException
-     */
-    public boolean isReachable(String ipV4) {
-        InetAddress ia = null;
-        try {
-            ia = InetAddress.getByName(ipV4);
-            return ia.isReachable(2048);
-        } catch (UnknownHostException e) {
-            e.printStackTrace();
-            return false;
-        } catch (IOException e) {
-            e.printStackTrace();
-            return false;
-        }
-    }
-
     /**
      * https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j/3.0.3
      */
@@ -972,4 +941,21 @@ public class CommSendService {
         }
         return hexString.toString();
     }
+
+    /**
+     * todo:不存在部分数据
+     *
+     * @param seatId
+     * @return
+     */
+    public Sim gggSimBySeatId(Long seatId) {
+        Seat seat = seatService.selectSeatBySeatId(seatId);
+        AjaxResult ar01 = commCheckService.checkOneSeatState(seat, true);
+        if (ar01.isSuccess()) {
+            Long simId = seatService.selectSeatBySeatId(seatId).getCurrentSimId();
+            return simService.selectSimBySimId(simId);
+        } else {
+            throw new IllegalArgumentException("error gggSimBySeatId");
+        }
+    }
 }

+ 0 - 2
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/DebugFaultService.java

@@ -103,11 +103,9 @@ public class DebugFaultService {
         return null;
     }
 
-    @Transactional
     public int deleteAll() {
         DebugFault q = new DebugFault();
         List<DebugFault> list = selectDebugFaultList(q);
-        Long[] refIds = new Long[list.size()];
         for (int i = 0; i < list.size(); i++) {
             deleteDebugFaultByRefId(list.get(i).getRefId());
         }

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

@@ -1,5 +1,6 @@
 package com.ruoyi.sim.service.impl;
 
+import com.ruoyi.sim.domain.vo.SimSocketVo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -17,7 +18,18 @@ 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 key) {
+    /**
+     * @param key
+     * @param limit include limit
+     * @return
+     */
+    public boolean isReachedMax(final SimSocketVo ssv, final int limit) {
+        final String key = ssv.toKey();
+        return (failedMap.containsKey(key) && failedMap.get(key).get() >= limit);
+    }
+
+    public int plus1(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
         if (!failedMap.containsKey(key)) {
             failedMap.put(key, new AtomicInteger(COUNT_ADD_1));
         } else {
@@ -26,7 +38,8 @@ public class FailedCountService {
         return failedMap.get(key).get();
     }
 
-    public int get(final String key) {
+    public int get(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
         if (failedMap.containsKey(key)) {
             return failedMap.get(key).get();
         } else {
@@ -34,8 +47,11 @@ public class FailedCountService {
         }
     }
 
-    public void reset0(final String key) {
-        failedMap.get(key).set(COUNT_0);
+    public void reset0(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
+        if (failedMap.containsKey(key)) {
+            failedMap.get(key).set(COUNT_0);
+        }
     }
 
     public void resetAll() {

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

@@ -36,7 +36,7 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     @Lazy
     private CommSendService commSendService;
     @Autowired
-    private SocketOldService socketOldService;
+    private SocketService socketService;
 
     /**
      * 查询考试集合
@@ -321,11 +321,15 @@ public class RealExamCollectionService extends Ele6RYBaseService {
             closeAllExamExcludeId(ref.getExamCollectionId());
         }
         // Step 4:尝试打开所有Socket,提前准备,允许有打开失败的
-
-        AjaxResult ar1 = socketOldService.openSocket();
+        // SocketOldService实现。
+        // AjaxResult ar1 = socketOldService.openSocket();
+        //
+        AjaxResult ar1 = socketService.tryOpenAll();
         if (ar1.isError()) {
             return ar1;
         }
+        //
+
         // 更新相关数据
 
 
@@ -344,11 +348,11 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     }
 
     /**
-     * 是否有一个open的考试集合。
+     * 是否有一个open的任何类型的集合。
      *
      * @return
      */
-    public boolean existOpened() {
+    public boolean existAtLeastOneOpened() {
         return (selectRealExamCollectionOpened() != null);
     }
 
@@ -385,7 +389,9 @@ public class RealExamCollectionService extends Ele6RYBaseService {
     }
 
     public AjaxResult closeAll() {
-        return socketOldService.closeSocket();
+        // SocketOldService实现
+        // return socketOldService.closeSocket();
+        return AjaxResult.success();
     }
 
     private void closeAllType(final String type) {

+ 110 - 23
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/RealExamService.java

@@ -13,6 +13,7 @@ import com.ruoyi.sim.domain.vo.RealExamVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamIngVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamPostVo;
 import com.ruoyi.sim.domain.vo.StudentRealExamPreVo;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -113,7 +114,12 @@ public class RealExamService {
     @Lazy
     private CommSendService commSendService;
     @Autowired
+    @Lazy
+    private CommCheckService commCheckService;
+    @Autowired
     private SimConfig simConfig;
+    @Autowired
+    private SocketService socketService;
 
     /**
      * examId 是否有效。
@@ -287,10 +293,12 @@ public class RealExamService {
      * [学生]开始考试
      *
      * @param examId
-     * @return RealExam
+     * @param studentBindIp
+     * @param type
+     * @return
      */
     @Transactional
-    public AjaxResult studentStartRealExam(final Long examId) {
+    public AjaxResult studentStartRealExam(final Long examId, final String studentBindIp, final String type) {
         l.info("studentStartRealExam = {}", examId);
         // todo: 暂时没有解决方案 检查 考试的sim和seat,是否正确对应。
         {
@@ -299,43 +307,120 @@ public class RealExamService {
             // l.info("fake re = {}", re);
         }
         // check id data.
+        // Step :检查参数有效性。
         {
-            AjaxResult arE1 = checkExamId(examId);
-            if (arE1.isError()) {
-                return arE1;
+            AjaxResult ar01 = checkExamId(examId);
+            if (ar01.isError()) {
+                return ar01;
             }
         }
+        if (StringUtils.isBlank(studentBindIp)) {
+            return AjaxResult.error("ip地址无效。");
+        }
         RealExam re = selectRealExamByExamId(examId);
-        Sim s = simService.selectSimBySimId(re.getSimId());
-        // Step 1 主动查询一次模拟器状态。获取模拟器在线/离线状态。
+        Seat seat = seatService.uniqueByBindIp(studentBindIp);
         {
-            AjaxResult arE3 = commSendService.checkOneSimStateActive(s);
-            if (arE3.isError()) {
-                return arE3;
+            if (seat == null) {
+                throw new IllegalArgumentException("XXX");
             }
         }
-        s = simService.selectSimBySimId(re.getSimId());
-        // Step 2 读取对应一台模拟器 所有故障部位值。检查模拟器所有的 真实的 故障部位 是否异常 或者 空值。特殊的故障部位要单独判断。
+        // Step :ping通 路由器。
+        {
+            AjaxResult ar = commCheckService.checkRouterState(simConfig.getRouterIp());
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step :ping通 学员端电脑。
+        {
+            AjaxResult ar = commCheckService.checkPingStudentPcState(studentBindIp);
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step :ping通 RS485。
+        {
+            AjaxResult ar = commCheckService.checkPingRs485State(seat.getSeatRs485Ip());
+            if (ar.isError()) {
+                // todo:重复
+                // 更新SimId
+                seatService.updateSimIdBySeatNum(seat.getSeatNum(), Seat.ID_0);
+                // 更新SocketState
+                seatService.updateSocketStateBySeatNum(seat.getSeatNum(), Seat.SocketState.OFFLINE);
+                return ar;
+            } else {
+                // Ping通不代表在线,Socket连接建立表示在线。
+            }
+        }
+        // Step :如果有缓存Socket并且可用,使用缓存Socket,检查并建立Socket连接;否则返回对应错误。
+        {
+            AjaxResult ar = socketService.openOne(seat.toSimSocketVo());
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step :发送通用询问指令,询问是连接的哪种型号的哪一台模拟器;否则返回对应错误。
+        {
+            AjaxResult ar = commCheckService.checkOneSeatState(seat, true);
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // 重新查询。已经确定simId了。
+        {
+            // 修改exam表对应examId的一条数据,填充并锁定seat_id和sim_id值。
+            // 设置上seatId和simId
+            re = selectRealExamByExamId(examId);
+            seat = seatService.uniqueByBindIp(studentBindIp);
+            re.setSeatId(seat.getSeatId());
+            re.setSimId(seat.getCurrentSimId());
+            l.debug("re = {}", re);
+            updateRealExam(re);
+        }
+        // 查询模拟器在线状态,纯DB查询。
+        {
+            AjaxResult ar = commCheckService.checkOneSimOnlineState(seat.getCurrentSimId());
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        Sim sim = simService.selectSimBySimId(re.getSimId());
+        // 检查模拟器类型
+        {
+            String targetSimType = re.getSimType();
+            AjaxResult ar = commCheckService.checkOneSimType(seat, true, targetSimType);
+            if (ar.isError()) {
+                return ar;
+            }
+        }
+        // Step 5:
+        {
+//            AjaxResult arE3 = commSendService.checkOneSimStateActive(seat);
+//            if (arE3.isError()) {
+//                return arE3;
+//            }
+        }
+        // Step 7:可换件检查,读取对应一台模拟器 所有故障部位值。检查模拟器所有的 真实的 故障部位 是否异常 或者 空值。特殊的故障部位要单独判断。
         if (SimDebugConfig.CHECK_REPLACE_EMPTY) {
-            AjaxResult arE2 = commSendService.readOneSimAllFaultCheck(s);
+            AjaxResult arE2 = commSendService.readOneSimAllFaultCheck(seat, sim);
             if (arE2.isError()) {
                 return arE2;
             }
         }
-        // Step 3 清除对应一台模拟器 所有 真实的 故障部位故障。
+        // Step 8:清除对应一台模拟器 所有 真实的 故障部位故障。
         {
             commSendService.clearOneSimAllFaultByExam(re);
         }
-        // Step 4 下发对应一台模拟器 出题选中的 故障位置故障。
+        // Step 9:下发对应一台模拟器 出题选中的 故障位置故障。
         {
             commSendService.writeOneSimAllSelectFaultByExam(re);
         }
-        // Step 5 读取对应一台模拟器 所有的 真实的 故障部位 电阻值代表值 作为出题值。
+        // Step 10:读取对应一台模拟器 所有的 真实的 故障部位 电阻值代表值 作为出题值。
         // 修改关联状态
         {
             commSendService.readOneSimAllFaultFirstTimeByExam(re);
         }
-        // Step 6 修改当前Exam状态。
+        // Step 11:修改当前exam_id状态。
         if (realExamFaultService.isType2ExamPrepareStartOk(re.getExamId())) {
             updateOneState(re.getExamId(), RealExam.State.SIM_PREPARE_OK);
             updateOneState(re.getExamId(), RealExam.State.ANSWERING);
@@ -358,13 +443,15 @@ public class RealExamService {
         }
         RealExam re = selectRealExamByExamId(examId);
         // 检查 seat_id 是否正确存在
-        {
+        // Java后端处理填写,不检查。
+        if (false) {
             if (!seatService.exist(re.getSeatId())) {
                 return AjaxResult.error("对应座Id不存在!");
             }
         }
+        // Java后端处理填写,不检查。
         // 检查 sim_id 是否正确存在
-        {
+        if (false) {
             if (!simService.existBySimId(re.getSimId())) {
                 return AjaxResult.error("对应模拟器Id不存在!");
             }
@@ -418,7 +505,7 @@ public class RealExamService {
     }
 
     /**
-     * [学生]交卷
+     * [学生]交卷考试
      *
      * @param examId
      * @return RealExam
@@ -438,15 +525,15 @@ public class RealExamService {
             }
         }
         // 检查一下模拟器状态。
-        Sim s = simService.selectSimBySimId(re.getSimId());
+        Seat seat = seatService.selectSeatBySeatId(re.getSeatId());
         // 如果模拟器离线
         {
-            AjaxResult arE3 = commSendService.checkOneSimStateActive(s);
+            // AjaxResult arE3 = commSendService.checkOneSimStateActive(seat);
+            AjaxResult arE3 = commCheckService.checkOneSeatState(seat, true);
             if (arE3.isError()) {
                 return arE3;
             }
         }
-        s = simService.selectSimBySimId(re.getSimId());
         // 最后读取一下模拟器电阻值。
         commSendService.readOneExamAtLast(re);
         if (realExamFaultService.isType2ExamPrepareSubmitOk(re.getExamId())) {

+ 111 - 1
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SeatService.java

@@ -1,8 +1,13 @@
 package com.ruoyi.sim.service.impl;
 
-import java.util.List;
+import java.util.*;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.sim.domain.Sim;
+import com.ruoyi.sim.domain.vo.SeatVo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.ruoyi.sim.mapper.SeatMapper;
@@ -82,6 +87,8 @@ public class SeatService {
     }
 
     // -------------------------------- tom add  --------------------------------
+    @Autowired
+    private SimService simService;
 
     public boolean exist(Long seatId) {
         if (seatId == null) {
@@ -96,4 +103,107 @@ public class SeatService {
         }
         return true;
     }
+
+    public List<Seat> listAllEnable() {
+        List<Seat> list = new ArrayList<>();
+        seatMapper.selectSeatList(new Seat()).stream().filter(Objects::nonNull).filter(s -> !StringUtils.equals(Seat.SocketState.DISABLE, s.getSeatRs485SocketState())).forEach(list::add);
+        return list;
+    }
+
+    /**
+     * 获取所有没有被禁用的 座 列表
+     *
+     * @return
+     */
+    public AjaxResult listAllEnableAj() {
+        List<Seat> list1 = listAllEnable();
+        List<SeatVo> list2 = new ArrayList<>();
+        for (Seat seat : list1) {
+            SeatVo vo = new SeatVo();
+            BeanUtils.copyProperties(seat, vo);
+            Sim sim = simService.selectSimBySimId(seat.getCurrentSimId());
+            if (seat != null && seat.getCurrentSimId() != null && seat.getCurrentSimId() != 0L) {
+                BeanUtils.copyProperties(sim, vo);
+            }
+            list2.add(vo);
+        }
+        return AjaxResult.success(list2);
+    }
+
+    public Seat uniqueBySeatNum(final Integer seatNum) {
+        Seat q = new Seat();
+        q.setSeatNum(seatNum);
+        List<Seat> list = seatMapper.selectSeatList(q);
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
+            return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Seat数据错误。");
+        }
+    }
+
+    public Seat uniqueByBindIp(final String bindIp) {
+        Seat q = new Seat();
+        q.setSeatBindIp(bindIp);
+        List<Seat> list = seatMapper.selectSeatList(q);
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
+            return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Seat数据错误。");
+        }
+    }
+
+    public Seat uniqueByRs485IpAndPort(final String rs485Ip, final int rs485Port) {
+        Seat q = new Seat();
+        q.setSeatRs485Ip(rs485Ip);
+        q.setSeatRs485Port(rs485Port);
+        List<Seat> list = seatMapper.selectSeatList(q);
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
+            return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Seat数据错误。");
+        }
+    }
+
+    public void updateSimIdBySeatNum(final Integer seatNum, final Long simId) {
+        Seat f = uniqueBySeatNum(seatNum);
+        f.setCurrentSimId(simId);
+        updateSeat(f);
+    }
+
+    public int updateAllEnableState(final String socketState) {
+        List<Seat> list = listAllEnable();
+        for (Seat seat : list) {
+            seat.setSeatRs485SocketState(socketState);
+            seatMapper.updateSeat(seat);
+        }
+        return list.size();
+    }
+
+    public List<String> listAllRs485Ip() {
+        List<Seat> list = listAllEnable();
+        Set<String> ips = new HashSet<>();
+        list.forEach(seat -> ips.add(seat.getSeatRs485Ip()));
+        return ips.stream().toList();
+    }
+
+    public void updateSocketStateBySeatNum(final Integer seatNum, final String socketState) {
+        Seat f = uniqueBySeatNum(seatNum);
+        f.setSeatRs485SocketState(socketState);
+        updateSeat(f);
+    }
+
+    public void updateSocketStateByRs485Ip(final String rs485Ip, final String socketState) {
+        Seat q = new Seat();
+        q.setSeatRs485Ip(rs485Ip);
+        selectSeatList(q).forEach(seat -> {
+            seat.setSeatRs485SocketState(socketState);
+            seatMapper.updateSeat(seat);
+        });
+    }
 }

+ 18 - 8
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SimService.java

@@ -91,7 +91,12 @@ public class SimService {
     }
 
     // -------------------------------- tom add  --------------------------------
+    private static final Logger l = LoggerFactory.getLogger(SimService.class);
 
+    /**
+     * @param simId
+     * @return true:存在,false:不存在
+     */
     public boolean existBySimId(final Long simId) {
         if (simId == null) {
             return false;
@@ -128,10 +133,13 @@ public class SimService {
         Sim q = new Sim();
         q.setSimNum(simNum);
         List<Sim> list = simMapper.selectSimList(q);
-        if (!list.isEmpty()) {
+        if (list.isEmpty()) {
+            return null;
+        } else if (list.size() == 1) {
             return list.get(0);
+        } else {
+            throw new IllegalArgumentException("Sim数据错误。");
         }
-        return null;
     }
 
     public List<Sim> listAll() {
@@ -152,7 +160,7 @@ public class SimService {
         listAll()
                 .stream()
                 .filter(Objects::nonNull)
-                .filter(s -> !Sim.State.DISABLE.equals(s.getSimState()))
+                .filter(s -> !StringUtils.equals(Sim.State.DISABLE, s.getSimState()))
                 .forEach(list::add);
         return list;
     }
@@ -205,16 +213,14 @@ public class SimService {
     }
 
     public boolean checkState(String simState) {
-        return Sim.STATE_SET.contains(simState);
+        return !Sim.STATE_SET.contains(simState);
     }
 
-    private static final Logger l = LoggerFactory.getLogger(SimService.class);
-
     @Transactional
-    public int updateSimStateBySimId(Long simId, String simState) {
+    public int updateSimStateBySimId(final Long simId, final String simState) {
         // check
         if (checkState(simState)) {
-
+            throw new IllegalArgumentException("simState wrong!");
         }
         //
         Sim q = selectSimBySimId(simId);
@@ -225,6 +231,10 @@ public class SimService {
         return updateSim(q);
     }
 
+    public int updateSimStateBySimNum(final String simNum, final String simState) {
+        return updateSimStateBySimId(uniqueBySimNum(simNum).getSimId(), simState);
+    }
+
     public boolean isSimStateBySimId(Long simId, String simState) {
         // check
         if (checkState(simState)) {

+ 87 - 58
ruoyi-sim/src/main/java/com/ruoyi/sim/service/impl/SocketService.java

@@ -2,10 +2,9 @@ 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.Seat;
 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;
@@ -14,6 +13,7 @@ import org.springframework.stereotype.Service;
 import java.io.IOException;
 import java.net.Socket;
 import java.util.HashMap;
+import java.util.List;
 
 import static com.ruoyi.sim.constant.CommConst.SOCKET_TIME_OUT;
 
@@ -26,11 +26,13 @@ public class SocketService {
 
     private static final Logger l = LoggerFactory.getLogger(SocketService.class);
 
-    private static final int INIT_SIZE = 16;
+    private static final int INIT_SIZE = 32;
     /**
      * 6 hours
+     * 1000L * 60 * 60 * 6
+     * 1000L * 60 * 5
      */
-    private static final long LIMIT = 1000L * 60 * 60 * 6;
+    private static final long TIMEOUT_LIMIT = 1000L * 60 * 60 * 6;
 
     /**
      * key: ip:port
@@ -42,15 +44,17 @@ public class SocketService {
     private SimConfig config;
     @Autowired
     private FailedCountService failedCountService;
+    @Autowired
+    private SeatService seatService;
 
     /**
-     * @param ip   ip v4.
-     * @param port
+     * @param ssv
      * @return true:socket ok!
      */
-    public boolean isOk(final String ip, final Integer port) {
-        if (cachedMap.containsKey(ip)) {
-            Socket s = cachedMap.get(buildKey(ip, port)).getSocket();
+    public boolean isOk(final SimSocketVo ssv) {
+        final String key = ssv.toKey();
+        if (cachedMap.containsKey(key) && cachedMap.get(key) != null) {
+            Socket s = cachedMap.get(key).getSocket();
             if (s != null) {
                 return (s.isConnected() && s.isBound() && !s.isClosed());
             }
@@ -58,19 +62,26 @@ public class SocketService {
         return false;
     }
 
+    public boolean isNotOk(final SimSocketVo ssv) {
+        return !isOk(ssv);
+    }
+
     /**
-     * @param ip   ip v4.
-     * @param port
+     * 暂时不考虑超时周期。
+     * todo:
+     *
+     * @param ssv
      * @return
      */
-    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");
+    public boolean isTimeout(SimSocketVo ssv) {
+        if (cachedMap.containsKey(ssv.toKey())) {
+            Long cached = cachedMap.get(ssv.toKey()).getOkTimeMillis();
+            if (cached == null || cached == 0L) {
+                return true;
+            }
+            return System.currentTimeMillis() - cached <= TIMEOUT_LIMIT;
         }
-        return ip + ":" + port;
+        return true;
     }
 
     /**
@@ -85,84 +96,102 @@ public class SocketService {
         }
         //
         try {
-            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);
+            if (isNotOk(ssv)) {
+                final String key = ssv.toKey();
+                l.info("openSocket cachedSocket is not ok!try new socket ip = {}:{}!", ssv.getIp(), ssv.getPort());
                 closeOne(ssv);
-                Socket s = new Socket(ip, port);
+                Socket s = new Socket(ssv.getIp(), ssv.getPort());
                 s.setSoTimeout(SOCKET_TIME_OUT);
-                SocketWrapValue value = new SocketWrapValue(ip, port, s, System.currentTimeMillis());
+                SocketWrapValue value = new SocketWrapValue(ssv.getIp(), ssv.getPort(), s, System.currentTimeMillis());
                 cachedMap.put(key, value);
                 // failed count reset.
-                failedCountService.reset0(key);
+                failedCountService.reset0(ssv);
+            } else {
+                l.info("openSocket cachedSocket cache ok!cached socket ip = {}:{}!", ssv.getIp(), ssv.getPort());
             }
+            Seat seat = seatService.uniqueByRs485IpAndPort(ssv.getIp(), ssv.getPort());
+            seat.setSeatRs485SocketState(Seat.SocketState.ONLINE);
+            seatService.updateSeat(seat);
+            return AjaxResult.success("Socket[" + ssv.getIp() + ":" + ssv.getPort() + "],创建成功!");
         } catch (IOException e) {
-            throw new RuntimeException(e);
-        } finally {
-
+            l.error(ssv.toString());
+            return AjaxResult.error("Socket[" + ssv.getIp() + ":" + ssv.getPort() + "],创建失败!");
         }
-        return AjaxResult.success("openOneSocket Success!");
     }
 
-
-
     /**
      * todo:部分返回Aj结果。
      *
-     * @param ssvs
      * @return
      */
-    public AjaxResult openAll(final SimSocketVo[] ssvs) {
+    public AjaxResult tryOpenAll() {
         if (!config.isCommGlobal()) {
             l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
             return AjaxResult.error("模拟器通信被禁用!");
         }
-        for (SimSocketVo ssv : ssvs) {
-            openOne(ssv);
+        List<Seat> allSeat = seatService.listAllEnable();
+        for (Seat s : allSeat) {
+            openOne(s.toSimSocketVo());
         }
-        return AjaxResult.success("openAllSocket Success!");
+        return AjaxResult.success("所有Socket,创建成功!");
     }
 
     public AjaxResult closeOne(final SimSocketVo ssv) {
-        String msgOk = "关闭Socket成功!";
         if (!config.isCommGlobal()) {
             l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
             return AjaxResult.error("模拟器通信被禁用!");
         }
-        final String key = buildKey(ssv.getIp(), ssv.getPort());
-        if (!cachedMap.containsKey(key)) {
-            cachedMap.remove(key);
-            return AjaxResult.success(msgOk);
-        } else {
-            try {
+        String msgOk = "关闭Socket成功!";
+        final String key = ssv.toKey();
+        try {
+            if (cachedMap.containsKey(key)) {
                 Socket s = cachedMap.get(key).getSocket();
                 s.getInputStream().close();
                 s.getOutputStream().close();
                 s.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            } finally {
-                cachedMap.remove(key);
-                // failed count reset.
-                failedCountService.reset0(key);
-                return AjaxResult.success(msgOk);
             }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            cachedMap.remove(key);
+            // failed count reset.
+            failedCountService.reset0(ssv);
+            return AjaxResult.success(msgOk);
         }
     }
 
-    public AjaxResult closeAll(final SimSocketVo[] ssvs) {
-        String msgOk = "关闭所有Socket成功!";
-        for (SimSocketVo ssv : ssvs) {
-            AjaxResult ar = closeOne(ssv);
-            if (ar != null && !ar.isSuccess()) {
-                return ar;
-            }
+    public AjaxResult closeAll() {
+        if (!config.isCommGlobal()) {
+            l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
+            return AjaxResult.error("模拟器通信被禁用!");
+        }
+        final String msgOk = "关闭所有Socket成功!";
+        List<Seat> allSeat = seatService.listAllEnable();
+        for (Seat s : allSeat) {
+            closeOne(new SimSocketVo(s.getSeatRs485Ip(), s.getSeatRs485Port()));
         }
+        failedCountService.resetAll();
         return AjaxResult.success(msgOk);
     }
 
+    public Socket get(final Seat seat) {
+        if (seat == null) {
+            throw new IllegalArgumentException("seat is null");
+        }
+        return get(new SimSocketVo(seat.getSeatRs485Ip(), seat.getSeatRs485Port()));
+    }
+
+    /**
+     * @param ssv
+     * @return
+     */
+    public Socket get(final SimSocketVo ssv) {
+        if (isNotOk(ssv)) {
+            openOne(ssv);
+        }
+        return cachedMap.get(ssv.toKey()).getSocket();
+    }
+
     /**
      * 初始化。
      */

+ 7 - 1
ruoyi-sim/src/main/resources/mapper/sim/RealExamMapper.xml

@@ -7,6 +7,7 @@
     <resultMap type="RealExam" id="RealExamResult">
         <result property="examId" column="exam_id"/>
         <result property="examCollectionId" column="exam_collection_id"/>
+        <result property="simType" column="sim_type"/>
         <result property="userId" column="user_id"/>
         <result property="seatId" column="seat_id"/>
         <result property="simId" column="sim_id"/>
@@ -27,6 +28,7 @@
     <sql id="selectRealExamVo">
         select exam_id,
                exam_collection_id,
+               sim_type,
                user_id,
                seat_id,
                sim_id,
@@ -49,6 +51,7 @@
         <include refid="selectRealExamVo"/>
         <where>
             <if test="examCollectionId != null ">and exam_collection_id = #{examCollectionId}</if>
+            <if test="simType != null  and simType != ''">and sim_type = #{simType}</if>
             <if test="userId != null ">and user_id = #{userId}</if>
             <if test="seatId != null ">and seat_id = #{seatId}</if>
             <if test="simId != null ">and sim_id = #{simId}</if>
@@ -68,13 +71,14 @@
     </select>
 
     <insert id="insertRealExam" parameterType="RealExam">
-        <selectKey keyProperty="examId" order="AFTER" resultType="java.lang.Long">
+        <selectKey keyProperty="exam_id" order="AFTER" resultType="java.lang.Long">
             select LAST_INSERT_ID()
         </selectKey>
         insert into mx_real_exam
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="examId != null">exam_id,</if>
             <if test="examCollectionId != null">exam_collection_id,</if>
+            <if test="simType != null">sim_type,</if>
             <if test="userId != null">user_id,</if>
             <if test="seatId != null">seat_id,</if>
             <if test="simId != null">sim_id,</if>
@@ -94,6 +98,7 @@
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="examId != null">#{examId},</if>
             <if test="examCollectionId != null">#{examCollectionId},</if>
+            <if test="simType != null">#{simType},</if>
             <if test="userId != null">#{userId},</if>
             <if test="seatId != null">#{seatId},</if>
             <if test="simId != null">#{simId},</if>
@@ -116,6 +121,7 @@
         update mx_real_exam
         <trim prefix="SET" suffixOverrides=",">
             <if test="examCollectionId != null">exam_collection_id = #{examCollectionId},</if>
+            <if test="simType != null">sim_type = #{simType},</if>
             <if test="userId != null">user_id = #{userId},</if>
             <if test="seatId != null">seat_id = #{seatId},</if>
             <if test="simId != null">sim_id = #{simId},</if>

+ 11 - 1
ruoyi-sim/src/main/resources/mapper/sim/SeatMapper.xml

@@ -10,6 +10,7 @@
         <result property="seatBindIp" column="seat_bind_ip"/>
         <result property="seatRs485Ip" column="seat_rs485_ip"/>
         <result property="seatRs485Port" column="seat_rs485_port"/>
+        <result property="seatRs485SocketState" column="seat_rs485_socket_state"/>
         <result property="currentUserId" column="current_user_id"/>
         <result property="currentSimId" column="current_sim_id"/>
         <result property="createBy" column="create_by"/>
@@ -25,6 +26,7 @@
                seat_bind_ip,
                seat_rs485_ip,
                seat_rs485_port,
+               seat_rs485_socket_state,
                current_user_id,
                current_sim_id,
                create_by,
@@ -42,6 +44,9 @@
             <if test="seatBindIp != null  and seatBindIp != ''">and seat_bind_ip = #{seatBindIp}</if>
             <if test="seatRs485Ip != null  and seatRs485Ip != ''">and seat_rs485_ip = #{seatRs485Ip}</if>
             <if test="seatRs485Port != null ">and seat_rs485_port = #{seatRs485Port}</if>
+            <if test="seatRs485SocketState != null  and seatRs485SocketState != ''">and seat_rs485_socket_state =
+                #{seatRs485SocketState}
+            </if>
             <if test="currentUserId != null ">and current_user_id = #{currentUserId}</if>
             <if test="currentSimId != null ">and current_sim_id = #{currentSimId}</if>
         </where>
@@ -53,7 +58,7 @@
     </select>
 
     <insert id="insertSeat" parameterType="Seat" useGeneratedKeys="true" keyProperty="seatId">
-        <selectKey keyProperty="seatId" order="AFTER" resultType="java.lang.Long">
+        <selectKey keyProperty="seat_id" order="AFTER" resultType="java.lang.Long">
             select LAST_INSERT_ID()
         </selectKey>
         insert into mx_seat
@@ -62,6 +67,7 @@
             <if test="seatBindIp != null and seatBindIp != ''">seat_bind_ip,</if>
             <if test="seatRs485Ip != null and seatRs485Ip != ''">seat_rs485_ip,</if>
             <if test="seatRs485Port != null">seat_rs485_port,</if>
+            <if test="seatRs485SocketState != null and seatRs485SocketState != ''">seat_rs485_socket_state,</if>
             <if test="currentUserId != null">current_user_id,</if>
             <if test="currentSimId != null">current_sim_id,</if>
             <if test="createBy != null">create_by,</if>
@@ -75,6 +81,7 @@
             <if test="seatBindIp != null and seatBindIp != ''">#{seatBindIp},</if>
             <if test="seatRs485Ip != null and seatRs485Ip != ''">#{seatRs485Ip},</if>
             <if test="seatRs485Port != null">#{seatRs485Port},</if>
+            <if test="seatRs485SocketState != null and seatRs485SocketState != ''">#{seatRs485SocketState},</if>
             <if test="currentUserId != null">#{currentUserId},</if>
             <if test="currentSimId != null">#{currentSimId},</if>
             <if test="createBy != null">#{createBy},</if>
@@ -92,6 +99,9 @@
             <if test="seatBindIp != null and seatBindIp != ''">seat_bind_ip = #{seatBindIp},</if>
             <if test="seatRs485Ip != null and seatRs485Ip != ''">seat_rs485_ip = #{seatRs485Ip},</if>
             <if test="seatRs485Port != null">seat_rs485_port = #{seatRs485Port},</if>
+            <if test="seatRs485SocketState != null and seatRs485SocketState != ''">seat_rs485_socket_state =
+                #{seatRs485SocketState},
+            </if>
             <if test="currentUserId != null">current_user_id = #{currentUserId},</if>
             <if test="currentSimId != null">current_sim_id = #{currentSimId},</if>
             <if test="createBy != null">create_by = #{createBy},</if>