仿真使用情况记录

This commit is contained in:
joylink_zhangsai 2020-12-08 11:23:35 +08:00
parent 15166c7fbd
commit 9562c726e3
9 changed files with 281 additions and 13 deletions

2
sql/20201208.sql Normal file
View File

@ -0,0 +1,2 @@
alter table user_simulation_stats modify role varchar(32) null comment '用户角色';

View File

@ -1,6 +1,7 @@
package club.joylink.rtss.services.script;
import club.joylink.rtss.services.script.IScriptSimulationService;
import club.joylink.rtss.entity.ScriptDraftWithBLOBs;
import club.joylink.rtss.services.IScriptDraftService;
import club.joylink.rtss.simulation.cbtc.ATS.ATSMessageCollectAndDispatcher;
import club.joylink.rtss.simulation.cbtc.ATS.operation.Operation;
import club.joylink.rtss.simulation.cbtc.GroupSimulationCache;
@ -22,9 +23,6 @@ import club.joylink.rtss.simulation.cbtc.member.SimulationMember;
import club.joylink.rtss.simulation.cbtc.robot.RobotLogicLoop;
import club.joylink.rtss.simulation.cbtc.script.ScriptActionBO;
import club.joylink.rtss.simulation.cbtc.script.ScriptBO;
import club.joylink.rtss.entity.ScriptDraftWithBLOBs;
import club.joylink.rtss.services.IScriptDraftService;
import club.joylink.rtss.services.script.IScriptService;
import club.joylink.rtss.vo.LoginUserInfoVO;
import club.joylink.rtss.vo.UserVO;
import club.joylink.rtss.vo.client.SocketMessageVO;
@ -36,11 +34,11 @@ import club.joylink.rtss.vo.client.script.ScriptActionVO;
import club.joylink.rtss.vo.client.script.ScriptVO;
import club.joylink.rtss.vo.client.simulationv1.SimulationMemberVO;
import club.joylink.rtss.websocket.StompMessageService;
import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
@ -195,7 +193,7 @@ public class ScriptSimulationService implements IScriptSimulationService {
}
// 构建一个仿真
Simulation simulation = this.groupSimulationService.create(loginUserInfoVO, draftScript.getMapId(), null,
Simulation.FunctionalType.SIMULATION);
Simulation.FunctionalType.SCRIPT_PREVIEW);
loadDraftScript(simulation, draftScriptId);
return simulation.getGroup();
}

View File

@ -16,12 +16,11 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class UserSimulationStatStatService implements IUserSimulationStatService {
public class UserSimulationStatService implements IUserSimulationStatService {
@Autowired
private UserSimulationStatsDAO userSimulationStatsDAO;

View File

@ -211,6 +211,10 @@ public class Simulation {
return Objects.equals(this.buildParams.getFunctionalType(), FunctionalType.SCRIPT_MAKING);
}
public boolean isScriptPreviewSimulation() {
return FunctionalType.SCRIPT_PREVIEW.equals(buildParams.getFunctionalType());
}
/**
* 获取该角色类型的所有成员
*/
@ -484,7 +488,11 @@ public class Simulation {
/**
* 剧本编制
*/
SCRIPT_MAKING;
SCRIPT_MAKING,
/**
* 剧本预览
*/
SCRIPT_PREVIEW;
}

View File

@ -0,0 +1,19 @@
package club.joylink.rtss.simulation.cbtc.event;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.ApplicationEvent;
@Getter
@Setter
public class SimulationSubscribeEvent extends ApplicationEvent {
private Long userId;
private String group;
public SimulationSubscribeEvent(Object source, Long userId, String group) {
super(source);
this.userId = userId;
this.group = group;
}
}

View File

@ -0,0 +1,22 @@
package club.joylink.rtss.simulation.cbtc.event;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.ApplicationEvent;
/**
* 用户退订仿真的所有订阅路径
*/
@Getter
@Setter
public class SimulationUnsubscribeAllEvent extends ApplicationEvent {
private Long userId;
private String group;
public SimulationUnsubscribeAllEvent(Object source, Long userId, String group) {
super(source);
this.userId = userId;
this.group = group;
}
}

View File

@ -0,0 +1,35 @@
package club.joylink.rtss.simulation.cbtc.message;
import club.joylink.rtss.simulation.cbtc.event.SimulationDestroyEvent;
import club.joylink.rtss.simulation.cbtc.event.SimulationSubscribeEvent;
import club.joylink.rtss.simulation.cbtc.event.SimulationUnsubscribeAllEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class UserSimulationStatsListen {
@Autowired
private UserSimulationStatsManager userSimulationStatsManager;
@Async("nsExecutor")
@EventListener
public void subscribeSimulation(SimulationSubscribeEvent event) {
userSimulationStatsManager.subscribeSimulation(event.getUserId(), event.getGroup());
}
@Async("nsExecutor")
@EventListener
public void unsubscribeAll(SimulationUnsubscribeAllEvent event) {
userSimulationStatsManager.unsubscribeAll(event.getUserId(), event.getGroup());
}
@Async("nsExecutor")
@EventListener
public void simulationDestroy(SimulationDestroyEvent event) {
userSimulationStatsManager.simulationDestroy(event.getSimulation());
}
}

View File

@ -0,0 +1,161 @@
package club.joylink.rtss.simulation.cbtc.message;
import club.joylink.rtss.exception.BusinessExceptionAssertEnum;
import club.joylink.rtss.services.user.IUserSimulationStatService;
import club.joylink.rtss.simulation.cbtc.GroupSimulationService;
import club.joylink.rtss.simulation.cbtc.Simulation;
import club.joylink.rtss.simulation.cbtc.build.SimulationBuildParams;
import club.joylink.rtss.simulation.cbtc.member.SimulationMember;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 用户使用仿真情况记录
*/
@Slf4j
@Component
public class UserSimulationStatsManager {
@Autowired
private GroupSimulationService groupSimulationService;
@Autowired
private IUserSimulationStatService iUserSimulationStatService;
/**
* k - userId
* v - 用户订阅的仿真的groups
*/
private final Map<Long, Set<SimulationUseInfo>> userAndUseInfosMap = new ConcurrentHashMap<>();
/**
* k - group
* v - 仿真使用记录
*/
private final Map<String, Set<SimulationUseInfo>> simulationAndUseInfosMap = new ConcurrentHashMap<>();
/**
* 仿真被订阅记录仿真使用记录
*/
public void subscribeSimulation(Long userId, String group) {
Simulation simulation = groupSimulationService.getSimulationByGroup(group);
if (simulation.isScriptMakingSimulation() || simulation.isScriptPreviewSimulation()) {
return;
}
Set<SimulationUseInfo> useInfos = userAndUseInfosMap.computeIfAbsent(userId, k -> new HashSet<>());
Optional<SimulationUseInfo> infoOptional = useInfos.stream().filter(info -> group.equals(info.getGroup())).limit(1).findFirst();
if (infoOptional.isPresent()) { //如果记录已经存在
SimulationUseInfo useInfo = infoOptional.get();
useInfo.restart();
} else { //如果记录不存在
SimulationUseInfo newInfo = new SimulationUseInfo(group, userId);
useInfos.add(newInfo);
Set<SimulationUseInfo> groupKeyUseInfos = simulationAndUseInfosMap.computeIfAbsent(group, k -> new HashSet<>());
groupKeyUseInfos.add(newInfo);
}
}
/**
* 用户将仿真所有订阅退订则仿真使用记录暂停
*/
public void unsubscribeAll(Long userId, String group) {
Simulation simulation = groupSimulationService.getSimulationByGroup(group);
if (simulation.isScriptMakingSimulation() || simulation.isScriptPreviewSimulation()) {
return;
}
SimulationUseInfo useInfo = findUseInfo(userId, group);
BusinessExceptionAssertEnum.SYSTEM_EXCEPTION.assertNotNull(useInfo, String.format("仿真[%s]的使用未被记录", group));
useInfo.pause();
}
/**
* 仿真销毁持久化数据
*/
public void simulationDestroy(Simulation simulation) {
if (simulation.isScriptMakingSimulation() || simulation.isScriptPreviewSimulation()) { //剧本编制和预览仿真不记录
return;
}
//暂停并从map中移除仿真使用记录
String group = simulation.getGroup();
Set<SimulationUseInfo> useInfos = simulationAndUseInfosMap.remove(group);
BusinessExceptionAssertEnum.SYSTEM_EXCEPTION.assertCollectionNotEmpty(useInfos, String.format("仿真[%s]的使用未被记录", group));
useInfos.forEach(info -> {
info.pause();
Set<SimulationUseInfo> infos = userAndUseInfosMap.get(info.getUserId());
infos.removeIf(i -> group.equals(i.getGroup()));
});
SimulationBuildParams buildParams = simulation.getBuildParams();
useInfos.forEach(info -> {
Long userId = info.getUserId();
SimulationMember member = simulation.findMemberByUserId(userId);
String memberType = member == null ? null : member.getType().name();
String prdType = buildParams.getProdType() == null ? null : buildParams.getProdType().getCode();
iUserSimulationStatService.addUserSimulationStats(userId, buildParams.getMap().getId(),
prdType, info.getDuration(), memberType);
});
}
private SimulationUseInfo findUseInfo(Long userId, String group) {
Set<SimulationUseInfo> infos = userAndUseInfosMap.get(userId);
if (CollectionUtils.isEmpty(infos)) {
return null;
} else {
Optional<SimulationUseInfo> optional = infos.stream().filter(info -> group.equals(info.getGroup())).limit(1).findFirst();
return optional.orElse(null);
}
}
/**
* 仿真使用信息
*/
@Getter
@Setter
public class SimulationUseInfo {
private String group;
private Long userId;
private LocalDateTime startTime = LocalDateTime.now();
private int duration;
private boolean pause;
public SimulationUseInfo(String group, Long userId) {
this.group = group;
this.userId = userId;
}
/**
* 暂停计时
*/
public void pause() {
if (!pause) {
duration = (int) (duration + Duration.between(startTime, LocalDateTime.now()).toSeconds());
this.pause = true;
}
}
/**
* 重新开始计时
*/
public void restart() {
if (pause) {
startTime = LocalDateTime.now();
pause = false;
}
}
}
}

View File

@ -2,6 +2,7 @@ package club.joylink.rtss.simulation.cbtc.message.websocket;
import club.joylink.rtss.simulation.cbtc.event.*;
import club.joylink.rtss.websocket.WebsocketConfig;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -48,6 +49,7 @@ public class SimulationSubscribeManager {
/**
* 发布仿真连接/断连事件
*
* @param user
* @param subscribeTopic
* @param destination
@ -58,12 +60,18 @@ public class SimulationSubscribeManager {
String destination, boolean sub) {
if (sub) {
log.info(String.format("用户[%s]订阅仿真[%s]", user.getName(), destination));
this.applicationContext.publishEvent(new SimulationSubscribeEvent(this, user.getUser().getId(), subscribeTopic.getId(destination)));
} else {
log.info(String.format("用户[%s]取消订阅仿真[%s]", user.getName(), destination));
//如果某个仿真的所有订阅都被取消
SimulationUserSubscribeInfo simulationUserSubscribeInfo = userSubInfoMap.get(user.getName());
if (simulationUserSubscribeInfo == null || simulationUserSubscribeInfo.isSubscribeInfoEmpty(destination)) {
this.applicationContext.publishEvent(new SimulationUnsubscribeAllEvent(this, user.getUser().getId(), subscribeTopic.getId(destination)));
}
}
switch (subscribeTopic) {
case Main:
case WeChatMini:{
case WeChatMini: {
if (sub) {
this.applicationContext.publishEvent(new SimulationUserConnectEvent(user.getUser().getId(), subscribeTopic.getId(destination), this));
} else {
@ -71,7 +79,7 @@ public class SimulationSubscribeManager {
}
break;
}
case SandBox:{
case SandBox: {
if (sub) {
this.applicationContext.publishEvent(new SandboxUserConnectEvent(user.getUser().getId(), subscribeTopic.getId(destination), this));
} else {
@ -79,7 +87,7 @@ public class SimulationSubscribeManager {
}
break;
}
case Drive:{
case Drive: {
if (sub) {
this.applicationContext.publishEvent(new Drive3DUserConnectEvent(user.getUser().getId(), subscribeTopic.getId(destination), this));
} else {
@ -150,6 +158,7 @@ public class SimulationSubscribeManager {
/**
* 断开连接删除此连接的所有订阅并查询此用户所有客户端都不再订阅的路径返回
*
* @param wsSessionId
* @return 此用户所有客户端都没有订阅的路径
*/
@ -177,9 +186,24 @@ public class SimulationSubscribeManager {
return false;
}
public boolean isSubscribeInfoEmpty(String destination) {
if (sessionSubMap == null || sessionSubMap.isEmpty()) {
return true;
}
for (Set<SubscribeInfo> infos : sessionSubMap.values()) {
for (SubscribeInfo info : infos) {
if (destination.equals(info.getDestination())) {
return false;
}
}
}
return true;
}
class SubscribeInfo {
String wsSessionId;
String subId;
@Getter
String destination;
public SubscribeInfo(String wsSessionId, String subId, String destination) {