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 d0aa1243a..748bf022c 100644 --- a/src/main/java/club/joylink/rtss/services/training2/Training2Service.java +++ b/src/main/java/club/joylink/rtss/services/training2/Training2Service.java @@ -10,11 +10,13 @@ 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.event.training2.*; 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.member.SimulationMember; import club.joylink.rtss.simulation.cbtc.script.ScriptBO; +import club.joylink.rtss.simulation.cbtc.training2.Index; import club.joylink.rtss.simulation.cbtc.training2.Operation2; import club.joylink.rtss.simulation.cbtc.training2.Step2; import club.joylink.rtss.simulation.cbtc.training2.Training2; @@ -26,12 +28,15 @@ import club.joylink.rtss.vo.client.WebSocketMessageType; import club.joylink.rtss.vo.client.factory.SocketMessageFactory; import club.joylink.rtss.websocket.StompMessageService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Semaphore; @@ -41,6 +46,7 @@ import java.util.function.Consumer; public class Training2Service { public static final String EXECUTE_JOB_NAME = "Training2"; public static final String SCORING_JOB_NAME = "Training2Scoring"; + public static final String TRAINING_INDEX_JOB_NAME = "Training2IndexStatistic"; public static final int RATE = 1000; @Autowired @@ -67,6 +73,9 @@ public class Training2Service { @Autowired private StompMessageService stompMessageService; + @Autowired + private ApplicationContext applicationContext; + /** * 完成步骤接口信号量 */ @@ -77,6 +86,9 @@ public class Training2Service { */ private Semaphore completeOperationSemaphore = new Semaphore(1); + /** + * 实训运行 + */ public void run(Simulation simulation) { Training2 training2 = simulation.getTraining2(); if (training2 == null || !training2.isStarted() || training2.isFinish()) { @@ -104,6 +116,19 @@ public class Training2Service { tryCompleteOperation(simulation, step, operation2); } + /** + * 统计运行 + */ + public void statisticRun(Simulation simulation) { + Training2 training2 = simulation.getTraining2(); + if (training2 == null || training2.isStop()) { + return; + } + List indexList = training2.getIndexList(); + if (!CollectionUtils.isEmpty(indexList)) { + indexList.stream().filter(Index::isContinuity).forEach(index -> index.getType().accept(simulation, index.getDataMap())); + } + } /** * 预览实训草稿 @@ -190,11 +215,8 @@ public class Training2Service { groupSimulationService.loadScenes(simulation.getId(), training2.getBgSceneJson()); } } - // 增加任务 - if (simulation.getJobMap().containsKey(EXECUTE_JOB_NAME)) { - simulation.removeJob(EXECUTE_JOB_NAME); - } - simulation.addJobIfAbsent(EXECUTE_JOB_NAME, () -> this.run(simulation), RATE); + // 增加实训任务 + addTrainingJob(simulation); training2.start(mode); simulationLifeCycleService.resume(simulation); } @@ -215,8 +237,7 @@ public class Training2Service { // 创建者退出,则清理实训 if (simulation.getCreator().getId().equals(user.getId())) { training2.finish(); - simulation.removeJob(SCORING_JOB_NAME); //移除打分监视任务 - simulation.removeJob(EXECUTE_JOB_NAME); //移除实训执行任务 + removeTrainingJob(simulation); } scoreMap = training2.mark(); } else { @@ -324,7 +345,10 @@ public class Training2Service { public void handle(SimulationOperationEvent event) { Simulation simulation = event.getSimulation(); Training2 training2 = simulation.getTraining2(); - if (training2 != null && Objects.equals(event.getSuccessful(), true)) { + if (training2 == null || training2.isStop()) { + return; + } + if (Objects.equals(event.getSuccessful(), true)) { // 获取运行步骤 Step2 step = training2.getCurrentRunStep(); // 没找到返回 @@ -350,10 +374,13 @@ public class Training2Service { operation2.getCount().incrementAndGet(); // 错误提示 // 发送操作完成信息 - sendOperationError(operation2, simulation); + applicationContext.publishEvent(new SimulationOperationErrorEvent(this, simulation, operation2)); } // 操作完成后,如果是测验模式则仿真启动 pauseOrStartSimulation(simulation, false); + // 指标计算 + applicationContext.publishEvent(new SimulationTrainingIndexEvent(this, simulation, training2 + , simOperation2.getOperationType())); } } } @@ -361,19 +388,25 @@ public class Training2Service { /** * 步骤提示 */ - public void sendStepTips(Step2 step, Simulation simulation) { - SocketMessageVO message = - SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Step_Tip, simulation.getId(), step.getDescription()); + @EventListener + public void handle(SimulationStepTipEvent event) { + Simulation simulation = event.getSimulation(); + Step2 step = event.getStep(); + SocketMessageVO message = SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Step_Tip + , simulation.getId(), step.getDescription()); stompMessageService.sendToUser(simulation.getSimulationUserIds(), message); } /** * 步骤完成 */ - public void sendStepFinish(Step2 step, Simulation simulation) { + @EventListener + public void handle(SimulationStepFinishEvent event) { + Simulation simulation = event.getSimulation(); + Step2 step = event.getStep(); if (Step2.StepStatus.isCompletion(step.getStepStatus())) { - SocketMessageVO message = - SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Step_Finish, simulation.getId(), "步骤完成"); + SocketMessageVO message = SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Step_Finish + , simulation.getId(), "步骤完成"); stompMessageService.sendToUser(simulation.getSimulationUserIds(), message); } } @@ -381,16 +414,21 @@ public class Training2Service { /** * 操作错误 */ - public void sendOperationError(Operation2 operation2, Simulation simulation) { - SocketMessageVO message = - SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Operate_Error, simulation.getId(), "操作错误"); + @EventListener + public void handle(SimulationOperationErrorEvent event) { + Simulation simulation = event.getSimulation(); + SocketMessageVO message = SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Operate_Error + , simulation.getId(), "操作错误"); stompMessageService.sendToUser(simulation.getSimulationUserIds(), message); } /** * 操作完成 */ - public void sendOperationFinish(Operation2 operation2, Simulation simulation) { + @EventListener + public void handle(SimulationOperationFinishEvent event) { + Simulation simulation = event.getSimulation(); + Operation2 operation2 = event.getOperation(); if (Step2.StepStatus.isCompletion(operation2.getStatus())) { SocketMessageVO message = SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Operate_Finish, simulation.getId(), "操作完成"); @@ -401,7 +439,10 @@ public class Training2Service { /** * 实训完成发送消息 */ - public void sendTrainingFinish(Training2 training2, Simulation simulation) { + @EventListener + public void handle(SimulationTrainingFinishEvent event) { + Simulation simulation = event.getSimulation(); + Training2 training2 = event.getTraining(); if (training2.isFinish()) { SocketMessageVO message = SocketMessageFactory.build(WebSocketMessageType.Simulation_Training_Finish, simulation.getId(), "实训完成"); @@ -409,6 +450,21 @@ public class Training2Service { } } + /** + * 计算实训指标消息 + */ + @EventListener + public void handle(SimulationTrainingIndexEvent event) { + Simulation simulation = event.getSimulation(); + Training2 training2 = event.getTraining(); + // 仿真操作触发指标计算 + if (!CollectionUtils.isEmpty(training2.getIndexList())) { + training2.getIndexList().stream().filter(index -> !index.isContinuity() && Objects.equals( + event.getOperationType().name(), index.getDataMap().get("operationType"))) + .forEach(index -> index.getType().accept(simulation, index.getDataMap())); + } + } + /** * 绘制草稿时加载实训背景 * TODO 后续看根据需求是否加载到最后一步 @@ -483,10 +539,13 @@ public class Training2Service { /** * 判断实训步骤是否完成 */ - private void checkTrainStepCompletion(Step2 step, Simulation simulation) { - step.doCompletionVail(); - // 发送步骤完成信息 - sendStepFinish(step, simulation); + private boolean checkTrainStepCompletion(Step2 step, Simulation simulation) { + boolean result = step.doCompletionVail(); + if (result) { + // 发送步骤完成信息 + applicationContext.publishEvent(new SimulationStepFinishEvent(this, simulation, step)); + } + return result; } /** @@ -498,7 +557,7 @@ public class Training2Service { if (nextStep == null) { // 步骤已经运行完毕 training2.finish(); // 发送实训完成消息 - sendTrainingFinish(training2, simulation); + applicationContext.publishEvent(new SimulationTrainingFinishEvent(this, simulation, training2)); return null; } return nextStep; @@ -516,7 +575,7 @@ public class Training2Service { // 发送步骤提示信息 if (!step.isPrompt()) { step.setPrompt(true); // 标识已发送过消息 - sendStepTips(step, simulation); + applicationContext.publishEvent(new SimulationStepTipEvent(this, simulation, step)); } return true; } @@ -573,7 +632,7 @@ public class Training2Service { if (Step2.StepStatus.isRunning(operation2.getStatus())) { boolean completion = operation2.doCompletion(); // 发送操作完成信息 - sendOperationFinish(operation2, simulation); + applicationContext.publishEvent(new SimulationOperationFinishEvent(this, simulation, operation2)); // 如果是最后一步,直接检查步骤有没有完成 if (step.getOperations().indexOf(operation2) == (step.getOperations().size() - 1) && completion) { checkStepCompletionAndSendNext(step, simulation); @@ -585,10 +644,8 @@ public class Training2Service { * 直接完成并尝试触发下一步 */ private void checkStepCompletionAndSendNext(Step2 step, Simulation simulation) { - boolean result = step.doCompletionVail(); + boolean result = checkTrainStepCompletion(step, simulation); if (result) { - // 发送步骤完成信息 - sendStepFinish(step, simulation); // 获取运行步骤 Step2 nextStep = getNextStepAndCheckTrainingStatus(simulation.getTraining2(), simulation); if (nextStep == null) { @@ -617,6 +674,30 @@ public class Training2Service { } } + /** + * 移除实训任务 + */ + private void removeTrainingJob(Simulation simulation) { + if (simulation.getJobMap().containsKey(SCORING_JOB_NAME)) { + simulation.removeJob(SCORING_JOB_NAME); //移除打分监视任务 + } + if (simulation.getJobMap().containsKey(EXECUTE_JOB_NAME)) { + simulation.removeJob(EXECUTE_JOB_NAME); //移除实训执行任务 + } + if (simulation.getJobMap().containsKey(TRAINING_INDEX_JOB_NAME)) { + simulation.removeJob(TRAINING_INDEX_JOB_NAME); // 移除指标记录任务 + } + } + + /** + * 增加实训任务 + */ + private void addTrainingJob(Simulation simulation) { + removeTrainingJob(simulation); + simulation.addJobIfAbsent(EXECUTE_JOB_NAME, () -> this.run(simulation), RATE); + simulation.addJobIfAbsent(TRAINING_INDEX_JOB_NAME, () -> this.statisticRun(simulation), RATE); + } + /** * 检验实训数据是否合规 */ diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationOperationErrorEvent.java b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationOperationErrorEvent.java new file mode 100644 index 000000000..ca8063d09 --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationOperationErrorEvent.java @@ -0,0 +1,17 @@ +package club.joylink.rtss.simulation.cbtc.event.training2; + +import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.event.AbstractSimulationEvent; +import club.joylink.rtss.simulation.cbtc.training2.Operation2; +import lombok.Getter; + +@Getter +public class SimulationOperationErrorEvent extends AbstractSimulationEvent { + + private Operation2 operation; + + public SimulationOperationErrorEvent(Object source, Simulation simulation, Operation2 operation) { + super(source, simulation); + this.operation = operation; + } +} diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationOperationFinishEvent.java b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationOperationFinishEvent.java new file mode 100644 index 000000000..33e1c55d0 --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationOperationFinishEvent.java @@ -0,0 +1,17 @@ +package club.joylink.rtss.simulation.cbtc.event.training2; + +import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.event.AbstractSimulationEvent; +import club.joylink.rtss.simulation.cbtc.training2.Operation2; +import lombok.Getter; + +@Getter +public class SimulationOperationFinishEvent extends AbstractSimulationEvent { + + private Operation2 operation; + + public SimulationOperationFinishEvent(Object source, Simulation simulation, Operation2 operation) { + super(source, simulation); + this.operation = operation; + } +} diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationStepFinishEvent.java b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationStepFinishEvent.java new file mode 100644 index 000000000..3cdce9c88 --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationStepFinishEvent.java @@ -0,0 +1,17 @@ +package club.joylink.rtss.simulation.cbtc.event.training2; + +import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.event.AbstractSimulationEvent; +import club.joylink.rtss.simulation.cbtc.training2.Step2; +import lombok.Getter; + +@Getter +public class SimulationStepFinishEvent extends AbstractSimulationEvent { + + private Step2 step; + + public SimulationStepFinishEvent(Object source, Simulation simulation, Step2 step) { + super(source, simulation); + this.step = step; + } +} diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationStepTipEvent.java b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationStepTipEvent.java new file mode 100644 index 000000000..3ed2b0e85 --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationStepTipEvent.java @@ -0,0 +1,17 @@ +package club.joylink.rtss.simulation.cbtc.event.training2; + +import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.event.AbstractSimulationEvent; +import club.joylink.rtss.simulation.cbtc.training2.Step2; +import lombok.Getter; + +@Getter +public class SimulationStepTipEvent extends AbstractSimulationEvent { + + private Step2 step; + + public SimulationStepTipEvent(Object source, Simulation simulation, Step2 step) { + super(source, simulation); + this.step = step; + } +} diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationTrainingFinishEvent.java b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationTrainingFinishEvent.java new file mode 100644 index 000000000..7c6c34065 --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationTrainingFinishEvent.java @@ -0,0 +1,17 @@ +package club.joylink.rtss.simulation.cbtc.event.training2; + +import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.event.AbstractSimulationEvent; +import club.joylink.rtss.simulation.cbtc.training2.Training2; +import lombok.Getter; + +@Getter +public class SimulationTrainingFinishEvent extends AbstractSimulationEvent { + + private Training2 training; + + public SimulationTrainingFinishEvent(Object source, Simulation simulation, Training2 training) { + super(source, simulation); + this.training = training; + } +} diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationTrainingIndexEvent.java b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationTrainingIndexEvent.java new file mode 100644 index 000000000..42b10b7db --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/event/training2/SimulationTrainingIndexEvent.java @@ -0,0 +1,21 @@ +package club.joylink.rtss.simulation.cbtc.event.training2; + +import club.joylink.rtss.simulation.cbtc.ATS.operation.Operation; +import club.joylink.rtss.simulation.cbtc.Simulation; +import club.joylink.rtss.simulation.cbtc.event.AbstractSimulationEvent; +import club.joylink.rtss.simulation.cbtc.training2.Training2; +import lombok.Getter; + +@Getter +public class SimulationTrainingIndexEvent extends AbstractSimulationEvent { + + private Training2 training; + + private Operation.Type operationType; + + public SimulationTrainingIndexEvent(Object source, Simulation simulation, Training2 training, Operation.Type operationType) { + super(source, simulation); + this.operationType = operationType; + this.training = training; + } +} diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Index.java b/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Index.java index aea31c066..e36e77b00 100644 --- a/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Index.java +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/training2/Index.java @@ -1,10 +1,13 @@ package club.joylink.rtss.simulation.cbtc.training2; import club.joylink.rtss.simulation.cbtc.Simulation; +import lombok.Getter; import java.util.Map; +import java.util.Objects; import java.util.function.BiConsumer; +@Getter public class Index { private int id; @@ -12,10 +15,34 @@ public class Index { private Map dataMap; + /** + * 指标名称 + */ + private String name; + + /** + * 指标描述 + */ + private String description; + + /** + * 统计频率 + */ + private CalculationRate rate; + + /** + * 连续性指标 + */ + public boolean isContinuity() { + return Objects.equals(CalculationRate.CONTINUITY, this.rate); + } + public enum Type implements BiConsumer> { - T1{ + T1 { @Override public void accept(Simulation simulation, Map dataMap) { + IndexValue indexValue = new IndexValue(simulation, this); + } }, @@ -26,4 +53,15 @@ public class Index { } }, } + + /** + * 计算频率 + */ + public enum CalculationRate { + // 触发式 + TRIGGERED, + + // 连续性计算 + CONTINUITY; + } } diff --git a/src/main/java/club/joylink/rtss/simulation/cbtc/training2/IndexValue.java b/src/main/java/club/joylink/rtss/simulation/cbtc/training2/IndexValue.java new file mode 100644 index 000000000..42941a3a2 --- /dev/null +++ b/src/main/java/club/joylink/rtss/simulation/cbtc/training2/IndexValue.java @@ -0,0 +1,40 @@ +package club.joylink.rtss.simulation.cbtc.training2; + +import club.joylink.rtss.simulation.cbtc.Simulation; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 指标值 + */ +@Data +@NoArgsConstructor +public class IndexValue { + /** + * 指标类型 + */ + private Index.Type type; + + /** + * 记录指标对象信息 + */ + private Map params; + + /** + * 指标值 + */ + private Object value; + + /** + * 统计时间 + */ + private LocalDateTime time; + + public IndexValue(Simulation simulation, Index.Type type) { + this.type = type; + this.time = simulation.getCorrectSystemTime(); + } +} 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 a4c442006..31e29d0b7 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 @@ -58,6 +58,11 @@ public class Training2 { private LocalDateTime updateTime; + /** + * 实训监控指标 + */ + private List indexList; + /* -------------------------------------- 运行时参数 -------------------------------------- */ /** @@ -85,6 +90,11 @@ public class Training2 { */ private ScriptBO.Mode mode; + /** + * 指标值信息 + */ + private Map> indexValueMap; + public Training2(DraftTraining2WithBLOBs draftTraining2, Simulation simulation) { this.id = draftTraining2.getId(); this.mapId = draftTraining2.getMapId(); @@ -122,9 +132,7 @@ public class Training2 { this.started = true; this.mode = mode; this.finish = false; - if (this.steps != null) { - this.steps.forEach(Step2::reset); - } + resetStepAndIndex(); } public boolean finish() { @@ -136,9 +144,7 @@ public class Training2 { public void reset() { this.started = false; this.finish = false; - if (this.steps != null) { - this.steps.forEach(Step2::reset); - } + resetStepAndIndex(); } public Operation2 getOperation(int id) { @@ -194,6 +200,24 @@ public class Training2 { return step; } + /** + * 是否已经停止 + */ + public boolean isStop() { + return !this.started || this.finish; + } + + private void resetStepAndIndex() { + if (!CollectionUtils.isEmpty(this.indexList)) { + this.indexValueMap = new HashMap<>(this.indexList.size()); + } else { + this.indexValueMap = new HashMap<>(); + } + if (this.steps != null) { + this.steps.forEach(Step2::reset); + } + } + public enum Type { SINGLE, SCENE,