WebSocket(5)---多人聊天系统

多人聊天系统

功能说明:多人聊天系统,主要功能点:

    1、当你登陆成功后,可以看到所有在线用户(实际开发可以通过redis实现,我这边仅仅用map集合)

    2、实现群聊功能,我发送消息,大家都可以看到。

先看案例效果:

      这里面有关在线人数有个bug,就是在线用户会被覆盖,lisi登陆的话,zhangsan在线信息就丢来,xiaoxiao登陆,lisi就丢来,这主要原因是因为我放的是普通集合,所以在线用户数据是无法共享

所以只能显示最后显示的用户,如果放到redis就不会有这个问题。

一、案例说明

1、UserChatController

@Controller
public class UserChatController {

    @Autowired
    private WebSocketService ws;

    /**
     * 1、登陆时,模拟数据库的用户信息
     */
    //模拟数据库用户的数据
    public static Map<String, String> userMap = new HashMap<String, String>();
    static{
        userMap.put("zhangsan", "123");
        userMap.put("lisi", "456");
        userMap.put("wangwu", "789");
        userMap.put("zhaoliu", "000");
        userMap.put("xiaoxiao", "666");
    }

    /**
     *2、 模拟用户在线进行页面跳转的时候,判断是否在线
     * (这个实际开发中肯定存在redis或者session中,这样数据才能共享)
     * 这里只是简单的做个模拟,所以暂且用普通map吧
     */
    public static Map<String, User> onlineUser = new HashMap<>();
    static{
        //key值一般是每个用户的sessionID(这里表示admin用户一开始就在线)
        onlineUser.put("123",new User("admin","888"));
    }
    
    
    /**
     *3、 功能描述:用户登录接口
     */
    @RequestMapping(value="login", method=RequestMethod.POST)
    public String userLogin( @RequestParam(value="username", required=true)String username, 
            @RequestParam(value="pwd",required=true) String pwd, HttpSession session) {

        //判断是否正确
        String password = userMap.get(username);
        if (pwd.equals(password)) {
            User user = new User(username, pwd);
            String sessionId = session.getId();

            //用户登陆成功就把该用户放到在线用户中...
            onlineUser.put(sessionId, user);
            //跳到群聊页面
            return "redirect:/group/chat.html";
        } else {
            return "redirect:/group/error.html";
        }
        
    }
    
    /**
     *4、 功能描述:用于定时给客户端推送在线用户
     */
    @Scheduled(fixedRate = 2000)
    public void onlineUser() {
        ws.sendOnlineUser(onlineUser);
    }
    
    /**
     *5、 功能描述 群聊天接口
     * message 消息体
     * headerAccessor 消息头访问器,通过这个获取sessionId
     */
    @MessageMapping("/group/chat")
    public void topicChat(InMessage message, SimpMessageHeaderAccessor headerAccessor){
        //这个sessionId是在HttpHandShakeIntecepter拦截器中放入的
        String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
        //通过sessionID获得在线用户信息
        User user = onlineUser.get(sessionId);
        message.setFrom(user.getUsername());
        ws.sendTopicChat(message);
        
    }    
}

 

2、握手请求的拦截器

/**
 * WebSocket握手请求的拦截器. 检查握手请求和响应, 对WebSocketHandler传递属性
 * 可以通过这个类的方法获取resuest,和response
 */
public class HttpHandShakeIntecepter implements HandshakeInterceptor{


    //在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
    //这个项目只在WebSocketSession这里存入sessionID
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {

        System.out.println("【握手拦截器】beforeHandshake");

        if(request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
            HttpSession session =  servletRequest.getServletRequest().getSession();
            String sessionId = session.getId();
            System.out.println("【握手拦截器】beforeHandshake sessionId="+sessionId);
            //这里将sessionId放入SessionAttributes中,
            attributes.put("sessionId", sessionId);
        }
        
        return true;
    }
 
    //在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头(这个项目没有用到该方法)
    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception exception) {
        System.out.println("【握手拦截器】afterHandshake");
        
        if(request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
            HttpSession session =  servletRequest.getServletRequest().getSession();
            String sessionId = session.getId();
            System.out.println("【握手拦截器】afterHandshake sessionId="+sessionId);
        }
    }
}

 

3、频道拦截器

/** 
 * 功能描述:频道拦截器 ,类似管道,可以获取消息的一些meta数据
 */
public class SocketChannelIntecepter extends ChannelInterceptorAdapter{

    /**
     * 在完成发送之后进行调用,不管是否有异常发生,一般用于资源清理
     */
    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel,
            boolean sent, Exception ex) {
        System.out.println("SocketChannelIntecepter->afterSendCompletion");
        super.afterSendCompletion(message, channel, sent, ex);
    }

    
    /**
     * 在消息被实际发送到频道之前调用
     */
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        System.out.println("SocketChannelIntecepter->preSend");
        
        return super.preSend(message, channel);
    }

    /**
     * 发送消息调用后立即调用
     */
    @Override
    public void postSend(Message<?> message, MessageChannel channel,
            boolean sent) {
        System.out.println("SocketChannelIntecepter->postSend");
        
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);//消息头访问器
        
        if (headerAccessor.getCommand() == null ) return ;// 避免非stomp消息类型,例如心跳检测
        
        String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
        System.out.println("SocketChannelIntecepter -> sessionId = "+sessionId);
        
        switch (headerAccessor.getCommand()) {
        case CONNECT:
            connect(sessionId);
            break;
        case DISCONNECT:
            disconnect(sessionId);
            break;
        case SUBSCRIBE:    
            break;
        
        case UNSUBSCRIBE:
            break;
        default:
            break;
        }
    }

    /**
     * 连接成功
     */
    private void connect(String sessionId){
        System.out.println("connect sessionId="+sessionId);
    }

    /**
     * 断开连接
     */
    private void disconnect(String sessionId){
        System.out.println("disconnect sessionId="+sessionId);
        //用户下线操作
        UserChatController.onlineUser.remove(sessionId);
    }
    
}

 

4、修改webSocket配置类

    既然写了两个拦截器,那么肯定需要在配置信息里去配置它们。

@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {


    /**
     *配置基站
     */
    public void registerStompEndpoints(StompEndpointRegistry registry) {

        registry.addEndpoint("/endpoint-websocket").addInterceptors(new HttpHandShakeIntecepter()).setAllowedOrigins("*").withSockJS();
    }

    /**
     * 配置消息代理(中介)
     * enableSimpleBroker 服务端推送给客户端的路径前缀
     * setApplicationDestinationPrefixes  客户端发送数据给服务器端的一个前缀
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        registry.enableSimpleBroker("/topic","/chat");
        registry.setApplicationDestinationPrefixes("/app");

    }

  
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors( new SocketChannelIntecepter());
    }

    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        registration.interceptors( new SocketChannelIntecepter());
    }

}

 

5、app.js

   登陆页面和群聊页面就不细聊,贴上代码就好。

   index.html

index.html

   chat.html

chat.html

  app.js

var stompClient = null;

//一加载就会调用该方法
function connect() {
    var socket = new SockJS('/endpoint-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        
        //订阅群聊消息
        stompClient.subscribe('/topic/chat', function (result) {
            showContent(JSON.parse(result.body));
        });
        
        //订阅在线用户消息
        stompClient.subscribe('/topic/onlineuser', function (result) {
            showOnlieUser(JSON.parse(result.body));
        });
        
        
    });
}


//断开连接
function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}

//发送聊天记录
function sendContent() {
    stompClient.send("/app/group/chat", {}, JSON.stringify({'content': $("#content").val()}));
    
}

//显示聊天记录
function showContent(body) {
    $("#record").append("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");
}

//显示实时在线用户
function showOnlieUser(body) {
    $("#online").html("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");
}


$(function () {
    
    connect();//自动上线
    
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
     
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() {
        sendContent(); 
    });
});

   gitHub源码https://github.com/yudiandemingzi/websocket

 

      我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,

天一亮,又是崭新的起点,又是未知的征程(上校1)

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值