前言
这是在页面实时查看日志基础上追加了清空日志文件内容后,广播通知前端重新连接。
什么要清空?
由于java -jar 输出日志文件随着时间越来越大
为什么要通知重连?
清空日志文件之后,socket会读取不到日志内容需要断开重新连接。
一、springboot集成websocket
maven配置:
参考上一章springboot集成websocket 实时输出日志到浏览器(一)_sakyoka的博客-CSDN博客
websocket配置:
package com.sakyoka.test.webscoketlog;
import org.springframework.context.annotation.Bean;
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.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
*
* 描述:开启WebSocket、注册ServerEndpointExporter实例、开启STOMP协议来传输基于代理
* @author sakyoka
* @date 2022年8月14日 2022
*/
@Configuration
@EnableWebSocket
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//添加一个stomp协议的endpoint
registry.addEndpoint("/server").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//添加一个topic代理节点
registry.enableSimpleBroker("/topic");
}
}
这次在原来基础上开启了stomp socket代理,注册了一个server的endpoint和一个/topic代理节点
二、业务代码
控制层代码:
package com.sakyoka.test.webscoketlog;
import java.io.File;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.sakyoka.test.utils.FileUtils;
import lombok.extern.log4j.Log4j;
/**
*
* 描述:log控制层
* @author sakyoka
* @date 2022年8月14日 上午11:28:25
*/
@RequestMapping("/log")
@Controller
@Log4j
public class LogController {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
private int count = 0;
@GetMapping("/logconsole")
public ModelAndView logPage() {
return new ModelAndView("/logconsole/logconsole");
}
@GetMapping("/testlog")
@ResponseBody
public String testlog() {
String string = "测试数据:" + count;
log.info(string);
count++;
return string;
}
@GetMapping("/emptylog")
@ResponseBody
public String emptylog(@RequestParam(value = "jarId", required = false)String jarId) {
if (WebSocketLog.SYSTEM_FLAG.equals(jarId)) {
FileUtils.writeFile(new File("D:\\system.log"), "", false);
log.info("清空日志内容 jarId:" + jarId);
simpMessagingTemplate.convertAndSend("/topic/emptylog", jarId);
return "OK";
}else {
//暂时没有其它jar 测试
return "not found jarId:" + jarId;
}
}
}
这次添加了一个emptylog的主动清除日志内容的接口测试。
页面代码:
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>jarLog</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="renderer" content="webkit">
<jsp:include page="/WEB-INF/views/common/commonstatic.jsp" flush="true" />
<!-- socketjs插件 -->
<script type="text/javascript" src="${root}/components/socketjs/sockjs.js"></script>
<script type="text/javascript" src="${root}/components/socketjs/stomp.js"></script>
<!-- jquery菜单 -->
<link rel="stylesheet" type="text/css" href="${root}/components/jquery/jquery.contextMenu.css"/>
<script type="text/javascript" src="${root}/components/jquery/jquery.contextMenu.js"></script>
</head>
<body>
<div style="background-color:black;width:99%; height:500px; padding: 10px" id="console-parent">
<div id="console" style="width:99%; height:95%; color:white;overflow-y: auto; overflow-x:hidden;"></div>
</div>
</body>
<script type="text/javascript" src="${root}/js/console.js"></script>
<script type="text/javascript" src="${root}/js/console-websocket.js"></script>
<script type="text/javascript" src="${root}/js/console-websocket-stomp.js"></script>
<script type="text/javascript">
//var port = window.location.port;//如果经过代理?这个经过网关是网关的端口
//var port = "${pageContext.request.serverPort}";//这个才是后端端口
var jarWebSocket;
var jarConsole;
//jarId根据实际定义传过来,现在只有本系统的日志打印可以定义为system,其它jar的可以定义一个uuid关联标识
var jarId = "system";
//@ServerEndpoint("/log")
var wsurl = 'ws://'+ ip +':'+ port + root +'/log?jarId=' + jarId;
//registry.addEndpoint("/server").withSockJS();
var serverurl = 'http://'+ ip +':'+ port + root + '/server';
$(function(){
//添加console-parent内容变化,调整滚动条位置,自动滚动最下面
$("#console").bind("DOMNodeInserted",function(e){
var height = $(this).prop("scrollHeight");
$(this).animate({scrollTop: height}, 10);
});
registerConsoleLogSocket();
registerEmptyLogSocket();
addRightClickListener();
});
/**
* 注册控制台打印的socket事件
*/
function registerConsoleLogSocket(){
jarConsole = new JarConsole();
jarConsole.load('console');
jarWebSocket= new JarWebSocket({
url: wsurl,
//获取后台返回信息
onmessage: function(event){
jarConsole.fill(event.data);
}
}).addEventListener();
}
/**
* 注册清空日志的socket事件,采用消息订阅形式
*/
function registerEmptyLogSocket(){
new StompSocketDefine({
serverUrl: serverurl,
subscribes: [{
subscribeUrl: '/topic/emptylog',
onmessage: function(res){
//后端通知需要重新连接
if (res.body == jarId && jarWebSocket.isConnect()){
jarWebSocket.close();
jarWebSocket.reset().reconnect();
}
}
}]
}).connect();
}
/**
* 添加右键菜单
*/
function addRightClickListener(){
var items = {
"clear": {
name: "清空控制台信息",
callback: function(){
jarConsole.clear();
}},
'emptyLog': {
name: "清空日志文件内容",
callback: function(){
var url = root + '/log/emptylog';
$.get(url, {jarId: jarId}, function(res){
console.log("emptylog result >>> " + res);
});
}
}
};
//右键菜单
$.contextMenu({
selector: '#console',
events:{preShow: function(){}},
items: items
});
}
</script>
</html>
registerEmptyLogSocket注册/server,订阅/topic/emptylog方法,后端清空的消息发送到这个路由上,接到对应jar的控制台断开重新连接;addRightClickListener 右键菜单,为了方便测试,提供的菜单按钮
封装的根据SockJS和stomp 封装的socket配置对象:
/**
* add by sakyoka 2022-08-26
* stomp协议的scoket
*
*/
var StompSocketDefine = function(config){
/**配置数据对象 */
config = config || {};
/**server 地址*/
var serverUrl = config.serverUrl;
/**订阅信息集合*/
var subscribes = config.subscribes || [];
/**创建socket*/
var socket = new SockJS(serverUrl);
/**使用stomp协议*/
var stompClient = Stomp.over(socket);
/**是否自动重连 */
var autoConnect = config.autoConnect || true;
/**判断是否主动关闭*/
var driving = false;
/** 尝试重新连接次数*/
var defaultFailTryConnTimes = config.failTryConnTimes || 5;
/**尝试累加次数 */
var tryConnTimes = 0;
/**是否连接中 */
var isConnect = false;
var _this = this;
/**
* 恢复初始状态值
*/
this.reset = function(){
autoConnect = config.autoConnect || true;
driving = false;
defaultFailTryConnTimes = config.failTryConnTimes || 5;
tryConnTimes = 0;
isConnect = false;
}
/**
* 获取client
*/
this.getStompClient = function(){
return stompClient;
}
/**
* 断开连接
*/
this.disconnect = function(){
driving = true;
if (stompClient != undefined){
stompClient.disconnect();
}
return this;
}
/**
* 连接
*/
this.connect = function(){
/**
* 链接服务
*/
stompClient.connect({}, function(frame){
isConnect = true;
driving = false;
tryConnTimes = 0;
console.log("stomp socket link state: "+ frame);
/**
* 遍历订阅地址
*/
for (var i in subscribes){
var subscribeObject = subscribes[i];
var subscribeUrl = subscribeObject.subscribeUrl;
var onmessage = subscribeObject.onmessage;
stompClient.subscribe(subscribeUrl, function(res){
if (onmessage){
onmessage(res);
}
});
}
}, function(){
isConnect = false;
console.log('stomp socket link error');
if (isConnect === false
&& autoConnect === true
&& driving === false){
_this.tryToConnect();
}
});
return this;
}
/**
* 尝试连接
*/
this.tryToConnect = function(){
console.log('stomp socket try to connect...');
socket = new SockJS(serverUrl);
stompClient = Stomp.over(socket);
var _tryToConnect = function (){
tryConnTimes += 1;
if (defaultFailTryConnTimes < tryConnTimes){
console.log('stomp socket try to connect times is more than max times:' + tryConnTimes);
return ;
}
if (isConnect === true){
return ;
}
_this.connect();
setTimeout(function(){
_tryToConnect();
}, 3000);
}
_tryToConnect();
}
/**
* 刷新页面前把连接关掉
*/
window.onbeforeunload = function(){
driving = true;
if (stompClient != undefined){
stompClient.disconnect();
}
}
}
三 测试
首先打开两个窗口
然后右键点击清空日志内容
结果:
可以看到两个窗口都有收到消息来自jarId为system,然后业务端口socket重新连接日志输出。
这就是消息广播到两个控制台了。
总结
1、配代理时候一开始配置topic 收不到消息,后面改成/topic就收到了registry.enableSimpleBroker("/topic");,可能是因为我没有配置前缀原因,待测。
2、日志清空在linux不存在文件多进程占用问题,但在window时候会有多进程问题,进程占用导致操作文件失败。
业务拓展
其实这种主动请求要后端广播发消息是其中一个应用例子,另外还可以是后端定时任务,定时清空后发送消息通知前端,就是主动与被动获取消息。
附件
springboot+websocket实例:实时读取日志升级版(追加消息广播,清空日志通知重新连接)-Java文档类资源-CSDN下载