SpringBoot集成WebSocket实现单用户登录(强制下线)

SpringBoot集成WebSocket实现单用户登录(强制下线)

一、Java后端

引入WebSocket依赖

<!-- SpringWebSocket依赖 -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocket类:用户登录逻辑处理,通过loginType(1:单用户登录,2:多用户登录)判断该账户是单用户登录还是多用户登录。
单用户:将userId和session键值对存入userInfoMap ,账户每次登录时判断是否已经存在userId,如果有则重置websocket,前端收到断开信息执行断开调用(清除前端token和vuex数据等);
多用户:每次登录都将token和session键值对存入userCountMap,如果存在用户大于1则发送异地登录短信。

@Component
@Slf4j
@ServerEndpoint("/webSocket/{token}/{loginType}")
public class WebSocket {
    // 单用户(用户登录标识)
    private static ConcurrentHashMap<String, Session> userInfoMap = new ConcurrentHashMap<>();
    // 单个账户用户在线个数(用户多地登录判断)
    private static ConcurrentHashMap<String, Integer> userCountMap = new ConcurrentHashMap<>();
    // 是否已经发过短信
    private static ConcurrentHashMap<String, Boolean> sendEmailFlagMap = new ConcurrentHashMap<>();
    // 在线人数
    private static int onlineCount = 0;
    private static SysUserService sysUserService;

    @Autowired
    public void setChatService(SysUserService sysUserService) {
        WebSocket.sysUserService = sysUserService;
    }
    
    /**
     * @description websocket发送消息
     * @param token
     * @param message
     */
    @OnMessage
    public void onMessage(@PathParam("token") String token, String message) {
        log.info(token);
        String userId = JwtUtil.getUserIdByJwtToken(token);
        try {
            // 向指定用户发送消息
            userInfoMap.get(userId).getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 建立连接调用的方法
     * @param session
     * @param token
     * @param loginType (1:单用户登录,2:多用户登录)
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token, @PathParam("loginType") int loginType) throws IOException {
        String userId = JwtUtil.getUserIdByJwtToken(token);
        // 单地登录、如果存在socket则强制下线
        if (userInfoMap.containsKey(userId) && loginType == 1) {
            // 关闭socket(强制下线)
            userInfoMap.get(userId).close();
            userInfoMap.remove(userId);
            // 重新开启socket(重置socket)
        } else if (loginType == 2) {
            // 保存session
            userInfoMap.put(token, session);
            // 单账户同一在线用户计数
            userCountMap.put(userId, userCountMap.getOrDefault(userId, 0) + 1);
            onlineCount = userInfoMap.size();
            log.info("建立连接成功,当前人数为" + onlineCount);
            // 如果同一在线用户大于1且没发过邮箱则发送邮箱
            if (userCountMap.get(userId) > 1 && sendEmailFlagMap.getOrDefault(token, true)) {
                sendEmailFlagMap.put(token, false);
                String email = sysUserService.getById(userId).getEmail();
                EmailUtil.sendEmail(email, "账号管理系统-异地登录提醒", "你的账号于" + LocalDateTime.now() + "在异地登录。");
            }
            return;
        }
        userInfoMap.put(userId, session);
        onlineCount = userInfoMap.size();
        log.info("建立连接成功,当前人数为" + onlineCount);
    }

    /**
     * 关闭链接调用接口
     * @param token
     * @param loginType (1:单用户登录,2:多用户登录)
     */
    @OnClose
    public void onClose(@PathParam("token") String token, @PathParam("loginType") int loginType) {
        log.info(token);
        String userId = JwtUtil.getUserIdByJwtToken(token);
        if (loginType == 1) {
            userInfoMap.remove(userId);
            onlineCount = userInfoMap.size();
            log.info("断开连接成功,当前人数为" + onlineCount);
        } else if (loginType == 2) {
            userInfoMap.remove(token);
            sendEmailFlagMap.remove(token);
            userCountMap.put(userId, userCountMap.getOrDefault(userId, 0) - 1);
            onlineCount = userInfoMap.size();
            log.info(userCountMap.toString());
        }
    }
}

二、Vue前端

前端调用initWebSocket方法获取websocket对象,将websocket对象存在Vuex。

import { getToken, removeToken } from '@/utils/auth'
import { MessageBox } from 'element-ui'
import router, { resetRouter } from '@/router'
import store from '@/store'

export default {
  initWebSocket() {
    console.log('initWebSocket方法')
    // 根据自己的方法获取userCode
    const token = getToken()
    // WebSocket地址为接口地址,http用ws、https用wws
    // var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws"
    const websocket = new WebSocket(`ws://localhost:8080/webSocket/${token}/${store.getters.loginType}`)
    websocket.onopen = this.webSocketOpen
    websocket.onerror = this.webSocketError
    websocket.onmessage = this.webSocketMessage
    websocket.onclose = this.webSocketClose
    return websocket
  },
  // 连接时调用
  webSocketOpen() {
    console.log('WebSocket连接成功')
  },
  // 错误时调用
  webSocketError() {
    console.log('WebSocket连接错误')
  },
  // 接收服务器返回的数据
  webSocketMessage(e) {
    console.log('服务器返回的消息:', e.data)
  },
  // 断开时调用
  webSocketClose(e) {
    console.log('WebSocket连接断开', e)
    removeToken()
    MessageBox.confirm('您的账户在别处登录, 您被强制下线!', '异地登录提醒', {
      closeOnClickModal: false,
      confirmButtonText: '确定',
      type: 'warning',
      showCancelButton: false
    }).then(() => {
      resetRouter()
      store.commit('RESET_STATE')
      router.push('/login')
    }).catch(() => {
    })
  }
}

三、Token工具类

public class JwtUtil {
    public static final long EXPIRE = 1000 * 60 * 60 * 24;  //存在时间
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";  //密钥

    public static String getJwtToken(String id, String username) {

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("ams-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("username", username)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            return false;
        }
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if (StringUtils.isEmpty(jwtToken)) {
                return false;
            }
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取用户id
     * @param jwtToken
     * @return
     */
    public static String getUserIdByJwtToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            return "";
        }
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }

    /**
     * 根据token获取用户账户
     * @param jwtToken
     * @return
     */
    public static String getUsernameByJwtToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            return "";
        }
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("username");
    }
}

四、总结

通过websocket保持前后端通信,一个websocket连接就是一个账户用户的登录。后端处理相应逻辑。

  • 2
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现Spring Boot集成WebSocket实现消息推送,需要进行以下步骤: 1. 添加Spring Boot WebSocket依赖 2. 创建WebSocket配置类 3. 创建WebSocket处理器类 4. 创建WebSocket拦截器类 5. 创建WebSocket消息模型类 6. 在Spring Boot中使用WebSocket 下面是一个简的示例代码: 1. 添加Spring Boot WebSocket依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建WebSocket配置类 创建一个WebSocket配置类,用于配置WebSocket相关的参数,如下所示: ``` @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHandler(), "/websocket").addInterceptors(new WebSocketInterceptor()); } } ``` 3. 创建WebSocket处理器类 创建一个WebSocket处理器类,用于处理WebSocket连接、断开连接和接收消息等操作,如下所示: ``` public class WebSocketHandler extends TextWebSocketHandler { private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { for (WebSocketSession s : sessions) { s.sendMessage(message); } } } ``` 4. 创建WebSocket拦截器类 创建一个WebSocket拦截器类,用于拦截WebSocket连接请求,如下所示: ``` public class WebSocketInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } } ``` 5. 创建WebSocket消息模型类 创建一个WebSocket消息模型类,用于封装WebSocket消息,如下所示: ``` public class WebSocketMessage { private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; } } ``` 6. 在Spring Boot中使用WebSocketSpring Boot中使用WebSocket非常简,只需要在Controller中注入WebSocketSession即可,如下所示: ``` @Controller public class WebSocketController { @Autowired private WebSocketSession session; @MessageMapping("/send") public void send(WebSocketMessage message) throws Exception { session.sendMessage(new TextMessage(message.getContent())); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值