添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
坚强的领结  ·  Cannot have aggregate ...·  1 年前    · 
没读研的小蝌蚪  ·  重置debezium ...·  1 年前    · 
< dependency > < groupId > org . springframework . boot < / groupId > < artifactId > spring - boot - starter - websocket < / artifactId > < / dependency >

2.登录方法中登录成功后将用户账号存入session

HttpSession session = request.getSession();
ServletContext application = session.getServletContext();
Integer userId = ShiroUtils.getUserId();
session.setAttribute("userId", userId);
Set<Integer> onlineUserSet = (Set)application.getAttribute("onlineUserSet");
onlineUserSet.add(userId);
application.setAttribute("onlineUserSet", onlineUserSet);
//System.out.println("当前在线用户:"+onlineUserSet.toString());

3.监听session变化判断登录用户人数和用户id

@WebListener
@Component
public class UserStatisticsListener implements HttpSessionListener,ServletContextListener {
    private ServletContext application = null;
    public static HttpSession httpSession = null;
    @Override
    public void contextDestroyed(ServletContextEvent s) {
        System.out.println("context Destroyed");
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        application = sce.getServletContext();
        Set<Integer> onlineUserSet = new HashSet<>();
        application.setAttribute("onlineUserSet", onlineUserSet);
        System.out.println("onlineUserSet2==>"+onlineUserSet);
     * 创建session
     * @param se
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        httpSession = se.getSession();
        System.out.println("sessionId==>"+se.getSession().getId());
     * 销毁session
     * @param se
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("销毁session");
        HttpSession session = se.getSession();
        Set<Integer> onlineUserSet = (Set<Integer>)application.getAttribute("onlineUserSet");
        Integer userId = (Integer)session.getAttribute("userId");
        onlineUserSet.remove(userId);
        application.setAttribute("onlineUserSet",onlineUserSet);
        onlineUserSet = (Set<Integer>)application.getAttribute("onlineUserSet");
        System.out.println("当前在线用户:"+onlineUserSet.toString());
        System.out.println(userId + "-->退出");
        //推送所有在线用户现有的在线用户id
        if(!CollectionUtils.isEmpty(onlineUserSet)){
            WebSocketConfiguration.sendInfo(onlineUserSet.toString(),onlineUserSet);

4.建立websocket连接

  1. 需要自定义HttpSessionConfigurator将Session
  2. 转化成HttpSession,否则websocket的sessionId和登录后存储的sessionId不一致,无法监听退出后的操作
    注意: 在使用@ServerEndpoint时如果项目引入aop则会出现启动异常,可
    在WebLogAspect 类中加入不扫描的包或类,如下:

    !execution(public * com.demo.WebSocketConfiguration…*(…))
@Aspect
@Component
public class WebLogAspect { /**
     * 扫描的包,类
    @Pointcut("execution(public * com.demo.*.*..*(..)) && !execution(public * com.demo.WebSocketConfiguration..*(..)) ")
    public void webLog(){}
    ......
@Component
@ServerEndpoint(value = "/websocket/{userId}",configurator = HttpSessionConfigurator.class)
@Slf4j
public class WebSocketConfiguration {
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     * Map的key为userId,List用于存放一个userId的多个终端,比如同一个userId,同时在手机端和PC登陆
    private static ConcurrentHashMap<Integer, List<WebSocketConfiguration>> webSocketMap = new ConcurrentHashMap<>();
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
     * 接收userId
    private Integer userId =




    
 null;
    private HttpSession httpSession = null;
     * 连接建立成功调用的方法
    @OnOpen
    public void onOpen(Session session,EndpointConfig config, @PathParam("userId") Integer userId) {
        if(null != userId){
            this.session = session;
            this.userId = userId;
            //httpSession赋值和登录使用同一个session
            this.httpSession = (HttpSession)config.getUserProperties().get(HttpSession.class.getName());
            List<WebSocketConfiguration> servers;
            List<WebSocketConfiguration> webSocketServers = new ArrayList<>();
            //将接入的客户端信息添加到内存
            if (webSocketMap.containsKey(userId)) {
                //查询当前userId以及当前的session是否已经存在,如果存在,先移除再新增,如果不存在,直接新增
                webSocketServers = webSocketMap.get(userId).stream().filter(o -> o.session.getId().equals(session.getId())).collect(Collectors.toList());
            if (webSocketMap.containsKey(userId) && webSocketServers.size() > 0) {
                webSocketServers = webSocketMap.get(userId);
                webSocketServers.removeIf(webSocketServer -> webSocketServer.session.getId().equals(session.getId()));
                servers = webSocketServers;
                servers.add(this);
                webSocketMap.put(userId, servers);
            } else {
                servers = null == webSocketMap.get(userId) ? new ArrayList<>() : webSocketMap.get(userId);
                servers.add(this);
                webSocketMap.put(userId, servers);
            log.info("用户【" + userId + "】sessionId:[" + session.getId() + "]连接成功");
            //推送给所有在线用户现在已上线的用户id
            if(null != this.httpSession){
                Set<Integer> onlineUserSet = (Set)this.httpSession.getServletContext().getAttribute("onlineUserSet");
                sendInfo(onlineUserSet.toString(),onlineUserSet);
            }else {
                sendInfo(userId);
     * 连接关闭调用的方法
    @OnClose
    public void onClose() {
        List<WebSocketConfiguration> webSocketServers = new ArrayList<>();
        if (webSocketMap.containsKey(userId)) {
            webSocketServers = webSocketMap.get(userId).stream().filter(o -> o.session.getId().equals(session.getId())).collect(Collectors.toList());
        if (webSocketMap.containsKey(userId) && webSocketServers.size() > 0) {
            webSocketServers = webSocketMap.get(userId);
            Iterator<WebSocketConfiguration> iterator = webSocketServers.iterator();
            while (iterator.hasNext()) {
                if (iterator.next().session.getId().equals(session.getId())) {
                    iterator.remove();
            webSocketMap.put(userId, webSocketServers);
            log.info("用户【" + userId +




    
 "】sessionId:[" + session.getId() + "]断开连接" );
        //调用销毁session
        if(null != userId && null != this.httpSession){
           // this.httpSession.invalidate();
            //发送断开消息
            sendInfo(userId);
        HttpSession httpSession1 = this.httpSession;
		//todo 待优化,暂时用Timer判定销毁session,优点:可以和前端交互判定浏览器关闭后的session销毁及及时推送给其他用户;缺点:前端自动退出登录后会执行两次销毁程序,则此时会有空指针异常抛出,但不影响程序使用
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("隔1分钟执行销毁session....");
                if(webSocketMap.containsKey(userId)){
                    List<WebSocketConfiguration> webSocketServers = webSocketMap.get(userId);
                    if(CollectionUtils.isEmpty(webSocketServers)){
                        if(null != httpSession1){
                            System.out.println("销毁...");
                            httpSession1.invalidate();
        }, 1000 * 60);
     * 收到客户端消息后调用的方法
     * 前端发送心跳包判断后台程序是否关闭
     * @param message 客户端发送过来的消息
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户【" + userId + "】sessionId:[" + session.getId() + "]发送消息给服务端:" + message);
        try {
            //判断userId是否存在
            if(webSocketMap.containsKey(userId)){
                sendMessage("ok");
            }else {
            //退送断线消息MessageUtils.Offline自定义用于前端判断,如:String Offline = "404session失效"
                sendMessage(MessageUtils.Offline);
        } catch (IOException e) {
            e.printStackTrace();
     * @param error
    @OnError
    public void onError(Throwable error) {
        log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
        error.printStackTrace();
     * 实现服务器主动推送
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
     * 发送自定义消息
    public static void sendInfo(String message, @PathParam("userIds") Set<Integer> userIds){
        log.info(JSON.toJSONString("所有客户端:" + webSocketMap));
        if (!CollectionUtils.isEmpty(userIds)) {
            userIds.forEach(i->{
                if(webSocketMap.containsKey(i)){
                    for (WebSocketConfiguration webSocketServer : webSocketMap.get(i)) {
                        try {
	                        //哪个终端有消息,就推送哪个
							//	if (sessionId.equals(webSocketServer.session.getId())) {
			                //    	webSocketServer.sendMessage(message);
			                //对该用户的所有终端都推送无需判断sessionId
                            webSocketServer.sendMessage(message);
                        } catch (IOException e) {
                            e.printStackTrace();
            });
     * 发送自定义消息
    public static void sendInfo(@PathParam("userId") Integer userId){
        if (userId != null && webSocketMap.containsKey(userId)) {
            for (WebSocketConfiguration webSocketServer : webSocketMap.get(userId)) {
                try {webSocketServer.sendMessage(MessageUtils.Offline);
                } catch (IOException e) {
                    e.printStackTrace();
        } else {
            log.error("用户" + userId + ",不在线!");

5.创建HttpSessionConfigurator.class

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator{
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        if(null != httpSession){
            sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

6.创建WebSocketConfig

自动注册使用@ServerEndpoint注解声明的websocket endpoint

@Configuration
public class WebSocketConfig {
     * ServerEndpointExporter 作用
     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
     * @return
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();

7.vue部分websocket.js

import {Notification} from 'element-ui'
var url =  'ws://' + location.host + '/socket/websocket/'
var ws;
var tt;
var lockReconnect = false;//避免重复连接
var clientId = localStorage.getItem("clientId")//缓存中取出客户端id
let count = 0;
//连接失败3次后会自动跳转到登录页面
var websocket = {
  Init: function(clientId) {
    this.clientId = localStorage.getItem("clientId")
    if ("WebSocket" in window) {
      ws = new WebSocket(url + clientId);
    } else if ("MozWebSocket" in window) {
      ws = new MozWebSocket(url + clientId);
    } else {
      console.log("您的浏览器不支持 WebSocket!");
      return;
    ws.onmessage = function(e) {
      messageHandle(e.data)
      heartCheck.start()
    ws.onclose = function() {
      console.log("连接已关闭")
      localStorage.clear()
      if(count <= 3){
        reconnect(clientId);
      }else {
        Notification({
          title: '错误',
          message: '连接已关闭',
          type: 'error',
        window.location.reload();
    ws.onopen = function(e) {
      console.log("连接成功")
      messageHandle(e.data)
    ws.onerror = function(e) {
      console.log("数据传输发生错误,后台服务关闭");
      if(count <= 3){
        reconnect(clientId);
      }else {
        Notification({
          title: '错误',
          message: '数据传输发生错误,服务关闭或网络不通!',
          type: 'error',
        localStorage.clear()
        window.location.reload();
      messageHandle(undefined)
  Send:function(sender,reception,body,flag){
    let data = {
      sender:sender,
      reception:reception,
      body:body,
      flag:flag
    let msg= JSON.stringify(data)
    ws.send(msg)
  getWebSocket(){
    return ws;
  getStatus() {
    if (ws.readyState == 0) {
      return "未连接";
    } else if (ws.readyState == 1) {
      return "已连接";
    } else if (ws.readyState == 2) {
      return "连接正在关闭";
    } else if (ws.readyState == 3) {
      return "连接已关闭";
export default websocket;
//根据消息标识做不同的处理
function messageHandle(message) {
  console.log(message,"msg")
  switch (message) {
    case '404session失效':
      //关闭连接跳转登录
      ws.close();
      break;
    case undefined :
      //连接失败
      count++;
      break;
    case 'ok' :
      //心跳消息成功
      count = 0;
      break;
    default:
      let msg = JSON.parse(message)
      //list通知其他用户现在在线的人的id,存储到onlineUser
      localStorage.setItem("onlineUser",msg)
      count = 0
      heartCheck.start();
function reconnect(sname) {
  if(lockReconnect) {
    return;
  lockReconnect = true;
  //没连接上会一直重连,设置延迟避免请求过多
  tt && clearTimeout(tt);
  tt = setTimeout(function () {
    console.log("执行断线重连...")
    websocket.Init(sname);
    lockReconnect = false;
  }, 5000);
//心跳检测
var heartCheck = {
  timeout: 1000 * 10 * 3, //3 = 30s
  timeoutObj: null,
  serverTimeoutObj: null,
  start: function(){
    var self = this;
    this.timeoutObj && clearTimeout(this.timeoutObj);
    this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
    this.timeoutObj = setTimeout(function(){
      //这里发送一个心跳,后端收到后,返回一个心跳消息,
      //onmessage拿到返回的心跳就说明连接正常
      console.log('心跳检测...');
      ws.send("发送心跳检测:"+ clientId );
      self.serverTimeoutObj = setTimeout(function() {
        if(ws.readyState!==1){
          ws.close();
      }, self.timeout);
    }, this.timeout)

8.App.vue页面全局引入websocket

import websocket from '@/config/websocket'
 created: function () {
      //从localStorage中获取用户信息,是登陆状态则能够进行webSocket重连
      let onlineUser = localStorage.getItem('onlineUser');
      if(onlineUser){
        let id = localStorage.getItem("clientId")
        console.log(id,"info")
        if(null != id){
          this.initWebsocket(id)
    methods: {
   	//调用方法
      initWebsocket(id){
        websocket.Init(id)

9.登录页面直接调用父组件Vue的 initWebsocket方法

login.vue 在登录成功存储及调用

localStorage.setItem("clientId",token.userInfo.id)
this.$parent.initWebsocket(token.userInfo.id)

10.页面刷新Websocket会断开,全局监听重连Websocket

main.js监听localStorage值的变化

var orignalSetItem = localStorage.setItem;
localStorage.setItem = function(key,newValue){
  var setItemEvent = new Event("setItemEvent");
  setItemEvent.key = key
  setItemEvent.newValue = newValue;
  window.dispatchEvent(setItemEvent);
  orignalSetItem.apply(this,arguments);

11.页面应用

在需要应用的页面比如user.vue使用

 <el-table-column  label="用户在线状态" width="100">
   <template slot-scope="scope">
      <span>{{isOnline(scope.row.id)}}</span>
    </template>
  </el-table-column>
 created() {
      let onlineUser = localStorage.getItem('onlineUser')
      if(onlineUser instanceof Array){
        this.online = onlineUser
      }else {
        if(onlineUser.indexOf(",") !== -1){
          let arr = onlineUser.split(",")
          this.online = arr
        }else {
          let arr = []
          arr.push(onlineUser)
          this.online = arr
    computed: {
      isOnline(){
        return function (val) {
          return this.online.findIndex(item => item == val) !== -1
    mounted() {
      //监听在线用户人数变化id
      window.addEventListener('setItemEvent', (e)=> {
       // let newVal = localStorage.getItem('onlineUser');
        if(e.key==="onlineUser"){
          this.online = e.newValue

最终的最终提示

退出登录时一定要清除localStorage

  • localStorage.clear()

socket代理配置

  • dev: {
    ‘/socket’: {
    //webSocket代理
    target: ‘ws://127.0.0.1:8888’, // 内网ip
    ws:true,//开启ws, 如果是http代理此处可以不用设置
    changeOrigin: true,
    pathRewrite: {
    ‘^/socket’: ‘/’
    }
    }
    }
说到websocket首先要说Http,Http大家都知道是一个网络通信协议,每当客户端浏览器需要访问后台时都会发一个请求,服务器给出响应后该连接就会关闭,请求只能有客户端发起,服务端是没办法主动发起请求的,对于消息推送的需求Http也可以满足要求,就是前端采用定时任务的方式去请求接口,这种轮询的方式是非常损耗服务器性能的,要尽量避免。所谓全双工指的是通信可以由任意一方发起,可以在两个方向上传输信息,采用websocket可以很好的实现消息推送的功能,从而避免了轮询的方式导致资源浪费的问题。 需求:需要在系统中展现,系统中用户的状态。展示用户是否在线、忙碌或者离线。做法: 使用 webSocket 建立链接,通过实时推送用户信息,达到用户状态的更新。 当用户登录时自动设置用户状态为上线,并推送用户信息到 前端。 当用户离线时自动设置用户状态为离线状态,并推送给前端。 通过 定时任务扫描在线用户,当在线用户的最后操作时间于当前时间差大于30 分钟(session失效时间),设置用户状态为离 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被。WebSocket通信协议于2011年被。vue端涉及业务就不贴了。 应甲方要求,我们要在页面按频次刷新后台目标数据,当然是首选websocket了,由后台向前端推送数据。前端修改刷新频次时,向后端发送数据。类似RabbitMQ管理后台自动刷新。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version&g Spring Boot整合websocket实现点对点聊天,离线接收消息 之前一直想在项目里实现一个聊天的功能,这次终于抽空做了个点对点以及离线接收消息的功能。后面有时间再进行一些细节的完善。先mark一下。 前端页面设置 js控制前端传送数据到后台,接收后台传来的数据 java实现后台控制 一、页面设置 css的样式不多,我直接写在html了 我的用户名是登录显示,获取session里的值,对象用户是从上一个后台传过来的。 如果是想测试功能,可以在页面给对象用户一个值,也可以利用获取后台已登录的用户, <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter... 在做服务端与客户端进行通讯时,比如websocket通讯与IM通讯等,需要自定义用户连接/登录时存储用户会话信息,断开连接/退出时移除用户会话信息,获取所以当前用户,群发消息等功能。所以需要来对用户/客户端信息进行存储。在上面搭建起来前后端分离的架构基础上,比如集成websocket做一个聊天室功能,需要获取并存取所有的连接用户的数据,就需要在连接打开时存储用户,连接关闭时移除用户。注:博客:关注公众号霸道的程序猿获取编程相关电子书、教程推送与免费下载。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。这里介绍websocet推送给指定的人群,以及全部用户的方式。... 最近项目中有用到了webSocket,然后在收到消息之后需要在不同的页面进行处理。所有就需要在不同的页面监听并进行对应的消息处理。 首先,在app.vue中添加socket初始化,并设置发送消息,接收消息和心跳检测的处理。 // App.vue export default { data() { return { // socket参数 socket: null, timeout: 10 * 1000, // 45秒一次心跳 timeoutObj: null, // 心跳