diff --git a/exam-framework/exam-spring-boot-starter-websocket/src/main/java/pc/exam/pp/framework/websocket/core/sender/AbstractWebSocketMessageSender.java b/exam-framework/exam-spring-boot-starter-websocket/src/main/java/pc/exam/pp/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
index 17449571..f3b42a20 100644
--- a/exam-framework/exam-spring-boot-starter-websocket/src/main/java/pc/exam/pp/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
+++ b/exam-framework/exam-spring-boot-starter-websocket/src/main/java/pc/exam/pp/framework/websocket/core/sender/AbstractWebSocketMessageSender.java
@@ -86,19 +86,19 @@ public abstract class AbstractWebSocketMessageSender implements WebSocketMessage
sessions.forEach(session -> {
// 1. 各种校验,保证 Session 可以被发送
if (session == null) {
- log.error("[doSend][session 为空, message({})]", message);
+ // log.error("[doSend][session 为空, message({})]", message);
return;
}
if (!session.isOpen()) {
- log.error("[doSend][session({}) 已关闭, message({})]", session.getId(), message);
+ // log.error("[doSend][session({}) 已关闭, message({})]", session.getId(), message);
return;
}
// 2. 执行发送
try {
session.sendMessage(new TextMessage(payload));
- log.info("[doSend][session({}) 发送消息成功,message({})]", session.getId(), message);
+ // log.info("[doSend][session({}) 发送消息成功,message({})]", session.getId(), message);
} catch (IOException ex) {
- log.error("[doSend][session({}) 发送消息失败,message({})]", session.getId(), message, ex);
+ // log.error("[doSend][session({}) 发送消息失败,message({})]", session.getId(), message, ex);
}
});
}
diff --git a/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/paper/EducationPaperParamController.java b/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/paper/EducationPaperParamController.java
index 8d1f1f08..36c9a5ec 100644
--- a/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/paper/EducationPaperParamController.java
+++ b/exam-module-exam/exam-module-exam-biz/src/main/java/pc/exam/pp/module/exam/controller/admin/paper/EducationPaperParamController.java
@@ -9,7 +9,9 @@ import org.springframework.web.bind.annotation.*;
import pc.exam.pp.framework.common.pojo.CommonResult;
import pc.exam.pp.module.exam.dal.dataobject.EducationPaperParam;
import pc.exam.pp.module.exam.service.paper.IEducationPaperParamService;
+
import static pc.exam.pp.module.infra.enums.ErrorCodeConstants.DEMO03_PAPER_SESSION_EXISTS;
+
/**
* 通用参数Controller
*
@@ -19,23 +21,18 @@ import static pc.exam.pp.module.infra.enums.ErrorCodeConstants.DEMO03_PAPER_SESS
@Tag(name = "管理后台 - 试卷通用参数")
@RestController
@RequestMapping("/exam/param")
-public class EducationPaperParamController
-{
+public class EducationPaperParamController {
@Autowired
private IEducationPaperParamService educationPaperParamService;
-
-
-
/**
* 获取通用参数详细信息
*/
@Operation(summary = "获取通用参数详细信息")
@GetMapping(value = "/getInfo")
- public CommonResult getInfo(@RequestParam("taskId") String taskId)
- {
- EducationPaperParam educationPaperParam= educationPaperParamService.selectEducationPaperParamByTaskId(taskId);
+ public CommonResult getInfo(@RequestParam("taskId") String taskId) {
+ EducationPaperParam educationPaperParam = educationPaperParamService.selectEducationPaperParamByTaskId(taskId);
return CommonResult.success(educationPaperParam);
}
@@ -44,8 +41,7 @@ public class EducationPaperParamController
*/
@Operation(summary = "新增通用参数")
@PostMapping
- public CommonResult add(@RequestBody EducationPaperParam educationPaperParam)
- {
+ public CommonResult add(@RequestBody EducationPaperParam educationPaperParam) {
return CommonResult.success(educationPaperParamService.insertEducationPaperParam(educationPaperParam));
}
@@ -54,8 +50,7 @@ public class EducationPaperParamController
*/
@Operation(summary = "修改通用参数")
@PutMapping
- public CommonResult edit(@RequestBody EducationPaperParam educationPaperParam)
- {
+ public CommonResult edit(@RequestBody EducationPaperParam educationPaperParam) {
return CommonResult.success(educationPaperParamService.updateEducationPaperParam(educationPaperParam));
}
@@ -64,43 +59,45 @@ public class EducationPaperParamController
*/
@Operation(summary = "删除通用参数")
@DeleteMapping("/{paramIds}")
- public CommonResult remove(@PathVariable String[] paramIds)
- {
+ public CommonResult remove(@PathVariable String[] paramIds) {
return CommonResult.success(educationPaperParamService.deleteEducationPaperParamByParamIds(paramIds));
}
/**
* 步骤条 (考场)
+ *
* @param taskId
* @return
*/
@Operation(summary = "步骤条 (考场)警告")
- @GetMapping( "/check-can-enter-step4/{taskId}")
- public CommonResult ckeckSession(@PathVariable("taskId") String taskId){
+ @GetMapping("/check-can-enter-step4/{taskId}")
+ public CommonResult ckeckSession(@PathVariable("taskId") String taskId) {
EducationPaperParam educationPaperParam = educationPaperParamService.selectEducationPaperParamByTaskId(taskId);
- if (educationPaperParam==null){
+ if (educationPaperParam == null) {
return CommonResult.error(DEMO03_PAPER_SESSION_EXISTS);
}
String isSession = educationPaperParam.getIsSession();
- if ("1".equals(isSession)){
+ if ("1".equals(isSession)) {
return CommonResult.error(DEMO03_PAPER_SESSION_EXISTS);
}
- return CommonResult.success("200");
+ return CommonResult.success("200");
}
+
/**
* 步骤条 (考场)
+ *
* @param taskId
* @return
*/
@Operation(summary = "步骤条 (考场)无警告")
- @GetMapping( "/check-can-enter-step4NoMsg/{taskId}")
- public CommonResult ckeckSessionNoMsg(@PathVariable("taskId") String taskId){
+ @GetMapping("/check-can-enter-step4NoMsg/{taskId}")
+ public CommonResult ckeckSessionNoMsg(@PathVariable("taskId") String taskId) {
EducationPaperParam educationPaperParam = educationPaperParamService.selectEducationPaperParamByTaskId(taskId);
- if (educationPaperParam==null){
+ if (educationPaperParam == null) {
return CommonResult.success("1_001_401_001");
}
String isSession = educationPaperParam.getIsSession();
- if ("1".equals(isSession)){
+ if ("1".equals(isSession)) {
return CommonResult.success("1_001_401_001");
}
return CommonResult.success("200");
diff --git a/exam-module-judgement/exam-module-judgement-biz/pom.xml b/exam-module-judgement/exam-module-judgement-biz/pom.xml
index fd13f8ac..c11c1b7d 100644
--- a/exam-module-judgement/exam-module-judgement-biz/pom.xml
+++ b/exam-module-judgement/exam-module-judgement-biz/pom.xml
@@ -185,6 +185,13 @@
3.0.1
+
+ jakarta.annotation
+ jakarta.annotation-api
+ 2.1.1
+
+
+
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/AutoToolsController.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/AutoToolsController.java
index d7bd9907..82a77a21 100644
--- a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/AutoToolsController.java
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/AutoToolsController.java
@@ -4,23 +4,34 @@ package pc.exam.pp.module.judgement.controller.admin.autoTools;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import pc.exam.pp.framework.common.pojo.CommonResult;
+import pc.exam.pp.framework.common.util.servlet.ServletUtils;
+import pc.exam.pp.framework.security.config.SecurityProperties;
+import pc.exam.pp.framework.security.core.util.SecurityFrameworkUtils;
+import pc.exam.pp.module.exam.dal.dataobject.EducationPaperParam;
+import pc.exam.pp.module.exam.dal.dataobject.EducationPaperTask;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestion;
import pc.exam.pp.module.exam.dal.dataobject.student.StuPaperScoreDO;
import pc.exam.pp.module.exam.dal.mysql.paper.EducationPaperQuMapper;
import pc.exam.pp.module.exam.dal.mysql.question.ExamQuestionMapper;
import pc.exam.pp.module.exam.dal.mysql.student.StuScoreVo;
+import pc.exam.pp.module.exam.service.paper.IEducationPaperParamService;
+import pc.exam.pp.module.exam.service.paper.IEducationPaperTaskService;
import pc.exam.pp.module.exam.service.stuPaperScore.StuPaperScoreService;
+import pc.exam.pp.module.judgement.controller.admin.autoTools.vo.StuInTheExam;
import pc.exam.pp.module.judgement.controller.admin.autoTools.vo.StuPaperReqVo;
import pc.exam.pp.module.judgement.controller.admin.autoTools.vo.StuPaperScoreInfoVo;
+import pc.exam.pp.module.judgement.controller.admin.autoTools.vo.StuTheExamInfo;
+import pc.exam.pp.module.judgement.service.TaskManager;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -38,6 +49,14 @@ public class AutoToolsController {
EducationPaperQuMapper educationPaperQuMapper;
@Resource
ExamQuestionMapper examQuestionMapper;
+ @Resource
+ TaskManager taskManager;
+ @Resource
+ SecurityProperties securityProperties;
+ @Autowired
+ IEducationPaperTaskService educationPaperTaskService;
+ @Autowired
+ IEducationPaperParamService educationPaperParamService;
@GetMapping("/getStuScoreInfo")
@Operation(summary = "通过学生ID、试卷ID获取")
@@ -70,4 +89,59 @@ public class AutoToolsController {
stuPaperScoreInfoVos.setPaperAnalysis(judgementStr);
return CommonResult.success(stuPaperScoreInfoVos);
}
+
+ /**
+ * 开始考试 通过websocket进行传输时间
+ * @param stuInTheExam 信息
+ * @return true
+ */
+ @PostMapping("/startExam")
+ public CommonResult startExam(@RequestBody StuInTheExam stuInTheExam) {
+ HttpServletRequest request = ServletUtils.getRequest();
+ String token = SecurityFrameworkUtils.obtainAuthorization(request,
+ securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
+ // 查找对应的task
+ EducationPaperParam educationPaperParam = educationPaperParamService.selectEducationPaperParamByTaskId(stuInTheExam.getTaskId());
+ // 定时上传文件时间
+ String time = educationPaperParam.getUploadTime();
+ // 将分钟继续转换成秒
+ stuInTheExam.setTimes(Integer.parseInt(time) * 60);
+ // 倒计时
+ AtomicInteger countdown = new AtomicInteger(stuInTheExam.getStartTimes());
+ // 创建初始返回数据
+ StuTheExamInfo stuTheExamInfo = new StuTheExamInfo();
+ // 返回数据-剩余时间
+ stuTheExamInfo.setTime(formatLongDuration(countdown.get()));
+ // 返回数据-上传文件状态 0:上传;1:不上传
+ stuTheExamInfo.setUpload(1);
+ // 返回数据-上传文件状态 0:结束;1:不结束
+ stuTheExamInfo.setEndStatus(1);
+ // 返回数据-网络状态
+ stuTheExamInfo.setNetwork("强");
+ // 创建对应的线程池
+ taskManager.startTask(stuInTheExam, stuTheExamInfo, token, countdown, new AtomicInteger(0));
+ return CommonResult.success(true);
+ }
+
+ /**
+ * 停止考试
+ * @return true
+ */
+ @GetMapping("/stopExam")
+ public CommonResult stopExam() {
+ HttpServletRequest request = ServletUtils.getRequest();
+ String token = SecurityFrameworkUtils.obtainAuthorization(request,
+ securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
+ // 删除对应的线程池
+ taskManager.stopTask(token);
+ return CommonResult.success(true);
+ }
+
+ public static String formatLongDuration(int totalSeconds) {
+ int hours = totalSeconds / 3600;
+ int minutes = (totalSeconds % 3600) / 60;
+ int seconds = totalSeconds % 60;
+
+ return String.format("%d:%02d:%02d", hours, minutes, seconds);
+ }
}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/vo/StuInTheExam.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/vo/StuInTheExam.java
new file mode 100644
index 00000000..4daf22b2
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/vo/StuInTheExam.java
@@ -0,0 +1,18 @@
+package pc.exam.pp.module.judgement.controller.admin.autoTools.vo;
+
+
+import lombok.Data;
+
+@Data
+public class StuInTheExam {
+ // 学生ID
+ // private String stuId;
+ // 倒计时开始时间 单位秒
+ private int startTimes;
+ // 定时上传文件时间
+ private int times;
+ // 任务ID
+ private String taskId;
+ // 延迟时间 min
+ private Integer delayTime;
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/vo/StuTheExamInfo.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/vo/StuTheExamInfo.java
new file mode 100644
index 00000000..744ae17d
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/autoTools/vo/StuTheExamInfo.java
@@ -0,0 +1,16 @@
+package pc.exam.pp.module.judgement.controller.admin.autoTools.vo;
+
+
+import lombok.Data;
+
+@Data
+public class StuTheExamInfo {
+ // 考试时间
+ private String time;
+ // 网络状态
+ private String network;
+ // 上传文件状态
+ private Integer upload;
+ // 是否结束考试
+ private Integer endStatus;
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/TaskManager.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/TaskManager.java
new file mode 100644
index 00000000..b22f33b3
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/service/TaskManager.java
@@ -0,0 +1,128 @@
+package pc.exam.pp.module.judgement.service;
+
+import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Resource;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Service;
+import pc.exam.pp.framework.common.enums.UserTypeEnum;
+import pc.exam.pp.framework.excel.core.convert.JsonConvert;
+import pc.exam.pp.module.infra.api.websocket.WebSocketSenderApi;
+import pc.exam.pp.module.judgement.controller.admin.autoTools.vo.StuInTheExam;
+import pc.exam.pp.module.judgement.controller.admin.autoTools.vo.StuTheExamInfo;
+
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TaskManager {
+
+ private final WebSocketSenderApi webSocketSenderApi; // 构造器注入,确保不是 null
+ private final ScheduledExecutorService scheduler; // 从配置类注入
+ private final Map> tasks = new ConcurrentHashMap<>();
+
+ /** 开始任务(每秒执行一次) */
+ public void startTask(StuInTheExam stuInTheExam, StuTheExamInfo stuTheExamInfo, String token, AtomicInteger countdown, AtomicInteger counter) {
+ // 已存在就不重复启动
+ tasks.computeIfAbsent(token, k ->
+ scheduler.scheduleAtFixedRate(safe(() -> {
+ // 每秒 + 1
+ int current = counter.incrementAndGet();
+ // 获取当前值并递减
+ int remaining = countdown.decrementAndGet();
+ log.debug("任务 {} tick at {}", token, System.currentTimeMillis());
+
+ if (current == stuInTheExam.getTimes()) {
+ // 提示学生端进行文件的上传
+ stuTheExamInfo.setUpload(0);
+ // 重新进行计时
+ counter.set(0);
+ }
+ // 如果计数减到0,取消任务
+ if (remaining <= 0) {
+ ScheduledFuture> future = tasks.get(token);
+ if (future != null) {
+ future.cancel(false);
+ tasks.remove(token);
+ // 发送最后一条消息
+ stuTheExamInfo.setEndStatus(0);
+ log.info("任务 {} 已完成并取消", token);
+ }
+ }
+ // 时间转变
+ String time = formatLongDuration(remaining);
+ stuTheExamInfo.setTime(time);
+ webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "InTheExam", stuTheExamInfo);
+ }), 0, 1, TimeUnit.SECONDS)
+ );
+ log.info("任务 {} 已启动", token);
+ }
+
+ /** 结束任务 */
+ public void stopTask(String token) {
+ ScheduledFuture> f = tasks.remove(token);
+ if (f != null) {
+ f.cancel(false);
+ log.info("任务 {} 已停止", token);
+ } else {
+ log.warn("任务 {} 不存在", token);
+ }
+ }
+
+ /** 全部停止(可选) */
+ public void stopAll() {
+ tasks.forEach((k, f) -> f.cancel(false));
+ tasks.clear();
+ log.info("所有任务已停止");
+ }
+
+ /** 应用关闭时收尾 */
+ @PreDestroy
+ public void onDestroy() {
+ stopAll();
+ scheduler.shutdownNow();
+ }
+
+ /** 包装任务,防止异常导致定时器中止 */
+ private Runnable safe(Runnable r) {
+ return () -> {
+ try {
+ r.run();
+ } catch (Throwable t) {
+ log.error("定时任务执行异常:{}", t.getMessage(), t);
+ }
+ };
+ }
+
+ public static String formatLongDuration(int totalSeconds) {
+ int hours = totalSeconds / 3600;
+ int minutes = (totalSeconds % 3600) / 60;
+ int seconds = totalSeconds % 60;
+
+ return String.format("%d:%02d:%02d", hours, minutes, seconds);
+ }
+}
+
+/** 线程池配置:单独放一个@Configuration */
+@Configuration
+class TaskSchedulerConfig {
+ @Bean
+ public ScheduledExecutorService scheduledExecutorService() {
+ // 合理的线程数即可;自定义线程名方便排查
+ return Executors.newScheduledThreadPool(8, new ThreadFactory() {
+ private final ThreadFactory df = Executors.defaultThreadFactory();
+ private final AtomicInteger idx = new AtomicInteger(1);
+ @Override public Thread newThread(Runnable r) {
+ Thread t = df.newThread(r);
+ t.setName("ws-task-" + idx.getAndIncrement());
+ t.setDaemon(true);
+ return t;
+ }
+ });
+ }
+}