本方案是为了解决前后台及时通信设计的,例如后台代码触发一个事件,可以及时的传递给前台,也就是消息的推送功能.
关于消息的推送,方案1是使用定时任务,Cron表达式设置每分钟处理一下后台逻辑进行事件的判断.方案2是使用webSocket建立消息通信通道,挂起一个线程进行时间的判断和消息推送.虽然都能实现消息推送的功能,但是方案二明显效率更高,对服务器造成的压力相对于方案1来说也更小,这里就简单介绍下使用第二种方案进行消息的及时推送实现方法.
首先说下使用webSocket进行访问是路径格式:ws:// path + “wsMy?jspCode=” + jspCode
以WS开头,后面可以追加参数,上文路径是中的wsMy是为了接下来的过滤器过滤,防止恶意访问.
首先添加webSocket依赖:
<!--spring-websocket-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.1.RELEASE</version>
</dependency>
接下来就是前台页面上的触发访问webSocket:
function getUserId() {
$.ajax({
type : "post",
url : "${ctx}/memo/getCurrentUserId",
beforeSend: function () {
//加载中
waitload();
},
success : function(data) {
currentUserId = data;
closewait();
//动态获取当前系统服务器IP地址
//若执行成功的话,则隐藏进度条提示
//${ctx}为定义的项目名称,此处不展示,因地制宜
var path = addrAndPort+"${ctx}/"
console.log("当前系统的IP及端口号为:"+path);
var userId = currentUserId;
if(userId==-1){
window.location.href="${ctx}/memo/testWebSocket";
}
var jspCode = userId;
var websocket;
if ('WebSocket' in window) {
//alert("webSocket"+"ws://" + path + "wsMy?jspCode=" + jspCode);
<shiro:hasPermission name="user_add_custommenu">
websocket = new WebSocket("ws://" + path + "wsMy?jspCode=" + jspCode);
</shiro:hasPermission>
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://" + path + "wsMy?jspCode=" + jspCode);
} else {
websocket = new SockJS("http://" + path + "wsMy/sockjs?jspCode=" + jspCode);
}
websocket.onopen = function(event) {
console.log("WebSocket:已连接");
console.log(event);
};
websocket.onmessage = function(event) {
if (event.data =="木有消息啊"){
//alert("木有消息啊,测试webSocket是否死亡")
}else {
alert("您有新的提醒,请进入系统首页进行查看")
window.flushParent();
var aaa = event.data.split("*");
var id = aaa[0];
var content = aaa[1];
window.layer.alert(content, {
skin: 'layui-layer-molv' //样式类名 自定义样式
,closeBtn: 1 // 是否显示关闭按钮
,anim: 1 //动画类型
,btn: ['取消提醒','确定'] //按钮
,icon: 6 // icon
,yes:function(){
$.ajax({
type : "post",
data :{
id :id
},
url : "${ctx}/memo/cancleNotepadByMap",
success : function(data) {
window.location.reload();
//若执行成功的话,则隐藏进度条提示
if (data != null && data != 'undefined'
&& data == 1) {
layer.msg('该提醒取消成功!', {icon: 6,time:1000});
parent.flushParent();
layer_close();
window.location.reload();
}else if (data == -1) {
layer.msg('记事本名称已经存在!', {icon: 5,time:1000});
}else if (data == 0) {
layer.msg('很抱歉!添加失败!', {icon: 5,time:1000});
}else{
layer.msg('系统异常!请与系统管理员联系!', {icon: 5,time:1000});
}
}
});
}
,btn2:function(){
layer.msg('按钮2')
}});
}
};
websocket.onerror = function(event) {
console.log("WebSocket:发生错误 ");
console.log(event);
};
websocket.onclose = function(event) {
console.log("WebSocket:已关闭");
console.log(event);
}
}
})
}
因为本案例中访问的是当前服务器的路径,所以要对当前服务器的IP以及端口号进行获取,然后赋值给webSocket的访问路径:
/*获取当前用户id*/
var addrAndPort = "";
function getAddrAndport() {
$.ajax({
type : "get",
url : "${ctx}/getAddrAndport",
beforeSend: function () {
//加载中
waitload();
},
success : function(data) {
addrAndPort = data;
}
})
}
后台获取IP以及端口号返回到前台:
@RequestMapping(value = "/getAddrAndport", method = { RequestMethod.GET,
RequestMethod.POST })
@ResponseBody
public Object getAddrAndport(HttpServletRequest request,
HttpServletResponse response) {
String addrAndPort = "";
addrAndPort = request.getLocalAddr()+":"+request.getLocalPort();
return addrAndPort;
}
之前查找资料,获取当前系统的IP以及端口号的方法,杂乱,还是直接从HttpServletRequest 中直接获取最为直接和准确.
当前台页面触发访问的时候,后台服务器接收到访问请求并且进行处理,这时候我把后台关于webSocket的请求分为三个文件:
1:HandShake.java
package com.yyx.webSocket;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* Created by zhangrui on 2018/1/22.
*/
public class HandShake implements HandshakeInterceptor{
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// TODO Auto-generated method stub
String jspCode = ((ServletServerHttpRequest) request).getServletRequest().getParameter("jspCode");
// 标记用户
// String userId = (String) ((ServletServerHttpRequest) request).getServletRequest().getSession().getAttribute("userId");
if(jspCode!=null){
attributes.put("jspCode", jspCode);
}else{
return false;
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
// TODO Auto-generated method stub
}
}
2.MyWebSocketConfig.java
package com.yyx.webSocket;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import javax.annotation.Resource;
/**
* Created by zhangrui on 2018/1/22.
*/
@Component
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Resource
MyWebSocketHandler handler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// TODO Auto-generated method stub
registry.addHandler(handler, "/wsMy");
registry.addHandler(handler, "/wsMy/sockjs").withSockJS();
}
}
3:MyWebSockethandler.java访问成功后的处理逻辑,本文中是使用线程写的,因人而异.
package com.yyx.webSocket;
import com.google.gson.GsonBuilder;
import com.yyx.zbhr.controller.MemoController;
import com.yyx.zbhr.entity.MemoEntity;
import com.yyx.zbhr.entity.NotepadEntity;
import com.yyx.zbhr.entity.User;
import com.yyx.zbhr.service.MMemoService;
import com.yyx.zbhr.service.MPersonInfoService;
import com.yyx.zbhr.utils.UserUtil;
import org.apache.commons.collections.map.HashedMap;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.*;
/**
* Created by zhangrui on 2018/1/22.
*/
@Component
public class MyWebSocketHandler implements WebSocketHandler {
public static final Map<String, WebSocketSession> userSocketSessionMap;
private static Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class);
private static Calendar fromCal=Calendar.getInstance();
@Autowired(required = false)
private MPersonInfoService mPersonInfoService;
@Autowired(required = false)
private MMemoService mMemoService;
static {
userSocketSessionMap = new HashMap<String, WebSocketSession>();
}
public static WebSocketSession session1 ;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// TODO Auto-generated method stub
//HttpSession httpSession = (HttpSession) session;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
//调用接口获取ID
//Long userId = mMemoService.getCurrentUserId();
Map<String,Object> map = new HashedMap();
/*//wsMy?jspCode=23*/
String uri = session.getUri().toString();
String[] split = uri.split("=");
int currentUserId = Integer.parseInt(split[1]);
// TODO 自动shiro获取id失败
map.put("userId",currentUserId);
String jspCode = (String) session.getHandshakeAttributes().get("jspCode");
if (userSocketSessionMap.get(jspCode) == null) {
userSocketSessionMap.put(jspCode, session);
}
new Thread(new Runnable() {
public void run() {
while(true){
Date date = new Date();
String format = simpleDateFormat.format(date);
List<NotepadEntity> notepadEntities = mMemoService.selectNotepadForNotice(map);
for (int i = 0; i < notepadEntities.size(); i++) {
String format1 = simpleDateFormat.format(notepadEntities.get(i).getStarTime());
String format2 = simpleDateFormat.format(notepadEntities.get(i).getEndTime());
StringBuffer message = new StringBuffer();
int flag1 = date.compareTo(notepadEntities.get(i).getEndTime());
int flag2 = date.compareTo(notepadEntities.get(i).getStarTime());
if (flag1 < 0 && flag2 >= 0) {
try {
String noteType = "";
if (notepadEntities.get(i).getNoteType() == 1){
noteType = "工作";
}else if (notepadEntities.get(i).getNoteType() == 2){
noteType = "生活";
}else if (notepadEntities.get(i).getNoteType() == 3){
noteType = "家庭";
}else if (notepadEntities.get(i).getNoteType() == 4){
noteType = "私密";
}else {
noteType = "其他";
}
message.append(notepadEntities.get(i).getId()+"*");
message.append("<b>日程类型:</b></br>"+noteType+"</br>");
message.append("<b>起始时间:</b></br>"+format1+"</br>");
message.append("<b>终止时间:</b></br>"+format2+"</br>");
message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
session.sendMessage(new TextMessage(message,true));
//session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型\":\"" + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题:\":\"" + notepadEntities.get(i).getTitle() + "\";\"内容:\":\"" + notepadEntities.get(i).getContent() + "\"")));
//session.sendMessage(new TextMessage("日程提醒\n日程事件:"+format1+"--"+format2+"\n"+"标题:"+notepadEntities.get(i).getTitle()+"\n"+"内容:"+notepadEntities.get(i).getContent()+"类型:"+noteType+"\n"+""));
} catch (IOException e) {
e.printStackTrace();
}
}else if(flag1>0){
try {
String noteType = "";
if (notepadEntities.get(i).getNoteType() == 1){
noteType = "工作";
}else if (notepadEntities.get(i).getNoteType() == 2){
noteType = "生活";
}else if (notepadEntities.get(i).getNoteType() == 3){
noteType = "家庭";
}else if (notepadEntities.get(i).getNoteType() == 4){
noteType = "私密";
}else {
noteType = "其他";
}
message.append(notepadEntities.get(i).getId()+"*");
message.append("<h3 style=\"color:red\">日程过期提醒</h3>");
message.append("<b>日程类型:</b></br>"+noteType+"</br>");
message.append("<b>起始时间:</b></br>"+format1+"</br>");
message.append("<b>终止时间:</b></br>"+format2+"</br>");
message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
session.sendMessage(new TextMessage(message,true));
//session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型\":\"" + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题:\":\"" + notepadEntities.get(i).getTitle() + "\";\"内容:\":\"" + notepadEntities.get(i).getContent() + "\"")));
//session.sendMessage(new TextMessage("日程提醒\n日程事件:"+format1+"--"+format2+"\n"+"标题:"+notepadEntities.get(i).getTitle()+"\n"+"内容:"+notepadEntities.get(i).getContent()+"类型:"+noteType+"\n"+""));
} catch (IOException e) {
e.printStackTrace();
}
}else {
String noteType = "";
if (notepadEntities.get(i).getNoteType() == 1){
noteType = "工作";
}else if (notepadEntities.get(i).getNoteType() == 2){
noteType = "生活";
}else if (notepadEntities.get(i).getNoteType() == 3){
noteType = "家庭";
}else if (notepadEntities.get(i).getNoteType() == 4){
noteType = "私密";
}else {
noteType = "其他";
}
//session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型 \":\" " + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题\":\"" + notepadEntities.get(i).getTitle() + "\";\"内容 \":\"" + notepadEntities.get(i).getContent() + "\"")));
//session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"number\":\"" + i + "\";\"deptId\":\"" + format + "\";\"事件:\":\"" + "这只是哥哥拿来测试的事件" + i + "\"")));
/* message.append("<b>日程类型:</b></br>"+noteType+"</br>");
message.append("<b>起始时间:</b></br>"+format1+"</br>");
message.append("<b>终止时间:</b></br>"+format2+"</br>");
message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
session.sendMessage(new TextMessage(message,true));*/
try {
session.sendMessage(new TextMessage("木有消息啊",true));
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
Thread.sleep(1000*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
// TODO Auto-generated method stub
//Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);
//msg.setDate(new Date());
// sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
session.sendMessage(message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
// TODO Auto-generated method stub
if (session.isOpen()) {
session.close();
}
Iterator<Map.Entry<String, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 移除Socket会话
while (it.hasNext()) {
Map.Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
// TODO Auto-generated method stub
System.out.println("Websocket:" + session.getId() + "已经关闭");
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 移除Socket会话
while (it.hasNext()) {
Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
//TODO 对webSocket资源进行回收
System.gc();
System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
public void broadcast(final TextMessage message) throws IOException {
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 多线程群发
while (it.hasNext()) {
final Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().isOpen()) {
new Thread(new Runnable() {
public void run() {
try {
if (entry.getValue().isOpen()) {
entry.getValue().sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
public void sendMessageToJsp(final TextMessage message,String type) throws IOException {
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 多线程群发
while (it.hasNext()) {
final Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().isOpen() && entry.getKey().contains(type)) {
new Thread(new Runnable() {
public void run() {
try {
if (entry.getValue().isOpen()) {
entry.getValue().sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
}
第三个文件是核心,具体的代码是实验代码,有一部分是冗余代码,粘贴出来仅供参考,值得一提的是,在webSocket进行sendMessage的时候,可选的message格式有多种,当时博主想传回类json,但是传到前台发现处理比较麻烦,所以就传回了StringBuffer类型的,至于StringBuffer和StringBuidder之间的选择,看情况决定,这里不多言.再展示下消息传递后的效果图:
对返回到前台的消息处理使用的layer进行弹出,比较好用,样式多.可以直接从layer官网查询样式进行使用,网址….百度.
最后一点,本文中使用的web容器及版本是Tomcat8.0.48,虽然有人说在Tomcat8.0之前,不支持webSocket消息通讯,说的是那时候webSocket协议未制定,但是….博主试了下使用Tomcat7.9可行,所以这里就不再多说废话啦,博主也只是个门外汉,更多更细的webSocket知识点还是留待大家自行发掘吧!
最后一点,请不要诟病博主的访问路径:getAddrAndport,虽然不规范,但是….这破习惯一下子难改啊…逐步改正.
完.