springboot整合中间件基本上都是maven导包。编写相关配置类,然后根据自己的业务编写相应的service用就完了
org.springframework.boot
spring-boot-starter-websocket
package com.jiuhou.gpfe.config;
import com.jiuhou.gpfe.extend.SessionAuthHandshakeInterceptor;
import com.jiuhou.gpfe.extend.UserHandshakeHandler;
import com.jiuhou.gpfe.extend.WebSocketChannelInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
private TaskScheduler taskScheduler;
/**
* Register STOMP endpoints mapping each to a specific URL and (optionally)
* enabling and configuring SockJS fallback options.
* 注册STOMP协议的端点以供连接websocket,
* 这里可以开启或者配置SocketJs
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp") //端点通道地址 ws://ip:port/stomp
//session拦截器,用于判断session是否存在
.addInterceptors(new SessionAuthHandshakeInterceptor())
//用户握手拦截器,判断websocket是否握手成功,并放入Principal用户,用于向指定用户发送消息
.setHandshakeHandler(new UserHandshakeHandler())
//配置允许跨域
.setAllowedOrigins("*");
//配置socketJS以使用http协议连接 使用socketJs连接地址为(http://ip:port/stomp)
.withSocketJs();
}
/**
* Configure options related to the processing of messages received from and
* sent to WebSocket clients.
* //配置消息
*/
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
registry.setMessageSizeLimit(128 * 1024);
registry.setSendBufferSizeLimit(512 * 1024);
registry.setSendTimeLimit(15 * 1000);
}
/**
* Configure the {@link org.springframework.messaging.MessageChannel} used for
* incoming messages from WebSocket clients. By default the channel is backed
* by a thread pool of size 1. It is recommended to customize thread pool
* settings for production use.
* 消息通道拦截器在发送消息过程中监听
*/
public void configureClientInboundChannel(ChannelRegistration regist) {
regist.interceptors(new WebSocketChannelInterceptor());
}
/**
* Configure message broker options.
* 设置websocket通知的主题或者队列,决定给客户端发送消息是采用队列queue方式或者分发topic方式
* 设置服务器端心跳,以保证websocket连接活着不会断掉
* 10000表示服务端给客户端两次发送心跳的最小时间 大于等于0,0表示不发送心跳
* 20000表示服务端接收客户端两次心跳的最小间隔 0表示不接受心跳。
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic/", "/queue/").setHeartbeatValue(new long[]{10000, 20000}).setTaskScheduler(taskScheduler);
//registry.enableStompBrokerRelay().setRelayHost().setRelayPort().setClientLogin().setClientPasscode().setAutoStartup()
//设置客户端请求路径的前缀
// registry.setApplicationDestinationPrefixes("/app");
//registry.setUserDestinationPrefix("/user"); //默认 /user
}
}
package com.jiuhou.gpfe.extend;
import com.jiuhou.gpfe.entity.Customer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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 javax.servlet.http.HttpSession;
import java.util.Map;
/**
* session验证拦截器
*/
public class SessionAuthHandshakeInterceptor implements HandshakeInterceptor {
private static final Logger logger = LogManager.getLogger(SessionAuthHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
//握手之前从session中获取用户
HttpSession session = getSession(serverHttpRequest);
if (session == null || session.getAttribute("customer") == null) {
logger.error("用户session为null,握手失败");
return false;
}
//业务中的用户类 需要实现Principal接口,并重写getName方法,发送指定用户的name
Customer customer = (Customer) session.getAttribute("customer");
logger.info("websocket用户{}握手成功", customer.getName());
map.put("customer", customer);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
}
private HttpSession getSession(ServerHttpRequest request) {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
return serverRequest.getServletRequest().getSession();
}
return null;
}
}
将业务用户和websocket的用户进行绑定
package com.jiuhou.gpfe.extend;
import com.jiuhou.gpfe.entity.Customer;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import java.security.Principal;
import java.util.Map;
/**
* 用户握手处理器
*/
public class UserHandshakeHandler extends DefaultHandshakeHandler {
/**
* 确定用户
* @param request
* @param wsHandler
* @param attributes
* @return
*/
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
//这里的customer是业务用户类,实现了Principal 接口 ,重写了getName方法为返回用户ID
//也可以重新创建一个Principal类 把session拦截器中放入的用户ID取出来创建新的Principal如注释部分
Customer customer = (Customer) attributes.get("customer");
// Principal principal = new Principal(){//确定websocket中用户名称为业务用户ID
// @Override
// public String getName() {
// return customer.getId()+"";
// }
// };
return customer;
}
}
配置消息通道拦截器可以拦截在wobSocket消息中发送的每一条消息的不同时期进行拦截,获取消息内容并修改消息内容
package com.jiuhou.gpfe.extend;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
public class WebSocketChannelInterceptor implements ChannelInterceptor {
/**
* Invoked before the Message is actually sent to the channel.
* This allows for modification of the Message if necessary.
* If this method returns {@code null} then the actual
* send invocation will not occur.
*/
public Message<?> preSend(Message<?> message, MessageChannel channel) {
// StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
// if(StompCommand.CONNECT.equals(accessor.getCommand())){
// Customer customer=(Customer) accessor.getSessionAttributes().get("customer");
// accessor.setUser(customer);
// }
return message;
}
@Override
public void postSend(Message<?> message, MessageChannel messageChannel, boolean b) {
}
@Override
public void afterSendCompletion(Message<?> message, MessageChannel messageChannel, boolean b, Exception e) {
}
@Override
public boolean preReceive(MessageChannel messageChannel) {
return true;
}
@Override
public Message<?> postReceive(Message<?> message, MessageChannel messageChannel) {
return message;
}
@Override
public void afterReceiveCompletion(Message<?> message, MessageChannel messageChannel, Exception e) {
}
}
ResponseResult为业务返回值结构体,根据自己业务框架的结构体进行修改
@RestController("ApiWebSocket")和 @MessageMapping("/notice")注解的地址拼接成客户端发送消息的路径。
@SendToUser("/topic/ws/notice")注解作用为将消息内容返回给发送消息的用户
package com.jiuhou.gpfe.controller.common;
import com.jiuhou.common.bean.ResponseResult;
import com.jiuhou.gpfe.entity.Customer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.messaging.simp.annotation.SendToUser;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
import java.util.Map;
@RestController("ApiWebSocket")
@MessageMapping("/api/ws")
public class WebSocketController {
private static final Logger logger= LogManager.getLogger(WebSocketController.class);
@MessageMapping("/notice")
@SendToUser("/topic/ws/notice")
public ResponseResult queneNotice(Map<String,String> param, Principal principal){
logger.info("当前用户{},发送信息{}",principal.getName(),param);
return ResponseResult.oK("成功订阅排队成功主题!"+param);
}
}
这里只是多余的配置一个类 方便管理订阅地址类,如果不需要的话可以直接注入调用消息模板类SimpMessagingTemplate进行发送消息
package com.jiuhou.gpfe.component;
import com.jiuhou.common.bean.ResponseResult;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
@Component
public class WebSocketService {
public static final Logger logger= LogManager.getLogger(WebSocketService.class);
private static final String QUEUE_NOTICE="/topic/ws/notice";
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
//给排队的用户发送通知
public void sendQueueNotice(Long customerId, ResponseResult responseResult){
logger.info("websocket给{}用户发送消息:{}",customerId,responseResult);
simpMessagingTemplate.convertAndSendToUser(customerId.toString(),QUEUE_NOTICE, responseResult);
}
}
1.npm 安装 stomp
// npm 安装Stomp
npm install stomp
1.导入Stomp.js
// 导入stomp.js
import Stomp from "stompjs";
创建连接有两种方式,一种是使用stomp客户端直接进行连接,一种是使用Socket.js,创建连接的地址是http形式。
1.使用stomp直接创建连接
1.引入Stomp客户端
import Stomp from "stompjs";
2.创建STOMP子协议的客户端对象
如果是加密的http协议https需要使用wss方式创建对象,http使用ws前缀进行连接
“/stomp”之前是项目地址。“/stomp”在java中WebSocket基本配置中配置。
let url = "wss://127.0.0.1:8001/gpfe/stomp";
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.client(url);
3.调用connect方法进行连接
this.stompClient.connect(headers,function());
4.订阅服务器端的主题,也就是发送消息的地址。
这里有个需要注意的坑,在服务器端webSocket配置中有个默认的订阅前缀“/user”,所有这里要加上
,否则接收不到服务器发送的信息。在WebSocket基本配置中可以更改此配置。
“/topic/ws/notice”这是服务器端给客户端发送的消息的地址,和WebSocketService中的地址进行对应。
this.stompClient.subscribe("/user/topic/ws/notice", response => {
// 订阅服务端提供的某个topic
console.log(response); // msg.body存放的是服务端发送给我们的信息
console.log("广播成功");
});
5.给服务器端发送消息
“/api/ws/notice”客户端访问服务器端的地址,和WebSocketController中访问地址对应。
this.stompClient.send(
"/api/ws/notice",
headers,
JSON.stringify({ sender: "123", chatType: "JOIN" })
); //用户加入接口
2.Socket.js方式创建连接
使用SocketJs只是在创建stompClient客户端的时候略有不同,需要先创建一个sockJS对象。其他stomp的使用同上。
注意:这里使用的地址为http开头的。调用Stomp的方法为over而不是client。
import SockJS from "sockjs-client";
import Stomp from "stompjs";
<script>
let socket = new SockJS("http[://127.0.0.1:8001/gpfe/stomp");
this.stompClient = Stomp.over(socket);
</script>
<script>
//SockJS使用
// import SockJS from "sockjs-client";
import Stomp from "stompjs";
export default {
name: "home",
components: {
HelloWorld
},
data() {
return {
contentText: "消息内容",
stompClient: null
};
},
methods: {
webSocket() {
=
// let socket = new SockJS("http://127.0.0.1:8001/gpfe/stomp");
// let url = "wss://127.0.0.1:8001/gpfe/stomp";
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.client(url);
// this.stompClient = Stomp.over(socket);
// 定义客户端的认证信息,按需求配置
let headers = {
Authorization: ""
};
console.log(this.stompClient);
this.stompClient.debugg = false;
// 向服务器发起websocket连接
this.stompClient.connect(
headers,
frame => {
console.log("我的websocke连接成功!");
console.log(frame);
this.stompClient.subscribe("/user/topic/ws/notice", response => {
// 订阅服务端提供的某个topic
console.log(response); // msg.body存放的是服务端发送给我们的信息
console.log("广播成功");
});
this.stompClient.send(
"/api/ws/notice",
headers,
JSON.stringify({ sender: "123", chatType: "JOIN" })
); //用户加入接口
},
err => {
// 连接发生错误时的处理函数
console.log("websocke连接失败");
console.log(err);
}
);
}
},
mounted() {
this.webSocket();
}
};
</script>
需要升级http为1.1
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
访问一定要加上token,否则肯定连不上,在连接是要加,发送消息也要加。