基于 SpringBoot+WebSocket 无DB实现在线聊天室

news2024/9/22 9:58:06

0 项目说明

0.1 样例展示

test

0.2 源码地址

GitHub:https://github.com/ShiJieCloud/web-chat

Gitee:https://gitee.com/suitbaby/web-chat

GitCode:I’m Jie / web-chat · GitCode

1 WebSocket 简介

1.1 HTTP

常用的 HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 AJAX 请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

HTTP 协议交互流程如下:

image-20230503111001921

1.2 WebSocket

1.2.1 WebSocket 协议

为了实现服务器主动向客户端发起消息,WebSocket 产生了,WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 协议有两部分:握手(基于 HTTP 协议)和数据传输:

  • 来自客户端的握手,形式如下:

    GET ws://localhost/chat HTTP/1.1
    Host:localhost
    Upgrade:websocket
    Connection:Upgrade
    Sec-websocket-Key:dGh1IHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Extensions:permessage-deflate
    Sec-WebSocket-Version:13
    
  • 来自服务器的握手,形式如下:

    HTTP/1.1 101 switching Protocols
    Upgrade:websocket
    Connection:Upgrade
    Sec-Websocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-Websocket-Extensions:permessage-deflate
    

字段说明:

头名称说明
Connection:Upgrade标识该HTTP请求是一个协议升级请求
Upgrade:WebSocket协议升级为WebSocket协议
Sec-WebSocket-Version:13客户端支持WebSocket的版本
Sec-WebSocket-Key:客户端采用base64编码的24位随机字符序列,服务器接受客户端HTTP协议升级的证明。要求服务端响应一个对应加密的sec-WebSocket-Accept头信息作为应答
Sec-WebSocket-Extensions协议扩展类型

1.2.2 WebSocket 交互

WebSocket 协议客户端服务端交互流程如图:

image-20230503111833212

2 使用教程

2.1 客户端(浏览器)

2.1.1 WebSocket 对象

实现 WebSocket 的 Web 浏览器将通过 WebSocket 对象公开所有必需的客户端功能(主要指支持HTML5的浏览器)。

使用以下方式创建 WebSocket 对象:

var ws = new Websocket(url);

参数 url 格式说明:ws://ip地址:端口号/资源名称,例如:let ws = new WebSocket("ws://localhost:8080/wechat")

2.1.2 WebSocket 事件

WebSocket 对象的相关事件

事件事件处理程序描述
openwebsocket对象.onopen连接建立时触发
messagewebsocket对象.onmessage客户端接收服务端数据时触发
errorwebsocket对象.onerror通信发生错误时触发
closewebsocket对象.onclose连接关闭时触发

2.1.3 WebSocket 方法

WebSocket 对象的相关方法:

方法描述
send()使用连接发送数据

2.2 服务端(JAVA)

Tomcat 的 7.0.5 版本开始支持 WebSocket,并且实现了 Java WebSocket 规范(JSR356)。Java WebSocket 应用由一系列的WebSocketEndpoints组成。Endpoint 是一个 Java 对象,代表 WebSocket 链接的一端,对于服务端,我们可以视为处理具体 WebSocket 消息的接口,类似一个 Controller 处理 HTTP 请求一样。

我们可以通过使用编程式或注解的方式定义 Endpoint。

2.2.1 编程式

第一种是编程式,即继承 javax.websocket.Endpoint 类并实现其方法。

public abstract class Endpoint {
    /**
     * 新会话启动时触发的事件。
     */
    public abstract void onOpen(Session session, EndpointConfig config);
    /**
     * 会话关闭时触发的事件。
     */
    public void onClose(Session session, CloseReason closeReason) {
        // NO-OP by default
    }
    /**
     * 发生协议错误时触发的事件。
     */
    public void onError(Session session, Throwable throwable) {
        // NO-OP by default
    }
}

2.2.2 注解式

第二种是注解式,即定义一个 PoJo 类,在类上添加 @ServerEndpoint 注解,标识该类为 Endpoint 实例。

Endpoint 实例会在 WebSocket 握手时创建,并在客户端与服务端链接过程中长时间有效,最后在链接关闭时结束。在 Endpoint 接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:

方法含义描述注解
onClose当会话关闭时调用。@OnClose
onOpen当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法。@onOpen
onError当连接过程中异常时调用。@OnError

2.2.3 服务端接收客户端数据

  • 编程式:通过为javax.websocket包下的Session对象添加MessageHandler消息处理器来接收消息
  • 注解式:定义Endpoint时,可以在PoJo类上添加@OnMessage注解指定接收消息的方法。

2.2.4 服务端发送数据给客户端

发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过javax.websocket包下的Session对象的getBasicRemote()获取同步消息发送的实例,然后调用其sendXxx()方法就可以发送消息,可以通过Session.getAsyncRemote获取异步消息发送实例。

3 聊天室实现

3.1 页面布局

3.1.1 登录页面

用户输入昵称即可登录聊天室

image-20230509213309899

3.1.2 聊天界面

聊天室主界面左部分为用户列表,显示聊天室所有在线用户,右部分为聊天区,包含聊天记录和消息输入框

image-20230509213559573

3.2 实现流程

聊天室项目,WebSocket 工作流程图如下:

流程图

3.3 WebSocket 消息格式

  • 客户端→服务端

    {
    	"toName":"张三",
    	"message":"你好"
    }
    
  • 服务端→客户端

    • 系统消息格式

      {
      	"isSystem":true,
        "status":true,
      	"fromName":null,
        "message":[
          "李四","王五"
        ]
      }
      
  • 推送聊天消息格式

    {
    	"isSystem":false,
      "status":false,
    	"fromName":"张三",
      "message":"你好"
    }
    

3.4 工程创建

3.4.1 创建项目

创建一个 SpringBoot 项目,导入以下依赖:

<!--web 依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf 依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--lombok 依赖-->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
<!-- WebSocket 依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
  <version>3.0.0</version>
</dependency>

3.4.2 引入静态资源文件

image-20230509214511001

3.5 登录功能

3.5.1 HTML 代码

由于只需要用户输入昵称,所以使用最简单的 form 表单提交即可,Controller 接口为/chat,提交方式为 Post

<form class="theme-form" action="/chat" method="post">
  <h2 class="text-center">聊天室</h2>
  <p class="text-center">我们一起聊天吧</p>
  <div class="form-group">
    <input class="form-control" type="text" name="username" placeholder="请输入自定义昵称" required>
  </div>
  <div class="form-group mb-0">
    <div class="text-end mt-3">
      <button class="btn btn-primary btn-block w-100" type="submit">进入聊天室</button>
    </div>
  </div>
</form>

3.5.2 Java 代码

在Controller中添加处理登录的方法,用于接收前端传来的登录请求。

@PostMapping("/chat")
public String doLogin(String username,HttpSession httpSession, Model model){
  //生成用户信息
  String userId = UUID.randomUUID().toString().replace("-", "");
  String avatarUrl = "/static/image/avatar/avatar-"+(System.currentTimeMillis()%43+1)+".png";
  User user = new User(userId, username, avatarUrl);
  //存储user到userMap中
  UserDb.userMap.put(userId,user);
  //将用户id保存到 HttpSession 中
  httpSession.setAttribute("loginUserId", userId);
  model.addAttribute("loginUser", user);
  return "chat";
}

3.6 聊天功能

3.6.1 Java 代码

  1. 创建 pojo 类 ChatEndPoint,在类上添加 @ServerEndpoint 注解,并指定 WebSocket 请求路径。在类中创建onOpen()、onMessage()、onClose()方法,并标注对应的注解,用于处理对应的WS事件:

    @Component
    @ServerEndpoint(value = "/wechat")
    public class ChatEndPoint {
    
        /**
         * 连接建立时被调用
         */
        @OnOpen
        public void onOpen(Session session, EndpointConfig config) {
           
        }
    
        /**
         * 接收到客户端发送的数据时被调用
         */
        @OnMessage
        public void onMessage(String message, Session session) {
           
        }
    
        /**
         * 连接关闭时被调用
         */
        @OnClose
        public voidonClose(Session session) {
    
        }
    }
    
  2. 我们需要在ChatEndPoint对象中添加HttpSession成员变量,用于取出登录逻辑中存入的用户id

    // 声明一个httpSession对象,从中获取登录用户id
    private HttpSession httpSession;
    
  3. 再声明一个javax.websocket包下的Session对象,通过该对象可以调用方法向指定的客户端(用户)发送消息

    private Session session;
    
  4. 因为每一个客户端都会对应一个ChatEndPoint对象,所以我们需要使用集合将所有客户端的ChatEndPoint对象保存起来,方便后续对指定用户单独操作

    // 用Map集合存储所有客户端对应的ChatEndPoint对象
    private static Map<String, ChatEndPoint> onlineUsers = new ConcurrentHashMap<>();
    
  5. 接下来我们需要实现onOpen()方法,在方法体中完成 WebSocket 连接建立后的需要执行的操作逻辑:

    1. 保存当前WS Session对象
    2. 保存HTTPSession 对象
    3. 将当前 ChatEndPoint 对象存储到Map集合中
    4. 遍历Map集合,调用ChatEndPoint对象的 WebSocket Session 成员变量的 sendText()方法将当前所有在线用户推送给所有客户端,如:chatEndPoint.session.getBasicRemote().sendText(message)
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
      //将局部Session对象赋值给成员session
      this.session = session;
      //获取httpSession对象
      HttpSession httpSession = (HttpSession)config.getUserProperties().get(HttpSession.class.getName());
      this.httpSession = httpSession;
    
      String userId = (String) httpSession.getAttribute("loginUserId");
      System.out.println(userId+"登录聊天室...."+HttpSession.class.getName());
    
      //将当前 ChatEndPoint 对象存储到onlineUsers Map中
      onlineUsers.put(userId, this);
    
      //将当前在线用户的用户名推送给所有的客户端
      //1.生成系统消息
      String message = MessageUtils.getMessage(true, true,null, UserDb.userMap.values());
      //2.调用方法推送消息
      broadcastAllUsers(message);
    }
    
  6. 由于在聊天室项目中,当用户向另一个用户发送消息时,客户端才会向服务端发送消息,所以在 onMessage() 方法中,只实现转发消息的逻辑即可:

    1. 将客户端传来的JSON字符串格式的数据转为对象
    2. 获取需要转发的用户id
    3. 获取需要向用户转发的消息
    4. 通过用户id从Map集合中获取客户端对应的 ChatEndPoint 对象,调用 sendText()方法将消息发送
    //将消息转发送给指定用户
    @OnMessage
    public void onMessage(String message, Session session) {
      try {
        //将message的json格式转为message对象
        ObjectMapper mapper = new ObjectMapper();
        Message mess = mapper.readValue(message, Message.class);
        
        //获取接收数据
        String toUserId = mess.getToUserId();
        String data = mess.getMessage();
        
        //获取当前登录的用户
        String loginUserId = (String) httpSession.getAttribute("loginUserId");
        
        //生成推送给用户的消息格式
        String resultMessage = MessageUtils.getMessage(false, false, loginUserId, data);
        
        //使用指定用户的session对象发送消息给指定用户
        onlineUsers.get(toUserId).session.getBasicRemote().sendText(resultMessage);
      } catch (JsonProcessingException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    
  7. 当用户关闭浏览器时,我们需要在onClose()中将用户下线的消息发送给客户端

    @OnClose
    public void onClose(Session session) {
      String loginUserId = (String) httpSession.getAttribute("loginUserId");
      System.out.println(loginUserId + "WS连接关闭了....");
      
      //移除用户Session
      onlineUsers.remove(loginUserId);
      //移除用户昵称
      UserDb.userMap.remove(loginUserId);
    
      //通知所有客户端用户下线
      Set<String> set = new HashSet<>();
      set.add(loginUserId);
      //1.生成系统消息
      String message = MessageUtils.getMessage(true,false, null, set);
      //2.调用方法推送消息
      broadcastAllUsers(message);
    }
    
  8. 服务端的主要代码已经完成,除此之外,我们还需要添加一些配置,否则我们编写的WebSocket并不会起到作用。

    我们需要将 ServerEndpointExporter 的实例添加到Spring容器中,ServerEndpointExporter实例会自动扫描所有的服务器端点,并添加所有带有 @ServerEndpoint 注解的类

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
    

    注:如果使用的是外置的 Tomcat 容器,则不需要自己提供 ServerEndpointExporter,因为它将由 Tomcat 容器自己提供和管理。

  9. 假如我们启动项目,此时会报空异常,因为我们在处理用户登录的逻辑中,将登录用户的id存储在HttpSession中,而我们在处理WebSocket连接建立后的onOpen() 中,用EndpointConfig对象的getUserProperties()去获取存储的HttpSession对象,因为我们没有将HttpSession存储到EndpointConfig中,所以获取不到,返回Null。

    我们需要重写ServerEndpointConfig.Configurator类的modifyHandshake()方法,通过HandshakeRequest对象获取我们需要的HttpSession,存储到ServerEndpointConfig中,才能通过getUserProperties()去获取存储的HttpSession对象:

    @Configuration
    public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
        @Override
        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
            HttpSession httpSession = (HttpSession) request.getHttpSession();
            //将httpsession对象存储到配置对象ServerEndpointConfig中
            sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
        }
    }
    

    其中ServerEndpointConfig 接口继承了 EndpointConfig 接口,源码如下:

    public interface ServerEndpointConfig extends EndpointConfig{
    	······  
    }
    

3.6.2 HTML 代码

  1. 在页面上使用ul li标签显示聊天室用户列表

    <ul class="list custom-scrollbar" id="chat-user-list">
      <!--用户列表-->
    </ul>
    
  2. 显示正在聊天的用户信息:用户头像、用户昵称

    <div class="d-flex chat-header clearfix">
      <!--用户头像-->
      <img id="to-user-avatar" class="rounded-circle" src="/static/image/avatar/avatar-1.png">
      <div class="flex-grow-1">
        <div class="about">
          <span class="name" id="to-user-name">用户昵称</span>
        </div>
      </div>
    </div>
    
  3. 聊天记录区域

    <ul id="chat-note">
    	<!--聊天记录-->
    </ul>
    
  4. 信息输入区域

    <div class="input-group text-box">
      <input class="form-control input-txt-bx"
             id="message-to-send" type="text"
             name="message-to-send"
             placeholder="输入信息">
      <button id="send-message" class="btn btn-primary input-group-text" type="button">发送</button>
    </div>
    

3.6.3 JS 代码

  1. 当点击左侧用户列表的用户头像时,显示与该用户聊天的聊天界面

    • 设置用户头像、用户昵称
    • 显示聊天界面
    • sessionStorage中获取到与该用户的聊天记录并显示到聊天界面
    /*切换聊天界面*/
    function open_chat(toUId,toUname,toAUrl) {
      //设置聊天区用户信息
      toUserAvatar.attr('src',toAUrl);
      toUserName.text(toUname);
    
      //显示聊天框
      $('.call-chat-body').removeClass('fade');
      toUserId = toUId;
    
      //获取历史聊天记录
      let chatData = sessionStorage.getItem(toUId);
      if (chatData != null) {
        //将聊天记录渲染到聊天区
        $('#chat-note').html(chatData);
      } else {
        $('#chat-note').html("");
      }
    }
    

    注:聊天记录存储到sessionStorage,key为聊天用户的id,非当前登录用户的id,获取聊天记录时通过用户id获取即可

  2. 创建 WebSocket 对象,指定 URL,url 格式为:ws://ip地址:端口号/资源名称

    //创建ws对象
    let ws = new WebSocket("ws://localhost:8080/wechat");
    

    注:/wechat为服务端的@ServerEndpoint(value = "/wechat")value

  3. 绑定 WebSocket 接收服务端发送数据的 onmessage 事件。

    • 通过事件对象获取服务端发送的数据并转为JSON对象。
    • 如果是系统消息,则进一步判断是同步在线用户还是通知用户下线:
      • 同步在线用户:即遍历用户列表拼接HTML字符串,渲染到页面中。
      • 通知用户下线:则将该用户从用户列表中移除,如果有用户正和该用户聊天,则隐藏聊天界面。
    • 如果不是系统消息,则为转发用户发送的消息,首先判断当前是否正在与发消息的人聊天:
      • 如果是,则直接渲染到聊天界面中,并存储到 sessionStorage 中。
      • 如果不是,则直接存储到 sessionStorage 中。
    //接收到服务端推送的消息后触发
    ws.onmessage = function (e) {
      //获取服务端推送的消息
      let dataStr = e.data;
      //将dataStr转为json对象
      let res = JSON.parse(dataStr);
    
      //判断是否是系统消息
      if (res.system) {
        //系统消息
    
        //用户列表
        let message = res.message;
        if (res.status) {
          //用户在线,拼接html
          let str = "";
          for (let user of message) {
            let userId = user.id;
            let username = user.username;
            let userAvatarUrl = user.avatarUrl;
            if (userId != loginUserId) {
              str += "<li id='" + userId + "' class=\"clearfix\"><div class=\"d-flex\"><img class=\"rounded-circle user-image\" src='" + userAvatarUrl + "' <div class=\"flex-grow-1\">" +
                "<div class=\"about\"><span class=\"name\" οnclick=\"open_chat('" + userId+"','"+username+"','"+userAvatarUrl + "')\">" + username + "</span></div></div></div></li>";
            }
          }
          //添加html
          chat_user_list.html(str);
        } else {
          //下线用户
          for (let id of message) {
            $("#" + id).remove()
            if(toUserId==id){
              $('.call-chat-body').addClass('fade');
            }
          }
        }
      } else {
        //不是系统消息
    
        let fromUserId = res.fromUserId;
        let message = res.message;
    
        let str = "<li><div class=\"message my-message\">"+message+"</div></li>";
        //是否正在与发消息的人聊天
        if(toUserId==fromUserId){
          //是,显示
          $('#chat-note').append(str);
        }
        //存储
        let chatData = sessionStorage.getItem(fromUserId);
        if (chatData != null) {
          chatData+=str;
        } else {
          chatData=str;
        }
        sessionStorage.setItem(fromUserId,chatData);
      }
    }
    
  4. 点击【发送】按钮,发送消息。

    • 获取用户输入的消息内容。
    • 渲染到聊天界面中。
    • 将消息数据转为JSON格式的字符串,并调用WebSocket 的send()方法将数据发送给服务端,服务端在收到消息后会调用对应的 onMessage()方法进行处理。
    $('#send-message').click(function () {
        //获取输入的内容
        let data = $('#message-to-send').val();
        //清空输入框内容
        $('#message-to-send').val("");
    
        let str = "<li class=\"clearfix\"><div class=\"message other-message\" style='float:right'>" + data + "</div></li>";
        $('#chat-note').append(str);
    
        //推送消息给服务器
        let json = {"toUserId": toUserId, "message": data}
        ws.send(JSON.stringify(json));
    })
    
  5. 到此,聊天室项目就结束了,使用Maven打包成jar包部署到服务器上试试吧😄😄😄

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

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

相关文章

【牛客刷题专栏】0x27:JZ29 顺时针打印矩阵(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

2023年Pycharm安装教程,附详细图解

简介 PyCharm是一款Python IDE&#xff0c;其带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如&#xff0c; 调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制等等。此外&#xff0c;该IDE提供了一些高级功能&a…

面向对象构造顺序与析构顺序详解

#include<bits/stdc.h> using namespace std;class animal{public:animal(){cout<<"调用animal构造"<<endl;}~animal(){cout<<"调用animal析构"<<endl;} };class verhical{public:verhical(){cout<<"调用verhica…

服装供应链管理包含哪些内容,如何选择服装供应链管理系统?

服装供应链管理是指对服装工厂的采购、生产、物流、仓储和销售等环节的管理&#xff0c;包括原材料的采购、成品的制造、配送、零售等多个环节。 选择合适的服装供应链管理系统&#xff0c;能够帮助服装工厂提高供应链管理的效率、优化库存管理、降低运营成本、保证服装生产的品…

TCP协议三次握手四次挥手详细分析

在学习TCP协议的时候&#xff0c;接触最多的就是TCP的三次握手和四次挥手。关于这个的介绍大多数都是文字和图示来分析的&#xff0c;但是具体到协议的内容时&#xff0c;有可能还是不清楚&#xff0c;下面我就通过具体协议来分析一下这个过程。 这里使用Wireshark网络分析工具…

【Linux】进程与文件系统(上)

由于这部分的知识很多很多&#xff0c;分成两回 目录 1.文件描述符 文件描述符 1.文件描述符 首先我们看一下几个小问题 1.你真的理解文件原理和操作了吗&#xff1f; 这不是语言的问题&#xff0c;而是操作系统的问题 2.是不是只有C/C有文件操作&#xff1f; 其他语…

【数据结构】结构最复杂实现最简单的双向带头循环链表

【数据结构】结构最复杂实现最简单的双向带头循环链表 一、前言二、目标三、实现1、初始化工作2、尾插2.1、图解思路2.2、代码实现 3、尾删3.1、图解思路3.2、代码实现 4、打印链表5、头插5.1、图解思路5.2、代码实现 6、头删6.1、图解思路6.2、代码实现 7、查找8、随机插入8.1…

数据结构与算法基础(青岛大学-王卓)(2)

第二弹火爆来袭中 这波是单链表的内容整理&#xff0c;废话不多说&#xff0c;上小龙虾呀(又到了龙虾季节了&#xff0c;哎&#xff0c;口水直流了~~) beautiful的分割线 文章目录 第二弹火爆来袭中这波是单链表的内容整理&#xff0c;废话不多说&#xff0c;上小龙虾呀(又到了…

【致敬未来的攻城狮计划】— 连续打卡第二十七天:瑞萨RA RA2E1 的 BTN触摸按键

文章目录 由于一些特殊原因&#xff1a; 系列文章链接&#xff1a;&#xff08;其他系列文章&#xff0c;请点击链接&#xff0c;可以跳转到其他系列文章&#xff09;或者参考我的专栏“ 瑞萨MCU ”&#xff0c;里面是 瑞萨RA2E1 系列文章。 24.RA2E1的 DMAC——数据传输 25.R…

DB2_sql_问题

db2新增字段指定顺序 这个是不能做到的&#xff0c;除非把表删除重新创建的&#xff01; 原理是这样子的&#xff1a;当你创建表时系统会记录下你的SEQ-ID,就是字段的顺序号&#xff0c;这个是根据字段先后顺序来生成的&#xff0c;系统默认显示的时候也是根据这个来的&#x…

linux:工具(命令)vi、vim文本编辑器详解。

linux:工具(命令)vi/vim文本编辑器详解。 因此&#xff0c;本质上vi和vim是同种东西&#xff0c;后面也会合起来说&#xff0c;但是使用上会使用vim&#xff0c;因为vim是加强版。 使用形式&#xff1a; 无论退出还是进入都需要去到 “命令模式”。 当使用vi/vim时就会进入“命…

「高性能MySQL」读书笔记(1)- MySQL架构

一、前言 本系列主要是记录阅读「高性能MySQL」期间笔记&#xff0c;记录在日常使用中忽略的知识、模糊的点&#xff0c;主要面对有一定MySQL使用经验的开发者。 本文是针对于MySQL一些基础定义的解释说明&#xff0c;会非常浅显通俗易懂。 二、MySQL的逻辑架构 简单梳理My…

PCL学习九:Registration-配准

参考引用 Point Cloud Library黑马机器人 | PCL-3D点云 1. 点云中的数学 函数求导 对于函数 f ( x ) x 2 f(x)x^2 f(x)x2 其一阶导数也是 x x x 的函数&#xff1a; d f d x 2 x \frac{df}{dx}2x dxdf​2x其二阶导为常数&#xff0c;与 x x x 无关&#xff1a; d 2 f d x…

【漏洞分析】CVE-2021-0920 Linux内核垃圾回收机制中的竞争UAF漏洞

漏洞发现&#xff1a;该漏洞早在2016年被 RedHat 内核开发人员发现并披露&#xff0c;但 Linux 内核社区直到 2021 年重新报告后才对该漏洞进行修补&#xff08;patch&#xff09;。Google的威胁分析小组&#xff08;Threat Analysis Group&#xff09;发现该漏洞在野外被使用&…

shell脚本----基础命令

文章目录 一、sort命令二、uniq命令三、 tr命令四、cut命令 一、sort命令 sort命令以行为单位对文件内容进行排序&#xff0c;也可以根据不同的数据类型来排序&#xff0c;比较的原则是从首字符向后&#xff0c;一次按ASCII码的值进行比较&#xff0c;最后按序输出。 ASCII码…

【P17】JMeter 边界提取器(Boundary Extractor)

文章目录 一、准备工作二、测试计划设计 一、准备工作 慕慕生鲜&#xff1a; http://111.231.103.117/#/login 进入网页后&#xff0c;登录&#xff0c;页面提供了账户和密码 搜索框输入“虾” 右键检查或按F12&#xff0c;打开调试工具&#xff0c;点击搜索 二、测试计划设…

详细版易学版TypeScript - 元组 枚举

一、元组(Tuple) 数组:合并了相同类型的对象 const myArr: Array<number> [1, 2, 3]; 元组(Tuple):合并了不同类型的对象 // 定义元组时就要确定好数据的类型&#xff0c;并一一对应 const tuple: [number, string] [12, "hi"]; // 添加内容时&#xff0c;不…

SQLIST数据库编程

目录 数据库简介 1.常用数据库 2. SQLite基础 3.创建SQLite数据库 虚拟中sqlite3安装 基础SQL语句使用 sqlite3编程 数据库简介 1.常用数据库 大型数据库 &#xff1a;Oracle 中型数据库 &#xff1a;Server是微软开发的数据库产品&#xff0c;主要支持windows平台 小型数据库…

( 位运算 ) 190. 颠倒二进制位 ——【Leetcode每日一题】

❓190. 颠倒二进制位 难度&#xff1a;简单 颠倒给定的 32 位无符号整数的二进制位。 提示&#xff1a; 请注意&#xff0c;在某些语言&#xff08;如 Java&#xff09;中&#xff0c;没有无符号整数类型。在这种情况下&#xff0c;输入和输出都将被指定为有符号整数类型&a…

Vue.js自定义指令及用Vue实现简单的学生信息管理系统

目录 一、自定义指令v-mycolor 自定义指令生命周期&#xff1a; 二、使用钩子函数的自定义指令 三、Vue实现简单的学生信息管理系统 除了核心功能默认内置的指令&#xff0c;Vue.js允许注册自定义指令。添加一个自定义指令&#xff0c;有两种方式&#xff1a; &#xff08;1…