1、依赖版本
springboot:2.2.5.RELEASE (springboot官网)
Vue:2.11.0 (element ui官网)
springboot pom中依赖 websocket
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<scope>provided</scope>
</dependency>
2、后端实现WebSocketServer
1):WebSocketServer实现类
@ServerEndpoint("/socket")//配置服务端点,类似mapping地址
@Component
public class WebSocketServer {
private static final Log log = Logs.getLog(WebSocketServer.class);
/**用于处理链接进来的用户信息 -- 自定义*/
private StringBuffer userIdentifiy = new StringBuffer();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userId*/
private String userId = "";
/** 以缓冲的传入二进制消息的最大长度,可以缓冲的传入文本消息的最大长度*/
@Value("${websocket.max-binary-message-buffer-size:2000}")
private int maxBinaryMessageBufferSize;
//自定义的接口类,用于实现其他处理(业务之类的)
public static WebSocketService webSocketService;
// 给类的 service 注入
@Autowired
public void setWebSocketService(WebSocketService webSocketService) {
WebSocketServer.webSocketService = webSocketService;
}
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session) {
//用于标识用户是否可以成功链接
boolean allowConnection = false;
// 获取请求参数
String paramInfo = null;
try {
// 参数以 Content-Type为 x-www-form-urlencoded 传输
if(StringUtils.isNotBlank(session.getQueryString())) {
paramInfo = new String(session.getQueryString().getBytes("iso-8859-1"), "utf-8");
}
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
return;
}
if(StringUtils.isNotBlank(paramInfo)) {
// 处理参数转为JSONObject
JSONObject userCache = urlParamSplit(paramInfo);
// token 判断必传、判断调用用途不能为空
if(userCache.containsKey("token") &&
!VerificationUtils.checkJsonKeyValueIsNull(userCache, WebSocketConstant.CONNECT_PURPOSE)) {
// 获取用户token信息,根据自己系统配置
UserInfo cache_user = xxx.getUserAuthInfo(userCache.getString("token"));
if(cache_user != null) {
// 用户编码
userCache.put(WebSocketConstant.CONNECT_USERCODE, cache_user.getUserCode());
this.session = session;
int maxSize = maxBinaryMessageBufferSize * 1024;// 2000K
// 可以缓冲的传入二进制消息的最大长度
session.setMaxBinaryMessageBufferSize(maxSize);
// 可以缓冲的传入文本消息的最大长度
session.setMaxTextMessageBufferSize(maxSize);
// 处理用户信息
userIdentifiy.setLength(0);
userIdentifiy.append(cache_user.getUserCode());// 用户编码
userIdentifiy.append("--");
// 其他需要的按需处理userIdentifiy.append(userCache.getString(WebSocketConstant.CONNECT_PURPOSE));// 用途
userIdentifiy.append(userCache.getString(WebSocketConstant.CONNECT_CLIENT_ID));// 客户端识别app/pc
this.userId = userIdentifiy.toString();
// 加入关系
WebSocketConstant.webSocketMap.put(userId,this);
WebSocketConstant.webSocketTokenMap.put(userId, userCache);
// 处理业务需要
webSocketService.dealBusiness(this.userId, userCache.getString(WebSocketConstant.CONNECT_PURPOSE), userCache);
try {
JSONObject returnObj = new JSONObject();
returnObj.put(WebSocketConstant.CONNECT_PURPOSE, WebSocketConstant.WEBSOCKET_CONNECTED);
sendMessage(returnObj.toJSONString());
} catch (IOException e) {
log.error("用户:" + userId + ",网络异常!");
}
allowConnection = true;
}
}
}
if(!allowConnection) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @param urlparam 带分隔的url参数
* @return
*/
public static JSONObject urlParamSplit(String urlparam){
JSONObject obj = new JSONObject();
String[] param = urlparam.split("&");
for(String keyvalue: param){
String[] pair = keyvalue.split("=");
if(pair.length==2){
obj.put(pair[0], pair[1]);
}
}
return obj;
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(WebSocketConstant.webSocketMap.containsKey(userId)){
// 业务操作
webSocketService.removeBusinessRecord(userId, WebSocketConstant.webSocketTokenMap.get(userId));
WebSocketConstant.webSocketMap.remove(userId);
WebSocketConstant.webSocketTokenMap.remove(userId);// 用户与token关系
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
try {
session.getBasicRemote().sendText("接收到消息:"+ message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
error.printStackTrace();
}
/**
* - 推送消息
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* -推送消息给某个用户
*
* @param message
* @param userId
* @throws IOException
*/
public static void sendInfo(String message, String userId) throws IOException {
if(StringUtils.isNotBlank(userId) && WebSocketConstant.webSocketMap.containsKey(userId)){
WebSocketConstant.webSocketMap.get(userId).sendMessage(message);
}else{
log.error("用户"+userId+",不在线!");
}
}
}
2):自定义 WebSocket 常量
/**
* WebSocket 常量定义
* @author chenwx
*
*/
public class WebSocketConstant {
/** 存放每个客户端对应的WebSocket对象。 */
public static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/** 存放每个客户端对应的userInfo对象。*/
public static ConcurrentHashMap<String, JSONObject> webSocketTokenMap = new ConcurrentHashMap<>();
/** webSocket连接成功 */
public static final String WEBSOCKET_CONNECTED = "connected";
/** 缓存用户编码 */
public static final String CONNECT_USERCODE = "userCode";
/** 缓存供应商编码 */
public static final String CONNECT_BIZCODE = "bizCode";
/** 用途 */
public static final String CONNECT_PURPOSE = "purpose";
/** 返回参数 */
public static final String CONNECT_RETURN_OBJECT = "result";
/** 连接客户端类型 app/pc */
public static final String CONNECT_CLIENT_TYPE = "clientId";
}
3、前端实现
1、创建一个WebSocket:
//判断是ip还是域名
let isIp = self.ipVerify(document.location.hostname);
// 域名访问需要用wss
let replaceCode = 'wss';
if (isIp) {
replaceCode = 'ws';
}
var socketUrl = this.webSocketUrl()
.replace('https', replaceCode)
.replace('http', replaceCode);
let websocket = new WebSocket(url);
//几个常用事件方法
websocket.onopen = function(event) {
//连接打开
};
websocket.onclose = function(event) {
//连接关闭
};
websocket.onmessage = function(event) {
//接收到消息
};
websocket.onerror = function(event) {
//发生错误
};
webSocketUrl() {
let baseUrl = this.baseUrl();
//本地部署的服务名称,用于websocket不在自己部署的服务中时,通过替换服务名称,直接连到对应的服务去
let serviceName = '/test/';
//websocket所在的服务名称
let socketServiceName = '/main-socket/';
return baseUrl.replace(serviceName,socketServiceName);
}
4、部署注意点
项目部署时,需要在nginx上配置路由代理转发,将socket的请求转发到对应的服务中去。
以 http://127.0.0.1:8002/gateway/test/socket?xx=sss为例,
图例为同一服务器统一部署,根据自身部署策略修整。
解决可能发生的以下错误:
WebSocket connection to ‘xxx’ failed: Error during WebSocket handshake: Unexpected response code 200