SpringBoot集成websocket实现后台消息推送

SpringBoot集成Websocket实现后台消息推送给前端

1.pom依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib -->
<dependency>
   <groupId>net.sf.json-lib</groupId>
   <artifactId>json-lib</artifactId>
   <version>2.3</version>
   <classifier>jdk15</classifier>
</dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.Websocket相关类

2.1.Websocket配置类

package com.alcode.websocket.config;

import com.alcode.websocket.handler.MyWebSocketHandler;
import com.alcode.websocket.interceptor.WebSocketInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @ClassName: WebSocketConfig
 * @Description: websocket配置类
 * @Author: Jane
 * @Date: 2020/7/4 12:58
 * @Version: V1.0
 **/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //handler是webSocket的核心,配置入口,设置跨域允许
	    registry.addHandler(new MyWebSocketHandler(), "/websocket/{id}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
    }
}

2.2.配置拦截器

配置一个拦截器,所有请去访问时先经过拦截器

package com.alcode.websocket.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

/**
 * @ClassName: WebSocketInterceptor
 * @Description: websocket拦截器
 * @Author: Jane
 * @Date: 2020/7/4 13:14
 * @Version: V1.0
 **/
@Slf4j
public class WebSocketInterceptor implements HandshakeInterceptor {
    /**
     * 在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
     *
     * @param request
     * @param response
     * @param webSocketHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest){
            // 这里获取用户id,用这个id知道是谁发的消息和给这个用户发消息
            String id = request.getURI().toString().split("id=")[1];
            log.info("当前按session的id={}", id);
            // 添加用户
            attributes.put("WEBSOCKET_USER_ID", id);
        }
        return true;
    }

    /**
     * 完成握手后执行该方法
     *
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @param webSocketHandler
     * @param e
     */
    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
        log.info("握手成功,进入websocket");
    }
}

2.3.编写handler

package com.alcode.websocket.handler;


import com.alcode.common.utils.SpringUtil;
import com.alcode.modules.Service.sys.SysNoticeService;
import com.alcode.modules.pojo.SysNotice;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.util.*;

/**
 * @ClassName: MyHandler
 * @Description: websocket处理器
 * @Author: Jane
 * @Date: 2020/7/4 13:23
 * @Version: V1.0
 **/
@Slf4j
@Component
public class MyWebSocketHandler implements WebSocketHandler {

    /**
     * 在线用户列表
     */
    private static final Map<String, WebSocketSession> users;


    static {
        users = Maps.newHashMap();
    }

    /**
     * 新增socket,建立新的连接
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("开始建立socket连接,session={}", session);
        String id = session.getUri().toString().split("id=")[1];
        log.info("获取到用户的id={}", id);
        if (!StringUtils.isEmpty(id)) {
            users.put(id, session);
            // websocket连接成功后,查一下通知表,把新消息推送给前端
            Map<String, Object> params = Maps.newHashMap();
            params.put("receive_user_id", id);
            params.put("is_read", 0);
            params.put("is_deleted", 0);
            params.put("msg_type", 1);
            SysNoticeService sysNoticeService = SpringUtil.getBean(SysNoticeService.class);
            Collection<SysNotice> sysNoticeCollection = sysNoticeService.listByMap(params);
            if (!ObjectUtils.isEmpty(sysNoticeCollection) && sysNoticeCollection.size() > 0) {
                for (SysNotice sysNotice : sysNoticeCollection) {
                    session.sendMessage(new TextMessage(sysNotice.getDetails() + "-" + sysNotice.getId()));
                }
            }
        }
        log.info("当前在线人数:num={}", users.size());
    }

    /**
     * 接收前端socket消息
     *
     * @param webSocketSession
     * @param webSocketMessage
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        try {
            JSONObject jsonObject = JSONObject.fromObject(webSocketMessage.getPayload());
            log.info("解析前端发的消息jsonObject={}", jsonObject.toString());
//            System.out.println(jsonObject.get("message") + ":来自" + (String) webSocketSession.getAttributes().get("WEBSOCKET_USERID") + "的消息");
            // 反馈前端
            sendMessageToUser(jsonObject.get("id") + "", new TextMessage("用户" + jsonObject.get("id") + ",服务器收到了你的消息,这是给你的反馈"));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 发送消息给指定用户
     *
     * @param userId
     * @param message
     * @return
     */
    public boolean sendMessageToUser(String userId, TextMessage message) {
        // 用户不在线发送失败
        if (ObjectUtils.isEmpty(users.get(userId))) {
            return false;
        }
        WebSocketSession session = users.get(userId);
        log.info("获取用户{}的session={}", userId, session);
        System.out.println("sendMessage:" + session);
        // socket未连接,发送失败
        if (!session.isOpen()) {
            return false;
        }
        try {
            // 使用用户对应的session发送消息
            session.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 发送消息给所有用户
     *
     * @param message
     * @return
     */
    public boolean sendMessageToAllUsers(TextMessage message) {
        boolean allSendSuccess = true;
        Set<String> userIds = users.keySet();
        WebSocketSession session = null;
        // 遍历用户列表,发送消息
        for (String userId : userIds) {
            try {
                session = users.get(userId);
                if (session.isOpen()) {
                    session.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
                allSendSuccess = false;
            }
        }

        return allSendSuccess;
    }


    /**
     * 连接出错时调用
     *
     * @param session
     * @param exception
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 连接出错,关闭连接
        if (session.isOpen()) {
            session.close();
        }
        log.info("用户{}连接出错", getUserId(session));
        // 从在线列表中移除该用户
        users.remove(getUserId(session));
    }

    /**
     * 关闭连接
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("用户{}关闭了连接", getUserId(session));
        // 移除用户
        users.remove(getUserId(session));
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 获取用户ID
     *
     * @param session
     * @return
     */
    private Integer getUserId(WebSocketSession session) {
        try {
            Integer userId = (Integer) session.getAttributes().get("WEBSOCKET_USER_ID");
            return userId;
        } catch (Exception e) {
            return null;
        }
    }
}

3.后台发送消息接口

调用handler中的方法即可推送消息

package com.alcode.websocket.controller.impl;

import com.alcode.common.config.RabbitmqConfig;
import com.alcode.websocket.handler.MyWebSocketHandler;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.socket.TextMessage;

import java.util.Map;

/**
 * @ClassName: WebsocketController
 * @Description: TODO
 * @Author: Jane
 * @Date: 2020/7/5 10:22
 * @Version: V1.0
 **/
@Slf4j
@Controller
@RequestMapping("/websocket")
public class WebsocketControllerImpl {

    @Autowired
    private MyWebSocketHandler handler;

    /**
     * 给指定用户发送消息
     *
     * @param userId
     */
    @RequestMapping("/send/{userId}")
    @ResponseBody
    public void sendTopicMessage(@PathVariable String userId) {
        log.info("后台给用户{}发送消息!", userId);
        handler.sendMessageToUser(userId, new TextMessage("用户" + userId + "服务器给你发了一条消息"));
    }

    /**
     * 给所有用户发送消息
     */
    @RequestMapping("/all")
    @ResponseBody
    public void sendMessageToAll(){
        log.info("后台给所有发送了消息");
        handler.sendMessageToAllUsers(new TextMessage("后台给所有用户发的消息"));
    }
}

4.前端代码

这里截取了部分我项目中的代码,前端采用Vue实现的

// 一下是new Vue中的一部分
<script>
      data() {
    return {
      websocket: null,
    }
  },
   created: {
        //创建WebSocket,项目中的逻辑是用户登陆成功后在回调中去才去创建连接的,这里是页面一创建就连接websocket
       this.connectWebSocket();
    },
   methods:{
       // 浏览器强制关闭时,关闭websocket连接
       window.onunload = () => {
       	this.closeWebSocket()
      },
       connectWebSocket() {
          console.log('开始...')
          // 建立websocket连接, 这里的userI在我项目是用户登陆成功后获取到的
          this.websocket = new WebSocket('ws://localhost:8000/codeGod/websocket/id=' + this.userId);
          // 打开webSocket连接时调用
          this.websocket.onopen = () => {
            console.log('onopen')
          }
          // 关闭webSocket连接时调用
          this.websocket.onclose = () => {
            console.log('onclose')
          }
          // 接收消息
          this.websocket.onmessage = (msg) => {
            console.log(msg.data);
            // 调用消息提示方法
            this.messageNotice(msg.data);
          }
        },
       // 发送消息
       send() {
         let postValue = {};
         postValue.sendUserId = this.userId;
         postValue.receiveUserId =
         postValue.message = "aaa";
         this.websocket.send(JSON.stringify(postValue));
       },
       // 关闭连接
      closeWebSocket(){
        if (null != this.websocket){
          this.websocket.close()
        }
      },
    // 消息提示,使用Element-UI中的控件来实现
    messageNotice(notice) {
      this.$notify({
        title: '通知',
        message: notice,
        duration: 0,
      });
    },
    }
</script>

5.遇到的问题

5.1.Websocket不能与springboot中的定时任务一起使用

websocket与springboot中的定时任务Scheduleding同时使用是会报java.lang.IllegalStateException: Unexpected use of scheduler ,原因是没有找到合适的Scheduler对象;需要我们自己注入一个 TaskScheduler对象,如下:

package com.alcode.websocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * @ClassName: WebSocketScheduledConfig
 * @Description: 解决不能同时使用Spring定时注解和WebSocket的问题
 * @Author: Jane
 * @Date: 2020/7/5 23:40
 * @Version: V1.0
 **/
@Configuration
public class WebSocketScheduledConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler();
        scheduling.setPoolSize(10);
        scheduling.initialize();
        return scheduling;
    }
}

5.2.在MyWebSocketHandlet中无法注入Bean

在项目中注入service层时提示不能注入,原因是因为要注入的这些Bean都是单例的,通过上下文来获取即可,如下:

手动管理Bean工具类,SpringUtil

package com.alcode.common.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @Description: 提供手动获取被spring管理的bean对象
 */
public class SpringUtil implements ApplicationContextAware {
	
	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		if (SpringUtil.applicationContext == null) {
			SpringUtil.applicationContext = applicationContext;
		}
	}

	// 获取applicationContext
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	// 通过name获取 Bean.
	public static Object getBean(String name) {
		return getApplicationContext().getBean(name);
	}

	// 通过class获取Bean.
	public static <T> T getBean(Class<T> clazz) {
		return getApplicationContext().getBean(clazz);
	}

	// 通过name,以及Clazz返回指定的Bean
	public static <T> T getBean(String name, Class<T> clazz) {
		return getApplicationContext().getBean(name, clazz);
	}

}

先注入SpringUtil工具类

@SpringBootApplication
public class App {

    public static void main(String[] args){
        SpringApplication.run(App.class,args);
    }

	// 注入SpringUtil
    @Bean
    public SpringUtil springUtil(){
        return new SpringUtil();
    }
}

注入后,直接通过SpringUtil调用getBean(Xxx.class)方法即可获取对应的Bean

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Freguez · duoyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值