由浅入深逐步理解spring boot中如何实现websocket
实现websocket的方式
1.springboot中有两种方式实现websocket,一种是基于原生的基于注解的websocket,另一种是基于spring封装后的WebSocketHandler
基于原生注解实现websocket
1)先引入websocket的starter坐标
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
2)编写websocket的Endpoint端点类
@ServerEndpoint(value = "/ws/{token}")
@Component
public class WebsocketHandler2 {private final static Logger log = LoggerFactory.getLogger(WebsocketHandler2.class);private static final Set<Session> SESSIONS = new ConcurrentSkipListSet<>(Comparator.comparing(Session::getId));private static final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);private static final Map<String, ScheduledFuture<?>> futures = new ConcurrentHashMap<>();@OnOpenpublic void onOpen(Session session, @PathParam("token") String token, EndpointConfig config) throws IOException {
// session.addMessageHandler(new PongHandler());ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> sendPing(session), 5, 5, TimeUnit.SECONDS);String queryString = session.getQueryString();futures.put(session.getId(), future);session.setMaxIdleTimeout(6 * 1000);SESSIONS.add(session);log.info("open connect sessionId={}, token={}, queryParam={}", session.getId(), token, queryString);String s = String.format("ws client(id=%s) has connected", session.getId());session.getBasicRemote().sendText(s);}static class PongHandler implements MessageHandler.Whole<PongMessage> {@Overridepublic void onMessage(PongMessage message) {ByteBuffer data = message.getApplicationData();String s = new String(data.array(), StandardCharsets.UTF_8);log.info("receive pong msg=> {}", s);}}@OnClosepublic void onClose(Session session, CloseReason reason) {log.info("session(id={}) close ,closeCode={},closeParse={}", session.getId(), reason.getCloseCode(), reason.getReasonPhrase());SESSIONS.remove(session);ScheduledFuture<?> future = futures.get(session.getId());if (future != null) {future.cancel(true);}}@OnMessagepublic void onMessage(String message, Session session) throws IOException {log.info("receive client(id={}) msg=>{}", session.getId(), message);String s = String.format("reply your(id=%s) msg=>【%s】", session.getId(), message);session.getBasicRemote().sendText(s);}@OnMessagepublic void onPong(PongMessage message, Session session) throws IOException {ByteBuffer data = message.getApplicationData();String s = new String(data.array(), StandardCharsets.UTF_8);log.info("receive client(id={}) pong msg=> {}", session.getId(), s);}@OnErrorpublic void onError(Session session, Throwable error) {log.error("Session(id={}) error occur ", session.getId(), error);}private void sendPing(Session session) {if (session.isOpen()) {String replyContent = String.format("Hello,client(id=%s)", session.getId());try {session.getBasicRemote().sendPing(ByteBuffer.wrap(replyContent.getBytes(StandardCharsets.UTF_8)));} catch (IOException e) {log.error("ping client(id={}) error", session.getId(), e);}return;}SESSIONS.remove(session);ScheduledFuture<?> future = futures.remove(session.getId());if (future != null) {future.cancel(true);}}
}
注解说明
@ServerEndpoint标记这个是一个服务端的端点类
@OnOpen 标记此方法是建立websocket连接时的回调方法
@OnMessage 标记此方法是接收到客户端消息时的回调方法
@OnClose标记此方法是断开websocke连接时的回调方法
@OnError标记此方法是websocke发生异常时的回调方法
@PathParam可以获取@ServerEndpoint注解中绑定的路径模板参数
方法参数说明
1) onOpen方法参数
onOpen的可用参数在tomcat源码 org.apache.tomcat.websocket.pojo.PojoMethodMapping#getOnOpenArgs可以看到
public Object[] getOnOpenArgs(Map<String,String> pathParameters,Session session, EndpointConfig config) throws DecodeException {return buildArgs(onOpenParams, pathParameters, session, config, null,null);}
因此可以看出@OnOpen所标记方法的合法参数有
(1)@PathParam标记的路径参数
(2)当前会话Session参数
(3)当前endpoint的配置详情EndpointConfig参数
2) onClose方法参数
onClose的可用参数在tomcat源码 org.apache.tomcat.websocket.pojo.PojoMethodMapping#getOnCloseArgs可以看到
public Object[] getOnCloseArgs(Map<String,String> pathParameters,Session session, CloseReason closeReason) throws DecodeException {return buildArgs(onCloseParams, pathParameters, session, null, null,closeReason);}
因此可以看出@OnClose所标记方法的合法参数有
(1)@PathParam标记的路径参数
(2)当前会话Session参数
(3)当前连接关闭的原因CloseReason参数
3) onError方法参数
onError的可用参数在tomcat源码 org.apache.tomcat.websocket.pojo.PojoMethodMapping#getOnErrorArgs可以看到
public Object[] getOnErrorArgs(Map<String,String> pathParameters,Session session, Throwable throwable) throws DecodeException {return buildArgs(onErrorParams, pathParameters, session, null,throwable, null);}
因此可以看出@OnError所标记方法的合法参数有
(1)@PathParam标记的路径参数
(2)当前会话Session参数
(3)发生异常的异常对象Throwable参数
4) onMessage方法参数
onMessage的可用参数在tomcat源码 org.apache.tomcat.websocket.pojo.PojoMethodMapping.MessageHandlerInfo#getMessageHandlers可以看到
public Set<MessageHandler> getMessageHandlers(Object pojo,Map<String,String> pathParameters, Session session,EndpointConfig config) {Object[] params = new Object[m.getParameterTypes().length];for (Map.Entry<Integer,PojoPathParam> entry :indexPathParams.entrySet()) {PojoPathParam pathParam = entry.getValue();String valueString = pathParameters.get(pathParam.getName());Object value = null;try {value = Util.coerceToType(pathParam.getType(), valueString);} catch (Exception e) {DecodeException de = new DecodeException(valueString,sm.getString("pojoMethodMapping.decodePathParamFail",valueString, pathParam.getType()), e);params = new Object[] { de };break;}params[entry.getKey().intValue()] = value;}Set<MessageHandler> results = new HashSet<>(2);if (indexBoolean == -1) {// Basicif (indexString != -1 || indexPrimitive != -1) {MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,session, config, null, params, indexPayload, false,indexSession, maxMessageSize);results.add(mh);} else if (indexReader != -1) {MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,session, config, null, params, indexReader, true,indexSession, maxMessageSize);results.add(mh);} else if (indexByteArray != -1) {MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,m, session, config, null, params, indexByteArray,true, indexSession, false, maxMessageSize);results.add(mh);} else if (indexByteBuffer != -1) {MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,m, session, config, null, params, indexByteBuffer,false, indexSession, false, maxMessageSize);results.add(mh);} else if (indexInputStream != -1) {MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,m, session, config, null, params, indexInputStream,true, indexSession, true, maxMessageSize);results.add(mh);} else if (decoderMatch != null && decoderMatch.hasMatches()) {if (decoderMatch.getBinaryDecoders().size() > 0) {MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo, m, session, config,decoderMatch.getBinaryDecoders(), params,indexPayload, true, indexSession, true,maxMessageSize);results.add(mh);}if (decoderMatch.getTextDecoders().size() > 0) {MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m, session, config,decoderMatch.getTextDecoders(), params,indexPayload, true, indexSession, maxMessageSize);results.add(mh);}} else {MessageHandler mh = new PojoMessageHandlerWholePong(pojo, m,session, params, indexPong, false, indexSession);results.add(mh);}} else {// ASyncif (indexString != -1) {MessageHandler mh = new PojoMessageHandlerPartialText(pojo,m, session, params, indexString, false,indexBoolean, indexSession, maxMessageSize);results.add(mh);} else if (indexByteArray != -1) {MessageHandler mh = new PojoMessageHandlerPartialBinary(pojo, m, session, params, indexByteArray, true,indexBoolean, indexSession, maxMessageSize);results.add(mh);} else {MessageHandler mh = new PojoMessageHandlerPartialBinary(pojo, m, session, params, indexByteBuffer, false,indexBoolean, indexSession, maxMessageSize);results.add(mh);}}return results;}}
因此可以看出@OnMessage所标记方法的合法参数有
(1)@PathParam标记的路径参数
(2)当前会话Session参数
(3)当数据是分块传输时,表示当前消息时是否是最后一块数据的boolean Boolean参数
(4)字符输入流Reader参数
(5)二进制输入流InputStream参数
(6)原始的ByteBuffer参数
(7)字节数组byte[]参数
(8)字符串string参数
(9) Pong响应PongMessage参数
注意:接收数据报文的参数(4)~(5),只能使用其中的一个,否则可能导致IO异常(IO流只能读取一次)
ping和pong
上面的代码中我额外给websocket会话增加了一个PongMessage的处理方法onPong,它的作用是接收客户端的pong回执消息。只有在服务端向客户端发送Ping请求时,服务端才能接收到Pong响应。这里的ping和pong就是类型于其他系统中的心跳机制,用来检测客户端、服务端双方是否还在线,如果超过了限定时间没有收到ping 和 pong消息,服务端就会主动断开连接。
因此我在建立websocke连接的时候给当前回话设置了最大空闲时间(超过这个时间没有数据报文传输,此连接就会自动断开),同时绑定了一个定时任务,这个定时任务会定时发送ping消息来保活。
这里的onPong方法不是必须的,没有它能保活,onPong只是用来得到一个ping结果的通知。
3)注册暴露端点
@Configuration
@EnableWebSocket
public class WebsocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter(){ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();//WebsocketHandler2如果是一个spring bean(即有@Component),则不需要调用setAnnotatedEndpointClasses方法,spring会自动探测有@ServerEndpoint注解的bean//WebsocketHandler2如果只是一个包含@ServerEndpoint注解的普通类(不是 spring bean),则需要在此调用setAnnotatedEndpointClasses方法,手动注册Endpoint类型
// serverEndpointExporter.setAnnotatedEndpointClasses(WebsocketHandler2.class );return serverEndpointExporter;}
}
配置类添加@EnableWebSocket,启用spring websocket功能.
另外还需配置一个Bean ServerEndpointExporter ;如果Endpoint类是一个spring bean(即有@Component),则不需要调用setAnnotatedEndpointClasses方法,spring会自动探测含有@ServerEndpoint注解的Bean;如果Endpoint类只是一个包含@ServerEndpoint注解的普通类(不是 spring bean),则需要在此调用setAnnotatedEndpointClasses方法,手动注册Endpoint类型。
注意:即使Endpoint类是spring bean ,WebsocketContainer也会再创建并使用这个类的一个新实例,也就是说这个Endpoint中不能使用spring相关的功能,典型的就是不能使用@Autowire等注解自动注入Bean。其原因是websocket的默认端点配置org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator获取endpoint实例的逻辑是反射调用构造方法去创建一个新对象
public class DefaultServerEndpointConfiguratorextends ServerEndpointConfig.Configurator {@Overridepublic <T> T getEndpointInstance(Class<T> clazz)throws InstantiationException {try {return clazz.getConstructor().newInstance();} catch (InstantiationException e) {throw e;} catch (ReflectiveOperationException e) {InstantiationException ie = new InstantiationException();ie.initCause(e);throw ie;}}
当然你可以通过注入静态属性的方式来绕过这个限制。

理论上说也可在@ServerEndpoint注解的configurator属性指定为spring的org.springframework.web.socket.server.standard.SpringConfigurator也可以自动注入Bean依赖.
@ServerEndpoint(value = "/echo", configurator = SpringConfigurator.class)public class EchoEndpoint {// ...}
SpringConfigurator它重写了获取Endpoint实例的方法逻辑getEndpointInstance,它是直接到spring容器中去取这个bean,而不是创建一个新实例.

但实际在spring boot项目中,上面的getEndpointInstance方法获取到的WebApplicationContext是null,也就没法从spring容器中获取这个Endpoint bean
基于spring WebSocketHandler实现websocket
了解WebSocketHandler
提前引入前面提到的websocket的starter 依赖
WebSocketHandler接口定义了5个方法,
afterConnectionEstablished:建立连接后的回调方法
handleMessage:接收到客户端消息后的回调方法
handleTransportError: 数据传输异常时的回调方法
afterConnectionClosed: 连接关闭后的回调方法
supportsPartialMessages: 是否支持数据分块传输(最后一个分块传输,isLast是true)
它有两个主要的子类, 一个是处理纯文本数据的TextWebSocketHandler ,另一个是处理二进制数据的BinaryWebSocketHandler。
我们实现websocket一般是继承这两个类,并重写相应的方法。一般都需要重写afterConnectionEstablished handleTransportError
handleTransportError afterConnectionClosed 这三个方法,除此之外,处理文本还要重写接收客户端消息后的回调方法handleTextMessage,处理二进制数据需要重写接收客户端消息后的回调方法handleBinaryMessage。如果有需要得到ping结果回调,还可以重写handlePongMessage方法
代码
@Component
public class WebsocketHandler1 extends TextWebSocketHandler {private final Logger log = LoggerFactory.getLogger(getClass());private static final Set<WebSocketSession> sessions = new ConcurrentSkipListSet<>(Comparator.comparing(WebSocketSession::getId));private static final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);private static final Map<String, ScheduledFuture<?>> futures = new ConcurrentHashMap<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {@SuppressWarnings("unchecked")AbstractWebSocketSession<Session> standardSession = (AbstractWebSocketSession) session;Session nativeSession = standardSession.getNativeSession();nativeSession.setMaxIdleTimeout(1000*4);ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> sendPing(session), 5, 5, TimeUnit.SECONDS);futures.put(session.getId(), future);log.info("open connect sessionId={}", session.getId());sessions.add(session);TextMessage msg = new TextMessage(String.format("ws client(id=%s) has connected", session.getId()));session.sendMessage(msg);}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {log.info("receive client(id={}) msg=>{}", session.getId(), message.getPayload());TextMessage msg = new TextMessage(String.format("reply your(id=%s) msg=>%s", session.getId(), message.getPayload()));session.sendMessage(msg);}@Overrideprotected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {ByteBuffer payload = message.getPayload();String s = new String(payload.array());log.info("receive client(id={}) pong msg=>{}", session.getId(),s);}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {log.error("client(id={}) error occur ", session.getId(), exception);}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {log.info("close ,status={}", status);sessions.remove(session);ScheduledFuture<?> future = futures.get(session.getId());if (future != null) {future.cancel(true);}}@Overridepublic boolean supportsPartialMessages() {return true;}private void sendPing(WebSocketSession session) {if (session.isOpen()) {String replyContent = String.format("Hello,client(id=%s)", session.getId());PingMessage msg = new PingMessage(ByteBuffer.wrap(replyContent.getBytes(StandardCharsets.UTF_8)));try {session.sendMessage(msg);} catch (IOException e) {log.error("ping client(id={}) error", session.getId(), e);}}}
}
springboot内置的WebSocketHandler
前端html代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebSocket Chat</title><style>body {font-family: Arial, sans-serif;}#chat-box {width: 100%;height: 300px;border: 1px solid #ccc;overflow-y: auto;padding: 10px;margin-bottom: 10px;background-color: #f9f9f9;white-space: pre-wrap;}#input-box {width: calc(50% - 90px);padding: 10px;margin-right: 10px;display: flex;justify-content: center}.btn {padding: 10px;}#btn-container {margin: 1px;display: flex;justify-content: center;gap: 5px;}#input-container {margin: 1px;display: flex;justify-content: center;gap: 5px;}</style>
</head>
<body><div id="chat-box"></div>
<div id="input-container"><input type="text" id="input-box" placeholder="Enter your message"/>
</div><div id="btn-container"><button id="connect-button" class="btn">Connect</button><button id="close-button" class="btn">Close</button><button id="clear-button" class="btn">Clear</button><button id="send-button" class="btn">Send</button>
</div><script>const chatBox = document.getElementById('chat-box');const inputBox = document.getElementById('input-box');const sendButton = document.getElementById('send-button');const connectBtn = document.getElementById('connect-button');const closeBtn = document.getElementById('close-button');const clearBtn = document.getElementById('clear-button');let ws = null;sendButton.addEventListener('click', () => {if (ws === null) {alert("no connect")return;}const message = inputBox.value;if (message) {ws.send(message);chatBox.innerHTML += 'You: ' + message + '\n';chatBox.scrollTop = chatBox.scrollHeight;inputBox.value = '';}});clearBtn.addEventListener('click', () => {chatBox.innerHTML = '';});closeBtn.addEventListener('click', () => {if (ws === null) {alert("no connect")return;}console.log("prepare close ws");ws.close(1000, 'Normal closure');});connectBtn.addEventListener('click', () => {if (ws !== null) {alert("already connected!")return;}let curWs = new WebSocket('ws://localhost:7001/ws/Hews2df?id=323&color=red');curWs.onopen = event => {ws = curWs;console.log('Connected to WebSocket server, event=>%s', JSON.stringify(event));};curWs.onmessage = event => {const message = event.data;chatBox.innerHTML += 'Server: ' + message + '\n';chatBox.scrollTop = chatBox.scrollHeight;};curWs.onclose = event => {ws = null;console.log('Disconnected from WebSocket server, close code=%s,close reason=%s', event.code, event.reason);};curWs.onerror = event => {console.log("error occur, event=>%s", JSON.stringify(event))};});</script></body>
</html>
演示效果

相关文章:
由浅入深逐步理解spring boot中如何实现websocket
实现websocket的方式 1.springboot中有两种方式实现websocket,一种是基于原生的基于注解的websocket,另一种是基于spring封装后的WebSocketHandler 基于原生注解实现websocket 1)先引入websocket的starter坐标 <dependency><grou…...
1-petalinux 问题记录-根文件系统分区问题
在MPSOC上使用SD第二分区配置根文件系统的时候,需要选择对应的bootargs,但是板子上有emmc和sd两个区域,至于配置哪一种mmcblk0就出现了问题,从vivado中的BlockDesign和MLK XCZU2CG原理图来看的话,我使用的SD卡应该属于…...
微信小程序的上拉刷新与下拉刷新
效果图如下: 上拉刷新 与 下拉刷新 代码如下: joked.wxml <scroll-view class"scroll" scroll-y refresher-enabled refresher-default-style"white" bindrefresherrefresh"onRefresh" refresher-triggered&qu…...
【大语言模型】ACL2024论文-05 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器
【大语言模型】ACL2024论文-05 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器 目录 文章目录 【大语言模型】ACL2024论文-05 GenTranslate: 大型语言模型是生成性多语种语音和机器翻译器目录摘要研究背…...
KPRCB结构之ReadySummary和DispatcherReadyListHead
ReadySummary: Uint4B DispatcherReadyListHead : [32] _LIST_ENTRY 请参考 _KTHREAD *__fastcall KiSelectReadyThread(ULONG LowPriority, _KPRCB *Prcb)...
批处理之for语句从入门到精通--呕血整理
文章目录 一、前言二、for语句的基本用法三、文本解析显神威:for /f 用法详解四、翻箱倒柜遍历文件夹:for /r五、仅仅为了匹配第一层目录而存在:for /d六、计数循环:for /l后记 for语句从入门到精通 一、前言 在批处理中&#…...
pycharm小游戏贪吃蛇及pygame模块学习()
由于代码量大,会逐渐发布 一.pycharm学习 在PyCharm中使用Pygame插入音乐和图片时,有以下这些注意事项: 插入音乐: - 文件格式支持:Pygame常用的音乐格式如MP3、OGG等,但MP3可能需额外安装库…...
redis实战--黑马商城 记录
一、视频地址 黑马程序员Redis入门到实战教程,深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 二、笔记地址 Redis基础篇Redis实战篇...
机器人技术革新:人工智能的强力驱动
内容概要 在当今世界,机器人技术与人工智能的结合正如星星与大海,彼此辉映。随着科技的不断进步,人工智能不仅仅是为机器人赋予了“聪明的大脑”,更是推动了整个行业的快速发展。回顾机器人技术的发展历程,我们会发现…...
漫途焊机安全生产监管方案,提升安全生产管理水平!
随着智能制造时代的到来,企业安全生产管理的重要性日益凸显。特别是在现代工厂中,焊机的安全生产监管成为了一个不容忽视的重要环节。传统的焊机安全生产监管方式存在诸多不足,如人工巡检频率低、数据延迟、安全隐患发现不及时等问题。因此&a…...
动态规划之两个数组的 dp(上)
文章目录 最长公共子序列不相交的线不同的子序列通配符匹配 最长公共子序列 题目:最长公共子序列 思路 选取s1的[0, i]区间以及s2的[0, j]区间作为研究对象 状态表示:dp[i][j]表示,s1的[0, i]区间以及s2的[0, j]区间内…...
DC-9靶机通关
这是这个系列的最后一个靶机了!!!经过前面的锻炼和学习,这次我的目标是尽量不借助任何教程或者提示来拿下这个靶机!!!下面我们看能不能成功!!! 1.实验环境 攻…...
前端注释都应该怎么写?
以下是一些前端注释的例子,展示了如何应用前面提到的建议: 1. 使用清晰、简洁的语言 // 计算两个数的平均值 function calculateAverage(a, b) {return (a b) / 2; }2. 描述代码的目的和功能 // 将日期格式化为 "YYYY-MM-DD" 的字符串 fun…...
深入解析缓存模式下的数据一致性问题
今天,我们来聊聊常见的缓存模式和数据一致性问题。 常见的缓存模式有:Cache Aside、Read Through、Write Through、Write Back、Refresh Ahead、Singleflight。 缓存模式 Cache Aside 在 Cache Aside 模式中,是把缓存当做一个独立的数据源…...
嵌入式常用功能之通讯协议1--IIC
嵌入式常用功能之通讯协议1--串口 嵌入式常用功能之通讯协议1--IIC(本文) 嵌入式常用功能之通讯协议1--SPI 一、IIC总线协议介绍 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司(现在的 NXP 半导体…...
【Wi-Fi】Wi-Fi 7(802.11be) Vs Wi-Fi 8 (802.11bn)
介绍 WiFi 7 (802.11be) 是 WiFi-6 (802.11ax) 的继任者,旨在提高数据速率并减少拥挤环境中的延迟。 WiFi 8 (8021.1bn)是后续标准,专注于提高 WLAN 连接的可靠性, 提高…...
Ubuntu软件包管理机制
文章目录 🍊自我介绍🍊Ubuntu软件包管理机制🍊软件安装命令详解: 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞关注评论收藏(一键四连)哦~ 🍊自我介绍 Hello,大家好…...
SpringBoot详解:概念、优点、运行方式、配置文件、异步请求及异常处理
一、什么是SpringBoot? SpringBoot是一个基于Spring框架的开源项目,它简化了Spring应用的初始搭建以及开发过程。它提供了自动配置、起步依赖、Actuator、命令行界面等特性,使得开发者可以快速构建出一个独立、生产级别的Spring应用。 二、…...
npm install -g @vue/cil 非常卡慢
安装 vue/cli 时遇到卡慢的情况通常和网络问题有关,特别是国内的网络环境下访问 npm 的服务器可能较慢。你可以尝试以下几种方法来加速: 使用淘宝镜像源 淘宝 NPM 镜像源对国内用户更加友好。你可以临时使用淘宝镜像源安装 vue/cli: npm inst…...
Windows 基础 (二):系统目录与环境变量
内容预览 ≧∀≦ゞ Windows 基础 2:系统目录与环境变量声明系统目录系统核心目录其他重要日志目录应用程序数据目录用户数据目录隐藏目录 环境变量1. 查看环境变量2. 设置永久环境变量3. 查看特定环境变量的值4. 环境变量的存储位置5. 自定义环境变量的应用 结语 Wi…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
