websocket
由于日志具有后端向前端推送的特性,需要使用websocket。
WebSocket API是下一代客户端-服务器的异步通信方法。
了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信。
本文使用的是spring cloud 框架,首先导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
后端
创建websocket 处理类
@Component//注册为组件
public class WebLogMysqlHandler extends WebSocketHandler{
@Autowired
private IAtpFileLogService filelogService;//导入日志处理的服务层
private final static Logger logger = LoggerFactory.getLogger(WebLogMysqlHandler.class);//slf4j控制台日志
@Override
protected void handleMessage(WebSocketSession session, TextMessage message) throws Exception {
if(message.getPayload()!=null) {//判断是否有数据
@SuppressWarnings("unchecked")
Map<String,String> map=JsonObjConverter.jsonConvertToMap(message.getPayload());//将json格式的数据转化为map形式,采用fastjson
if(map.containsKey("taskId")&&map.containsKey("time")) {
String taskId=map.get("taskId");
Date time = DateUtil.string2Date(map.get("time"), DateUtil.PATTERN_STANDARD19H);; //设置时间格式
time = DateUtil.dateAddMinutes(time, -5);
Integer num=Integer.valueOf(5);
boolean isEmpty=false;
Integer requestCount=0;
int state = 0;
session.sendMessage(getInfRuleTaskLog("开始执行>>>"));//向前端发送消息
while(session.isOpen()) {
List<AtpFileGenLogInfoDTO> logs=null;
try {
logs = filelogService.getXlsLog(taskId, time, num);//从mysql数据库获取日志
} catch (Exception e) {
logger.error("ATP任务执行异常",e);
session.sendMessage(getErrorTaskLog("ATP任务执行异常"));
closeSession(session);
e.printStackTrace();
}
if(logs!=null && logs.size()>0) {
isEmpty=false;
time=logs.get(logs.size()-1).getGenTime();
state = logs.get(logs.size()-1).getStatus();
for(AtpFileGenLogInfoDTO log:logs) {
Thread.sleep(250);
// System.out.println("log"+log.toString());
session.sendMessage(new TextMessage(JsonObjConverter.objConvertToJson(log)));
}
//closeSession(session);
//break;
}else if(!isEmpty){
Thread.sleep(4000);
isEmpty=true;
}else {
System.out.println(state + "---------------------");
// 4s没有接收到日志
if(3 == state) {
/* 任务结束 */
session.sendMessage(getFinishRuleTaskLog());
AtpConfigDataTaskManager.deletStatu(taskId);
closeSession(session);
break;
} else if(1 == state) {
/* 任务错误 */
session.sendMessage(getErrorTaskLog());
AtpConfigDataTaskManager.deletStatu(taskId);
closeSession(session);
break;
} else if(0 == state) {
Thread.sleep(4000);
}
}
if(requestCount++ > 300) {//日志任务限时
session.sendMessage(getErrorTaskLog("ATP导表任务执行失败:任务执行时间过长"));
closeSession(session);
}
}
}else {
if(session!=null) {
session.close();
}
}
}
}
/* 封装提示日志 */
private TextMessage getInfRuleTaskLog(String msg) {
AtpFileGenLogInfoDTO log=new AtpFileGenLogInfoDTO();
log.setStatus(0);
log.setComment(msg);
log.setGenTime(new Date());
return new TextMessage(JsonObjConverter.objConvertToJson(log));//向前端发送的日志是json格式
}
重写了
/**
* 收到消息时触发的回调
*/
void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
配置websocket,采用注解的方式
@Configuration
@EnableWebSocket
public class LogMysqlWebSocketConfig implements WebSocketConfigurer{
@Autowired
private WebLogMysqlHandler webMysqlLogHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(webMysqlLogHandler, "/ATP/xlsLog").setAllowedOrigins("*").withSockJS();
}
}
执行任务过程中向mysql存储日志就行
filelogUtil.insertLog(taskId,projectId,"V","","开始导入表",3,0);
前端
采用sockJS,具体使用可以百度。
var sock = new SockJS(logUrl); //使用sockjs与websocket通信 logUrl为"服务器地址"+"/ATP/xlsLog"
sock.onopen = function(){
console.log("opening");
var data="{\"taskId\":\""+taskId+"\",\"time\":\""+time+"\"}";
sock.send(data);//发送任务id,请求任务相应的日志
// 开始进度条
timeTask = window.setInterval("changeProgress()",500);
};
sock.onmessage = function(e){
//console.log("Receive message: " , e.data);
// 删除加载效果
$(".overlay").remove();
var resultJson = eval("(" + e.data + ")");
var level= resultJson.level;
var msg= resultJson.message;
var status = resultJson.status;
var time= datetimeFormat_1(resultJson.time);
//console.info(resultJson);
if(status == 0){
/* 任务运行 */
} else if(status == 1){
/* 任务错误 */
notifyError();
} else if(status == 2){
/* 任务成功 */
notifySuccess(configProjectId);
}
addLogInf(level,time,msg);
$("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
};
sock.onclose = function(){
console.log("Closing");
//日志打印完成激活下载按钮
$("#btnDownload").removeClass("disabled");
};
function notifySuccess(configProjectId){
var content="<span onclick=openDetail(\""+configProjectId+"\")>任务执行成功,点击查看工程数据</span>";
Lobibox.notify('success', {
width: 370,
closable: true,
closeOnClick:true,
delay: false,
sound: false,
size: 'mini',
rounded: true,
position: 'center top',
msg: content
/* onClick: function(){
var url = "/zcconfig/edit?id="+ zcConfigProjectId;
openView($(".content-wrapper"), url);
} */
});
clearTimeTask();
}
综上,采用websocket和sockJS结合完成日志的即时通讯。