使用基于stom协议的websocket实现,自己封装了一个简单易用的websocket-starter,方便使用,具体集成如下所示:
1. 导入websocket-spring-boot-starter
在pom.xml文件导入websocket-spring-boot-starter依赖包,包在文末会提供,由于是自己封装的包,所以拿到后需要在本地仓库进行注册,如下所示:
<dependency>
<groupId>com.lydon</groupId>
<artifactId>websocket-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
2. 配置相关信息
在springboot的yml配置以下配置信息:
spring:
websocket:
endpoint: chat #示例值
subscribe-prefixs: /public/,/notice/ #示例值,名称前后使用/符号
destination-prefixes: /app #示例值
其中字段释义如下所示:
endpoint: 客户端连接服务端的端点信息,客户端根据此端点连接服务端。
subscribe-prefixs: 服务端的订阅地址前缀,客户端根据此地址前缀订阅服务端的发布,服务端发布时需指定发该地址前缀。
destination-prefixes: 服务端提供的接收客户端信息的地址前缀,当客户端需向服务端发消息时,需指定服务端地址。
3. 实现拦截器
当需要对客户端与服务端交互的信息进行拦截处理时,可通过新增拦截类实现WebSocketInterceptor接口类并重写的preSend方法对消息进行拦截,并在类头上加注解@TgWebSocketInterceptor,示例代码如下所示:
package com.lydon.websocket.example;
import com.sun.security.auth.*;
import com.lydon.websocket.annotation.TgWebSocketInterceptor;
import com.lydon.websocket.subscribe.WebSocketInterceptor;
import org.springframework.messaging.Message;
import org.springframework.messaging.*;
import org.springframework.messaging.simp.*;
import org.springframework.messaging.simp.stomp.*;
import org.springframework.messaging.support.*;
import org.springframework.stereotype.*;
import org.springframework.util.*;
import java.security.*;
import java.util.*;
@TgWebSocketInterceptor
public class WebSocketInterceptorDemo implements WebSocketInterceptor {
/**
* 绑定user到websocket conn上
* @param message
* @param channel
* @return
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
System.out.println("----------------------:");
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
System.out.println(raw);
}
System.out.println(">>>>>>>>>>>>>>");
String username = accessor.getFirstNativeHeader("username");
String token = accessor.getFirstNativeHeader("token");
System.out.println("token:" + token);
if (StringUtils.isEmpty(username)) {
return null;
}
// 绑定user
Principal principal = new UserPrincipal(username);
accessor.setUser(principal);
}
return message;
}
}
其中accessor.getCommand()为获取当前操作,与StompCommand.CONNECT进行匹配,具体可匹配的类型有如下所示:
CONNECT:连接操作
DISCONNECT:断开操作
SUBSCRIBE:订阅操作
UNSUBSCRIBE:取消订阅操作
SEND:发送操作
MESSAGE:消息
ERROR:错误
示例代码中的红色字样为重要代码实现,若无此实现则无法辨别websocket用户,并进行精准推送。
4. 示例代码
前端在编写websocket代码前需引入sockjs.min.js、stomp.min.js、jquery.js三个js文件(文件在调用demo有):
后端需引入SimpMessagingTemplate类来实现消息发布,如:
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
4.1. 建立连接
前端代码示例:
//chat为上述章节“配置相关信息”中的endpoint值
var socket = new SockJS("http://127.0.0.1:8080/chat");
client = Stomp.over(socket);
client.connect({
username: username,//用户名,在WebSocketInterceptor中绑定websocket用户
token:'我是token' //自定义参数
}, function (succ) {
console.log('client connect success:', succ);
}, function (error) {
console.log('client connect error:', error);
});
4.2. 订阅
前端订阅代码一般写在建立连接成功之后,如下图所示:
4.2.1. 公共订阅
前端订阅公共消息代码:
// /message/public中的public为上述章节“配置相关信息”中的subscribe-prefixs值,/message为固定前缀,不可更改
client.subscribe("/message ", function (res) {
console.log('收到消息---/message:',res);
});
后端发布订阅代码:
Message message=new Message();//自定义的消息结构体
message.setContent("后台通知消息");
message.setFrom("admin");
simpMessagingTemplate.convertAndSend( "/message", message);//发送给所有订阅的人,
message为上述章节“配置相关信息”中的subscribe-prefixs值
4.2.2. 个人订阅
个人订阅是指后端发布消息到指定的已订阅客户端,一般用于精准推送。
前端个人订阅:
// /user/notice中的notice为上述章节“配置相关信息”中的subscribe-prefixs值,/user为固定前缀,不可更改
client.subscribe("/user/notice", function (res) {
console.log('个人消息:',res)
});
后端发布个人订阅:
相比公共订阅的发送方法多了一个发送者的参数,字段类型为String,需指定目标订阅者。
Message message=new Message();
message.setContent("后台通知消息");
message.setFrom("admin");
message.setTo(userName);
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/notice", message); //发送
给指定订阅的人,notice为上述章节“配置相关信息”中的subscribe-prefixs值
4.3发送消息
前端发送代码:
var messageModel = {};//自定义参数
messageModel.type = 1;
messageModel.content = '你好,' + new Date().getTime();
messageModel.from = username;
client.send("/app/hello", {}, JSON.stringify(messageModel));
其中/app为上述章节“配置相关信息”中的destination-prefixes值,hello为后端接收的接口名,messageModel为自定义参数结构体,与后端接收的结构体保持一致。
后端接收代码:
@MessageMapping("/hello")
public void hello(@Payload Message message) {
System.out.println(message);
}
其中@MessageMapping注解配置的hello为前端请求的地址后缀,两者需匹配上,自定义接收参数的结构体加上@Payload注解。
整体的示例代码如下所示:
package com.lydon.test;
import org.springframework.messaging.handler.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
@Controller
public class GreetingController {
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
/**
* 接收前端消息。
* @param message
*/
@MessageMapping("/hello")
public void hello(@Payload com.lydon.websocket.subscribe.Message message) {
System.out.println(message);
}
/**
* 后台主动发送消息,所有订阅到/message/public的客户端都会受到此消息。
* @param
*/
@RequestMapping("/notifyHello")
@ResponseBody
public void notifyHello() {
com.lydon.websocket.subscribe.Message message=new com.lydon.websocket.subscribe.Message();
message.setContent("后台通知消息");
message.setFrom("admin");
System.out.println(message);
simpMessagingTemplate.convertAndSend( "/notice", message);
}
/**
* 后台主动发送消息,局部订阅收到此消息。
* @param
*/
@RequestMapping("/sendToUser")
@ResponseBody
public void sendToUser(String userName) {
com.lydon.websocket.subscribe.Message message=new com.lydon.websocket.subscribe.Message();
message.setContent("后台通知消息");
message.setFrom("admin");
message.setTo(userName);
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/notice", message);
}
/**
* 使用注解的方式接收客户端信息后并返回消息
* @param message
* @return
*/
@MessageMapping("/hello1")
@SendTo("/message")
public com.lydon.websocket.subscribe.Message hello1(@Payload com.lydon.websocket.subscribe.Message message) {
System.out.println(message);
com.lydon.websocket.subscribe.Message returnMessage = new com.lydon.websocket.subscribe.Message();
returnMessage.setContent("转发2," + message.getContent());
return returnMessage;
}
}