基于SpringBoot、STOMP使用WebSocket实现聊天室功能

原文连接: 基于SpringBoot、STOMP使用WebSocket实现聊天室功能

WebSocket:

新项目中有一个模块需求使用到了WebSocket,因为之前没用过,所以做了一些研究。

关于WebSocket,简单说两句:最初前端与后端交互都是基于Http协议,前端发送request,后端返回response。存在的问题就是:response永远是被动的,不能主动发起。如果要想保持与后端的长连接,最初的实现方式基本都是ajax轮询或者http long poll。这两种方式都需要占用很多的资源,并且Http还是一个无状态协议。而WebSocket是HTML5出的东西,通俗点就是可以实现前端只发送一次请求,后端就可以与前端保持长连接,并实时的传输数据。最直白的例子就是聊天系统的实现,点对点两个人聊天,你一句我一句。也可以群嗨,一大群人叽叽歪歪,这样每个人对于其他人来说可以理解为一个广播系统,其他人可以理解为自己的订阅者。

项目中基于SpringBoot和STOMP,其中的逻辑相对复杂,这儿仅写一个简单的Demo:实现点对点聊天功能。话不多说,直接开搞。

代码中的具体解释看注释,都描述的很清楚。

首先了解下STOMP:

Stomp是一种简单(流)文本定向消息协议,提供了一个可互操作的链接格式。允许stomp客户端与任意stomp消息代理(Broker)进行交互。

一:新建一个SpringBoot项目,选择Security、Thymeleaf和WebSocket依赖。

WechatIMG45.jpeg

二:Spring Security的简单配置

本例只是实现了一个简单的聊天室程序。例子中有两个用户,互相发送消息给彼此,所以我们在这里我们先对Spring Security做一些简单的配置。对于Spring Security这里不做特别多的解释,Spring Security是专门针对基于Spring的项目安全框架,和Shiro一样可以实现程序的认证和权限控制。

这里主要是分配两个用户,名字为“Michael”和“Janet”,密码都是“freedom”。

package cn.js.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.authorizeRequests()
.antMatchers("/","/login")//设置Spring Security对/和/"login"路径不拦截
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")//设置SpringSecurity的登录页面为/login
.defaultSuccessUrl("/chat")//登录成功后转向/chat路径
.permitAll()
.and()
.logout()
.permitAll();
}
//在内存中分配两个用户Michael和Janet,密码都为freedom,角色都是USER
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder
.inMemoryAuthentication()
.withUser("Michael").password("freedom").roles("USER")
.and()
.withUser("Janet").password("freedom").roles("USER");
}
// /resources/static/目录下的静态资源,Spring Security不拦截
public void configure(WebSecurity webSecurity)throws Exception{
webSecurity.ignoring().antMatchers("/resources/static/**");
}

}

三:配置WebSocket

package cn.js.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
//此注解表示开启WebSocket支持。通过此注解开启使用STOMP协议来传输基于代理(message broker)的消息。
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
//注册一个名为/endpointChat的节点,并指定使用SockJS协议。
stompEndpointRegistry.addEndpoint("/endpointChat").withSockJS();
}
//配置消息代理(Message Broker),可以理解为信息传输的通道
public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry){
//点对点式应增加一个/queue的消息代理。相应的如果是广播室模式可以设置为"/topic"
messageBrokerRegistry.enableSimpleBroker("/queue");
}
}

四:编写控制器

package cn.js.websocket.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import java.security.Principal;

@Controller
public class WebSocketController {
@Autowired
//通过SimpMessagingTemplate模板向浏览器发送消息。如果是广播模式,可以直接使用注解@SendTo
private SimpMessagingTemplate simpMessagingTemplate;

//开启STOMP协议来传输基于代理的消息,这时控制器支持使用@MessageController,就像使用@RequestMapping是一样的
//当浏览器向服务端发送请求时,通过@MessageController映射/chat这个路径
@MessageMapping("/chat")
//在SpringMVC中,可以直接在参数中获得principal,其中包含当前用户的信息
public void handleChat(Principal principal,String msg){
//下面的代码就是如果发送人是Michael,接收人就是Janet,发送的信息是message,反之亦然。
if(principal.getName().equals("Michael")){
//通过SimpMessagingTemplate的convertAndSendToUser向用户发送消息。
//第一参数表示接收信息的用户,第二个是浏览器订阅的地址,第三个是消息本身
simpMessagingTemplate.convertAndSendToUser("Janet","/queue/notifications",
principal.getName() + "-发送:" + msg);
} else {
simpMessagingTemplate.convertAndSendToUser("Michael","/queue/notifications",
principal.getName() + "-发送:" + msg);
}
}
}

五:添加脚本

将sockjs.min.js(SockJS的客户端脚本)、stomp.js(STOMP协议的客户端脚本)、jquery-3.1.1.js(jQuery)放置在src/main/resources/static下,(可在文末GitHub中Clone项目得到三个文件)

六:编写登录和聊天室页面

因为此Demo基于Thymeleaf,所以在src/main/resources/templates下新建一个最简单的login.html页面,代码如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>聊天室登录页面</title>
</head>
<body>
<div th:if="${param.error}">
无效的账号和密码
</div>
<div th:if="${param.logout}">
您已注销
</div>
<form th:action="@{/login}" method="post">
<div><label>账号:<input type="text" name="username"/></label></div>
<div><label>密码:<input type="password" name="password"/></label></div>
<div><input type="submit" value="登录"/></div>
</form>
</body>
</html>

在src/main/resources/templates下新建chat.html页面,代码如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>聊天页面</title>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.js}"></script>
<script th:src="@{jquery-3.1.1.js}"></script>
</head>
<body>
<p>
聊天室
</p>

<form id="JanetForm">
<textarea rows="4" cols="60" name="text"></textarea>
<input type="submit"/>
</form>

<script th:inline="javascript">
$('#JanetForm').submit(function (e) {
e.preventDefault();
var text = $('#JanetForm').find('textarea[name="text"]').val();
sendSpittle(text);
});
//    连接endpoint为"/endpointChat"的节点
var sock = new SockJS("/endpointChat");
var stomp = Stomp.over(sock);
//    连接WebSocket服务端
stomp.connect('guest','guest',function (frame) {
//        订阅/user/queue/notifications发送的消息,这里与在控制器的messagingTemplate.convertAndSendToUser中定义的订阅地址保持一致。
//        这里多了一个/user,并且这个user是必须的,使用了/user才会发送消息到指定的用户
stomp.subscribe("/user/queue/notifications",handleNotification);
});
function handleNotification(message) {
$('#output').append("<b>收到了:" + message.body + "</b><br/>")
}
function sendSpittle(text) {
//        表示向后端路径/chat发送消息请求,这个是在控制器中@MessageMapping中定义的。
stomp.send("/chat",{},text);
}
$('#stop').click(function () {
{sock.close()}
});
</script>
<div id="output"></div>
</body>
</html>

七:增加页面的viewController,指定页面的跳转

package cn.js.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
public void addViewControllers(ViewControllerRegistry viewControllerRegistry){
viewControllerRegistry.addViewController("/login").setViewName("/login");
viewControllerRegistry.addViewController("/chat").setViewName("/chat");
}
}

八:测试

这样我们开两个浏览器窗口,地址为localhost:8080/login,分别用Michael和Janet两个用户登录,就可以互相发送消息聊天了。

WechatIMG48.jpeg
WechatIMG50.jpeg

项目Github地址:https://github.com/jia-shun/websocket

欢迎关注我的GitHub。

参考资料:Java EE开发的颠覆者:SpringBoot 实战

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容