springboot2.0+websocket集成【群发消息+单对单】(一)

现在用springboot集成websocket变的很方便快捷了,下面简单写个小demo,实现群发消息,单对单的聊天。主要是功能,界面将就一下。
参考:
http://tech.lede.com/2017/03/08/qa/websocket+spring/
https://blog.csdn.net/mr_zhuqiang/article/details/46618197

开发工具是IDEA,2018.2.3


springboot2.0+websocket集成【群发消息+单对单】(一)_第1张图片
新建项目
springboot2.0+websocket集成【群发消息+单对单】(一)_第2张图片
默认创建springboot的方式
springboot2.0+websocket集成【群发消息+单对单】(一)_第3张图片
填写项目简介
springboot2.0+websocket集成【群发消息+单对单】(一)_第4张图片
用到的依赖包

依照上图的流程,新建一个简单的工程。
然后在pom文件中添加一些额外用到的插件。

完整的pom.xml文件:



    4.0.0

    com.example
    websocketdemo1
    0.0.1-SNAPSHOT
    war

    websocketdemo1
    springboot2.0+websocket的集成,实现群发消息+单对单消息推送.

    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.5.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
    

    

        
        
            com.alibaba
            fastjson
            LATEST
        

        
            org.springframework.boot
            spring-boot-starter-freemarker
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-websocket
        

        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-tomcat
            provided
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                    true
                    
                    true
                
            
            
                org.apache.maven.plugins
                maven-surefire-plugin
                
                    
                    true
                
            
        
    

1. 新建MyHandshake实现HandshakeInterceptor接口

从名字大致上就可以猜测出这个接口的含义,handshake,Interceptor。
这里主要是实现2个方法。

//握手之前干啥,常用来注册用户信息,绑定 WebSocketSession
beforeHandshake
//握手之后干啥
afterHandshake

完整的实现

package com.example.websocketdemo1.websocket;

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.stereotype.Service;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author linyun
 * @date 2018/9/13 下午3:12
 */
@Slf4j
@Service
public class MyHandshake implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            // 从session中获取到当前登录的用户信息. 作为socket的账号信息. session的的WEBSOCKET_USERNAME信息,在用户打开页面的时候设置.
            String userName = (String) servletRequest.getSession().getAttribute("WEBSOCKET_USERNAME");
            attributes.put("WEBSOCKET_USERNAME", userName);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}

2. 新建MyHandler实现WebSocketHandler接口

主要是负责消息的分发,用户统计等。

package com.example.websocketdemo1.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author linyun
 * @date 2018/9/13 下午3:26
 */
@Slf4j
@Service
public class MyHandler implements WebSocketHandler {

    /**
     * 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】
     */
    private final static List SESSIONS = Collections.synchronizedList(new ArrayList<>());

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("链接成功......");
        SESSIONS.add(session);
        String userName = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
        if (userName != null) {
            JSONObject obj = new JSONObject();
            // 统计一下当前登录系统的用户有多少个
            obj.put("count", SESSIONS.size());
            users(obj);
            session.sendMessage(new TextMessage(obj.toJSONString()));
        }
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception {
        log.info("处理要发送的消息");
        JSONObject msg = JSON.parseObject(message.getPayload().toString());
        JSONObject obj = new JSONObject();
        if (msg.getInteger("type") == 1) {
            //给所有人
            obj.put("msg", msg.getString("msg"));
            sendMessageToUsers(new TextMessage(obj.toJSONString()));
        } else {
            //给个人
            String to = msg.getString("to");
            obj.put("msg", msg.getString("msg"));
            sendMessageToUser(to, new TextMessage(obj.toJSONString()));
        }
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (session.isOpen()) {
            session.close();
        }
        log.info("链接出错,关闭链接......");
        SESSIONS.remove(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        log.info("链接关闭......" + closeStatus.toString());
        SESSIONS.remove(session);
    }

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

    /**
     * 给所有在线用户发送消息
     *
     * @param message
     */
    public void sendMessageToUsers(TextMessage message) {
        for (WebSocketSession user : SESSIONS) {
            try {
                if (user.isOpen()) {
                    user.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 给某个用户发送消息
     *
     * @param userName
     * @param message
     */
    public void sendMessageToUser(String userName, TextMessage message) {
        for (WebSocketSession user : SESSIONS) {
            if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * 将系统中的用户传送到前端
     *
     * @param obj
     */
    private void users(JSONObject obj) {
        List userNames = new ArrayList<>();
        for (WebSocketSession webSocketSession : SESSIONS) {
            userNames.add((String) webSocketSession.getAttributes().get("WEBSOCKET_USERNAME"));
        }
        obj.put("users", userNames);
    }
}

3. 实现WebSocketConfigurer接口

主要的配置文件,很简单

package com.example.websocketdemo1.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @author linyun
 * @date 2018/9/13 下午3:41
 */
@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {


    @Autowired
    private MyHandshake handshake;

    @Autowired
    private MyHandler handler;

    /**
     * 实现 WebSocketConfigurer 接口,重写 registerWebSocketHandlers 方法,这是一个核心实现方法,配置 websocket 入口,允许访问的域、注册 Handler、SockJs 支持和拦截器。
     * 

* registry.addHandler()注册和路由的功能,当客户端发起 websocket 连接,把 /path 交给对应的 handler 处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。 *

* addInterceptors,顾名思义就是为 handler 添加拦截器,可以在调用 handler 前后加入我们自己的逻辑代码。 *

* setAllowedOrigins(String[] domains),允许指定的域名或 IP (含端口号)建立长连接,如果只允许自家域名访问,这里轻松设置。如果不限时使用”*”号,如果指定了域名,则必须要以 http 或 https 开头。 * * @param registry */ @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { //部分 支持websocket 的访问链接,允许跨域 registry.addHandler(handler, "/echo").addInterceptors(handshake).setAllowedOrigins("*"); //部分 不支持websocket的访问链接,允许跨域 registry.addHandler(handler, "/sockjs/echo").addInterceptors(handshake).setAllowedOrigins("*").withSockJS(); } }

4. 页面模拟聊天

模拟用户登录,存储用户信息到session中。

package com.example.websocketdemo1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * @author linyun
 * @date 2018/9/13 下午3:45
 */
@Controller
@RequestMapping("/socket")
public class WebSocketController {

    /**
     * 第一个用户
     *
     * @param request
     * @return
     */
    @RequestMapping("/chat1")
    public String chat1(HttpServletRequest request) {
        // 假设用户tom登录,存储到session中
        request.getSession().setAttribute("WEBSOCKET_USERNAME", "tom");
        return "chat1";
    }

    /**
     * 第二个用户登录
     *
     * @param request
     * @return
     */
    @RequestMapping("/chat2")
    public String chat2(HttpServletRequest request) {
        // 假设用户jerry登录,存储到session中
        request.getSession().setAttribute("WEBSOCKET_USERNAME", "jerry");
        return "chat2";
    }

    /**
     * 第三个用户登录
     *
     * @param request
     * @return
     */
    @RequestMapping("/chat3")
    public String chat3(HttpServletRequest request) {
        // 假设用户jack登录,存储到session中
        request.getSession().setAttribute("WEBSOCKET_USERNAME", "jack");
        return "chat3";
    }

}

页面都是一样的代码,分别新建3份,chat1.html,chat2.html,chat3.html。




    测试websocket
    
    
    
    
    


发送给所有人
发送给单人

5. 页面的支持

修改配置文件,我比较喜欢yml的。所以重命名配置文件为yml格式

application.yml
spring:
  ####配置 页面模板的参数,
  freemarker:
    charset: utf-8
    suffix: .html
    content-type: text/html
    settings:
      ##格式化这个项目中,页面数字的显示,小数位数最多显示10位
      number_format: 0.##########
  http:
    encoding:
      charset: UTF-8
  #### 配置静态资源的地址。html文件中,可以直接使用/static/目录
  resources:
    static-locations: "classpath:/"

6.测试

服务跑起来。
然后浏览器打开3个页面,分别访问:

http://localhost:8080/socket/chat1
http://localhost:8080/socket/chat2
http://localhost:8080/socket/chat3

效果如下:


springboot2.0+websocket集成【群发消息+单对单】(一)_第5张图片
查看窗口的调试模式

可以看到最先打开的页面返回的用户信息只有tom,后面的用户加入就出现了jerry和jack。
测试发送一条群消息


springboot2.0+websocket集成【群发消息+单对单】(一)_第6张图片
群消息效果

下面查看单人消息,先刷新3个页面,保证都加载了3个用户信息。发送的时候选择另外用户推送消息,另外2个人是收不到消息的。


单对单消息

完整项目 git:[email protected]:tulongx/websocketdemo1.git

以上。

你可能感兴趣的:(springboot2.0+websocket集成【群发消息+单对单】(一))