由浅入深逐步理解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…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
