From 8243193972bba93043717a48ad42f309bb8ecadb Mon Sep 17 00:00:00 2001 From: weizhihong Date: Thu, 18 Aug 2022 16:03:43 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=AE=9E=E8=AE=AD=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E3=80=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskExecutorConfiguration.java | 14 ++ .../SimulationTrainingV2Controller.java | 41 ++++ .../services/training2/Training2Service.java | 193 ++++++++++++++++-- .../rtss/simulation/cbtc/Simulation.java | 6 + .../simulation/cbtc/training2/Operation2.java | 79 +++++++ .../rtss/simulation/cbtc/training2/Step2.java | 92 ++++++++- .../simulation/cbtc/training2/Training2.java | 38 +++- 7 files changed, 441 insertions(+), 22 deletions(-) create mode 100644 src/main/java/club/joylink/rtss/controller/simulation/SimulationTrainingV2Controller.java diff --git a/src/main/java/club/joylink/rtss/configuration/TaskExecutorConfiguration.java b/src/main/java/club/joylink/rtss/configuration/TaskExecutorConfiguration.java index 8c1575ac5..777a6a7a9 100644 --- a/src/main/java/club/joylink/rtss/configuration/TaskExecutorConfiguration.java +++ b/src/main/java/club/joylink/rtss/configuration/TaskExecutorConfiguration.java @@ -61,4 +61,18 @@ public class TaskExecutorConfiguration { return taskExecutor; } + /** + * 新实训监控 + */ + @Bean("trainingV2Executor") + public TaskExecutor trainExecutor(Environment env) { + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + taskExecutor.setThreadNamePrefix("ns-training-v2-executor-"); + taskExecutor.setCorePoolSize(2); + taskExecutor.setMaxPoolSize(2); + taskExecutor.setQueueCapacity(100); + taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + taskExecutor.initialize(); + return taskExecutor; + } } diff --git a/src/main/java/club/joylink/rtss/controller/simulation/SimulationTrainingV2Controller.java b/src/main/java/club/joylink/rtss/controller/simulation/SimulationTrainingV2Controller.java new file mode 100644 index 000000000..5b297b965 --- /dev/null +++ b/src/main/java/club/joylink/rtss/controller/simulation/SimulationTrainingV2Controller.java @@ -0,0 +1,41 @@ +package club.joylink.rtss.controller.simulation; + +import club.joylink.rtss.controller.advice.AuthenticateInterceptor; +import club.joylink.rtss.services.training2.Training2Service; +import club.joylink.rtss.vo.AccountVO; +import club.joylink.rtss.vo.LoginUserInfoVO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/training2Simulation") +public class SimulationTrainingV2Controller { + + @Autowired + private Training2Service training2Service; + + /** + * 预览实训信息 + */ + @GetMapping("/{trainingId}/preview/draft") + public String previewDraft(@PathVariable Long trainingId + , @RequestAttribute(name = AuthenticateInterceptor.LOGIN_INFO_KEY) LoginUserInfoVO loginUserInfoVO) { + return training2Service.previewDraft(trainingId, loginUserInfoVO); + } + + /** + * 预览实训信息 + */ + @PutMapping("/{group}/start") + public void startTraining2(@PathVariable String group, @RequestAttribute AccountVO user) { + training2Service.startTraining2(group, user); + } + + /** + * 结束剧本演出 + */ + @GetMapping("/{group}/finish") + public Integer finishScript(@PathVariable String group) { + return 1; + } +} diff --git a/src/main/java/club/joylink/rtss/services/training2/Training2Service.java b/src/main/java/club/joylink/rtss/services/training2/Training2Service.java index 0dd9ff080..3b8fe7555 100644 --- a/src/main/java/club/joylink/rtss/services/training2/Training2Service.java +++ b/src/main/java/club/joylink/rtss/services/training2/Training2Service.java @@ -1,30 +1,107 @@ package club.joylink.rtss.services.training2; +import club.joylink.rtss.dao.DraftTraining2DAO; +import club.joylink.rtss.dao.PublishedTraining2DAO; import club.joylink.rtss.entity.training2.DraftTraining2WithBLOBs; +import club.joylink.rtss.simulation.cbtc.ATS.operation.AtsOperationDispatcher; +import club.joylink.rtss.simulation.cbtc.GroupSimulationCache; +import club.joylink.rtss.simulation.cbtc.GroupSimulationService; import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.SimulationLifeCycleService; +import club.joylink.rtss.simulation.cbtc.event.SimulationOperationEvent; +import club.joylink.rtss.simulation.cbtc.exception.SimulationException; +import club.joylink.rtss.simulation.cbtc.exception.SimulationExceptionType; +import club.joylink.rtss.simulation.cbtc.member.MemberManager; +import club.joylink.rtss.simulation.cbtc.training2.Operation2; import club.joylink.rtss.simulation.cbtc.training2.Step2; import club.joylink.rtss.simulation.cbtc.training2.Training2; +import club.joylink.rtss.vo.AccountVO; +import club.joylink.rtss.vo.LoginUserInfoVO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.function.Function; + +@Service public class Training2Service { public static final String JOB_NAME = "Training2"; public static final int RATE = 1000; + @Autowired + private AtsOperationDispatcher atsOperationDispatcher; + + @Autowired + private PublishedTraining2DAO training2DAO; + + @Autowired + private DraftTraining2DAO trainingDao; + + @Autowired + private MemberManager memberManager; + + @Autowired + private GroupSimulationService groupSimulationService; + + @Autowired + private GroupSimulationCache groupSimulationCache; + + @Autowired + private SimulationLifeCycleService simulationLifeCycleService; + public void run(Simulation simulation) { - Training2 training2 = new Training2(null, null); //从仿真里拿到Training2 - for (Step2 step : training2.getSteps()) { - if (!step.isCompletion()) { - if (!step.isTrigger()) { - if (step.getTriggerCondition().getValue(boolean.class)) { - step.setTrigger(true); - //前端提示 - } - } - if (step.getCompletionCondition().getValue(boolean.class)) { - step.setCompletion(true); - } else { - break; - } + Training2 training2 = simulation.getTraining2(); + if (training2 == null || !training2.isStarted() || training2.isFinish()) { + return; + } + // 获取运行步骤 + Step2 step = training2.getSteps().stream() + .filter(s -> !s.isCompletion()).findFirst().orElseGet(null); + if (step == null) { // 步骤已经运行完毕 + training2.finish(); + return; + } + // 步骤触发 + if (!step.checkTrigger()) { + return; + } + // 获取步骤中未完成的操作 + Operation2 operation2 = step.getOperations().stream() + .filter(o -> !o.isCompletion()).findFirst().orElseGet(null); + if (operation2 == null) { // 未完成操作为空,检查步骤是否全部完成 + step.checkCompletion(); + return; + } + // 操作是否已触发 + if (!operation2.checkTrigger()) { + return; + } + // 检验结果存在延迟,如果已操作过,200ms内重复检验 + if (operation2.isOperated() + && !operation2.getOperatedTime().plus(200, ChronoUnit.MILLIS).isBefore(LocalDateTime.now())) { + operation2.checkCompletion(); + return; + } + if (!step.getSimulationMember().isRobot()) { // 是否是机器人 + // 发送前端提示信息 + if (!step.isPrompt()) { + step.setPrompt(true); // 标识已发送过消息 } + return; + } + // 机器人执行操作 + if (operation2 instanceof Operation2.SimOperation2) { + Operation2.SimOperation2 simOperation2 = (Operation2.SimOperation2) operation2; + atsOperationDispatcher.execute(simulation, step.getSimulationMember(), + simOperation2.getOperationType().name(), simOperation2.getParams()); + } else { + operation2.doOperated(); + operation2.checkCompletion(); } } @@ -33,4 +110,92 @@ public class Training2Service { Training2 training2 = new Training2(draftTraining2, simulation); simulation.addJobIfAbsent(JOB_NAME, () -> this.run(simulation), RATE); } + + /** + * 预览实训草稿 + */ + public String previewDraft(Long trainingId, LoginUserInfoVO loginUserInfoVO) { + DraftTraining2WithBLOBs draftTraining2 = trainingDao.selectByPrimaryKey(trainingId); + if (draftTraining2 == null) { + throw new SimulationException(SimulationExceptionType.Data_Not_Exist, "实训不存在"); + } + if (!StringUtils.hasText(draftTraining2.getBgSceneJson())) { + throw new SimulationException(SimulationExceptionType.Invalid_Operation, String.format("实训{id:[%s]}没有背景", trainingId)); + } + Simulation simulation = createSimulation(draftTraining2.getMapId(), loginUserInfoVO + , Simulation.FunctionalType.SCRIPT_PREVIEW, (s -> new Training2(draftTraining2, s))); + return simulation.getId(); + } + + /** + * 开启实训 + */ + public void startTraining2(String group, AccountVO user) { + Simulation simulation = groupSimulationCache.getSimulationByGroup(group); + if (simulation == null) { + throw new SimulationException(SimulationExceptionType.Invalid_Operation, "仿真不存在"); + } + if (!simulation.getCreator().getId().equals(user.getId())) { + throw new SimulationException(SimulationExceptionType.Invalid_Operation, "无权限开启实训"); + } + Training2 training2 = simulation.getTraining2(); + if (training2 == null) { + throw new SimulationException(SimulationExceptionType.Invalid_Operation, "实训数据不存在"); + } + if (training2.isNeedReloadScenes()) { + groupSimulationService.loadScenes(simulation.getId(), training2.getBgSceneJson()); + } + training2.start(); + simulationLifeCycleService.resume(simulation); + } + + @Async("trainingV2Executor") + @EventListener + public void handle(SimulationOperationEvent event) { + Simulation simulation = event.getSimulation(); + Training2 training2 = simulation.getTraining2(); + if (training2 != null && Objects.equals(event.getSuccessful(), true)) { + // 获取运行步骤 + Step2 step = training2.getSteps().stream().filter(s -> s.isTrigger() && !s.isCompletion()) + .findFirst().orElseGet(null); + // 没找到返回 + if (step == null) { + return; + } + // 角色不对 + if (!event.getMember().equals(step.getSimulationMember())) { + return; + } + // 获取步骤中未完成的操作 + Operation2 operation2 = step.getOperations().stream().filter(o -> o.isTrigger() && !o.isCompletion()) + .findFirst().orElseGet(null); + if (operation2 == null) { + return; + } + Operation2.SimOperation2 simOperation2 = (Operation2.SimOperation2) operation2; + if (simOperation2.getOperationType().name().equals(event.getOperate())) { + operation2.doOperated(); // 标识已操作过 + operation2.checkCompletion(); // 检查是否已操作 + } else { + // 错误操作数增加 + operation2.getCount().incrementAndGet(); + // 错误提示 + } + } + } + + /** + * 实训时创建仿真对象 + */ + private Simulation createSimulation(Long mapId, LoginUserInfoVO loginUserInfoVO + , Simulation.FunctionalType functionalType, Function function) { + // 构建仿真,加载背景, + Simulation simulation = groupSimulationService.create(loginUserInfoVO, mapId, null, functionalType); + Training2 training2 = function.apply(simulation); + groupSimulationService.loadScenes(simulation.getId(), training2.getBgSceneJson()); + simulationLifeCycleService.pause(simulation); + simulation.setTraining2(training2); + simulation.addJobIfAbsent(JOB_NAME, () -> this.run(simulation), RATE); + return simulation; + } } diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/Simulation.java b/src/main/java/club/joylink/rtss/simulation/cbtc/Simulation.java index 356973b33..2c317e6b4 100644 --- a/src/main/java/club/joylink/rtss/simulation/cbtc/Simulation.java +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/Simulation.java @@ -27,6 +27,7 @@ import club.joylink.rtss.simulation.cbtc.exception.SimulationExceptionType; import club.joylink.rtss.simulation.cbtc.member.SimulationMember; import club.joylink.rtss.simulation.cbtc.member.SimulationUser; import club.joylink.rtss.simulation.cbtc.script.ScriptBO; +import club.joylink.rtss.simulation.cbtc.training2.Training2; import club.joylink.rtss.simulation.vo.SimulationInfoVO; import club.joylink.rtss.vo.AccountVO; import club.joylink.rtss.vo.client.fault.FaultRuleVO; @@ -87,6 +88,11 @@ public class Simulation extends club.joylink.rtss.simulation.Simulation operations; @@ -22,20 +27,99 @@ public class Step2 { private Valuable failureCondition; + /* -------------------------------------- 运行时参数 -------------------------------------- */ + /** + * 触发 + */ private boolean trigger; + /** + * 完成 + */ private boolean completion; + /** + * 失败 + */ + private boolean failure; + + /** + * 消息提示 + */ + boolean prompt; + + /** + * 剧本演出开始时间 + */ + private LocalDateTime startTime; + + /** + * 用时 + */ + private long remainTime; + + /** + * 操作次数 + */ + private int count; + public Step2(Step2VO vo) { this.id = vo.getId(); this.description = vo.getDescription(); } - public Step2(Step2VO vo, SimulationDataRepository repository) { + public Step2(Step2VO vo, Simulation simulation) { this(vo); - Valuable triggerCondition = vo.getTriggerCondition().convert2BO(repository); - Valuable completionCondition = vo.getCompletionCondition().convert2BO(repository); + Valuable triggerCondition = vo.getTriggerCondition().convert2BO(simulation.getRepository()); + Valuable completionCondition = vo.getCompletionCondition().convert2BO(simulation.getRepository()); + if (!StringUtils.isEmpty(vo.getMemberId())) { + this.simulationMember = simulation.getSimulationMemberById(vo.getMemberId()); + } this.triggerCondition = triggerCondition; this.completionCondition = completionCondition; } + + /** + * 判断设备是否准备就绪 + */ + public boolean checkTrigger() { + if (!this.trigger) { + boolean result = this.triggerCondition == null; + if (!result) { + result = this.triggerCondition.getValue(boolean.class); + } + this.trigger = result; + if (this.trigger) { // 触发时记录开始时间 + this.startTime = LocalDateTime.now(); + } + } + return this.trigger; + } + + /** + * 检查完成状态 + */ + public boolean checkCompletion() { + if (!this.completion) { + // 操作是否都已完成 + boolean result = this.operations.stream().allMatch(Operation2::isCompletion); + if (result) { + result = this.completionCondition == null; + if (!result) { + result = this.completionCondition.getValue(boolean.class); + } + this.completion = result; + } + // 步骤完成后检查是否失败 + if (result) { + // 整步骤完成耗费时间 + this.remainTime = LocalDateTime.now().getNano() - this.startTime.getNano(); + // 操作错误总数 + this.count = this.operations.stream().mapToInt(o -> o.getCount().get()).sum(); + // 判断是否失败 + this.failure = this.failureCondition != null && this.failureCondition.getValue(boolean.class); + } + } + return this.completion; + } } diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Training2.java b/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Training2.java index a778bb81e..30168d058 100644 --- a/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Training2.java +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Training2.java @@ -1,6 +1,5 @@ package club.joylink.rtss.simulation.cbtc.training2; -import club.joylink.rtss.entity.training2.DraftTraining2; import club.joylink.rtss.entity.training2.DraftTraining2WithBLOBs; import club.joylink.rtss.simulation.cbtc.Simulation; import club.joylink.rtss.util.JsonUtils; @@ -52,12 +51,43 @@ public class Training2 { private LocalDateTime updateTime; + /* -------------------------------------- 运行时参数 -------------------------------------- */ + + /** + * 剧本背景是否需要重新加载(重新开始剧本时) + */ + private boolean needReloadScenes; + + /** + * 剧本是否已经开始演出 + */ + private boolean started; + + /** + * 剧本是否已经完成 + */ + private boolean finish; + + /** + * 剧本演出开始时间(现实时间) + */ + private LocalDateTime startTime; + public Training2(DraftTraining2WithBLOBs draftTraining2, Simulation simulation) { labels = JsonUtils.readCollection(draftTraining2.getLabelJson(), List.class, String.class); List step2VOS = JsonUtils.readCollection(draftTraining2.getStepJson(), List.class, Step2VO.class); - steps = step2VOS.stream() - .map(vo -> new Step2(vo, simulation.getRepository())) - .collect(Collectors.toList()); + steps = step2VOS.stream().map(vo -> new Step2(vo, simulation)).collect(Collectors.toList()); + } + + public void start() { + this.startTime = LocalDateTime.now(); + this.needReloadScenes = true; + this.started = true; + } + + public boolean finish() { + this.finish = true; + return true; } public enum Type {