1.场景需求
后台攻城狮和前端攻城狮一起开发时,经常受到前端攻城狮的骚扰,动不动就来一句,那谁,帮我看一下接口访问出什么错了。。。我刚刚上传的参数过来了吗。。。你返回的是什么。。。我请求过去了吗。。。
好吧,就是这样的一种情况,然后我希望让他们自己去看后台日志,而又不想给他们登陆服务器的权限TAT。那就想办法把访问日志实时输出到web页面,这样他们打开页面就可以了。
2.特别鸣谢
1)特别感谢http://blog.csdn.net/xiao__gui/article/details/50041673的启发,该文章中利用的是linux中的tail日志,本文即是受此启发,基本思路一模一样,感谢感谢。
2)感谢国产博客中各种websocket教程,获益颇多,本文其实也是一个websocket教程,只是稍作拓展。
3.进入正题
配置websocket,大部分web项目都会用到spring框架吧,所有这里贴spring websocket的配置,不用spring的请自行去掉spring部分。spring使用4.0以上,tomcat使用7.0.68版本。
1)pom.xml中包的引入:
com.fasterxml.jackson.core
jackson-annotations
2.3.0
com.fasterxml.jackson.core
jackson-core
2.3.1
com.fasterxml.jackson.core
jackson-databind
2.3.3
org.springframework
spring-messaging
4.0.5.RELEASE
org.springframework
spring-websocket
4.0.5.RELEASE
org.springframework
spring-webmvc
4.0.5.RELEASE
com.google.code.gson
gson
2.3.1
javax.servlet
javax.servlet-api
3.1.0
provided
2)websocket逻辑代码,需要一个config和一个handle
configuration:
package com.he.websocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
* WebScoket配置处理器
* @author he
* @Date 2016年03月15日 下午1:15:09
*/
@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws").addInterceptors(new HttpSessionHandshakeInterceptor());
registry.addHandler(myHandler(), "/ws/sockjs").addInterceptors(new HttpSessionHandshakeInterceptor()).withSockJS();
}
@Bean
public WebsocketHandler myHandler() {
return new WebsocketHandler();
}
}
handle:
package com.he.websocket;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.he.entity.Message;
@Component
public class WebsocketHandler extends TextWebSocketHandler {
protected static final Logger LOG = Logger.getLogger(WebsocketHandler.class);
public static final Map userSocketSessionMap;
static {
userSocketSessionMap = new HashMap();
}
/**
* 建立连接后
*/
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
String uid = (String) session.getAttributes().get("uid");
if (userSocketSessionMap.get(uid) == null) {
userSocketSessionMap.put(uid, session);
}
LOG.warn("======建立连接完成======");
}
/**
* 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage> message) throws Exception {
if(message.getPayloadLength()==0)return;
Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);
String msgString = message.getPayload().toString();
LOG.warn("收到的消息是:" + msgString);
LOG.warn("发送的对象是:" + msg.getTo());
msg.setDate(new Date());
sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
LOG.warn("======消息处理结束======");
}
/**
* 消息传输错误处理
*/
public void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
Iterator> it = userSocketSessionMap
.entrySet().iterator();
LOG.warn("======消息传输错误======");
// 移除Socket会话
while (it.hasNext()) {
Entry entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
}
/**
* 关闭连接后
*/
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
LOG.warn("Websocket:" + session.getId() + "已经关闭");
Iterator> it = userSocketSessionMap
.entrySet().iterator();
// 移除Socket会话
LOG.warn("======关闭连接======");
while (it.hasNext()) {
Entry entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
LOG.warn("Socket会话已经移除:用户ID" + entry.getKey());
break;
}
}
}
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
*
* @param message
* @throws IOException
*/
public void broadcast(final TextMessage message) throws IOException {
Iterator> it = userSocketSessionMap
.entrySet().iterator();
LOG.warn("======群发======");
// 多线程群发
while (it.hasNext()) {
final Entry entry = it.next();
if (entry.getValue().isOpen()) {
// entry.getValue().sendMessage(message);
new Thread(new Runnable() {
public void run() {
try {
if (entry.getValue().isOpen()) {
entry.getValue().sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
/**
* 给某个用户发送消息
*
* @param userName
* @param message
* @throws IOException
*/
public void sendMessageToUser(String uid, TextMessage message)
throws IOException {
WebSocketSession session = userSocketSessionMap.get(uid);
LOG.warn("======给某个用户发送消息======");
if (session != null && session.isOpen()) {
session.sendMessage(message);
}
}
}
3)web.xml中配置spring和springMVC,因为借助与springMVC来拦截处理websocket请求,如果用其他MVC框架,请自行替换。不想换也可以用springMVC,侵入性很低,并且本来就可以多种MVC共存。
contextConfigLocation
/WEB-INF/classes/applicationContext.xml
SpringMVC
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/classes/applicationContext.xml
1
SpringMVC
*.do
注意web.xml的头部:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
4)spring的xml文件中对websocket的配置:
注意spring的xml的头部:
xmlns:websocket="http://www.springframework.org/schema/websocket"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
">
5)日志接收页面:
tail loghtml,body
{
height:100%;
width:100%;
}
$(document).ready(function() {
// 指定websocket路径
var websocket = new WebSocket('ws://localhost:8080/websocket/ws.do');
websocket.onmessage = function(event) {
// 接收服务端的实时日志并添加到HTML页面中
$("#log-container div").append(event.data + "
// 滚动条滚动到最低部
$("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
};
});