前两篇博客演示了广播式的websocket 推送。
广播式有自己的应用场景,但是广播式不能解决我门一个常见的场景,即消息由谁发送、由谁接收的问题。
本例中演示了一个简单的聊天室程序。例子中只有两个用户,互相发送消息给彼此,因需要用户相关内容,所以这里引入了最简单的spring Security相关内容。
本文原代码会在文章末尾给出,但是原代码中包含
spring boot +WebSocket 广播式(一)、
spring boot +WebSocket 广播式(二)
的代码
本文目录:
新建maven 工程,pom.xml 文件内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.us.examplegroupId>
<artifactId>springWebSocketartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.3.0.RELEASEversion>
parent>
<properties>
<start-class>com.us.Applicationstart-class>
<maven.compiler.target>1.8maven.compiler.target>
<maven.compiler.source>1.8maven.compiler.source>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
dependencies>
project>
package com.us.example.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;
/**
* Created by yangyibo on 16/12/29.
*/
@Configuration
@EnableWebSocketMessageBroker
//通过EnableWebSocketMessageBroker 开启使用STOMP协议来传输基于代理(message broker)的消息,此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) { //endPoint 注册协议节点,并映射指定的URl
//注册一个名字为"endpointChat" 的endpoint,并指定 SockJS协议。 点对点-用
registry.addEndpoint("/endpointChat").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(message broker)
//点对点式增加一个/queue 消息代理
registry.enableSimpleBroker("/queue","/topic");
}
}
package com.us.example.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{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/","/login").permitAll()//根路径和/login路径不拦截
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") //2登陆页面路径为/login
.defaultSuccessUrl("/chat") //3登陆成功转向chat页面
.permitAll()
.and()
.logout()
.permitAll();
}
//4在内存中配置两个用户 wyf 和 wisely ,密码和用户名一致,角色是 USER
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("wyf").password("wyf").roles("USER")
.and()
.withUser("wisely").password("wisely").roles("USER");
}
//5忽略静态资源的拦截
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/static/**");
}
}
package com.us.example.bean;
/**
* Created by yangyibo on 16/12/29.
*/
public class Message {
private String name;
public String getName(){
return name;
}
}
package com.us.example.controller;
import com.us.example.bean.Message;
import com.us.example.bean.Response;
import com.us.example.service.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.security.Principal;
/**
* Created by yangyibo on 16/12/29.
*
*/
@Controller
public class WebSocketController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat")
//在springmvc 中可以直接获得principal,principal 中包含当前用户的信息
public void handleChat(Principal principal, Message message) {
/**
* 此处是一段硬编码。如果发送人是wyf 则发送给 wisely 如果发送人是wisely 就发送给 wyf。
*/
if (principal.getName().equals("wyf")) {
//通过convertAndSendToUser 向用户发送信息,
// 第一个参数是接收消息的用户,第二个参数是浏览器订阅的地址,第三个参数是消息本身
messagingTemplate.convertAndSendToUser("wisely",
"/queue/notifications", principal.getName() + "-send:"
+ message.getName());
} else {
messagingTemplate.convertAndSendToUser("wyf",
"/queue/notifications", principal.getName() + "-send:"
+ message.getName());
}
}
}
为HTML 提供便捷的路径映射。
package com.us.example.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{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("/login");
registry.addViewController("/chat").setViewName("/chat");
}
}
package com.us.example;
/**
* Created by yangyibo on 16/12/29.
*/
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import static org.springframework.boot.SpringApplication.run;
@ComponentScan(basePackages ="com.us.example")
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = run(Application.class, args);
}
}
添加脚本,将stomp.js、sockjs.min.js 以及jQuery 脚本放在src/main/resources/static下。
在src/main/resources/templates 下新建 login.html。(源代码会在文章底部给出)
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<meta charset="UTF-8" />
<head>
<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。(源代码会在文章底部给出)
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8" />
<head>
<title>Hometitle>
<script th:src="@{sockjs.min.js}">script>
<script th:src="@{stomp.min.js}">script>
<script th:src="@{jquery.js}">script>
head>
<body>
<p>
聊天室
p>
<form id="wiselyForm">
<textarea rows="4" cols="60" name="text">textarea>
<input type="submit"/>
form>
<script th:inline="javascript">
$('#wiselyForm').submit(function(e){
e.preventDefault();
var text = $('#wiselyForm').find('textarea[name="text"]').val();
sendSpittle(text);
});
//链接endpoint名称为 "/endpointChat" 的endpoint。
var sock = new SockJS("/endpointChat");
var stomp = Stomp.over(sock);
stomp.connect('guest', 'guest', function(frame) {
/**
订阅了/user/queue/notifications 发送的消息,这里雨在控制器的 convertAndSendToUser 定义的地址保持一致,
* 这里多用了一个/user,并且这个user 是必须的,使用user 才会发送消息到指定的用户。
* */
stomp.subscribe("/user/queue/notifications", handleNotification);
});
function handleNotification(message) {
$('#output').append("Received: " + message.body + "
")
}
function sendSpittle(text) {
stomp.send("/chat", {}, JSON.stringify({ 'name': text }));//3
}
$('#stop').click(function() {sock.close()});
script>
<div id="output">div>
body>
html>
打开两个浏览器,访问http://localhost:8080/login 进行登录,两个用户是wyf 和 wisely 账号密码一样。
登录成功后就可以相互发送信息聊天了。
本文参考:《JavaEE开发的颠覆者:Spring Boot实战 》
本文源代码:https://github.com/527515025/springBoot.git