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">
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
URI uri = request.getURI();
List
Optional
//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
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.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