vue3+ts+java使用WebSocket传输数据

一、环境

系统:win11

IDE:vscode

框架:electron22.0.0+vite2+vue3+typescript4.8.4+springboot2.2.5+jdk1.8

二、websocket介绍

2.1 由来

        WebSocket未出现之前,浏览器和服务器之间的通信是通过Web的poll技术进行通信,就是浏览器不停的对服务器主动发动请求,每发起一次新的HTTP请求,就是开启一次新的TCP链接,HTTP协议本身就是一种开销非常大的协议,所以这种方式效率低下。于是就出现了WebSocket协议。

        下面是采用poll方式的代码示例:

setInterval(() => {// 查询注册机列表getRegisterInfo().then(res => {isHost.value = store.state.onHost;   }).catch(err => {console.log('getComputerList err:', err);});
}, 1000);

为了页面及时更新,会像服务器产生大量的请求,造成资源浪费。

2.2 WebSocket通信过程

        WebSocket是一种完全不同于HTTP的协议,但是它需要通过HTTP协议的GET请求,将HTTP协议升级为WebSocket协议。升级的过程被称为握手(handshake)。当浏览器和服务器握手成功后,则可以开始根据WebSocket定义的通信帧格式开始通信了。WebSocket协议的通信帧也分为控制数据帧和普通数据帧,前者用于控制WebSocket链接状态,后者用于承载数据。

        握手过程就是将HTTP协议升级为WebSocket协议的过程。在HTTP的GET请求头部添加信息如下:

Upgrade: websocket      #规定必需的字段,其值必需为 websocket , 如果不是则握手失败;
Connection: Upgrade  #规定必需的字段,值必需为 Upgrade , 如果不是则握手失败;
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==    #必需字段,一个随机的字符串;
Sec-WebSocket-Protocol: chat, superchat   #可选字段,可以用于标识应用层的协议;
Sec-WebSocket-Version: 13  #必需字段,代表了 WebSocket 协议版本,值必需是 13 , 否则
握手失败;

当服务器端,成功验证了以上信息后,则会返回一个形如以下信息的响应:

HTTP/1.1 101 Switching Protocols   #101代表握手成功的状态码
Upgrade: websocket  #规定必需的字段,其值必需为 websocket , 如果不是则握手失败;
Connection: Upgrade #规定必需的字段,值必需为 Upgrade , 如果不是则握手失败;
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  #规定必需的字段,该字段的值是通过固定字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 加上请求中 Sec-WebSocket-Key 字段的值,然后再对其结果通过 SHA1 哈希算法求出的结果。
Sec-WebSocket-Protocol: chat  #对应于请求中的 Sec-WebSocket-Protocol 字段;

2.3 WebSocket 优缺点

        优点:

        1、使用资源少。创建连接后,数据叫唤的包头较少;

        2、能实现及时通信。长连接,实时通信;

        3、更好的二进制支持。能更好的处理二进制内容;

        4、支持拓展。用户可以拓展协议,实现部分自定义的子协议。

        缺点:

        1、使用WebSocket,长连接,会占用一定资源;

        2、浏览器品类多,支持程度不同,可能问题多;

        3、与poll相比,代码复杂度将上升,完全依赖于websocket,要多写逻辑对websocket状态进行监控,对开发者要求也会高一些。

       没有完美的事物,我们讨论优缺点的目的是它适合什么场景,在要求实时性较高的应用时,那么WebSocket会更适合。如果基本都是操作的应用,实时性要求很低,那么WebSocket使用的资源成本就是不合适的。

2.4 浏览器支持

      WebSocket - Web API 接口参考 | MDNWebSocket 对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。icon-default.png?t=N7T8https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket以上是供参考的API接口,本文不做赘述,自己进入使用。

三、前端使用示例

文件名称

具体代码

/** @Descripttion: 封装socket方法* @version:* @Date: 2021-08-06 11:14:39* @LastEditTime: 2021-10-26 14:06:34*/
import { store } from "../store";
import { ElMessage } from "element-plus";
import { Base64 } from "js-base64";
import { updateComputerIsValid } from "/@/service/AppService";
import { localIp, playDutySound } from "../CommonUtil";
import { deal3004Procotol, deal3005Procotol, deal3006Procotol } from "/@/service/WebsocketService"; //业务代码interface socket {websocket: any;connectURL: string;socket_open: boolean;hearbeat_timer: any;hearbeat_interval: number;is_reonnect: boolean;reconnect_count: number;reconnect_current: number;ronnect_number: number;reconnect_timer: any;reconnect_interval: number;// init: (receiveMessage: Function | null) => any;init: () => any;receive: (message: any) => void;heartbeat: () => void;send: (data: any, callback?: any) => void;close: () => void;reconnect: () => void;webSocketBack?: (message: any) => void;
}const socket: socket = {websocket: null,connectURL: import.meta.env.VITE_WEBSOCKET_MONITOR_URL + localIp().replaceAll(".", ""),// 开启标识socket_open: false,// 心跳timerhearbeat_timer: null,// 心跳发送频率hearbeat_interval: 3000,// 是否自动重连is_reonnect: true,// 重连次数reconnect_count: 5000,// 已发起重连次数reconnect_current: 1,// 网络错误提示此时ronnect_number: 0,// 重连timerreconnect_timer: null,// 重连频率 不能设置的太小,否则会出现一次重连未返回的时候,下一次又开始重连reconnect_interval: 6000,// init: (receiveMessage: Function | null) => {init: () => {if (!("WebSocket" in window)) {// if (!("WebSocket" in window)) {ElMessage.warning("浏览器不支持WebSocket");return null;}// 已经创建过连接不再重复创建if (socket.websocket) {return socket.websocket;}socket.websocket = new WebSocket(socket.connectURL);socket.websocket.onmessage = (e: any) => {// if (receiveMessage) {//     receiveMessage(e);// }if (socket.webSocketBack) {socket.webSocketBack(e);}};socket.websocket.onclose = (e: any) => {console.log("websocket--关闭", socket.reconnect_current,e);if (socket.hearbeat_timer) {clearInterval(socket.hearbeat_timer);}//业务代码- 置位为1updateComputerIsValid(localIp(), 1);socket.socket_open = false;// 需要重新连接if (socket.is_reonnect) {console.log("websocket--需要重新连接", socket.is_reonnect,socket.reconnect_interval);socket.reconnect_timer = setTimeout(() => {console.log("websocket--重连", socket.reconnect_current);// 超过重连次数if (socket.reconnect_current > socket.reconnect_count &&socket.reconnect_count > -1) {console.log("websocket--超过重连次数");clearTimeout(socket.reconnect_timer);socket.is_reonnect = false;return;}// 记录重连次数socket.reconnect_current++;//清除 socket.websocketsocket.websocket = null;socket.reconnect();}, socket.reconnect_interval);}};// 连接成功socket.websocket.onopen = function () {console.log("websocket--连接成功");//业务代码updateComputerIsValid(localIp(), 0);socket.socket_open = true;socket.is_reonnect = true;// 开启心跳socket.heartbeat();};// 连接发生错误socket.websocket.onerror = function () {console.log("websocket--发生错误!关闭执行重连");socket.websocket.onclose();};},send: (data, callback = null) => {// 开启状态直接发送if (socket.websocket.readyState === socket.websocket.OPEN) {socket.websocket.send(JSON.stringify(data));if (callback) {callback();}// 正在开启状态,则等待1s后重新调用} else {clearInterval(socket.hearbeat_timer);if (socket.ronnect_number < 1) {// ElMessage({// 	type: 'error',// 	message: i18n.global.t('chat.unopen'),// 	duration: 0,// })console.log("服务关闭了!");}socket.ronnect_number++;}},receive: (message: any) => {let params = Base64.decode(JSON.parse(message.data).data);params = JSON.parse(params);return params;},heartbeat: () => {if (socket.hearbeat_timer) {clearInterval(socket.hearbeat_timer);}socket.hearbeat_timer = setInterval(() => {let diffMs = Number(new Date()) - Number(store.state.webSocketLastTime);console.log("websocket--上次间隔时间:", diffMs, "3秒以上才发送心跳包");if (diffMs > 0) {let data = {// languageId: store.state.users.language,content: "ping",};var sendDara = {encryption_type: "base64",data: Base64.encode(JSON.stringify(data)),};socket.send(sendDara);store.commit("setWebSocketLastTime", new Date());console.log("websocket--心跳发送",sendDara,"更新时间:",store.state.webSocketLastTime);}}, socket.hearbeat_interval);},close: () => {clearInterval(socket.hearbeat_timer);socket.is_reonnect = false;socket.websocket.onclose();},/*** 重新连接*/reconnect: () => {//websocket存在且不想重连的时候if (!socket.is_reonnect) {// if (socket.websocket && !socket.is_reonnect) {console.log("websocket--存在但是不需要重连的时候,关闭", socket.websocket, socket.is_reonnect);socket.close();}socket.init();},/*** 业务代码--数据处理 * @param backMessage*/webSocketBack(backMessage: any) {store.commit("setWebSocketLastTime", new Date());console.log("websocket-接受到的信息" + JSON.stringify(backMessage),"更新的时间:",store.state.webSocketLastTime);const wsData = backMessage.data.split("|");const wsDataCode = backMessage.data.split("|")[0];// 零位是协议号switch (wsDataCode) {// 值班机获取 提醒间隔时间 后的处理case "3002": {console.log("收到ws:3002: " + JSON.stringify(wsData));const setHost = wsData[1];store.commit("setDutyConfirmTime", Number(wsData[2]));if (setHost === "0") {store.commit("setLocalComputerDutyState", 0);store.commit("setOnDutyState", 0);} else {store.commit("setLocalComputerDutyState", 1);store.commit("setOnDutyState", 1);}break;}case "3003": {console.log("收到ws:3003", wsDataCode);if (wsData[1] === "0") {playDutySound();if (store.state.onDutyState === 0) {} else if (store.state.onDutyState === 1) {、playDutySound();store.commit("setOnDutyState", true);}} else if (wsData[1] === "1") {store.commit("setOnDutyState", false);}break;}case "3004": {//更新store中的数据deal3004Procotol(wsData);break;}case "3005": {//更新store中的数据deal3005Procotol(wsData);break;}case "3006": {//更新store中的数据deal3006Procotol(wsData);break;}}},
};export default socket;

其中业务代码请不用关注,自己实现自己的业务逻辑即可。

1001错误

对于重连时间的设置,如果设置的时间太短,会出现反复1001错误(The WebSocket session [] timeout expired)关闭再重连的现象:

把服务端关闭后

每次错误返回中间有两次重连操作,所以调整了重连间隔时间,错误消失,推论:一次重连结果还未出来的时候,又发起了地址一样的连接请求,造成冲突,会关闭上次连接,这次关闭会引发上次连接的重连,这就造成了反复重连。目前我采用的是拉长重连时间,比较简单,可以尝试通过判断连接状态来阻止一次连接没完成之前再次连接。

流程图

启动连接

//APP.VUE
import socket from "/@/utils/websocket";onMounted(async () => {socket.init();
});

四、后端服务

引入依赖包

<dependency><!-- websocket --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

客户端ip获取:

在mainApplication上添加下面注解:

@ServletComponentScan("**.**.filter")  //防止 @WebListener 无效
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;@javax.servlet.annotation.WebFilter(filterName = "sessionFilter",urlPatterns = "/*")
@Order(1)
public class WebFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest req= (HttpServletRequest) servletRequest;req.getSession().setAttribute("ip",req.getRemoteHost());filterChain.doFilter(servletRequest,servletResponse);}
}

WebSocket配置类

在这里也做了IP的获取

@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator {/*** 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {Map<String, Object> attributes = sec.getUserProperties();HttpSession session = (HttpSession) request.getHttpSession();if (session != null) {attributes.put(GlobalContants.IP_ADDR, session.getAttribute("ip"));Enumeration<String> names = session.getAttributeNames();while (names.hasMoreElements()) {String name = names.nextElement();attributes.put(name, session.getAttribute(name));}}}
}

Websocket接收

import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.deyou.cabin.monitor.common.GlobalContants;
import com.deyou.cabin.monitor.common.GlobalParams;
import com.deyou.cabin.monitor.common.utils.AssembleDownProtocolUtils;
import com.deyou.cabin.monitor.common.utils.CommonServeUtils;
import com.deyou.cabin.monitor.config.WebSocketConfig;
import com.deyou.cabin.monitor.model.WebSocketModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Slf4j
@Component
//@RequiredArgsConstructor
@ServerEndpoint(value = "/websocket/monitor/{code}",configurator = WebSocketConfig.class)
public class WebsocketController {/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session,@PathParam(value = "code") String code) {try{session.setMaxIdleTimeout(30000);}catch (Exception e){log.error(e.getMessage(),e);}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(Session session) {try{log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), }catch (Exception e){log.error(e.getMessage(),e);}}/*** 收到客户端消息后调用的方法** @param message*            */@OnMessagepublic void onMessage(String message, Session session) {try{log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);}catch (Exception ex){log.error(ex.getMessage(),ex);}}@OnErrorpublic void onError(Session session, Throwable error) {log.error("websocket发生错误:" + session.getId() + "---" + error.getMessage(),error);}public void sendMessageToAll(String message, Session fromSession) {try{//GlobalParams.webSocketModelMap是全局变量 ConcurrentHashMap<String, WebSocketModel> webSocketModelMapfor (Map.Entry<String, WebSocketModel> sessionEntry : GlobalParams.webSocketModelMap.entrySet()) {Session toSession = sessionEntry.getValue().getSession();// 排除掉自己if (!fromSession.getId().equals(toSession.getId())) {log.info("服务端给客户端[{}][{}]发送消息{}", toSession.getId(),sessionEntry.getValue().getWebSocketCode(), message);sendMessToOne(message,toSession);}}}catch (Exception e){log.error(e.getMessage(),e);}}public void sendMessageToAll(String message) {try {//GlobalParams.webSocketModelMap是全局变量 ConcurrentHashMap<String, WebSocketModel> webSocketModelMapfor (Map.Entry<String, WebSocketModel> sessionEntry : GlobalParams.webSocketModelMap.entrySet()) {Session toSession = sessionEntry.getValue().getSession();log.info("服务端给客户端[{}][{}]发送消息{}", toSession.getId(), sessionEntry.getValue().getWebSocketCode(), message);sendMessToOne(message, toSession);}} catch (Exception e) {log.error(e.getMessage(), e);}}public void sendMessToOne(String message, Session toSession) {try {// 尝试过锁住方法,还是不行,这里锁住webSocketMap,让多线程,访问不同对象,也能同步synchronized(GlobalParams.webSocketModelMap){String toId = toSession.getId();if (StringUtils.isNotBlank(toId) && GlobalParams.webSocketModelMap.containsKey(toId)) {GlobalParams.webSocketModelMap.get(toId).getSession().getBasicRemote().sendText(message);}}} catch (Exception e) {log.error("服务端发送消息给客户端失败,"+e.getMessage(),e);}}}

其中,synchronized(GlobalParams.webSocketModelMap)中GlobalParams.webSocketModelMap是我记录当前在线的websocket的信息。上边代码的注释中已经写了,这个锁的目的是为了解决websocket服务端下发时出现的错误“The remote endpoint was in state [STREAM_WRITING] which is an invalid state for called method”的错误,问题的引发场景和分析个人记录如下: 

1.因为在 @OnMessage中,我有两个方法同时使用了session,导致session多线程不安全,发生的频次少都可能不出现这个问题!

2.JSON.toJSONString(GlobalParams.webSocketModelMap) 其中带有session,会引发这个问题 解决办法:加异步锁,但是需要锁定 ConcurrentHashMap<String, WebSocketModel>。

使用

@Resource
private WebsocketController websocketService;
try{websocketService.sendMessToOne(sendMes, toSession);
}catch (Exception e){log.error(e.getMessage(),e);
}

5、结束

连接地址:ws://IP:PORT/websocket/monitor/{code} ,其中code是你自己定义的值。


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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

20230924清远博物馆和图书馆

为了漂流来清远&#xff0c;但是一个城市&#xff0c;想快速了解她的年龄&#xff0c;不就得去博物馆图书馆吗&#xff0c;云想衣裳花想容&#xff0c;春风拂槛露华浓。若非群玉山头见&#xff0c;会向瑶台月下逢。 学校她也曾因历史而不断迁移。 清远她呀&#xff0c;原来已…

计算机二级python简单应用题刷题笔记(一)

计算机二级python简单应用题刷题笔记&#xff08;一&#xff09; 1、词频统计&#xff1a;键盘输入一组我国高校所对应的学校类型&#xff0c;以空格分隔&#xff0c;共一行。2、找最大值、最小值、平均分&#xff1a;键盘输入小明学习的课程名称及考分等信息&#xff0c;信息间…

【计算机网络笔记一】网络体系结构

IP和路由器概念 两台主机如何通信呢&#xff1f; 首先&#xff0c;主机的每个网卡都有一个全球唯一地址&#xff0c;MAC 地址&#xff0c;如 00:10:5A:70:33:61 查看 MAC 地址&#xff1a; windows: ipconfig / alllinux&#xff1a;ifconfig 或者 ip addr 同一个网络的多…

【计算机基础】让我们重新认识一下Visual Stduio及其操作,知识点汇总!!

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

Nodejs 相关知识

Nodejs是一个js运行环境&#xff0c;可以让js开发后端程序&#xff0c;实现几乎其他后端语言实现的所有功能&#xff0c;能够让js与其他后端语言平起平坐。 nodejs是基于v8引擎&#xff0c;v8是Google发布的开源js引擎&#xff0c;本身就是用于chrome浏览器的js解释部分&#…

C语言指向二维数组的四种指针以及动态分配二维数组的五种方式

文章目录 应用场景可能指向二维数组的指针动态分配二维数组 应用场景 当二维数组作为结构成员或返回值时&#xff0c;通常需要根据用户传递的参数来决定二维数组的大小&#xff0c;此时就需要动态分配二维数组。 可能指向二维数组的指针 如果现在有一个二维数组a[3][2]&…

OpenGLES:单纹理贴图

一.概述 最近疏于写博客&#xff0c;接下来会陆续更新这段时间OpenGLES的一些开发过程。 前两篇OpenGLES的博客讲解了怎样使用OpenGLES实现相机普通预览和多宫格滤镜 在相机实现过程中&#xff0c;虽然使用到了纹理&#xff0c;但只是在生成一个纹理之后&#xff0c;使用纹理…

vuepress+gitee免费搭建个人在线博客(无保留版)

文章目录 最终效果&#xff0c;一睹为快&#xff01;一、工具选型二、什么是VuePress三、准备工作3.1 node 安装3.2 Git安装3.3 Gitee账号注册 四、搭建步骤4.1 初始化VuePress4.2 安装VuePress4.3 初始化目录4.4 编写文章 五、部署到Gitee5.1 创建仓库5.2 个人空间地址设置4.3…

【JVM内存区域及创建对象的过程】

文章目录 JVM内存区域及创建对象的过程JVM内存区域JDK1.6、1.7、1.8内存区域的变化&#xff1f;创建对象的过程类的声明周期&#xff1a; JVM内存区域及创建对象的过程 JVM内存区域 JVM 内存区域最粗略的划分可以分为 堆 和栈&#xff0c;当然&#xff0c;按照虚拟机规范&…

2023华为杯数学建模竞赛E题

一、前言 颅内出血&#xff08;ICH&#xff09;是由多种原因引起的颅腔内出血性疾病&#xff0c;既包括自发性出血&#xff0c;又包括创伤导致的继发性出血&#xff0c;诊断与治疗涉及神经外科、神经内科、重症医学科、康复科等多个学科&#xff0c;是临床医师面临的重要挑战。…

Linux 操作技巧

目录 一、shell-命令解释器 二、Linux中的特殊符号 三、命令历史--history 一、shell-命令解释器 shell——壳&#xff0c;命令解释器&#xff0c;负责解析用户输入的命令 ——内置命令&#xff08;shell内置&#xff09; ——外置命令&#xff0c;在文件系统的某个目录下&…

如何取消显示Notepad++每行显示的CRLF符号

新电脑中重新安装了Nodepad&#xff0c;打开记事本后发现出现了许多黑底的CR|LF标记&#xff0c;特别碍眼。 如何取消呢&#xff1f; 视图 -> 显示符号 -> 取消勾选 显示行尾符操作步骤 预期效果

SpringBoot结合Vue.js+axios框架实现增删改查功能+网页端实时显示数据库数据(包括删除多条数据)

本文适用对象&#xff1a;已有基础的同学&#xff0c;知道基础的SpringBoot配置和Vue操作。 在此基础上本文实现基于SpringBoot和Vue.js基础上的增删改查和数据回显、刷新等。 一、实时显示数据库数据 实现步骤&#xff1a; 第1步&#xff1a;编写动态请求响应类&#xff1a…

Ubuntu上线一个JAVA环境微服务架构的系统

项目介绍 项目背景: 已经有一套系统,迁移部署到新服务器,并使用不同数据,相同框架,提供对应业务服务 单机测试,从裸机-系统安装-软件架构-部署-数据迁移-发版-上线,整体流程与思路分享,包含后端、数据,测试、网络、运维等相关事务。 项目目的: 部署并迁移系统,…

linux下CentOS安装mysql-5.7

linux下安装mysql只需要在root用户下安装&#xff0c;普通用户也能使用 1.检查&#xff1a; 通过以下两条命令查看改系统下是否已存在mysql。 ps ajx | grep mysql ps ajx | grep mariadb通过指令如果只显示如下两条信息&#xff0c;则当前系统下不存在MySQL。 就可以直接进…

【初阶数据结构】二叉树全面知识总结

二叉树详解 树的概念及其结构树的概念树的相关概念树的表示方法孩纸兄弟表示法双亲表示法&#xff08;并查集&#xff09; 树的实际应用 二叉树二叉树的概念二叉树的种类二叉树的性质二叉树的存储结构 二叉树顺序结构的实现堆的概念及结构堆向上、向下调整法堆的插入堆的删除堆…

华为云云耀云服务器L实例评测 | 使用Docker快速搭建博客系统

使用Docker快速搭建wordpress博客系统. 文章目录 使用Docker快速搭建wordpress博客系统.需要了解部署与管理工具介绍安装Docker配置镜像加速器下载镜像创建数据库容器创建wordpress博客容器访问博客初始化配置博客扩展和管理 WordPress总结 需要了解 本文主要讲述快速搭建自己的…

Apache Doris 快速入门

1. 基本概念 FE&#xff0c;Frontend&#xff0c;前端节点&#xff0c;接收用户查询请求&#xff0c;SQL解析&#xff0c;执行计划生成&#xff0c;元数据管理&#xff0c;节点管理等 BE&#xff0c;Backend&#xff0c;后端节点&#xff0c;数据存储&#xff0c;执行查询计划…

[npm]脚手架本地全局安装1

[npm]脚手架本地全局安装1 npm link 全局安装npm install 全局安装卸载全局安装的脚手架 该文章是你的脚手架已经开发完成的前提下&#xff0c;你想要本地全局安装该脚手架&#xff0c;便于本地使用脚手架的命令的情况 npm link 全局安装 如果本地开发的项目是个脚手架&#…

Redis学习笔记--002

Redis的JAVA客户端 文章目录 Redis的JAVA客户端一、Redis的Java客户端的种类二、Jedis2.1、使用步骤2.2、Jedis连接池 三、[SpringDataRedis](https://spring.io/projects/spring-data-redis)3.1、介绍3.2、RedisTemplate3.3、SpringDataRedis使用步骤3.4、SpringDataRedis的序…