“编程式 WebSocket” 实现简易 online QQ在线聊天项目

news2024/11/24 5:58:52

目录

一、需求分析与演示

1.1、需求分析

1.2、效果演示

二、客户端、服务器开发

2.1、客户端开发

2.2、服务器开发


一、需求分析与演示


1.1、需求分析

需求:实现一个 online QQ在线聊天项目,当用户登录上自己的账号后,将会显示在线,并可以向自己的好友进行在线聊天,退出登录后,将会把当前用户下线的消息推送给该用户的所有好友,并标识“下线”。

分析:以上需求中,当用户上线后,将玩家上线消息推送给他所有的好友,以及在聊天时,将消息及时的推送给好友,最核心的就是基于 WebSocket 的消息推送机制,接下里我们就来看看如何使用 WebSocket + Spring Boot 实现在线聊天功能~

1.2、效果演示

 

二、客户端、服务器开发


2.1、客户端开发

js 代码编写:创建 WebSocket 实例,重写以下四个方法:

  • onopen:websocket 连接建立成功触发该方法.
  • onmessage(重点实现):接收到服务器 websocket 响应后触发该请求.
  • onerror:websocket 连接异常时触发该方法.
  • onclose:websocket 连接断开时触发该方法.

另外,我们可以再加一个方法,用来监听页面关闭(刷新、跳转)事件,进行手动关闭 websocket,为了方便处理用户下线 如下:

        //监听页面关闭事件,页面关闭之前手动操作 websocket 
        window.onbeforeunload = function() {
            websocket.close();
        }

a)首先用户点击对应的聊天对象,开启聊天框(使用字符串拼接,最后使用 jQuery 的 html 方法填充即可),并使用 sessionStorage 通过对方的 id 获取聊天信息(这里我们约定以用户 id 为键,聊天信息为值进行存储)

Ps:sessionStotage 类似于 服务器开发中的 HttpSession ,以键值对的方式通过 setItem 方法存储当前用户,通过 getItem 方法获取会话信息。

        //点击用户卡片,开启聊天框
        function startChat(nickname, photo, id) {
            //修改全局对方头像和 id
            otherPhoto = photo;
            otherId = id;
            var containerRight = "";
            containerRight += '<div class="userInfo">';
            containerRight += '<span>'+nickname+'</span>';
            containerRight += '</div>';
            containerRight += '<div class="chatList">';
            containerRight += '</div>';
            containerRight += '<div class="editor">';
            containerRight += '<textarea id="messageText" autofocus="autofocus" maxlength="500" placeholder="请在这里输入您想发送的消息~"></textarea>';
            containerRight += '<div class="sendMsg">';
            containerRight += '<button id="sendButton" onclick="sendMsg()">发送</button>';
            containerRight += '</div>';
            containerRight += '</div>';
            //拼接
            jQuery(".container-right").html(containerRight);
            //清空聊天框
            
            //使用 sessionStorage 获取对话信息
            var chatData = sessionStorage.getItem(otherId);
            if(chatData != null) {
                //说明之前有聊天
                jQuery(".chatList").html(chatData);
            }
        }

为了方便获取当前用户,和对方信息,创建以下三个全局变量:

        //自己的头像
        var selfPhoto = "";
        //对方的头像和id
        var otherPhoto = "";
        var otherId = -1;

当前用户信息通过 ajax 获取即可,如下:

        //获取当前登录用户信息
        function getCurUserInfo() {
            jQuery.ajax({
                type: "POST",
                url: "/user/info",
                data: {},
                async: false,
                success: function(result) {
                    if(result != null && result.code == 200) {
                        //获取成功,展示信息
                        jQuery(".mycard > .photo").attr("src", result.data.photo);
                        jQuery(".mycard > .username").text(result.data.nickname+"(自己)");
                        //修改全局头像(selfPhoto)
                        selfPhoto = result.data.photo;
                    } else {
                        alert("当前登录用户信息获取失败!");
                    }
                }
            });
        }
        getCurUserInfo();

b)接下来就是当前用户发送消息给对方了,这时就需要用到我们的 websocket 消息推送机制,具体的,创建 websocket 实例,实现以下四大方法:

        //创建 WebSocket 实例
        //TODO: 上传云服务器的时候需要进行修改路径
        var host = window.location.host;
        var websocket = new WebSocket("ws://"+host+"/chat");

        websocket.onopen = function() {
            console.log("连接成功");
        }

        websocket.onclose = function() {
            console.log("连接关闭");
        }

        websocket.onerror = function() {
            console.log("连接异常");
        }

        //监听页面关闭事件,页面关闭之前手动操作 websocket 
        window.onbeforeunload = function() {
            websocket.close();
        }

        //处理服务器返回的响应(一会重点实现)
        websocket.onmessage = function(e) {
            //获取服务器推送过来的消息

        }

接着创建一个 sendMsg() 方法,用来发送聊天信息,首先还是一如既往的非空校验(发送的聊聊天消息不能为空),接着还需要校验消息推送的工具 websocket 是否连接正常(websocket.readState == websocket.OPEN 成立表示连接正常),连接正常后,首先将用户发送的消息在客户端界面上进行展示,再将发送的消息使用 JSON 格式(JSON.stringify)进行封装,这是 websocket 的 send 方法发送消息约定的格式,包装后使用 websocket.send 发送数据,接着不要忘记使用 sessionStorage 存储当前用户发送的消息,最后清除输入框内容,如下 js 代码:

        //发送信息
        function sendMsg() {
            //非空校验           
            var message = jQuery("#messageText");
            if(message.val() == "") {
                alert("发送信息不能为空!");
                return;
            }
            //触发 websocket 请求前,先检验 websocket 连接是否正常(readyState == OPEN 表示连接正常)
            if (websocket.readyState == websocket.OPEN) {
                //客户端展示
                var chatDiv = "";   
                chatDiv += '<div class="self">';
                chatDiv += '<div class="msg">'+message.val()+'</div>';
                chatDiv += '<img src="'+selfPhoto+'" class="photo" alt="">';
                chatDiv += '</div>';
                jQuery(".chatList").append(chatDiv);
                //消息发送给服务器
                var json = {
                    "code": otherId,
                    "msg": message.val()
                };
                websocket.send(JSON.stringify(json));
                //使用 sessionStorage 存储对话信息
                var chatData = sessionStorage.getItem(otherId);
                if(chatData != null) {
                    chatDiv = chatData + chatDiv;
                }
                sessionStorage.setItem(otherId, chatDiv);
                //清除输入框内容
                message.val("");
            } else {
                alert("当前您的连接已经断开,请重新登录!");
                location.assign("/login.html");
            }
        }

c)我们该如何接收对方推送过来的消息呢?这时候我们就需要来重点实现 websocket 的 onmessage 方法了~ onmessage 方法中有一个参数,这个参数便是响应信息,通过 .data 获取这个参数的 JSON 数据,这个 JSON 格式数据需要通过 JSON.parse 方法转化成 js 对象,这样就拿到了我们需要的约定的数据(约定的数据是前后端交互时同一的数据格式)~ 

这里的响应有以下 4 种可能,我们通过约定数据格式中的 msg 字段进行区分:

  1. 初始化好友列表(init)
  2. 推送上线消息(online)
  3. 下线(offline)
  4. 聊天消息(msg)

前三个响应都很好处理,这里主要讲一下第四个:“拿到聊天消息后,首先进行检验,只有对方的 id 和我们发送给对方消息时携带的对方 id 相同时,才将消息进行展示,最后使用 sessionStorage 存储对象信息”,如下代码:

        //处理服务器返回的响应
        websocket.onmessage = function(e) {
            //获取服务器推送过来的消息
            var jsonInfo = e.data;
            //这里获取到的 jsonInfo 是 JSON 格式的数据,我们需要将他转化成 js 对象
            var result = JSON.parse(jsonInfo);
            if(result != null) {
                //这里的响应有四种可能:1.初始化好友列表(init) 2.推送上线消息(online) 3.下线(offline) 4.聊天消息(msg)
                if(result.msg == "init") {
                    //1.初始化好友列表
                    var friendListDiv = "";
                    for(var i = 0; i < result.data.length; i++) {
                        //获取每一个好友信息
                        var friendInfo = result.data[i];
                        friendListDiv += '<div class="friend-card" id="'+friendInfo.id+'" onclick="javascript:startChat(\''+friendInfo.nickname+'\', \''+friendInfo.photo+'\','+friendInfo.id+')">';
                        friendListDiv += '<img src="'+friendInfo.photo+'" class="photo"  alt="">';
                        friendListDiv += '<span class="username">'+friendInfo.nickname+'</span>';
                        //判断是否在线
                        if(friendInfo.online == "在线") {
                            friendListDiv += '<span class="state" id="state-yes">'+friendInfo.online+'</span>';
                        } else {
                            friendListDiv += '<span class="state" id="state-no">'+friendInfo.online+'</span>';
                        }
                        friendListDiv += '</div>';
                    }
                    //拼接
                    jQuery("#friends").html(friendListDiv);
                } else if(result.msg == "online") {
                    //2.推送上线消息
                    var state = jQuery("#"+result.data+" > .state");
                    state.text("在线");
                    state.attr("id", "state-yes");
                } else if(result.msg == "offline"){
                    //3.推送下线消息
                    var state = jQuery("#"+result.data+" > .state");
                    state.text("离线");
                    state.attr("id", "state-no");
                } else if(result.msg == "msg"){
                    //4.聊天消息
                    var chatDiv = "";
                    chatDiv += '<div class="other">';
                    chatDiv += '<img src="'+otherPhoto+'" class="photo" alt="">';
                    chatDiv += '<div class="msg">'+result.data+'</div>';
                    chatDiv += '</div>';
                    //只有和我聊天的人的 id 和我们发送的对象 id 一致时,才将消息进行拼接
                    if(otherId == result.code) {
                        jQuery(".chatList").append(chatDiv);
                    } 
                    //使用 sessionStorage 存储对话信息
                    var chatData = sessionStorage.getItem(result.code);
                    if(chatData != null) {
                        chatDiv = chatData + chatDiv;
                    }
                    sessionStorage.setItem(result.code, chatDiv);

                } else {
                    //5.错误情况
                    alert(result.msg);
                }
            } else {
                alert("消息推送错误!");
            }
        }

这样客户端开发就完成了~(这里的 html 和 css 代码就不展示了,大家可以自己下来设计一下)

2.2、服务器开发

a)首先需要引入 websocket 依赖,如下:

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

b)创建一下两个类:

1. WebSocketAPI:继承 TextWebSocketHandler ,重写那四大方法,实现相应逻辑。

WebSocketConfig:用来配置 WebSocket 的类(让 Spring 框架知道程序中使用了 WebSocket),重写 registerWebSocketHandlers 方法,就是用来注册刚刚写到的 WebSocketAPI 类,将他与客户端创建的 WebSocket 对象联系起来,如下:

2. 其中  addInterceptors(new HttpSessionHandshakeInterceptor()) 就是在把 HttpSession 中的信息注册到 WebSocketSession 中,让 WebSocketSession 能拿到 HttpSession 中的信息。

这里直接上代码,每段代码的意思我都十分详细的写在上面了,如果还有不懂的 -> 私信我~

import com.example.demo.common.AjaxResult;
import com.example.demo.common.AppVariable;
import com.example.demo.common.UserSessionUtils;
import com.example.demo.entity.ChatMessageInfo;
import com.example.demo.entity.FollowInfo;
import com.example.demo.entity.UserInfo;
import com.example.demo.entity.vo.UserinfoVO;
import com.example.demo.service.FollowService;
import com.example.demo.service.UserService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class WebSocketAPI extends TextWebSocketHandler {

    @Autowired
    private UserService userService;

    @Autowired
    private FollowService followService;

    private ObjectMapper objectMapper = new ObjectMapper();

    //用来存储每一个客户端对应的 websocketSession 信息
    public static ConcurrentHashMap<Integer, WebSocketSession> onlineUserManager = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //用户上线,加入到 onlineUserManager,推送上线消息给所有客户端

        //1.获取当前用户信息(是谁在建立连接)
        // 这里能够获取到 session 信息,依靠的是注册 websocket 的时候,
        // 加上的 addInterceptors(new HttpSessionHandshakeInterceptor()) 就是把 HttpSession 中的 Attribute 都拿给了 WebSocketSession 中
        //注意:此处的 userinfo 有可能为空,因为用户有可能直接通过 url 访问私信页面,此时 userinfo 就为 null,因此就需要 try catch
        try {
            UserInfo userInfo = (UserInfo) session.getAttributes().get(AppVariable.USER_SESSION_KEY);
            //2.先判断当前用户是否正在登录,如果是就不能进行后面的逻辑
            WebSocketSession socketSession = onlineUserManager.get(userInfo.getId());
            if(socketSession != null) {
                //当前用户已登录,就要告诉客户端重复登录了
                session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(AjaxResult.fail(403, "当前用户正在登录,禁止多开!"))));
                session.close();
                return;
            }
            //3.拿到身份信息之后就可以把当前登录用户设置成在线状态了
            onlineUserManager.put(userInfo.getId(), session);
            System.out.println("用户:" + userInfo.getUsername() + "进入聊天室");
            //4.将当前在线的用户名推送给所有的客户端
            //4.1、获取当前用户的好友(相互关注)中所有在线的用户
            //注意:这里的 init 表示告诉客户端这是在初始化好友列表
            List<ChatMessageInfo> friends = getCurUserFriend(session);
            AjaxResult ajaxResult = AjaxResult.success("init", friends);
            //把好友列表消息推送给当前用户
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(ajaxResult)));
            //将当前用户上线消息推送给所有他的好友(通过 id)
            for(ChatMessageInfo friend : friends) {
                WebSocketSession friendSession = onlineUserManager.get(friend.getId());
                if(friendSession != null) {
                    friendSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.success("online", userInfo.getId()))));
                }
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
            //说明此时的用户未登录
            //先通过 ObjectMapper 包装成一个 JSON 字符串
            //然后用 TextMessage 进行包装,表示是一个 文本格式的 websocket 数据包
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.fail(403, "您尚未登录!"))));
        }
    }

    /**
     * 获取所有在线用户信息
     * @return
     */
    private List<ChatMessageInfo> getCurUserFriend(WebSocketSession session) throws IOException {
        //1.筛选出当前用户相互关注的用户
        //1.1获取当前用户所有关注的用户列表
        List<ChatMessageInfo> resUserinfoList = new ArrayList<>();
        try {
            UserInfo curUserInfo = (UserInfo) session.getAttributes().get(AppVariable.USER_SESSION_KEY);
            List<FollowInfo> followInfos = followService.getConcernListByUid(curUserInfo.getId());
            //好友列表(相互关注的用户列表)
            for(FollowInfo followInfo : followInfos) {
                //1.2获取被关注的人的 id 列表,检测是否出现了关注当前用户的人
                List<FollowInfo> otherList =  followService.getConcernListByUid(followInfo.getFollow_id());
                for(FollowInfo otherInfo : otherList) {

                    //1.3检测被关注的人是否也关注了自己
                    if(followInfo.getUid().equals(otherInfo.getFollow_id())) {
                        //1.4相互关注的用户
                        UserInfo friendInfo = userService.getUserById(otherInfo.getUid());
                        ChatMessageInfo chatMessageInfo = new ChatMessageInfo();
                        chatMessageInfo.setId(friendInfo.getId());
                        chatMessageInfo.setNickname(friendInfo.getNickname());
                        chatMessageInfo.setPhoto(friendInfo.getPhoto());
                        //设置在线信息(在 onlineUserManager 中说明在线)
                        if(onlineUserManager.get(friendInfo.getId()) != null) {
                            chatMessageInfo.setOnline("在线");
                        } else {
                            chatMessageInfo.setOnline("离线");
                        }
                        resUserinfoList.add(chatMessageInfo);
                    }
                }
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.fail(403, "您尚未登录!"))));
        }
        return resUserinfoList;
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        //实现处理发送消息操作
        UserInfo userInfo = (UserInfo) session.getAttributes().get(AppVariable.USER_SESSION_KEY);
        //获取客户端发送过来的数据(数据载荷)
        String payload = message.getPayload();
        //当前这个数据载荷是一个 JSON 格式的字符串,就需要解析成 Java 对象
        AjaxResult request = objectMapper.readValue(payload, AjaxResult.class);
        //对方的 id
        Integer otherId = request.getCode();
        //要发送给对方的消息
        String msg = request.getMsg();
        //将消息发送给对方
        WebSocketSession otherSession = onlineUserManager.get(otherId);
        if(otherSession == null) {
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.fail(403,"对方不在线!"))));
            return;
        }
        otherSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.success(userInfo.getId(),"msg",  msg))));
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        try {
            //用户下线,从 onlineUserManager 中删除
            UserInfo userInfo = (UserInfo) session.getAttributes().get(AppVariable.USER_SESSION_KEY);
            onlineUserManager.remove(userInfo.getId());
            //通知该用户的所有好友,当前用户已下线
            List<ChatMessageInfo> friends = getCurUserFriend(session);
            //将当前用户下线消息推送给所有他的好友(通过 id)
            for(ChatMessageInfo friend : friends) {
                WebSocketSession friendSession = onlineUserManager.get(friend.getId());
                if(friendSession != null) {
                    friendSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.success("offline", userInfo.getId()))));
                }
            }
        } catch(NullPointerException e) {
            e.printStackTrace();
            //说明此时的用户未登录
            //先通过 ObjectMapper 包装成一个 JSON 字符串
            //然后用 TextMessage 进行包装,表示是一个 文本格式的 websocket 数据包
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.fail(403, "您尚未登录!"))));
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        try {
            //用户下线,从 onlineUserManager 中删除
            UserInfo userInfo = (UserInfo) session.getAttributes().get(AppVariable.USER_SESSION_KEY);
            onlineUserManager.remove(userInfo.getId());
            //通知该用户的所有好友,当前用户已下线
            List<ChatMessageInfo> friends = getCurUserFriend(session);
            //将当前用户下线消息推送给所有他的好友(通过 id)
            for(ChatMessageInfo friend : friends) {
                WebSocketSession friendSession = onlineUserManager.get(friend.getId());
                if(friendSession != null) {
                    friendSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.success("offline", userInfo.getId()))));
                }
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
            //说明此时的用户未登录
            //先通过 ObjectMapper 包装成一个 JSON 字符串
            //然后用 TextMessage 进行包装,表示是一个 文本格式的 websocket 数据包
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(AjaxResult.fail(403, "您尚未登录!"))));
        }
    }

}

 

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

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

相关文章

我用nodejs和electron实现了一个简单的聊天软件-----chat 开源

翎&#x1f3a5;项目演示地址 &#x1f517;https://www.bilibili.com/video/BV1Fg4y1u76d/ 希望观众老爷给个免费的三连支持一下新人up主 ♻️项目基本介绍 翎是基于electron(vue2)和nodejs实现的简单聊天软件,其中用websocket和http进行通讯传递,数据库使用了mysql数据库,…

二进制插入与查找组成一个偶数最接近的两个素数

二进制插入 链接&#xff1a;二进制插入_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a;给定两个32位整数n和m&#xff0c;同时给定i和j&#xff0c;将m的二进制数位插入到n的二进制的第j到第i位,保证n的第j到第i位均为零&#xff0c;且m的二进制位数小于等于i-j1&#xff…

Qt Quick - Popup

Qt Quick - Popup使用总结一、概述二、Popup 的布局三、弹出分级四、弹出定位五、定制化一、概述 Popup是类似弹出式用户界面控件的基本类型。它可以与Window或ApplicationWindow一起使用。 import QtQuick.Window 2.2import QtQuick.Controls 2.12ApplicationWindow {id: win…

力推美团企业版 美团究竟意欲何为?

已经拥有930万活跃商家的美团公司&#xff0c;正在充分整合自身的“供应链”优势&#xff0c;冲向B端市场。 3月31日&#xff0c;据36氪消息显示&#xff0c;美团将于近期正式上线面向To B市场的业务“美团企业版”&#xff0c;定位企业消费赛道。美团企业版会为企业客户提供消…

ZeroTier 内网穿透

ZeroTier 内网穿透 官网注册账号&#xff0c;创建自己的局域网段, 登录官网 创建网络&#xff1a; 点击创建好的网络&#xff0c;进入设置界面进行设置, 选择 public 模式,点击入设置页面 地址随便选择 说明没有设备链接 下载客户端 &#xff0c;下载 安装客户端&#xf…

高级数据结构与算法 | 三元搜索树(Ternary Search Tree)

文章目录TernarySearchTree基本概念介绍原理插入查找删除代码实现TernarySearchTree 基本概念 介绍 Ternary Search Tree&#xff08;三元搜索树&#xff09;&#xff0c;它是由 Bentley 和 Sedgewick 在 1997 年提出的一种基于 Trie 的思想改良的一种数据结构&#xff0c;其…

【GCU体验】基于PyTorch + GCU跑通ResNet50模型并测试GCU性能

一、环境 地址&#xff1a;启智社区:https://openi.pcl.ac.cn/ 二、计算卡介绍 云燧T20是基于邃思2.0芯片打造的面向数据中心的第二代人工智能训练加速卡&#xff0c;具有模型覆盖面广、性能强、软件生态开放等特点&#xff0c;可支持多种人工智能训练场景。同时具备灵活的可…

win10 64位 环境下安装CUDA 11.8和 cuDNN v8.6.0

win10 64位 环境下安装CUDA 11.8和 cuDNN v8.6.0 1 安装 NVIDIA 显卡驱动程序 下载地址&#xff1a;http://www.nvidia.cn/Download/index.aspx?langcn ​​​​​​ 下载文件&#xff1a;531.41-desktop-win10-win11-64bit-international-nsd-dch-whql 选择适合自己电脑的显…

DeepFM论文翻译

1.摘要 为了最大化推荐系统的CTR&#xff0c;学习用户行为的复杂交叉特征很关键。 尽管有很大进步&#xff0c;现有的方法无论对低阶还是高阶的交叉特征&#xff0c;似乎还是有很强的bias, 或者需要专门的特征工程。 本文&#xff0c;我们证明了得出一个能强化高阶和低阶交叉特…

前端实现自动化测试

什么是前端测试 我们经常说的单元测试其实只是前端测试的一种。前端测试分为单元测试&#xff0c;UI 测试&#xff0c;集成测试和端到端测试。 ● 单元测试&#xff1a;是指对软件中的最小可测试单元进行检查和验证&#xff0c;通常指的是独立测试单个函数。 ● UI 测试&#…

2023美赛Y题二手帆船价格--成品论文、思路、数据、代码

2023美赛Y题二手帆船价格 第一时间在CSDN分享 最新进度在文章最下方卡片&#xff0c;加入获取一手资源&#xff1a;2023美赛Y题二手帆船价格–成品论文、思路、数据、代码 可以提供关于帆船特性的信息: BoatTrader (https://www.boattrader.com/):一个网站&#xff0c;允许您根…

WindowsGUI自动化测试项目实战+辛酸过程+经验分享

WindowsGUI自动化测试项目实战辛酸过程经验分享一、前言⚜ 起因⚜ 项目要求⚜ 预研过程⚜⚜ 框架选型⚜⚜ 关于UIaotumation框架⚜ 预研成果二、项目介绍&#x1f493; 测试对象&#x1f493; 技术栈&#x1f493; 项目框架说明三、项目展示&#x1f923; 界面实现效果&#x1…

【深度学习】windows10环境配置详细教程

【深度学习】windows10环境配置详细教程 文章目录【深度学习】windows10环境配置详细教程Anaconda31.安装Anaconda32.卸载Anaconda33.修改Anaconda3安装虚拟环境的默认位置安装cuda/cudnn1.安装合适的CUDA2.安装对应的CUDNN3.卸载CUDA/CUDNNconda虚拟环境独立安装cuda/cudnn1.搭…

随想录Day55--动态规划: 392.判断子序列 , 115.不同的子序列

392.判断子序列 思路 &#xff08;这道题也可以用双指针的思路来实现&#xff0c;时间复杂度也是O(n)&#xff09; 动态规划五部曲分析如下&#xff1a; 1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和…

基线配置管理在网络中的重要性

在网络环境中&#xff0c;配置通常被认为具有不可估量的价值&#xff0c;因为设备配置的微小变化可以在几分钟内成就或破坏整个网络基础设施。 这些配置分为两部分&#xff1a;启动配置和运行配置。在网络设备中&#xff0c;默认情况下&#xff0c;第一个配置版本被视为运行和…

el-input-number的精度问题

前言 el-input-number 饿了么的数字输入框组件&#xff0c;在项目中听常用的。而这个组件比较常用的属性就是精度设置&#xff0c;给组件添加属性precision 。 其实吧&#xff0c;之前一直没怎么研究&#xff0c;保留几位小数就直接填几就好了&#xff0c;比如保留两位小数&am…

4.mysql内置函数

目录 日期函数 字符串函数 数学函数 其它函数 日期函数 获得当前年月日:

<点云>Bin-picking数据集

题目&#xff1a;工业料仓拣选的大规模6D物体姿态估计数据集 Abstract 介绍了一种新的公共数据集&#xff0c;用于6D对象姿态估计和用于工业bin-picking的实例分割。数据集包括合成场景和真实场景。对于这两者&#xff0c;提供了包括6D姿势 (位置和方向) 的点云、深度图像和注…

【华为机试真题详解JAVA实现】—从单向链表中删除指定值的节点

目录 一、题目描述 二、解题代码 一、题目描述 输入一个单向链表和一个节点的值,从单向链表中删除等于该值的节点,删除后如果链表中无节点则返回空指针。 链表的值不能重复。 构造过程,例如输入一行数据为: 6 2 1 2 3 2 5 1 4 5 7 2 2 则第一个参数6表示输入总共6个节点,…

C++基础语法(内存管理)

我们在学习C语言的时候&#xff0c;可以在栈区中使用内存空间&#xff0c;但栈区的空间毕竟很有限而且随着栈的销毁&#xff0c;该栈里的数据都会被销毁掉。因此我们学习了堆&#xff0c;堆的空间比栈要大很多很多&#xff0c;并且堆区空间的数据&#xff0c;只要我们不主动释放…