代码提交
This commit is contained in:
@@ -27,14 +27,6 @@ somkit:
|
||||
windows: D://RocksDB//fmt
|
||||
linux: /usr/local/rocksdb/fmt
|
||||
column-family: default
|
||||
logging:
|
||||
socket:
|
||||
#日志文件地址
|
||||
log-file-path: ./logs/fmt-server.log
|
||||
#最大读取展示行数
|
||||
max-read-length: 500
|
||||
#读取间隔时间 毫秒
|
||||
read-interval: 1000
|
||||
|
||||
logging:
|
||||
config: classpath:logback-spring.xml
|
||||
@@ -3,15 +3,13 @@
|
||||
<!-- Spring Boot 默认日志配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<!-- 定义日志的根目录 -->
|
||||
<property name="LOG_HOME" value="./logs"/>
|
||||
<!-- 定义日志文件名称 -->
|
||||
<property name="APP_NAME" value="fmt-server"/>
|
||||
|
||||
<!-- 日志输出到控制台 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
<filter class="cn.somkit.fmt.filter.LogStashFilter">
|
||||
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
@@ -19,43 +17,8 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!-- 指定日志文件的名称 -->
|
||||
<file>${LOG_HOME}/${APP_NAME}.log</file>
|
||||
<!--
|
||||
当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
|
||||
TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
|
||||
-->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--
|
||||
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
|
||||
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
|
||||
-->
|
||||
<fileNamePattern>${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
|
||||
<!--
|
||||
可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
|
||||
且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
|
||||
那些为了归档而创建的目录也会被删除。
|
||||
-->
|
||||
<MaxHistory>365</MaxHistory>
|
||||
<!--
|
||||
当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
|
||||
-->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<!-- 日志输出格式: -->
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] [%logger{50}] -> %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<!-- 指定日志输出级别,以及启动的Appender -->
|
||||
<root level="INFO">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
444
src/main/resources/static/common/js/LogMonitorAdaptive.js
Normal file
444
src/main/resources/static/common/js/LogMonitorAdaptive.js
Normal file
@@ -0,0 +1,444 @@
|
||||
class LogMonitorAdaptive {
|
||||
constructor(container, opts = {}) {
|
||||
this.container = typeof container === 'string'
|
||||
? document.querySelector(container)
|
||||
: container;
|
||||
if (!this.container) throw new Error('容器未找到');
|
||||
|
||||
/* ========== 配置项及其含义 ========== */
|
||||
this.cfg = {
|
||||
/* --- 尺寸相关 --- */
|
||||
width: null, // 日志容器宽度。null=100%,可填数字(px)或字符串('80%'/'20rem')
|
||||
height: null, // 日志容器高度。null=100vh,可填数字(px)或字符串('400px'/'50vh')
|
||||
|
||||
/* --- 日志条数与滚动 --- */
|
||||
maxLines: 5000, // 最大保留日志条数,超出后自动删除最旧日志
|
||||
autoScroll: true, // 出现新日志时是否自动滚动到底部
|
||||
|
||||
/* --- 主题与外观 --- */
|
||||
theme: 'dark', // 主题:'dark' | 'light',控制整体配色
|
||||
fontSize: 14, // 日志文字大小,单位 px
|
||||
wordWrap: false, // 日志内容是否自动换行(true=换行,false=横向滚动)
|
||||
|
||||
/* --- 日志内容格式 --- */
|
||||
showTimestamp: true, // 是否显示时间戳
|
||||
showLevel: true, // 是否显示日志级别标签
|
||||
timeFormat: 'HH:mm:ss.SSS', // 时间戳格式,可自定义
|
||||
|
||||
/* --- 日志级别 & 过滤 --- */
|
||||
levels: ['debug', 'info', 'warn', 'error', 'success', 'system'], // 可用日志级别
|
||||
// 过滤功能基于 levels;如只想显示 info/warn,可在这里删减
|
||||
|
||||
/* --- 功能开关 --- */
|
||||
enableFilter: true, // 顶部是否显示级别过滤复选框
|
||||
enableSearch: true, // 是否启用搜索框与高亮功能
|
||||
enableExport: true, // 是否允许导出日志文件
|
||||
enableClear: true, // 是否提供“清空”按钮
|
||||
enablePause: true, // 是否提供“暂停/继续”按钮
|
||||
enableThemeToggle: true, // 是否提供“切换主题”按钮
|
||||
enableFullscreen: true, // 是否提供“全屏”按钮
|
||||
enableFontSize: true, // 是否提供“字体大小 +/-”按钮
|
||||
enableWordWrap: true, // 是否提供“换行/不换行”切换按钮
|
||||
|
||||
...opts
|
||||
};
|
||||
|
||||
/* 强制确保 levels 是数组,防止 forEach 报错 */
|
||||
if (!Array.isArray(this.cfg.levels)) {
|
||||
this.cfg.levels = ['info', 'warn', 'error'];
|
||||
}
|
||||
|
||||
this.isPaused = false;
|
||||
this.isFullscreen = false;
|
||||
this.logs = [];
|
||||
this.filters = new Set();
|
||||
this.searchTerm = '';
|
||||
this.highlightIndex = -1;
|
||||
this.highlightEls = [];
|
||||
|
||||
this.initDOM();
|
||||
this.bindResize();
|
||||
this.bindGlobalEvents();
|
||||
}
|
||||
|
||||
/* ------------------------ 初始化 ------------------------ */
|
||||
initDOM() {
|
||||
this.container.innerHTML = '';
|
||||
this.setContainerSize(); // 宽高自适应
|
||||
this.applyContainerStyle();
|
||||
|
||||
// 顶部导航栏
|
||||
this.navbar = document.createElement('div');
|
||||
Object.assign(this.navbar.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '8px 12px',
|
||||
backgroundColor: this.cfg.theme === 'dark' ? '#2d2d30' : '#f3f3f3',
|
||||
borderBottom: `1px solid ${this.cfg.theme === 'dark' ? '#555' : '#ccc'}`,
|
||||
gap: '10px',
|
||||
fontSize: '14px',
|
||||
flexWrap: 'wrap'
|
||||
});
|
||||
|
||||
// 按钮组
|
||||
if (this.cfg.enablePause) {
|
||||
this.pauseBtn = this.createBtn('⏸ 暂停', () => this.togglePause(), {w: '72px'});
|
||||
this.navbar.appendChild(this.pauseBtn);
|
||||
}
|
||||
if (this.cfg.enableClear) {
|
||||
this.navbar.appendChild(this.createBtn('🗑 清空', () => this.clear(), {w: '72px'}));
|
||||
}
|
||||
if (this.cfg.enableExport) {
|
||||
this.navbar.appendChild(this.createBtn('💾 导出', () => this.export(), {w: '72px'}));
|
||||
}
|
||||
|
||||
// 主题切换按钮
|
||||
if (this.cfg.enableThemeToggle) {
|
||||
this.themeBtn = this.createBtn(
|
||||
this.cfg.theme === 'dark' ? '☀️ 明亮' : '🌙 暗黑',
|
||||
() => this.toggleTheme(),
|
||||
{w: '80px'}
|
||||
);
|
||||
this.navbar.appendChild(this.themeBtn);
|
||||
}
|
||||
|
||||
// 字体大小
|
||||
if (this.cfg.enableFontSize) {
|
||||
this.navbar.appendChild(this.createBtn('A-', () => this.changeFontSize(-1), {w: '36px'}));
|
||||
this.navbar.appendChild(this.createBtn('A+', () => this.changeFontSize(1), {w: '36px'}));
|
||||
}
|
||||
|
||||
// 换行
|
||||
if (this.cfg.enableWordWrap) {
|
||||
this.wrapBtn = this.createBtn(
|
||||
this.cfg.wordWrap ? '⏎ 换行' : '⏎ 不换行',
|
||||
() => this.toggleWordWrap(),
|
||||
{w: '90px'}
|
||||
);
|
||||
this.navbar.appendChild(this.wrapBtn);
|
||||
}
|
||||
|
||||
// 全屏
|
||||
if (this.cfg.enableFullscreen) {
|
||||
this.navbar.appendChild(this.createBtn('⛶ 全屏', () => this.toggleFullscreen(), {w: '72px'}));
|
||||
}
|
||||
|
||||
// 过滤复选框
|
||||
if (this.cfg.enableFilter) {
|
||||
this.cfg.levels.forEach(lvl => {
|
||||
this.navbar.appendChild(this.createCheckbox(lvl));
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索框
|
||||
if (this.cfg.enableSearch) {
|
||||
const searchWrap = document.createElement('span');
|
||||
searchWrap.style.marginLeft = 'auto';
|
||||
searchWrap.style.display = 'flex';
|
||||
searchWrap.style.alignItems = 'center';
|
||||
searchWrap.style.gap = '4px';
|
||||
|
||||
this.searchInput = document.createElement('input');
|
||||
Object.assign(this.searchInput.style, {
|
||||
padding: '6px 8px',
|
||||
fontSize: '14px',
|
||||
border: `1px solid ${this.cfg.theme === 'dark' ? '#555' : '#bbb'}`,
|
||||
borderRadius: '4px',
|
||||
backgroundColor: this.cfg.theme === 'dark' ? '#3c3c3c' : '#fff',
|
||||
color: 'inherit',
|
||||
width: '260px'
|
||||
});
|
||||
this.searchInput.placeholder = '搜索…';
|
||||
this.searchInput.oninput = () => {
|
||||
this.searchTerm = this.searchInput.value.trim();
|
||||
this.renderVisible();
|
||||
};
|
||||
searchWrap.appendChild(this.searchInput);
|
||||
|
||||
['↑', '↓'].forEach((arr, idx) => {
|
||||
searchWrap.appendChild(
|
||||
this.createBtn(arr, () => this.navigateHighlight(idx), {w: '30px'})
|
||||
);
|
||||
});
|
||||
this.navbar.appendChild(searchWrap);
|
||||
}
|
||||
|
||||
// 日志视窗
|
||||
this.viewport = document.createElement('div');
|
||||
this.applyViewportStyle();
|
||||
this.container.appendChild(this.navbar);
|
||||
this.container.appendChild(this.viewport);
|
||||
|
||||
this.renderVisible();
|
||||
}
|
||||
|
||||
/* ------------------------ 样式 ------------------------ */
|
||||
setContainerSize() {
|
||||
// 如果用户未指定宽高,则自适应
|
||||
if (this.cfg.width === null) {
|
||||
this.container.style.width = '100%';
|
||||
} else {
|
||||
this.container.style.width = typeof this.cfg.width === 'number' ? `${this.cfg.width}px` : this.cfg.width;
|
||||
}
|
||||
|
||||
if (this.cfg.height === null) {
|
||||
//this.container.style.height = '100vh';
|
||||
const bodyMargin = parseFloat(getComputedStyle(document.body).marginTop || 0) +
|
||||
parseFloat(getComputedStyle(document.body).marginBottom || 0);
|
||||
this.container.style.height = `calc(100vh - ${bodyMargin}px - 2px)`;
|
||||
} else {
|
||||
this.container.style.height = typeof this.cfg.height === 'number' ? `${this.cfg.height}px` : this.cfg.height;
|
||||
}
|
||||
}
|
||||
|
||||
applyContainerStyle() {
|
||||
Object.assign(this.container.style, {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: `1px solid ${this.cfg.theme === 'dark' ? '#444' : '#ccc'}`,
|
||||
borderRadius: '6px',
|
||||
fontFamily: 'Consolas, "Courier New", monospace',
|
||||
fontSize: `${this.cfg.fontSize}px`,
|
||||
lineHeight: 1.45,
|
||||
backgroundColor: this.cfg.theme === 'dark' ? '#1e1e1e' : '#fafafa',
|
||||
color: this.cfg.theme === 'dark' ? '#d4d4d4' : '#222',
|
||||
overflow: 'hidden'
|
||||
});
|
||||
}
|
||||
|
||||
applyViewportStyle() {
|
||||
Object.assign(this.viewport.style, {
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
padding: '8px',
|
||||
whiteSpace: this.cfg.wordWrap ? 'pre-wrap' : 'pre',
|
||||
wordBreak: 'break-word'
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------ 工具 ------------------------ */
|
||||
createBtn(text, handler, {w} = {}) {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = text;
|
||||
Object.assign(btn.style, {
|
||||
padding: '6px 10px',
|
||||
fontSize: '14px',
|
||||
backgroundColor: this.cfg.theme === 'dark' ? '#444' : '#e7e7e7',
|
||||
color: 'inherit',
|
||||
border: `1px solid ${this.cfg.theme === 'dark' ? '#666' : '#ccc'}`,
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
minWidth: w || 'auto'
|
||||
});
|
||||
btn.onmouseenter = () => btn.style.backgroundColor = this.cfg.theme === 'dark' ? '#555' : '#d0d0d0';
|
||||
btn.onmouseleave = () => btn.style.backgroundColor = this.cfg.theme === 'dark' ? '#444' : '#e7e7e7';
|
||||
btn.onclick = handler;
|
||||
return btn;
|
||||
}
|
||||
|
||||
createCheckbox(level) {
|
||||
const lbl = document.createElement('label');
|
||||
lbl.style.color = this.cfg.theme === 'dark' ? '#ccc' : '#333';
|
||||
lbl.style.fontSize = '13px';
|
||||
lbl.style.display = 'flex';
|
||||
lbl.style.alignItems = 'center';
|
||||
|
||||
const chk = document.createElement('input');
|
||||
chk.type = 'checkbox';
|
||||
chk.checked = true;
|
||||
chk.style.margin = '0 4px';
|
||||
chk.onchange = () => {
|
||||
if (chk.checked) this.filters.delete(level);
|
||||
else this.filters.add(level);
|
||||
this.renderVisible();
|
||||
};
|
||||
lbl.appendChild(chk);
|
||||
lbl.append(level.toUpperCase());
|
||||
return lbl;
|
||||
}
|
||||
|
||||
/* ------------------------ 日志渲染 ------------------------ */
|
||||
log(message, level = 'info') {
|
||||
if (!this.cfg.levels.includes(level)) level = 'info';
|
||||
if (this.isPaused) return;
|
||||
|
||||
const ts = this.cfg.showTimestamp ? this.formatTime(new Date()) : '';
|
||||
this.logs.push({message, level, ts, id: Date.now() + Math.random()});
|
||||
|
||||
if (this.logs.length > this.cfg.maxLines) this.logs.shift();
|
||||
this.renderVisible();
|
||||
}
|
||||
|
||||
renderVisible() {
|
||||
this.viewport.innerHTML = '';
|
||||
this.highlightEls = [];
|
||||
this.highlightIndex = -1;
|
||||
|
||||
const filtered = this.logs.filter(l => !this.filters.has(l.level));
|
||||
const frag = document.createDocumentFragment();
|
||||
const regex = this.searchTerm ? new RegExp(`(${this.searchTerm})`, 'gi') : null;
|
||||
|
||||
filtered.forEach(entry => {
|
||||
const line = document.createElement('div');
|
||||
line.style.display = 'flex';
|
||||
line.style.margin = '1px 0';
|
||||
|
||||
if (this.cfg.showTimestamp && entry.ts) {
|
||||
const tsSpan = document.createElement('span');
|
||||
tsSpan.textContent = entry.ts + ' ';
|
||||
tsSpan.style.color = this.cfg.theme === 'dark' ? '#6a9955' : '#008000';
|
||||
tsSpan.style.minWidth = '90px';
|
||||
line.appendChild(tsSpan);
|
||||
}
|
||||
|
||||
if (this.cfg.showLevel) {
|
||||
const lvlSpan = document.createElement('span');
|
||||
lvlSpan.textContent = `[${entry.level.toUpperCase()}]`;
|
||||
const colors = {
|
||||
debug: '#9c27b0', info: '#2196f3', warn: '#ff9800',
|
||||
error: '#f44336', success: '#4caf50', system: '#00bcd4'
|
||||
};
|
||||
lvlSpan.style.color = colors[entry.level] || colors.info;
|
||||
lvlSpan.style.minWidth = '70px';
|
||||
lvlSpan.style.fontWeight = 'bold';
|
||||
line.appendChild(lvlSpan);
|
||||
}
|
||||
|
||||
const msgSpan = document.createElement('span');
|
||||
msgSpan.style.flex = 1;
|
||||
let msg = entry.message;
|
||||
if (regex) {
|
||||
if (regex.test(entry.message)) {
|
||||
line.style.backgroundColor = 'rgba(255,235,59,0.25)';
|
||||
this.highlightEls.push(line);
|
||||
}
|
||||
msg = entry.message.replace(regex, '<mark style="background:#ffeb3b;color:#000;">$1</mark>');
|
||||
}
|
||||
msgSpan.innerHTML = msg;
|
||||
line.appendChild(msgSpan);
|
||||
frag.appendChild(line);
|
||||
});
|
||||
|
||||
this.viewport.appendChild(frag);
|
||||
if (this.cfg.autoScroll && !this.isPaused) {
|
||||
this.viewport.scrollTop = this.viewport.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------ 交互 ------------------------ */
|
||||
togglePause() {
|
||||
this.isPaused = !this.isPaused;
|
||||
this.pauseBtn.textContent = this.isPaused ? '▶ 继续' : '⏸ 暂停';
|
||||
this.pauseBtn.style.backgroundColor = this.isPaused
|
||||
? (this.cfg.theme === 'dark' ? '#d32f2f' : '#ff5252')
|
||||
: (this.cfg.theme === 'dark' ? '#444' : '#e7e7e7');
|
||||
}
|
||||
|
||||
toggleTheme() {
|
||||
this.cfg.theme = this.cfg.theme === 'dark' ? 'light' : 'dark';
|
||||
this.themeBtn.textContent = this.cfg.theme === 'dark' ? '☀️ 明亮' : '🌙 暗黑';
|
||||
this.applyContainerStyle();
|
||||
this.navbar.style.backgroundColor = this.cfg.theme === 'dark' ? '#2d2d30' : '#f3f3f3';
|
||||
this.renderVisible();
|
||||
}
|
||||
|
||||
changeFontSize(delta) {
|
||||
this.cfg.fontSize = Math.max(10, Math.min(24, this.cfg.fontSize + delta));
|
||||
this.container.style.fontSize = `${this.cfg.fontSize}px`;
|
||||
}
|
||||
|
||||
toggleWordWrap() {
|
||||
this.cfg.wordWrap = !this.cfg.wordWrap;
|
||||
this.wrapBtn.textContent = this.cfg.wordWrap ? '⏎ 不换行' : '⏎ 换行';
|
||||
this.applyViewportStyle();
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
if (!this.isFullscreen) {
|
||||
if (this.container.requestFullscreen) this.container.requestFullscreen();
|
||||
else if (this.container.webkitRequestFullscreen) this.container.webkitRequestFullscreen();
|
||||
else if (this.container.msRequestFullscreen) this.container.msRequestFullscreen();
|
||||
} else {
|
||||
if (document.exitFullscreen) document.exitFullscreen();
|
||||
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
|
||||
else if (document.msExitFullscreen) document.msExitFullscreen();
|
||||
}
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
}
|
||||
|
||||
navigateHighlight(dir) {
|
||||
if (!this.highlightEls.length) return;
|
||||
this.highlightIndex = (this.highlightIndex + (dir === 0 ? -1 : 1) + this.highlightEls.length) % this.highlightEls.length;
|
||||
this.highlightEls.forEach((el, idx) => {
|
||||
el.style.outline = idx === this.highlightIndex ? '2px solid #ffeb3b' : 'none';
|
||||
});
|
||||
this.highlightEls[this.highlightIndex].scrollIntoView({block: 'nearest', behavior: 'smooth'});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.logs = [];
|
||||
this.renderVisible();
|
||||
}
|
||||
|
||||
export() {
|
||||
const lines = this.logs.map(l => `${l.ts} [${l.level.toUpperCase()}] ${l.message}`);
|
||||
const blob = new Blob([lines.join('\n')], {type: 'text/plain;charset=utf-8'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `log_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
formatTime(date) {
|
||||
const t = date.toTimeString().slice(0, 8);
|
||||
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
||||
return `${t}.${ms}`;
|
||||
}
|
||||
|
||||
bindResize() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.setContainerSize(); // 响应窗口变化
|
||||
});
|
||||
}
|
||||
|
||||
bindGlobalEvents() {
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && this.searchInput) {
|
||||
this.searchInput.value = '';
|
||||
this.searchTerm = '';
|
||||
this.renderVisible();
|
||||
}
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
this.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* 快捷方法 */
|
||||
info(msg) {
|
||||
this.log(msg, 'info');
|
||||
}
|
||||
|
||||
warn(msg) {
|
||||
this.log(msg, 'warn');
|
||||
}
|
||||
|
||||
error(msg) {
|
||||
this.log(msg, 'error');
|
||||
}
|
||||
|
||||
success(msg) {
|
||||
this.log(msg, 'success');
|
||||
}
|
||||
|
||||
debug(msg) {
|
||||
this.log(msg, 'debug');
|
||||
}
|
||||
|
||||
system(msg) {
|
||||
this.log(msg, 'system');
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/**
|
||||
* WebSocket封装工具类
|
||||
* <pre>
|
||||
* 代码示例:
|
||||
* let FmtSocket = new FmtSocket(
|
||||
* {url:'websocket服务器地址',id:'唯一标识',timeout:心跳检测间隔时间(毫秒)}
|
||||
* );
|
||||
* //调用初始化方法
|
||||
* FmtSocket.init((data) => {
|
||||
* //初始化完成之后的回调函数 ata为监听到的数据
|
||||
* //可以在这里做接收到消息后的逻辑处理
|
||||
* console.log(data);
|
||||
* });
|
||||
* //调用发送消息方法 message 消息字符串或对象
|
||||
* FmtSocket.sendMsg(message, () => {
|
||||
* //发送消息成功之后的回调函数
|
||||
* });
|
||||
* </pre>
|
||||
*/
|
||||
class FmtSocket {
|
||||
id;
|
||||
#options;
|
||||
#ws;
|
||||
#entity;
|
||||
#deepProxyObj;
|
||||
#proxyMsg;
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#options = this.#extend({
|
||||
url: 'http://127.0.0.1/socket/ws',//后台websocket服务地址
|
||||
id: this.#uuid(),//如果没传此参数,创建一个随机的UUID传给后台服务作为标识
|
||||
timeout: 30000 //心跳检测间隔时间 默认30秒
|
||||
}, options);
|
||||
this.id = this.#options.id;
|
||||
this.#ws = null;
|
||||
//send:发送类型 HeartBeat:心跳 Logging:消息
|
||||
this.#entity = {send: null, data: null};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化websocket
|
||||
* @param {Function} callback (type, data) 回调函数
|
||||
*/
|
||||
init(callback){
|
||||
if(callback && typeof callback === 'function'){
|
||||
this.#proxyMsg = [];
|
||||
this.#deepProxyObj = this.#deepProxy(this.#proxyMsg,callback);
|
||||
}
|
||||
this.#createWebSocket();
|
||||
}
|
||||
/**
|
||||
* 对象、数组变化监听(增删改)
|
||||
* @param {Object} obj
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
#deepProxy(obj, callback){
|
||||
if (typeof obj === 'object') {
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] === 'object') {
|
||||
obj[key] = this.#deepProxy(obj[key], callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Proxy(obj, {
|
||||
/**
|
||||
* @param {Object, Array} target 设置值的对象
|
||||
* @param {String} key 属性
|
||||
* @param {any} value 值
|
||||
* @param {Object} receiver this
|
||||
*/
|
||||
set: (target, key, value, receiver)=> {
|
||||
if (typeof value === 'object') {
|
||||
value = this.#deepProxy(value, callback);
|
||||
}
|
||||
let cbType = target[key] === undefined ? 'create' : 'modify';
|
||||
//排除数组修改length回调
|
||||
if (!(Array.isArray(target) && key === 'length')) {
|
||||
if (cbType === 'create') {
|
||||
callback(value);
|
||||
}
|
||||
}
|
||||
return Reflect.set(target, key, value, receiver);
|
||||
},
|
||||
deleteProperty(target, key) {
|
||||
return Reflect.deleteProperty(target, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
#createWebSocket(){
|
||||
try {
|
||||
if ('WebSocket' in window) {
|
||||
this.#ws = new WebSocket((this.#options.url + "?id=" + this.#options.id)
|
||||
.replace("http", "ws")
|
||||
.replace("https", "wss"));
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket通讯');
|
||||
return false;
|
||||
}
|
||||
this.#onopen();
|
||||
this.#onmessage();
|
||||
this.#onerror();
|
||||
this.#onclose();
|
||||
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
|
||||
window.onbeforeunload = ()=> {
|
||||
this.#ws.close();
|
||||
};
|
||||
} catch (e) {
|
||||
console.log("WebSocket:链接失败", e);
|
||||
alert('WebSocket:链接失败');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 关闭websocket链接
|
||||
* @param {Function} callback
|
||||
*/
|
||||
closeWebSocket(callback){
|
||||
if(this.#ws){
|
||||
this.#ws.close();
|
||||
}
|
||||
if(callback && typeof callback === 'function'){
|
||||
callback();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 发送消息
|
||||
* @param {String, Object} message
|
||||
* @param {Function} callback
|
||||
*/
|
||||
sendMsg(message, callback){
|
||||
if(!message || typeof message !== 'string' || typeof message !== 'object'){
|
||||
alert("FmtSocket sendMsg message is not null or message is not a string or object");
|
||||
return false;
|
||||
}
|
||||
if(callback && typeof callback !== 'function'){
|
||||
alert("FmtSocket sendMsg callback is not a function");
|
||||
return false;
|
||||
}
|
||||
this.#emptyObjectPropertyValues(this.#entity);
|
||||
this.#entity.send = 'Message';
|
||||
this.#entity.data = message;
|
||||
if (this.#ws.readyState === this.#ws.CONNECTING) {
|
||||
// 正在开启状态,等待1s后重新调用
|
||||
let socket = this;
|
||||
setTimeout(function () {
|
||||
socket.#ws.send(JSON.stringify(socket.#entity));
|
||||
callback();
|
||||
}, 1000)
|
||||
}else{
|
||||
this.#ws.send(JSON.stringify(this.#entity));
|
||||
callback();
|
||||
}
|
||||
}
|
||||
#onopen(){
|
||||
this.#ws.onopen = (event) => {
|
||||
console.log("WebSocket:链接开启");
|
||||
}
|
||||
}
|
||||
#onmessage(){
|
||||
this.#ws.onmessage = (event) => {
|
||||
this.#emptyObjectPropertyValues(this.#entity);
|
||||
this.#entity = JSON.parse(event.data);
|
||||
//心跳检测
|
||||
if(this.#entity.send === 'HeartBeat'){
|
||||
this.#heartBeat();
|
||||
}
|
||||
if(this.#deepProxyObj){
|
||||
this.#deepProxyObj.splice(0);//赋值前先清空,保证消息数组里只有一条最新的数据
|
||||
let data = JSON.parse(JSON.stringify(this.#entity));
|
||||
this.#deepProxyObj.push(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
#onerror(){
|
||||
this.#ws.onerror = (event) => {
|
||||
console.log("WebSocket:发生错误", event);
|
||||
}
|
||||
}
|
||||
#onclose(){
|
||||
this.#ws.onclose = (event) => {
|
||||
console.log("WebSocket:链接关闭");
|
||||
}
|
||||
}
|
||||
#heartBeat(){
|
||||
setTimeout(()=> {
|
||||
this.#emptyObjectPropertyValues(this.#entity);
|
||||
if (this.#ws.readyState === this.#ws.CLOSING
|
||||
|| this.#ws.readyState === this.#ws.CLOSED){
|
||||
alert("WebSocket通讯服务链接已关闭");
|
||||
return false;
|
||||
}
|
||||
this.#entity.send = 'HeartBeat';
|
||||
this.#entity.data = '心跳检测';
|
||||
if (this.#ws.readyState === this.#ws.CONNECTING) {
|
||||
// 正在开启状态,等待1s后重新调用
|
||||
setTimeout(()=> {
|
||||
this.#ws.send(JSON.stringify(this.#entity));
|
||||
}, 1000)
|
||||
}else{
|
||||
this.#ws.send(JSON.stringify(this.#entity));
|
||||
}
|
||||
},this.#options.timeout);
|
||||
}
|
||||
/**
|
||||
* 扩展,source相同属性的值去覆盖target, target如果没有这个属性就新增
|
||||
* @param {Object} target
|
||||
* @param {Object} source
|
||||
* @returns {Object}
|
||||
*/
|
||||
#extend(target, source){
|
||||
if(typeof target !== 'object') return null;
|
||||
if(typeof source !== 'object') return null;
|
||||
for (const key in source) {
|
||||
// 使用for in会遍历数组所有的可枚举属性,包括原型。
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
#uuid(len, radix){
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||
let uuid = [], i;
|
||||
radix = radix || chars.length;
|
||||
if (len) {
|
||||
// Compact form
|
||||
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
|
||||
} else {
|
||||
// rfc4122, version 4 form
|
||||
let r;
|
||||
// rfc4122 requires these characters
|
||||
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
||||
uuid[14] = '4';
|
||||
// Fill in random data. At i==19 set the high bits of clock sequence as
|
||||
// per rfc4122, sec. 4.1.5
|
||||
for (i = 0; i < 36; i++) {
|
||||
if (!uuid[i]) {
|
||||
r = 0 | Math.random() * 16;
|
||||
uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuid.join('');
|
||||
}
|
||||
#emptyObjectPropertyValues(person){
|
||||
Object.keys(person).forEach(key => (person[key] = null));
|
||||
}
|
||||
}
|
||||
5232
src/main/resources/static/common/js/sockjs.js
Normal file
5232
src/main/resources/static/common/js/sockjs.js
Normal file
File diff suppressed because it is too large
Load Diff
488
src/main/resources/static/common/js/stomp.js
Normal file
488
src/main/resources/static/common/js/stomp.js
Normal file
@@ -0,0 +1,488 @@
|
||||
// Generated by CoffeeScript 1.7.1
|
||||
|
||||
/*
|
||||
Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0
|
||||
|
||||
Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/)
|
||||
Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com)
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var Byte, Client, Frame, Stomp,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
__slice = [].slice;
|
||||
|
||||
Byte = {
|
||||
LF: '\x0A',
|
||||
NULL: '\x00'
|
||||
};
|
||||
|
||||
Frame = (function() {
|
||||
var unmarshallSingle;
|
||||
|
||||
function Frame(command, headers, body) {
|
||||
this.command = command;
|
||||
this.headers = headers != null ? headers : {};
|
||||
this.body = body != null ? body : '';
|
||||
}
|
||||
|
||||
Frame.prototype.toString = function() {
|
||||
var lines, name, skipContentLength, value, _ref;
|
||||
lines = [this.command];
|
||||
skipContentLength = this.headers['content-length'] === false ? true : false;
|
||||
if (skipContentLength) {
|
||||
delete this.headers['content-length'];
|
||||
}
|
||||
_ref = this.headers;
|
||||
for (name in _ref) {
|
||||
if (!__hasProp.call(_ref, name)) continue;
|
||||
value = _ref[name];
|
||||
lines.push("" + name + ":" + value);
|
||||
}
|
||||
if (this.body && !skipContentLength) {
|
||||
lines.push("content-length:" + (Frame.sizeOfUTF8(this.body)));
|
||||
}
|
||||
lines.push(Byte.LF + this.body);
|
||||
return lines.join(Byte.LF);
|
||||
};
|
||||
|
||||
Frame.sizeOfUTF8 = function(s) {
|
||||
if (s) {
|
||||
return encodeURI(s).match(/%..|./g).length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
unmarshallSingle = function(data) {
|
||||
var body, chr, command, divider, headerLines, headers, i, idx, len, line, start, trim, _i, _j, _len, _ref, _ref1;
|
||||
divider = data.search(RegExp("" + Byte.LF + Byte.LF));
|
||||
headerLines = data.substring(0, divider).split(Byte.LF);
|
||||
command = headerLines.shift();
|
||||
headers = {};
|
||||
trim = function(str) {
|
||||
return str.replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
_ref = headerLines.reverse();
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
line = _ref[_i];
|
||||
idx = line.indexOf(':');
|
||||
headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
|
||||
}
|
||||
body = '';
|
||||
start = divider + 2;
|
||||
if (headers['content-length']) {
|
||||
len = parseInt(headers['content-length']);
|
||||
body = ('' + data).substring(start, start + len);
|
||||
} else {
|
||||
chr = null;
|
||||
for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) {
|
||||
chr = data.charAt(i);
|
||||
if (chr === Byte.NULL) {
|
||||
break;
|
||||
}
|
||||
body += chr;
|
||||
}
|
||||
}
|
||||
return new Frame(command, headers, body);
|
||||
};
|
||||
|
||||
Frame.unmarshall = function(datas) {
|
||||
var data;
|
||||
return (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*"));
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
data = _ref[_i];
|
||||
if ((data != null ? data.length : void 0) > 0) {
|
||||
_results.push(unmarshallSingle(data));
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
};
|
||||
|
||||
Frame.marshall = function(command, headers, body) {
|
||||
var frame;
|
||||
frame = new Frame(command, headers, body);
|
||||
return frame.toString() + Byte.NULL;
|
||||
};
|
||||
|
||||
return Frame;
|
||||
|
||||
})();
|
||||
|
||||
Client = (function() {
|
||||
var now;
|
||||
|
||||
function Client(ws) {
|
||||
this.ws = ws;
|
||||
this.ws.binaryType = "arraybuffer";
|
||||
this.counter = 0;
|
||||
this.connected = false;
|
||||
this.heartbeat = {
|
||||
outgoing: 10000,
|
||||
incoming: 10000
|
||||
};
|
||||
this.maxWebSocketFrameSize = 16 * 1024;
|
||||
this.subscriptions = {};
|
||||
}
|
||||
|
||||
Client.prototype.debug = function(message) {
|
||||
var _ref;
|
||||
return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0;
|
||||
};
|
||||
|
||||
now = function() {
|
||||
if (Date.now) {
|
||||
return Date.now();
|
||||
} else {
|
||||
return new Date().valueOf;
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._transmit = function(command, headers, body) {
|
||||
var out;
|
||||
out = Frame.marshall(command, headers, body);
|
||||
if (typeof this.debug === "function") {
|
||||
this.debug(">>> " + out);
|
||||
}
|
||||
while (true) {
|
||||
if (out.length > this.maxWebSocketFrameSize) {
|
||||
this.ws.send(out.substring(0, this.maxWebSocketFrameSize));
|
||||
out = out.substring(this.maxWebSocketFrameSize);
|
||||
if (typeof this.debug === "function") {
|
||||
this.debug("remaining = " + out.length);
|
||||
}
|
||||
} else {
|
||||
return this.ws.send(out);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._setupHeartbeat = function(headers) {
|
||||
var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1;
|
||||
if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) {
|
||||
return;
|
||||
}
|
||||
_ref1 = (function() {
|
||||
var _i, _len, _ref1, _results;
|
||||
_ref1 = headers['heart-beat'].split(",");
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
||||
v = _ref1[_i];
|
||||
_results.push(parseInt(v));
|
||||
}
|
||||
return _results;
|
||||
})(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1];
|
||||
if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
|
||||
ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
|
||||
if (typeof this.debug === "function") {
|
||||
this.debug("send PING every " + ttl + "ms");
|
||||
}
|
||||
this.pinger = Stomp.setInterval(ttl, (function(_this) {
|
||||
return function() {
|
||||
_this.ws.send(Byte.LF);
|
||||
return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0;
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
|
||||
ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
|
||||
if (typeof this.debug === "function") {
|
||||
this.debug("check PONG every " + ttl + "ms");
|
||||
}
|
||||
return this.ponger = Stomp.setInterval(ttl, (function(_this) {
|
||||
return function() {
|
||||
var delta;
|
||||
delta = now() - _this.serverActivity;
|
||||
if (delta > ttl * 2) {
|
||||
if (typeof _this.debug === "function") {
|
||||
_this.debug("did not receive server activity for the last " + delta + "ms");
|
||||
}
|
||||
return _this.ws.close();
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._parseConnect = function() {
|
||||
var args, connectCallback, errorCallback, headers;
|
||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
headers = {};
|
||||
switch (args.length) {
|
||||
case 2:
|
||||
headers = args[0], connectCallback = args[1];
|
||||
break;
|
||||
case 3:
|
||||
if (args[1] instanceof Function) {
|
||||
headers = args[0], connectCallback = args[1], errorCallback = args[2];
|
||||
} else {
|
||||
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2];
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3];
|
||||
break;
|
||||
default:
|
||||
headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.host = args[4];
|
||||
}
|
||||
return [headers, connectCallback, errorCallback];
|
||||
};
|
||||
|
||||
Client.prototype.connect = function() {
|
||||
var args, errorCallback, headers, out;
|
||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
||||
out = this._parseConnect.apply(this, args);
|
||||
headers = out[0], this.connectCallback = out[1], errorCallback = out[2];
|
||||
if (typeof this.debug === "function") {
|
||||
this.debug("Opening Web Socket...");
|
||||
}
|
||||
this.ws.onmessage = (function(_this) {
|
||||
return function(evt) {
|
||||
var arr, c, client, data, frame, messageID, onreceive, subscription, _i, _len, _ref, _results;
|
||||
data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function() {
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = arr.length; _i < _len; _i++) {
|
||||
c = arr[_i];
|
||||
_results.push(String.fromCharCode(c));
|
||||
}
|
||||
return _results;
|
||||
})()).join('')) : evt.data;
|
||||
_this.serverActivity = now();
|
||||
if (data === Byte.LF) {
|
||||
if (typeof _this.debug === "function") {
|
||||
_this.debug("<<< PONG");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof _this.debug === "function") {
|
||||
_this.debug("<<< " + data);
|
||||
}
|
||||
_ref = Frame.unmarshall(data);
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
frame = _ref[_i];
|
||||
switch (frame.command) {
|
||||
case "CONNECTED":
|
||||
if (typeof _this.debug === "function") {
|
||||
_this.debug("connected to server " + frame.headers.server);
|
||||
}
|
||||
_this.connected = true;
|
||||
_this._setupHeartbeat(frame.headers);
|
||||
_results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0);
|
||||
break;
|
||||
case "MESSAGE":
|
||||
subscription = frame.headers.subscription;
|
||||
onreceive = _this.subscriptions[subscription] || _this.onreceive;
|
||||
if (onreceive) {
|
||||
client = _this;
|
||||
messageID = frame.headers["message-id"];
|
||||
frame.ack = function(headers) {
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
return client.ack(messageID, subscription, headers);
|
||||
};
|
||||
frame.nack = function(headers) {
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
return client.nack(messageID, subscription, headers);
|
||||
};
|
||||
_results.push(onreceive(frame));
|
||||
} else {
|
||||
_results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0);
|
||||
}
|
||||
break;
|
||||
case "RECEIPT":
|
||||
_results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0);
|
||||
break;
|
||||
case "ERROR":
|
||||
_results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0);
|
||||
break;
|
||||
default:
|
||||
_results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
})(this);
|
||||
this.ws.onclose = (function(_this) {
|
||||
return function() {
|
||||
var msg;
|
||||
msg = "Whoops! Lost connection to " + _this.ws.url;
|
||||
if (typeof _this.debug === "function") {
|
||||
_this.debug(msg);
|
||||
}
|
||||
_this._cleanUp();
|
||||
return typeof errorCallback === "function" ? errorCallback(msg) : void 0;
|
||||
};
|
||||
})(this);
|
||||
return this.ws.onopen = (function(_this) {
|
||||
return function() {
|
||||
if (typeof _this.debug === "function") {
|
||||
_this.debug('Web Socket Opened...');
|
||||
}
|
||||
headers["accept-version"] = Stomp.VERSIONS.supportedVersions();
|
||||
headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',');
|
||||
return _this._transmit("CONNECT", headers);
|
||||
};
|
||||
})(this);
|
||||
};
|
||||
|
||||
Client.prototype.disconnect = function(disconnectCallback, headers) {
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
this._transmit("DISCONNECT", headers);
|
||||
this.ws.onclose = null;
|
||||
this.ws.close();
|
||||
this._cleanUp();
|
||||
return typeof disconnectCallback === "function" ? disconnectCallback() : void 0;
|
||||
};
|
||||
|
||||
Client.prototype._cleanUp = function() {
|
||||
this.connected = false;
|
||||
if (this.pinger) {
|
||||
Stomp.clearInterval(this.pinger);
|
||||
}
|
||||
if (this.ponger) {
|
||||
return Stomp.clearInterval(this.ponger);
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.send = function(destination, headers, body) {
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
if (body == null) {
|
||||
body = '';
|
||||
}
|
||||
headers.destination = destination;
|
||||
return this._transmit("SEND", headers, body);
|
||||
};
|
||||
|
||||
Client.prototype.subscribe = function(destination, callback, headers) {
|
||||
var client;
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
if (!headers.id) {
|
||||
headers.id = "sub-" + this.counter++;
|
||||
}
|
||||
headers.destination = destination;
|
||||
this.subscriptions[headers.id] = callback;
|
||||
this._transmit("SUBSCRIBE", headers);
|
||||
client = this;
|
||||
return {
|
||||
id: headers.id,
|
||||
unsubscribe: function() {
|
||||
return client.unsubscribe(headers.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Client.prototype.unsubscribe = function(id) {
|
||||
delete this.subscriptions[id];
|
||||
return this._transmit("UNSUBSCRIBE", {
|
||||
id: id
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.begin = function(transaction) {
|
||||
var client, txid;
|
||||
txid = transaction || "tx-" + this.counter++;
|
||||
this._transmit("BEGIN", {
|
||||
transaction: txid
|
||||
});
|
||||
client = this;
|
||||
return {
|
||||
id: txid,
|
||||
commit: function() {
|
||||
return client.commit(txid);
|
||||
},
|
||||
abort: function() {
|
||||
return client.abort(txid);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Client.prototype.commit = function(transaction) {
|
||||
return this._transmit("COMMIT", {
|
||||
transaction: transaction
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.abort = function(transaction) {
|
||||
return this._transmit("ABORT", {
|
||||
transaction: transaction
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.ack = function(messageID, subscription, headers) {
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
headers["message-id"] = messageID;
|
||||
headers.subscription = subscription;
|
||||
return this._transmit("ACK", headers);
|
||||
};
|
||||
|
||||
Client.prototype.nack = function(messageID, subscription, headers) {
|
||||
if (headers == null) {
|
||||
headers = {};
|
||||
}
|
||||
headers["message-id"] = messageID;
|
||||
headers.subscription = subscription;
|
||||
return this._transmit("NACK", headers);
|
||||
};
|
||||
|
||||
return Client;
|
||||
|
||||
})();
|
||||
|
||||
Stomp = {
|
||||
VERSIONS: {
|
||||
V1_0: '1.0',
|
||||
V1_1: '1.1',
|
||||
V1_2: '1.2',
|
||||
supportedVersions: function() {
|
||||
return '1.1,1.0';
|
||||
}
|
||||
},
|
||||
client: function(url, protocols) {
|
||||
var klass, ws;
|
||||
if (protocols == null) {
|
||||
protocols = ['v10.stomp', 'v11.stomp'];
|
||||
}
|
||||
klass = Stomp.WebSocketClass || WebSocket;
|
||||
ws = new klass(url, protocols);
|
||||
return new Client(ws);
|
||||
},
|
||||
over: function(ws) {
|
||||
return new Client(ws);
|
||||
},
|
||||
Frame: Frame
|
||||
};
|
||||
|
||||
if (typeof exports !== "undefined" && exports !== null) {
|
||||
exports.Stomp = Stomp;
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined" && window !== null) {
|
||||
Stomp.setInterval = function(interval, f) {
|
||||
return window.setInterval(f, interval);
|
||||
};
|
||||
Stomp.clearInterval = function(id) {
|
||||
return window.clearInterval(id);
|
||||
};
|
||||
window.Stomp = Stomp;
|
||||
} else if (!exports) {
|
||||
self.Stomp = Stomp;
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
@@ -40,7 +40,7 @@
|
||||
<span class="ax-line"></span>
|
||||
</div>
|
||||
<div class="ax-item">
|
||||
<a th:href="@{/logging/index}" class="ax-text">在线日志</a>
|
||||
<a th:href="@{/logging/index}" target="_blank" class="ax-text">在线日志</a>
|
||||
<span class="ax-line"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,51 +11,40 @@
|
||||
<meta content="telephone=no" name="format-detection" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>在线日志</title>
|
||||
|
||||
<link th:href="@{/axui-v2.1.1/css/ax.css}" rel="stylesheet" type="text/css" >
|
||||
<link th:href="@{/axui-v2.1.1/css/ax-response.css}" rel="stylesheet" type="text/css" >
|
||||
</head>
|
||||
<body>
|
||||
<header class="ax-header">
|
||||
<div class="ax-row">
|
||||
<div class="ax-col">
|
||||
<a th:href="@{/download/index}" class="ax-logo">
|
||||
<img th:src="@{/common/images/logo.png}" alt="File Management"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ax-nav">
|
||||
<div class="ax-item">
|
||||
<a th:href="@{/download/index}" class="ax-text">文件列表</a>
|
||||
<span class="ax-line"></span>
|
||||
</div>
|
||||
<div class="ax-item">
|
||||
<a th:href="@{/upload/index}" class="ax-text">文件上传</a>
|
||||
<span class="ax-line"></span>
|
||||
</div>
|
||||
<div class="ax-item ax-selected">
|
||||
<a th:href="@{/logging/index}" class="ax-text">在线日志</a>
|
||||
<span class="ax-line"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="ax-space-header"></div>
|
||||
<div id="RootDiv" class="ax-border ax-margin">
|
||||
|
||||
</div>
|
||||
<script th:src="@{/axui-v2.1.1/js/ax.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<script th:src="@{/common/js/basic.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<script th:src="@{/common/js/fmt-socket.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<!-- 日志容器 -->
|
||||
<div id="logContainer"></div>
|
||||
<script th:src="@{/common/js/LogMonitorAdaptive.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<script th:src="@{/common/js/sockjs.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<script th:src="@{/common/js/stomp.js}" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" th:inline="javascript" charset="utf-8">
|
||||
window.onload = () => {
|
||||
const socket = new FmtSocket({url: Fmt.ctx() + '/socket/ws'});
|
||||
socket.init((data) => {
|
||||
if (data.send === 'Logging') {
|
||||
const log = data.data;
|
||||
const logDiv = document.createElement('div');
|
||||
logDiv.innerHTML = '<pre><strong>' + log + '</strong></pre>';
|
||||
document.querySelector('#RootDiv').appendChild(logDiv);
|
||||
}
|
||||
const logger = new LogMonitorAdaptive('#logContainer', {
|
||||
theme: 'dark',
|
||||
maxLines: 5000,
|
||||
fontSize: 14,
|
||||
enableFilter: true,
|
||||
enableSearch: true,
|
||||
enableExport: true,
|
||||
enableClear: true,
|
||||
enablePause: true,
|
||||
enableThemeToggle: true,
|
||||
enableFullscreen: true,
|
||||
enableFontSize: true,
|
||||
enableWordWrap: true,
|
||||
showTimestamp: true, // 是否显示时间戳
|
||||
showLevel: true, // 是否显示日志级别标签
|
||||
});
|
||||
|
||||
const socket = new SockJS('/fmt/ws-logs');
|
||||
const stomp = Stomp.over(socket);
|
||||
stomp.connect({}, () => {
|
||||
stomp.subscribe('/topic/logs', (payload) => {
|
||||
const log = JSON.parse(payload.body);
|
||||
console.log(log);
|
||||
logger.log(log.message, log.level);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user