From 25450fc781d2a24d07155237e54fb2e3d211eca8 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 5 Dec 2023 16:37:30 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=97=AE=E8=AF=8A=E5=A4=A7=E5=8E=85?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../supervision/config/JwtInterceptor.java | 21 ---------------- .../constant/UserTokenConstant.java | 3 --- .../controller/UserController.java | 10 -------- .../controller/WebSocketServer.java | 25 ++++++++++++------- .../service/impl/DiagnoseHallServiceImpl.java | 5 +--- .../usermanage/KickUserListener.java | 8 +++--- .../usermanage/UserWebSocketDTO.java | 16 ++++++++++++ 7 files changed, 38 insertions(+), 50 deletions(-) create mode 100644 virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java diff --git a/virtual-patient-common/src/main/java/com/supervision/config/JwtInterceptor.java b/virtual-patient-common/src/main/java/com/supervision/config/JwtInterceptor.java index 8513504d..6dfb5735 100644 --- a/virtual-patient-common/src/main/java/com/supervision/config/JwtInterceptor.java +++ b/virtual-patient-common/src/main/java/com/supervision/config/JwtInterceptor.java @@ -58,27 +58,6 @@ public class JwtInterceptor implements HandlerInterceptor { return true; } - public void checkSingleLogin(String userId, JWT currentJwt) { - - if (Boolean.FALSE.equals(redisTemplate.hasKey(UserTokenConstant.TOKEN_CACHE + userId))) { - throw new BusinessException("用户已被踢下线或超时,请重新登录", 505); - } - String value = redisTemplate.opsForValue().get(UserTokenConstant.TOKEN_CACHE + userId); - - long redisCacheTime = Long.parseLong(String.valueOf(value)); - Object currentJwtIssueTimeObject = currentJwt.getPayload("issueTime"); - long currentJwtIssueTime = Long.parseLong(String.valueOf(currentJwtIssueTimeObject)); - if (redisCacheTime == currentJwtIssueTime) { - // 如果相等,说明这个token就是最新的,直接放行 - return; - } else if (currentJwtIssueTime > redisCacheTime) { - // 如果当前请求时间,大于Redis缓存时间,说明重新登录了,这个时候要把最新的放到缓存中 - redisTemplate.opsForValue().set(UserTokenConstant.TOKEN_CACHE + userId, String.valueOf(System.currentTimeMillis()), 1000 * 5L, TimeUnit.MILLISECONDS); - } else { - // 走到这里,说明redisCacheTime是最新的,说明当前用户请求了一个新的token,那么原来的用户就踢掉 - throw new BusinessException("当前用户已在其他地方登录!", 505); - } - } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, diff --git a/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java b/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java index 29466321..2e1d4769 100644 --- a/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java +++ b/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java @@ -1,9 +1,6 @@ package com.supervision.constant; public interface UserTokenConstant { - - String TOKEN_CACHE = "USER:LOGIN:TOKEN:"; - String USER_ID_CACHE = "USER:ID:CACHE"; String KICK_CHANNEL = "USER:KICK:CHANNEL"; diff --git a/virtual-patient-web/src/main/java/com/supervision/controller/UserController.java b/virtual-patient-web/src/main/java/com/supervision/controller/UserController.java index 0af12668..c692e824 100644 --- a/virtual-patient-web/src/main/java/com/supervision/controller/UserController.java +++ b/virtual-patient-web/src/main/java/com/supervision/controller/UserController.java @@ -43,19 +43,9 @@ public class UserController { throw new BusinessException("密码错误"); } String token = TokenUtil.creatToken(JSONUtil.toJsonStr(user.get())); - // 将用户的token保存起来,超时时间为5分钟 - redisTemplate.opsForValue().set(UserTokenConstant.TOKEN_CACHE + user.get().getId(), String.valueOf(System.currentTimeMillis()), 1000 * 5L, TimeUnit.MILLISECONDS); return token; } - @ApiOperation("token心跳") - @PostMapping("keepaliveToken") - public void keepaliveToken() { - User user = UserUtil.getUser(); - // 每次心跳都设置为5分钟之后 - redisTemplate.expire(UserTokenConstant.TOKEN_CACHE + user.getId(), 1000 * 5L, TimeUnit.MILLISECONDS); - } - @ApiOperation("踢用户下线") @GetMapping("kickUser") public void kickUser(String userId) { diff --git a/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java b/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java index 1a122a26..05b1b078 100644 --- a/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java +++ b/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java @@ -1,7 +1,10 @@ package com.supervision.controller; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import com.supervision.constant.UserTokenConstant; +import com.supervision.usermanage.UserWebSocketDTO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -29,10 +32,12 @@ public class WebSocketServer { * 有客户端连接成功 */ @OnOpen - public void onOpen(Session session, @PathParam(value = "uid") String uid) { + public void onOpen(Session session, @PathParam(value = "uid") String uid) throws IOException { log.info("用户:{}登录,缓存到Redis", uid); + // 链接之前先把之前的用户踢下线(ignoreSessionId防止把当前用户踢下线) + redisTemplate.convertAndSend(UserTokenConstant.KICK_CHANNEL, JSONUtil.toJsonStr(new UserWebSocketDTO(uid, session.getId()))); SESSION_POOL.put(uid, session); - redisTemplate.opsForSet().add(UserTokenConstant.USER_ID_CACHE, uid); + redisTemplate.opsForHash().put(UserTokenConstant.USER_ID_CACHE, uid, session.getId()); } /** @@ -40,9 +45,9 @@ public class WebSocketServer { */ @OnClose public void onClose(Session session, @PathParam(value = "uid") String uid) { - redisTemplate.opsForSet().remove(UserTokenConstant.USER_ID_CACHE, uid); + redisTemplate.opsForHash().delete(UserTokenConstant.USER_ID_CACHE, uid, session.getId()); SESSION_POOL.remove(uid); - log.info("用户:{}关闭,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForSet().size(UserTokenConstant.USER_ID_CACHE)); + log.info("用户:{}关闭,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForHash().size(UserTokenConstant.USER_ID_CACHE)); } /** @@ -50,18 +55,20 @@ public class WebSocketServer { */ @OnError public void onError(Session session, @PathParam(value = "uid") String uid, Throwable throwable) { - redisTemplate.opsForSet().remove(UserTokenConstant.USER_ID_CACHE, uid); + redisTemplate.opsForHash().delete(UserTokenConstant.USER_ID_CACHE, uid, session.getId()); SESSION_POOL.remove(uid); - log.error("用户:{}发生错误,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForSet().size(UserTokenConstant.USER_ID_CACHE), throwable); + log.error("用户:{}发生错误,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForHash().size(UserTokenConstant.USER_ID_CACHE), throwable); } // 实现一个方法用于踢下线用户,走的是Redis的消息队列 - public void kickUser(String userId) throws IOException { + public void kickUser(String userId, String ignoreSessionId) throws IOException { log.info("尝试主动踢用户:{}下线", userId); Session session = SESSION_POOL.get(userId); - if (ObjectUtil.isNotEmpty(session)) { + // 只有不是忽略剔除的sessionId才可以踢下线 + if (ObjectUtil.isNotEmpty(session) && !StrUtil.equals(ignoreSessionId, session.getId())) { session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "用户被踢下线")); - log.info("主动踢用户:{}下线成功", userId); + SESSION_POOL.remove(userId); + log.info("主动踢用户:{},sessionId:{} 下线成功", userId, session.getId()); return; } log.info("主动踢用户:{}下线,未找到用户,踢下线失败", userId); diff --git a/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java b/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java index 0996558b..586c1eda 100644 --- a/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java +++ b/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java @@ -23,11 +23,8 @@ public class DiagnoseHallServiceImpl implements DiagnoseHallService { @Override public boolean achieveDiagnoseResource() { long humanMaxNumber = Long.parseLong(resourceNumber); - Long currentUserNum = redisTemplate.opsForSet().size(UserTokenConstant.USER_ID_CACHE); + long currentUserNum = redisTemplate.opsForHash().size(UserTokenConstant.USER_ID_CACHE); // 如果小于数字人最大连接数,则可以连接 - if (null == currentUserNum){ - return true; - } return currentUserNum <= humanMaxNumber; } } diff --git a/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java b/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java index b2fc923e..c1311808 100644 --- a/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java +++ b/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java @@ -1,5 +1,6 @@ package com.supervision.usermanage; +import cn.hutool.json.JSONUtil; import com.supervision.constant.UserTokenConstant; import com.supervision.controller.WebSocketServer; import lombok.extern.slf4j.Slf4j; @@ -20,10 +21,11 @@ public class KickUserListener implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { - String userId = message.toString(); - log.info("Redis的Channel:{}收到踢用户下线消息:{}", UserTokenConstant.KICK_CHANNEL, userId); + String messageString = message.toString(); + UserWebSocketDTO user = JSONUtil.toBean(messageString, UserWebSocketDTO.class); + log.info("Redis的Channel:{}收到踢用户{}下线消息", UserTokenConstant.KICK_CHANNEL, user.getUserId()); try { - webSocketServer.kickUser(userId); + webSocketServer.kickUser(user.getUserId(), user.getIgnoreSessionId()); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java b/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java new file mode 100644 index 00000000..42a50b7a --- /dev/null +++ b/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java @@ -0,0 +1,16 @@ +package com.supervision.usermanage; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class UserWebSocketDTO { + + private String userId; + + /** + * 忽略提除的ID + */ + private String ignoreSessionId; +} From 24d943fe431d02480f1f46f52cd16b239bfe5e36 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 5 Dec 2023 17:58:23 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E9=97=AE=E8=AF=8A=E5=A4=A7=E5=8E=85?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + virtual-patient-common/pom.xml | 6 +++ .../constant/UserTokenConstant.java | 3 +- virtual-patient-web/pom.xml | 1 + .../controller/WebSocketServer.java | 26 ++++++----- .../pojo/vo/AskPhysicalResultReqVO.java | 1 - .../service/impl/DiagnoseHallServiceImpl.java | 13 +++--- .../usermanage/UserResourceCheck.java | 45 +++++++++++++++++++ .../usermanage/UserWebSocketDTO.java | 1 + 9 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 virtual-patient-web/src/main/java/com/supervision/usermanage/UserResourceCheck.java diff --git a/pom.xml b/pom.xml index a4a59a2e..48b22f29 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ 2.3.31 8.0.26 1.5.22 + 2.2.5 diff --git a/virtual-patient-common/pom.xml b/virtual-patient-common/pom.xml index 39517bdf..4eb50d36 100644 --- a/virtual-patient-common/pom.xml +++ b/virtual-patient-common/pom.xml @@ -38,6 +38,12 @@ redis.clients jedis + + + com.baomidou + lock4j-redis-template-spring-boot-starter + ${lock4j.version} + diff --git a/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java b/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java index 2e1d4769..a45b5bd2 100644 --- a/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java +++ b/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java @@ -1,7 +1,8 @@ package com.supervision.constant; public interface UserTokenConstant { - String USER_ID_CACHE = "USER:ID:CACHE"; + String USER_WEBSOCKET_CACHE = "USER:ID:CACHE"; String KICK_CHANNEL = "USER:KICK:CHANNEL"; + } diff --git a/virtual-patient-web/pom.xml b/virtual-patient-web/pom.xml index 34155a28..4830043f 100644 --- a/virtual-patient-web/pom.xml +++ b/virtual-patient-web/pom.xml @@ -58,6 +58,7 @@ spring-boot-starter-websocket + diff --git a/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java b/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java index 05b1b078..0aa39942 100644 --- a/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java +++ b/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java @@ -2,12 +2,11 @@ package com.supervision.controller; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; import com.supervision.constant.UserTokenConstant; -import com.supervision.usermanage.UserWebSocketDTO; -import lombok.RequiredArgsConstructor; +import com.supervision.usermanage.UserResourceCheck; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -28,16 +27,19 @@ public class WebSocketServer { //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。 private static final ConcurrentHashMap SESSION_POOL = new ConcurrentHashMap<>(); + @Value("${human.resourceMaxNumber}") + private String resourceNumber; + + @Autowired + private UserResourceCheck userResourceCheck; + /** * 有客户端连接成功 */ @OnOpen - public void onOpen(Session session, @PathParam(value = "uid") String uid) throws IOException { - log.info("用户:{}登录,缓存到Redis", uid); - // 链接之前先把之前的用户踢下线(ignoreSessionId防止把当前用户踢下线) - redisTemplate.convertAndSend(UserTokenConstant.KICK_CHANNEL, JSONUtil.toJsonStr(new UserWebSocketDTO(uid, session.getId()))); + public void onOpen(Session session, @PathParam(value = "uid") String uid) { + userResourceCheck.achieveDiagnoseResourceAndOpenConnection(uid, session); SESSION_POOL.put(uid, session); - redisTemplate.opsForHash().put(UserTokenConstant.USER_ID_CACHE, uid, session.getId()); } /** @@ -45,9 +47,9 @@ public class WebSocketServer { */ @OnClose public void onClose(Session session, @PathParam(value = "uid") String uid) { - redisTemplate.opsForHash().delete(UserTokenConstant.USER_ID_CACHE, uid, session.getId()); + redisTemplate.opsForHash().delete(UserTokenConstant.USER_WEBSOCKET_CACHE, uid, session.getId()); SESSION_POOL.remove(uid); - log.info("用户:{}关闭,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForHash().size(UserTokenConstant.USER_ID_CACHE)); + log.info("用户:{}关闭,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForHash().size(UserTokenConstant.USER_WEBSOCKET_CACHE)); } /** @@ -55,9 +57,9 @@ public class WebSocketServer { */ @OnError public void onError(Session session, @PathParam(value = "uid") String uid, Throwable throwable) { - redisTemplate.opsForHash().delete(UserTokenConstant.USER_ID_CACHE, uid, session.getId()); + redisTemplate.opsForHash().delete(UserTokenConstant.USER_WEBSOCKET_CACHE, uid, session.getId()); SESSION_POOL.remove(uid); - log.error("用户:{}发生错误,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForHash().size(UserTokenConstant.USER_ID_CACHE), throwable); + log.error("用户:{}发生错误,从Redis中移除,当前连接数为:{}", uid, redisTemplate.opsForHash().size(UserTokenConstant.USER_WEBSOCKET_CACHE), throwable); } // 实现一个方法用于踢下线用户,走的是Redis的消息队列 diff --git a/virtual-patient-web/src/main/java/com/supervision/pojo/vo/AskPhysicalResultReqVO.java b/virtual-patient-web/src/main/java/com/supervision/pojo/vo/AskPhysicalResultReqVO.java index d30311b1..8174b7b7 100644 --- a/virtual-patient-web/src/main/java/com/supervision/pojo/vo/AskPhysicalResultReqVO.java +++ b/virtual-patient-web/src/main/java/com/supervision/pojo/vo/AskPhysicalResultReqVO.java @@ -18,7 +18,6 @@ public class AskPhysicalResultReqVO { private String locationCode; @ApiModelProperty("初步诊断ID") - @NotBlank(message = "初步诊断ID不能为空") private String primaryId; @NotBlank(message = "流程ID不能为空") diff --git a/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java b/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java index 586c1eda..9bbcf208 100644 --- a/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java +++ b/virtual-patient-web/src/main/java/com/supervision/service/impl/DiagnoseHallServiceImpl.java @@ -1,8 +1,9 @@ package com.supervision.service.impl; -import cn.hutool.core.util.ObjectUtil; +import com.baomidou.lock.annotation.Lock4j; import com.supervision.constant.UserTokenConstant; import com.supervision.service.DiagnoseHallService; +import com.supervision.usermanage.UserResourceCheck; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -14,17 +15,13 @@ import org.springframework.stereotype.Service; @Slf4j public class DiagnoseHallServiceImpl implements DiagnoseHallService { - private final RedisTemplate redisTemplate; - - @Value("${human.resourceMaxNumber}") - private String resourceNumber; + private final UserResourceCheck userResourceCheck; + @Lock4j(name = "achieveDiagnoseResource") @Override public boolean achieveDiagnoseResource() { - long humanMaxNumber = Long.parseLong(resourceNumber); - long currentUserNum = redisTemplate.opsForHash().size(UserTokenConstant.USER_ID_CACHE); // 如果小于数字人最大连接数,则可以连接 - return currentUserNum <= humanMaxNumber; + return userResourceCheck.achieveDiagnoseResource(); } } diff --git a/virtual-patient-web/src/main/java/com/supervision/usermanage/UserResourceCheck.java b/virtual-patient-web/src/main/java/com/supervision/usermanage/UserResourceCheck.java new file mode 100644 index 00000000..4d18a7ea --- /dev/null +++ b/virtual-patient-web/src/main/java/com/supervision/usermanage/UserResourceCheck.java @@ -0,0 +1,45 @@ +package com.supervision.usermanage; + +import cn.hutool.json.JSONUtil; +import com.baomidou.lock.annotation.Lock4j; +import com.supervision.constant.UserTokenConstant; +import com.supervision.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; + +import javax.websocket.*; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class UserResourceCheck { + + @Value("${human.resourceMaxNumber}") + private String resourceNumber; + + private final RedisTemplate redisTemplate; + + @Lock4j(name = "achieveDiagnoseResource") + public boolean achieveDiagnoseResource() { + long humanMaxNumber = Long.parseLong(resourceNumber); + long currentSize = redisTemplate.opsForHash().size(UserTokenConstant.USER_WEBSOCKET_CACHE); + // 如果小于数字人最大连接数,则可以连接 + return currentSize < humanMaxNumber; + } + + @Lock4j(name = "achieveDiagnoseResourceAndOpenConnection") + public void achieveDiagnoseResourceAndOpenConnection(String uid, Session session){ + // 如果小于数字人最大连接数,则可以连接 + if (!achieveDiagnoseResource()) { + throw new BusinessException("暂时没有资源,建立连接失败"); + } + log.info("用户:{}登录,缓存到Redis", uid); + // 链接之前先把之前的用户踢下线(ignoreSessionId防止把当前用户踢下线) + // 注意,这里如果用户没有进到问诊页面,只是在问诊大厅时,是不会被踢掉的.(因为这时没有建立websocket连接) + redisTemplate.convertAndSend(UserTokenConstant.KICK_CHANNEL, JSONUtil.toJsonStr(new UserWebSocketDTO(uid, session.getId()))); + redisTemplate.opsForHash().put(UserTokenConstant.USER_WEBSOCKET_CACHE, uid, session.getId()); + } +} diff --git a/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java b/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java index 42a50b7a..063401f1 100644 --- a/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java +++ b/virtual-patient-web/src/main/java/com/supervision/usermanage/UserWebSocketDTO.java @@ -13,4 +13,5 @@ public class UserWebSocketDTO { * 忽略提除的ID */ private String ignoreSessionId; + }