Java
1.pom坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.websocket 配置
/**
* websocket stomp方式
* 四种方式分别见: https://juejin.cn/post/6844903976727494669#heading-27
*
* @author wh
* @date 2021-11-25
*/
// TODO:如何解决服务器重启websocket前端连接 循环?
@Slf4j
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* 添加这个Endpoint,这样在网页中就可以通过websocket连接上服务,
* 也就是我们配置websocket的服务地址,并且可以指定是否使用socketjs
*
* @param registry
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/*
* 1. 将 /ws/ep路径注册为STOMP的端点,
* 用户连接了这个端点后就可以进行websocket通讯,支持socketJs
* 2. setAllowedOriginPatterns("*")表示可以跨域
* 3. withSockJS()表示支持socktJS访问
* 4. addInterceptors 添加自定义拦截器,这个拦截器是上一个demo自己定义的获取httpsession的拦截器
* 5. addInterceptors 添加拦截处理,这里MyPrincipalHandshakeHandler 封装的认证用户信息
*/
// https://blog.csdn.net/jxysgzs/article/details/110818712 这个问题的sendToUser 解决了
registry.addEndpoint("/ws/ep").setAllowedOriginPatterns("*").withSockJS();
}
/**
* 配置客户端入站通道拦截器
* 设置输入消息通道的线程数,默认线程为1,可以自己自定义线程数,最大线程数,线程存活时间
*
* @param registration
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
// log.info("--websocket信息发送前--");
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor != null) {
// 判断是否是连接Command 如果是,需要获取token对象
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
final String token = accessor.getFirstNativeHeader(Constant.HEADER_TOKEN);
if (!TokenUtil.validateToken(token)) {
return null;
}
final LoginUser user = TokenUtil.getUserFromToken(token);
UserUtil.setUser(user);
// sendToUser 需要与这里的user获取的principal一样
accessor.setUser(new SocketUser(user));
log.info("websocket 连接成功");
}
}
return message;
}
});
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 设置广播节点
registry.enableSimpleBroker("/queue");
}
}
上述代码中
registerStompEndpoints()方法用来注册端点,vue项目通过此端点进行连接
configureClientInboundChannel()方法用来增加拦截器进行token验证,其中判断了command,仅在command为CONNECT时才进行判断
configureMessageBroker()方法用来配置广播节点,配置了此节点才可以实现点对点发送
package com.christo.christoboot.bo;
import com.christo.christoboot.LoginUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.security.Principal;
/**
* websocket专用user 因为要实现Principal的getName 这个name必须具有唯一性。所以获取userId
*
* @author wh
* @date 2021-11-25
*/
@Data
@NoArgsConstructor
public class SocketUser implements Principal {
private LoginUser user;
private String id;
public SocketUser(LoginUser user) {
this.user = user;
this.id = user.getId().toString();
}
@Override
public String getName() {
return id;
}
}
websocket的sendToUser方法需要传递一个String类型的user,这个user就是我们在accessor中set的user,他是获取user中的name进行点对点发送的,我们平时的User类中一般存储的name字段都是名称,不能作为一个点对点发送的唯一识别码,所以这边增加一个SocketUser类,获取name时实际获取userId即可
@GetMapping(value = "ws")
public R<String> wsTest(String message, String to) {
ChatMsgDTO chatMsgDTO = new ChatMsgDTO();
chatMsgDTO.setTime(LocalDateTime.now());
chatMsgDTO.setContent(message);
chatMsgDTO.setTo(to);
simpMessagingTemplate.convertAndSendToUser(to, "/queue/chat",chatMsgDTO);
return R.ok();
}
这边写一个发送到指定用户的测试方法,根据上方讲解,这边的to即为我们的User类中的id,chatMsgDTO是我随便定义的一个类,实际开发中需要传递什么类型的消息自定义即可
Vue
vue中实现连接与监听就比较容易了,首先我们需要install对应的js文件
npm install sockjs-client
npm install stompjs
然后在页面中进行引入
import SockJS from "sockjs-client";
import Stomp from "stompjs";
我选择在页面登录时进行socket的连接与订阅,所以讲下方代码放在login完成之后的代码里即可
websocketRun() {
const sock = new SockJS('http://127.0.0.1:2525/ws/ep');
const client = Stomp.over(sock);
const token = getToken();
const that =this
client.connect({Authorization: token}, () => {
console.log("连接websocket成功111")
// //连接成功后订阅消息接口
//订阅个人消息
client.subscribe('/user/queue/chat', function(res){
const data = JSON.parse(res.body);
that.$notify({
title: data.to,
message: data.content
});
});
});
}
其中值得注意的点
1.SockJs中的协议是http而不是ws,这样做的原因我暂时不太了解,但是如果换成ws:// console中会进行报错
2.在connect中将我们登陆完成获得的token传入以通过我们后台设定的拦截器校验
3.在连接完成之后,进行subsribe时的url需要以/user开头,这个是因为后台sendToUser点对点发送的规范,必须以/user开头,是个写死的规范,监听方法里通过res.body获取到我们发送的消息进行解码进行对应的处理,我这里只是测试一下,所以仅仅是notify一下。
4.几个容易出错的点 1.端口不要写错啦 2.端点不要写错,是config中配置的/ws/ep 3.监听user时发送的to字段,一定要是Principal对象的getName
小结
1.后台代码中还有很多地方可以改进,实际项目使用中代码应该更为严谨,提供的代码仅记录我的学习过程
2.前端代码中也只是简单的测试了一下,实际使用中还涉及到stomp对象全局封装和断线重连等问题,也仅记录我的学习过程