SpringWebsocket +Stomp+SockJS实现消息订阅和推送

1、直接上干货,以下为pom.xml配置:

xmlns="http://maven.apache.org/POM/4.0.0"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

charites-parent

cn.linkedcare

0.3-SNAPSHOT

4.0.0

 

charites-notification

cn.linkedcare

charites-core

${project.version}

cn.linkedcare

charites-his

${project.version}

org.springframework

spring-websocket

org.springframework.boot

spring-boot-starter-websocket

${spring.boot.version}

org.springframework

spring-messaging

 

2、配置websocket的消息监听信息:

package cn.linkedcare.charites.notification;

 

import cn.linkedcare.charites.notification.extensions.XAccessTokenHandshakeHandler;

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;

 

@Configuration

@EnableWebSocketMessageBroker

public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

@Override

public void configureMessageBroker(MessageBrokerRegistry config) {

//分别表示在客户端上主题和个人的消息订阅

config.enableSimpleBroker("/topic", "/queue");

//客户端向系统发送消息

config.setApplicationDestinationPrefixes("/app");

}

 

@Override

public void registerStompEndpoints(StompEndpointRegistry registry) {

//前段可以通过此链接接入消息推送通道

registry.addEndpoint("/notification")

// 在接受客户端订阅消息握手时,增加一个XAccessTokenHandshakeHandler类来判断此用户到底

//是谁,因为在我的项目中不会在header中上传USER信息,所以针对个人消息的推送采用了这种方

//式。在握手时把TOKEN传过来,根据TOKEN来识别用户。很多都是直接通过User来发的,如果不是

//采用User的方式,资料真的很少,开始我一直想通过自己编写一个header的方法来实现,但是不

//行。后来还是项目老大找了很多资料才解决。

.setHandshakeHandler(new XAccessTokenHandshakeHandler())

//这个设置主要为了解决跨域问题。

.setAllowedOrigins("*")

.withSockJS();

}

}

 

3、XAccessTokenHandsharkeHandler类:

 

package cn.linkedcare.charites.notification.extensions;

 

import cn.linkedcare.charites.core.security.Authentication;

import org.apache.http.NameValuePair;

import org.apache.http.client.utils.URLEncodedUtils;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.server.RequestUpgradeStrategy;

import org.springframework.web.socket.server.support.DefaultHandshakeHandler;

import java.net.URI;

import java.security.Principal;

import java.util.List;

import java.util.Map;

import java.util.Objects;

import java.util.Optional;

 

public class XAccessTokenHandshakeHandler extends DefaultHandshakeHandler {

 

public XAccessTokenHandshakeHandler() {

}

 

public XAccessTokenHandshakeHandler(RequestUpgradeStrategy requestUpgradeStrategy) {

super(requestUpgradeStrategy);

}

//重写了datermineUser方法,此为识别不同订阅者的关键方法。

@Override

protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map attributes) {

URI uri = request.getURI();

List pairs = URLEncodedUtils.parse(uri, "UTF-8");

Optional token = pairs.stream()

//Authentication方法为我本地解析Token的方法。

.filter(v -> Objects.equals(v.getName(), Authentication.TOKEN_HEADER_NAME))

.findFirst();

if (token != null && token.isPresent()) {

final Authentication authentication = Authentication.verify(token.get().getValue());

if (authentication != null) {

//将userId返回给用户,作为识别此用户的唯一标识。

return () -> authentication.userId;

}

}

return super.determineUser(request, wsHandler, attributes);

}

}

 

4、Authentication类:

package cn.linkedcare.charites.core.security;

 

import com.auth0.jwt.JWTSigner;

import com.auth0.jwt.JWTVerifier;

import com.auth0.jwt.JWTVerifyException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

import java.io.IOException;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

import java.security.SignatureException;

import java.util.HashMap;

import java.util.Map;

 

public class Authentication {

public final static String TOKEN_HEADER_NAME = "x-access-token";

private static final Logger LOG = LoggerFactory.getLogger(Authentication.class);

private final static String issuer = "lc";

private final static String secret = "258252597758";

public String tenantId;

public String userId;

public String userType;

public String applicationId;

public String companyId;

public String clinicId;

public String departmentId;

public String jobCode;

public static Authentication verify(String token) {

try {

final JWTVerifier verifier = new JWTVerifier(secret);

final Map claims = verifier.verify(token);

Authentication authentication = new Authentication();

authentication.tenantId = (String) claims.getOrDefault("tid", null);

authentication.userId = (String) claims.getOrDefault("uid", null);

authentication.userType = (String) claims.getOrDefault("typ", null);

authentication.applicationId = (String) claims.getOrDefault("app", null);

authentication.companyId = (String) claims.getOrDefault("ltd", null);

authentication.clinicId = (String) claims.getOrDefault("org", null);

authentication.departmentId = (String) claims.getOrDefault("dpt", null);

authentication.jobCode = (String) claims.getOrDefault("job", null);

return authentication;

} catch (JWTVerifyException | NoSuchAlgorithmException |

SignatureException | IOException | InvalidKeyException e) {

LOG.error(e.getMessage(), e);

}

return null;

}

public static String sign(Authentication authentication) {

 

final long iat = System.currentTimeMillis() / 1000L; // issued at claim

final long exp = iat + 10 * 24 * 60 * 60L; // expires claim. In this case the token expires in 60 seconds

final JWTSigner signer = new JWTSigner(secret);

final HashMap claims = new HashMap<>();

claims.put("iss", issuer);

claims.put("exp", exp);

claims.put("iat", iat);

claims.put("tid", authentication.tenantId);

claims.put("uid", authentication.userId);

claims.put("typ", authentication.userType);

claims.put("app", authentication.applicationId);

claims.put("ltd", authentication.companyId);

claims.put("org", authentication.clinicId);

claims.put("dpt", authentication.departmentId);

claims.put("job", authentication.jobCode);

return signer.sign(claims);

}

}

 

5、当前端执行某一操作的时候,例如预约顾客到店了,服务端此时可根据顾客的相关资料,推送一个详细给本店的相关人员,以下为消息推送到某些人的具体处理类。

package cn.linkedcare.charites.notification.application;

 

import cn.linkedcare.charites.core.utilities.Validator;

import cn.linkedcare.charites.notification.data.UserMessageDataAccessor;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.messaging.simp.SimpMessagingTemplate;

import org.springframework.stereotype.Component;

 

@Component

public class SystemMessageNotifierImpl implements SystemMessageNotifier {

private final static Logger logger = LoggerFactory.getLogger(SystemMessageNotifierImpl.class);

 

@Autowired

private SimpMessagingTemplate template;

 

@Autowired

private UserMessageDataAccessor dataAccessor;

 

@Override

public void notify(String topic, NotificationScope scope, Object payload) {

try {

String[] operatorIds = scope.getOperatorIds();

if (operatorIds != null && operatorIds.length > 0) {

for (String id : operatorIds) {

if (Validator.isNullOrEmpty(id)) {

continue;

}

//根据握手时返回的Id信息,将消息推送给个人。推送给多个人的时候在外边执行循环。

this.template.convertAndSendToUser(id, "/queue/" + topic, payload);

}

}

//这个注释掉的方法为推送消息给订阅了某已主题的所有人。

//this.template.convertAndSend("/topic/" + topic, payload);

} catch (Exception e) {

logger.info(e.toString());

}

}

}

 

6、客户端核心代码:

var stompClient;

var token = $window.sessionStorage.token;

//通过notification进行握手,进入消息推送通道。同时在后面传入识别身份的token

var ws = new SockJS('http://localhost:8080/notification?x-access-token=' + token);

stompClient = Stomp.over(ws);

var headers = {};

// headers['x-access-token'] = $window.sessionStorage.token;

stompClient.connect(headers, function (frame) {

$log.info('Connected: ' + frame);

//在应用中"/user"为关键字,以它为前缀的的订阅表示为个人订阅。服务端可以通过握手时的个人识

//别标志,在推送消息时只推送给个人。

stompClient.subscribe('/user/queue/visit', function (message) {

$rootScope.$broadcast("customer-visit", JSON.parse(message.body));

});

//订阅了另一个个人消息。

stompClient.subscribe('/user/queue/prescription', function (message) {

$rootScope.$broadcast("prescription", JSON.parse(message.body));

});

});

需要引入的JS:sockjs.min.js

stomp.min.js

这里主要是我实现功能的代码,如果要了解相信的概念还是要去看Spring官网的说明文档, 只有英文的,暂时没有汉译版本的。然后下面的参考资料是我在学习的时候看了还可以的,新手可以先看下增加一点概念。

 

参考资料:

http://blog.csdn.net/pacosonswjtu/article/details/51914567

http://blog.csdn.net/yingxiake/article/details/51224929

你可能感兴趣的:(Websocket)