Spring Boot 集成 WebSocket 实例 | 前端持续打印远程日志文件更新内容(模拟 tail 命令)

这个是我在 CSDN 的第一百篇原则博文,留念😎

#1 需求说明

先说下项目结构,后端基于 Spring Boot 3,前端为 node.js 开发的控制台程序。现在希望能够在前端模拟 tail 命令,持续输出后端的日志文件。

#2 技术方案

#2.1 基于轮询(PASS)

这个方案实施较为简单,通过前端不断(定时)发起请求,并携带已读的内容坐标(position),询问后端日志文件是否有更新,判断依据为当前文件大小大于 position。若有变动,则读取更新的内容,回显在前端控制台。

此方案会产生非常多的请求,如果定时间隔设置不好,会有明显的延迟,故不采用。

#2.2 WebSocket 长连接

  1. 前端开启一个 WebSocket
  2. 后端监听到长连接后,启动文件变动检测线程
  3. 若文件发生变动,则读取更新内容,发送到前端

#3 实施

#3.1 后端改造

关于 Spring Boot 与 WebSocket 的集成,请转到:springboot集成websocket持久连接(权限过滤+拦截)

首先,我们定义一个监听文件变动并读取最新内容的工具类(借助于 common-io 包):

class FileTail(val path:Path, val handler: Consumer<String>, delay:Long=1000): FileAlterationListenerAdaptor() {private val watcher = FileSystems.getDefault().newWatchService()private val MODE = "r"private var reader = RandomAccessFile(path.toFile(), MODE)private var position= reader.length()// 使用 JDK 自带的 WatchService ,发现不能正常读取文件追加的内容private var monitor: FileAlterationMonitor = FileAlterationMonitor(delay)init {// 初始化监视器,只检测同名的文件FileAlterationObserver(path.parent.toFile()) { f: File -> f.name == path.name }.also { observer->observer.addListener(this)monitor.addObserver(observer)monitor.start()}}override fun onFileChange(file: File) {reader.seek(position)val bytes = mutableListOf<Byte>()val tmp = ByteArray(1024)var readSize: Intwhile ((reader.read(tmp).also { readSize = it }) != -1) {for (i in 0..< readSize){bytes.add(tmp[i])}}position += bytes.sizehandler.accept(String(bytes.toByteArray()))}fun stop() {reader.close()monitor.stop()}
}

再定义长连接的通信处理类:

@Component
class FileTailWsHandler : TextWebSocketHandler() {private val logger = LoggerFactory.getLogger(javaClass)companion object {val monitors = mutableMapOf<String, FileTail>()}override fun afterConnectionEstablished(session: WebSocketSession) {try{val textFile = Paths.get("logs/spring.log")// 加入队列monitors[session.id] = FileTail(textFile,{ text -> session.sendMessage(TextMessage(text)) })}catch (e:Exception){logger.error("处理客户端消息失败", e)session.sendMessage(TextMessage("服务器出错:${ExceptionUtils.getMessage(e)}"))session.close(CloseStatus.SERVER_ERROR)}}override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {logger.info("客户端(${session.id}${session.remoteAddress} 断开连接...")monitors.remove(session.id)?.stop()}
}

编写配置类,启用上述的组件:

@Component
class WsInterceptor : HandshakeInterceptor {private val logger = LoggerFactory.getLogger(javaClass)override fun beforeHandshake(request: ServerHttpRequest,response: ServerHttpResponse,wsHandler: WebSocketHandler,attributes: MutableMap<String, Any>): Boolean {if(logger.isDebugEnabled){logger.debug("WS 握手开始:${request.uri} 客户端=${request.remoteAddress}")request.headers.forEach { name, v -> logger.debug("[HEADER] $name = $v") }}//此处可以进行鉴权//写入属性值,方便在 handler 中获取attributes[F.PARAMS]    = request.headers.getFirst(F.PARAMS)?: EMPTY// 返回 true 才能建立连接return true}override fun afterHandshake(request: ServerHttpRequest,response: ServerHttpResponse,wsHandler: WebSocketHandler,exception: Exception?) {}
}@Configuration
@EnableWebSocket
class SocketConfig : WebSocketConfigurer {private val logger = LoggerFactory.getLogger(javaClass)@Resourcelateinit var interceptor: WsInterceptor@Resourcelateinit var fileTailHandler:FileTailWsHandleroverride fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {registry.addHandler(fileTailHandler, "/ws/file-tail").addInterceptors(interceptor)}
}

#3.2 前端(node.js)

请先安装依赖:npm i -D ws

/*** 跟踪远程日志文件* @param {*} ps*/
const _tailRemoteFile = async ps=>{let url = remoteUrl("/ws/file-tail")let index = url.indexOf("://")let headers = {}headers.params = JSON.stringify(ps)const client = new WebSocket(`ws${url.substring(index)}`, { headers })client.on('open', ()=> console.debug(chalk.magenta(`与服务器连接成功 🤝`)))// client.on('close',()=> console.debug(chalk.magenta(`\n与服务器连接关闭 👋`)))client.on('error', e=> {console.debug(chalk.red(e))})client.on('message', /** @param {Buffer} buf */buf=>{let line = buf.toString()if(line.endsWith("\n") || line.endsWith("\r\n"))line = line.substring(0, line.length-2)console.debug(line)})
}

#3.3 看看效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/744467.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯练习:景区导游

视频 UP主的博客 暴力做法&#xff0c;能过 42%数据。如果内存开 1e410 能过 40%&#xff0c;如果开 2e510就只能过 25% #include<bits/stdc.h> #define int long long #define endl \n const int N 1e410; using namespace std; //存两点的距离 typedef pair<in…

豆瓣书影音存入Notion

使用Python将图书和影视数据存放入Notion中。 &#x1f5bc;️介绍 环境 Python 3.10 &#xff08;建议 3.11 及以上&#xff09;Pycharm / Vs Code / Vs Code Studio 项目结构 │ .env │ main.py - 主函数、执行程序 │ new_book.txt - 上一次更新书籍 │ new_video.…

全球首个 AI 软件工程师 Devin它来了!

如果您想每日获取AI最新新闻,欢迎关注文章底部的公众号 Cognition AI 发布 AI 软件工程师 Devin 初创公司 Cognition 近日发布公告,宣布推出全球首个 AI 软件工程师 Devin,并号称会彻底改变人类构建软件的方式。Devin 擅长长期推理能力,可以自主规划和完成软件项目,并在此…

ElasticSearch深度分页问题如何解决

文章目录 概述解决方法深度分页方式from size深度分页之scrollsearch_after 三种分页方式比较 概述 Elasticsearch 的深度分页问题是指在大数据集上进行大量分页查询时可能导致的性能下降和资源消耗增加的情况。这种情况通常发生在需要访问大量数据的情形下&#xff0c;比如用…

【OpenBayes 官方教程】数据读写绑定功能

本教程主要为大家介绍怎样在 OpenBayes 上进行数据的绑定以及如何使用已绑定的数据&#xff0c;新朋友点击下方链接注册后&#xff0c;即可获得 4 小时 RTX 4090 5 小时 CPU 的免费使用时长哦&#xff01; 注册链接 注册 - OpenBayes 首先&#xff0c;创建一个新的容器。 然…

非光滑非凸规划

目录 一&#xff0c;非凸函数的近端梯度下降 1&#xff0c;凸函数的近端梯度下降 2&#xff0c;非凸函数的近端梯度下降 一&#xff0c;非凸函数的近端梯度下降 1&#xff0c;凸函数的近端梯度下降 参考近端梯度下降 2&#xff0c;非凸函数的近端梯度下降

11.17定时调度(血干JAVA系类)

定时调度 11.17.1 Timer 类11.17.2 TimerTask 类11.17.3范例——定时操作【例11.52】建立TimerTask的子类【例11.53】建立测试类&#xff0c;进行任务调度 11.17.1 Timer 类 11.17.2 TimerTask 类 要想执行具体的任务&#xff0c;则必须使用Tim erTas k类。Tim erTas k类是一个…

工业界真实的推荐系统(小红书)-离散特征处理、矩阵补充模型、双塔模型

课程特点&#xff1a;系统、清晰、实用&#xff0c;原理和落地经验兼具 b站&#xff1a;https://www.bilibili.com/video/BV1HZ421U77y/?spm_id_from333.337.search-card.all.click&vd_sourceb60d8ab7e659b10ea6ea743ede0c5b48 讲义&#xff1a;https://github.com/wangsh…

vid2vid(Video-to-Video Synthesis)论文详读和理解

论文&#xff1a;https://arxiv.org/abs/1808.06601 代码&#xff1a;https://github.com/NVIDIA/vid2vid

北斗卫星推动数智油田建设

北斗卫星推动数智油田建设 中国石油大港油田采油三厂深入推动北斗智能终端在智能巡检、安全监督、油井导航、坐标测绘等多场景应用&#xff0c;实现了人工查井向智能巡检的变革。截至2月下旬&#xff0c;场景覆盖率达100%&#xff0c;高效助推大港南部“双高”老区数智油田建设…

史上最牛Linux详解,看完直接带你由入门到精通!

第一部分&#xff1a;入门 第二部分&#xff1a;成为一名linux高级用户&#xff1a; 第三部分&#xff1a;成为一名Linux系统管理员 第四部分&#xff1a;成为一名Linux服务器管理员 因文章内容过长&#xff0c;目录先放这些&#xff0c;因为接下来还要放一些内容 小编13年上海…

5款可以免费使用的 UI 设计软件

在我们分享五个有用的原型工具之前&#xff0c;完成原型并优化界面。这是 UI 设计师的任务。UI 设计软件对设计师来说非常重要。UI 设计工具的使用是否直接影响最终结果&#xff0c;然后有人会问&#xff1a;UI 界面设计用什么软件&#xff1f;一些 UI 设计师和对 UI 设计感兴趣…

二,几何相交---4,BO算法---(4)可能的三种情况

从上到下&#xff0c;扫描线经过有三种情况&#xff1a; 第一种情况&#xff0c;加入线段e的左端点&#xff0c;那么原来的状态pred->suc变成pred->e->suc 第二种情况&#xff0c;经过线段e的右端点&#xff0c;状态pred->e->suc&#xff0c;变成pred->suc&a…

SimplifyRODataLoads - 优化阅读笔记

// 只支持 X86 static cl::opt<bool> SimplifyRODataLoads("simplify-rodata-loads",cl::desc("通过用相应节中找到的常数替换内存操作数&#xff0c;简化来自只读节的加载"),cl::cat(BoltOptCategory));测试用例&#xff1a; ./build4/bin/llvm-li…

如何在Linux部署Docker Registry本地镜像仓库并实现无公网IP远程连接

文章目录 1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址 Docker Registry 本地镜像仓库,简单几步结合cpolar内网穿透工具实现远程pull or push (拉取和推送)…

为什么拥有C语言基础的人,依然学不会C++?

为什么拥有C语言基础的人&#xff0c;依然学不会C? 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言 的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&am…

Vue+OpenLayers7入门到实战:OpenLayers加载必应地图(BingMap)

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上如何加载必应地图(BingMap)。 需要提前到必应开发者中心申请对应的地图访问api key才可以加载地图。 二、依赖和使用 "ol": "7.5.2"使用npm安装…

docker引擎

目录 一、Docker引擎发展历程 二、docker引擎架构 三、docker引擎分类 四、docker引擎安装 4.1安装条件 4.2 使用rpm存储库安装 4.2.1设置存储库 4.2.2安装docker引擎 4.2.3启动docker,并设置docker开机自启动 五、卸载docker引擎 5.1.卸载 Docker 引擎、CLI、conta…

如何使用vscode创建Node.js服务并结合内网穿透实现远程访问本地服务

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

使用API有效率地管理Dynadot域名,使用API设置域名隐私保护

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…