当前位置: 首页 > article >正文

Spring WebSocket 与 STOMP 协议结合实现私聊私信功能

目录

    • 后端
      • pom.xml
      • Config配置类
      • Controller类
      • DTO
    • 前端
      • 安装相关依赖
      • websocketService.js接口
      • javascript
      • html
      • CSS
    • 效果展示
      • 简单测试连接:
    • 报错解决方法
      • 1、vue3 使用SockJS报错 ReferenceError: global is not defined
    • 功能补充拓展
      • 1. 安全性和身份验证
      • 2. 异常处理
      • 3. 消息广播的功能
      • 4. 配置 WebSocket 消息缓存和负载均衡
      • 5. 客户端连接管理
      • 6. WebSocket 消息格式和编码
      • 总结
    • 后面将继续完善,待更新...

后端

pom.xml

		<!-- Spring Boot WebSocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- Spring Boot 数据库支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- MySQL 驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- Thymeleaf(如果你使用了模板) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- Spring Boot Web 支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>

Config配置类

注意:允许源根据自己项目修改

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;@Configuration
@EnableWebSocketMessageBroker
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/queue", "/topic","/user");config.setApplicationDestinationPrefixes("/app");}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/ws")
//        在 WebSocket 握手时,我们可以通过 URL 参数或者 HTTP headers 传递用户身份信息。
//                .addInterceptors(new MyHandshakeInterceptor())// 添加拦截器.setAllowedOrigins("http://127.0.0.1:8889", "http://localhost:8889", "http://localhost:8888", "http://127.0.0.1:8888", "http://localhost:8000","http://localhost:8890","http://127.0.0.1:8890").withSockJS();  // 添加 SockJS 支持}
}

Controller类

import com.tianwen.mapper.UserMessagesMapper;
import com.tianwen.user.dtos.MessageDTO;
import com.tianwen.user.pojos.Messages;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import java.time.LocalDateTime;@Controller
public class ChatController {@Autowiredprivate UserMessagesMapper userMessagesMapper;@Autowiredprivate SimpMessagingTemplate messagingTemplate; // 注入消息模板,用于发送消息到指定目的地@MessageMapping("/chat.sendMessage")//这个注解用来监听来自前端的 WebSocket 消息,路径是 /app/chat.sendMessage,当前端发送消息到这个路径时,sendMessage 方法会被触发。
//    @SendTo("/topic/messages")//这个注解表明处理完消息后,返回的消息将广播给订阅了 /topic/messages 路径的所有客户端。public MessageDTO sendMessage(MessageDTO messageDTO) throws Exception {System.out.println("接收到的message:"+messageDTO);// 1. 可以在这里进行私信存储到数据库操作Messages messages = new Messages();messages.setSenderId(messageDTO.getSenderId());messages.setReceiverId(messageDTO.getReceiverId());messages.setContent(messageDTO.getContent());messages.setCreateTime(LocalDateTime.now());// 2. 保存私信消息(插入操作)if (userMessagesMapper.insert(messages) <= 0) {// 如果插入失败,可以返回错误或做其他处理return null;}// 3. 实时将消息转发给接收者String receiverIdStr = String.valueOf(messageDTO.getReceiverId());  // 将 receiverId 转换为 StringString receiverDestination = "/user/" + receiverIdStr + "/queue/messages";//通过 SimpMessagingTemplate 的 convertAndSendToUser 方法,将消息实时推送给接收者。//推送的目标是 /user/{receiverId}/queue/messages,该路径是给特定用户的私有消息队列。messagingTemplate.convertAndSendToUser(receiverIdStr, receiverDestination, messageDTO);return messageDTO;}
}

DTO

import lombok.Data;
@Data
public class MessageDTO {private Integer senderId;private Integer receiverId;private String content;
}

前端

安装相关依赖

npm install sockjs-client@latest
npm install @stomp/stompjs sockjs-client
npm install global    
npm i --save-dev @types/sockjs-client 

websocketService.js接口

注意:服务器地址根据自己的修改(application.yml)

// websocketService.js
// Stomp.js:用于处理 STOMP 协议,它在 WebSocket 基础上实现了消息订阅、发送等功能。
import { Stomp } from "@stomp/stompjs";
//SockJS:是一个用于实现 WebSocket 的库,它为 WebSocket 提供了回退机制(例如 HTTP 长轮询等),确保在不同浏览器和网络环境下的兼容性。
import SockJS from "sockjs-client/dist/sockjs.min.js";
export default {connect(onMessageReceived) {//使用 SockJS 和 Stomp 创建一个 WebSocket 客户端,连接到后端的 WebSocket 服务 http://localhost:8000/ws。const socket = new SockJS("http://localhost:8000/ws"); // 使用 SockJS 连接const stompClient = Stomp.over(socket);const userId = JSON.parse(localStorage.getItem("userId"));stompClient.connect({}, () => {console.log("本人消息队列ID:", userId);//在连接成功后,通过 stompClient.subscribe 订阅 /topic/messages,接收从后端广播过来的消息。// stompClient.subscribe("/topic/messages", (messageOutput) => {//   onMessageReceived(JSON.parse(messageOutput.body));// });// 订阅当前用户的私有消息队列stompClient.subscribe("/user/" + userId + "/queue/messages",(messageOutput) => {onMessageReceived(JSON.parse(messageOutput.body)); // 处理接收到的私聊消息});// 订阅当前用户的私有消息队列;// stompClient.subscribe(//   "/user/" + userId + "/queue/messages",//   function (messageOutput) {//     const message = JSON.parse(messageOutput.body);//     console.log("接收到私信:", message);//   }// );});// 连接到 WebSocket 后,订阅用户消息// stompClient.connect({}, function (frame) {//   // 获取当前用户ID//   const userId = getCurrentUserId(); // 假设这个方法能够获取当前用户的ID//   // 订阅接收者的消息队列// stompClient.subscribe(//   "/user/" + userId + "/queue/messages",//   function (messageOutput) {//     const message = JSON.parse(messageOutput.body);//     console.log("接收到私信:", message);//   }// );// });},sendMessage(message) {console.log("发送消息接口1:", message);const socket = new SockJS("http://localhost:8000/ws"); // 使用 SockJS 连接const stompClient = Stomp.over(socket);stompClient.connect({}, () => {//当用户输入消息时,通过 stompClient.send 方法将消息发送到 /app/chat.sendMessage,这个路径会将消息推送到后端进行处理。stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(message));});},
};

javascript

<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import type { IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
const editorRef = shallowRef();
import websocketService from "@/api/websocketService.js";
import {ref,onMounted,
} from "vue";
import websocketService from "@/api/websocketService.js";const receiverIdAnswer = ref();
const sendPrivateMessage = () => {dialogVisible.value = true;
};
const sendPrivateMessage = async (userId) => {receiverIdAnswer.value = userId;dialogVisible.value = true;const response = await getAuthorDetailsByUserId(userId);console.log("response", userId);privateMessagesUser.value = response.data;console.log("privateMessagesUser", privateMessagesUser.value);
};const dialogVisible = ref(false);interface Message {id: string;senderId: string;receiverId: string;content: string;
}const messages = ref<Message[]>([]); // 明确指定消息数组的类型const newMessage = ref("");onMounted(() => {websocketService.connect((message: Message) => {// 明确指定回调函数的参数类型messages.value.push(message);});
});const sendMessage = () => {if (newMessage.value.trim()) {const message: Message = {// 明确声明消息类型id: Date.now().toString(), // 使用当前时间戳作为唯一 IDsenderId: userInfo.value.id, // Example senderreceiverId: receiverIdAnswer.value, // Example receivercontent: newMessage.value,};websocketService.sendMessage(message);newMessage.value = "";}
};
const editorConfig: Partial<IEditorConfig> = {placeholder: "请输入...",MENU_CONF: {},
};
const handleCreated = (editor) => {editorRef.value = editor;
};
// 排除富文本的菜单项
const toolbarConfigPrivateMessages: Partial<IToolbarConfig> = {// toolbar 配置excludeKeys: ["headerSelect","blockquote","|","bold","underline","italic","group-more-style", // 排除菜单组,写菜单组 key 的值即可"color","bgColor","|","fontSize","fontFamily","lineHeight","bulletedList","numberedList","todo","group-justify","group-indent","insertLink","group-video","insertTable","codeBlock","divider","undo","redo","fullScreen",],
};
</script>

html

 <!-- 私信聊天框 --><el-dialog v-model="dialogVisible"><template #title><div style="text-align: center; font-weight: bold">{{ privateMessagesUser.username }}</div><hr class="line" /></template><div class="chat-container"><div class="messages" ref="messagesContainer"><divv-for="message in messages":key="message.id":class="{'my-message': message.senderId === userInfo.id,'other-message': message.senderId !== userInfo.id,}"><div style="display: flex; flex-direction: row"><div><el-image:src="userInfo.avatarUrl"style="width: 45px; border-radius: 50%"></el-image></div><div style="margin-top: 5px; margin-left: 10px"><div><strong>{{ userInfo.username }}</strong></div><divclass="message-bubble message-green"v-html="message.content"></div></div></div></div></div></div><div style="border: 1px solid #ccc"><Toolbarstyle="border-bottom: 1px solid #ccc":editor="editorRef":defaultConfig="toolbarConfigPrivateMessages"mode="default"/><Editorstyle="height: 200px; overflow-y: hidden"v-model="newMessage"@keyup.enter="sendMessage":defaultConfig="editorConfig"mode="default"@onCreated="handleCreated"/></div><template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="sendMessage">发送</el-button></template></el-dialog>

CSS

<style scoped>
/* 私信样式 */
/* 标题居中 */
/* .private-message-dialog {
} */
.line {border-top: 1px solid #ccc; /* 直线的样式,可以修改颜色 */
}/* 聊天框滚动 */
.chat-container {display: flex;flex-direction: column;height: 300px;overflow-y: auto;box-sizing: border-box; /* 让 padding 和 border 包含在宽度和高度内 */
}.chat-container > * {width: 100%; /* 确保所有子元素不会超出容器宽度 */box-sizing: border-box; /* 确保子元素的宽度计算不受 padding 和 border 影响 */
}/* 消息容器 */
.messages {display: flex;flex-direction: column;gap: 10px;padding: 10px;max-height: 250px;overflow-y: auto;
}/* 发送方和接收方的消息样式 */
.my-message {/* text-align: right; */border-radius: 10px;height: auto;/* padding: 5px 10px; */
}.other-message {/* text-align: left; */border-radius: 10px;/* padding: 5px 10px; */
}.message-bubble {/* max-width: 70%; */padding: 0px 10px;border-radius: 10px;/* height: 30px; *//* margin: 0px 0px 0px 0px; *//* word-wrap: break-word; *//* line-height: 1.4; */font-size: 14px;display: flex;align-items: center;justify-content: center;
}.message-green {background-color: #57c457; /* 微信消息绿色 */color: white;align-self: flex-end; /* 让气泡靠右显示 */
}
</style>

效果展示

在这里插入图片描述

简单测试连接:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

报错解决方法

1、vue3 使用SockJS报错 ReferenceError: global is not defined

解:

import SockJS from “sockjs-client”;

修改为:

import SockJS from “sockjs-client/dist/sockjs.min.js”;

并安装依赖

npm i --save-dev @types/sockjs-client

功能补充拓展

以下是一些可能的补充和优化,确保 WebSocket 能够顺利运行并且高效处理消息。

1. 安全性和身份验证

如果你的 WebSocket 服务需要进行身份验证(如用户登录),你可以考虑在 WebSocket 握手时验证用户身份。你可以在 WebSocketConfig 中添加一个 HandshakeInterceptor 来拦截握手请求,获取 HTTP header 或 URL 参数中的用户信息,确保只有经过身份验证的用户能够连接。

例如:

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/ws").addInterceptors(new MyHandshakeInterceptor()) // 添加拦截器.setAllowedOrigins("http://localhost:8889").withSockJS();
}

其中 MyHandshakeInterceptor 可以用来在 WebSocket 握手期间传递用户信息。

2. 异常处理

在 WebSocket 消息处理过程中,你可能会遇到一些异常,如数据库操作失败或消息传输失败等。在控制器中,你可以捕获这些异常并返回相应的错误消息,确保系统更加健壮。

例如:

@MessageMapping("/chat.sendMessage")
public MessageDTO sendMessage(MessageDTO messageDTO) {try {// 消息处理逻辑} catch (Exception e) {log.error("发送消息失败", e);return new MessageDTO("error", "消息发送失败");}
}

3. 消息广播的功能

目前,你的代码实现了将消息发送到指定用户的功能(私信)。如果你希望实现群聊功能或全局广播,可以进一步扩展 @SendTo 注解。这个注解的使用使得你可以将处理后的消息发送给所有订阅某个特定主题的客户端。

例如,广播消息给所有订阅 /topic/messages 的客户端:

@MessageMapping("/chat.sendMessage")
@SendTo("/topic/messages")
public MessageDTO sendMessage(MessageDTO messageDTO) {// 处理消息逻辑return messageDTO;  // 返回的消息会广播给所有订阅 /topic/messages 的客户端
}

4. 配置 WebSocket 消息缓存和负载均衡

如果你的系统需要处理大量的 WebSocket 连接,可能会面临性能和可扩展性的问题。在这种情况下,考虑使用 Redis 等消息队列作为消息代理,可以通过 @EnableWebSocketMessageBroker 配置远程消息代理。

例如,通过 Redis 实现消息广播:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/queue", "/topic", "/user");config.setApplicationDestinationPrefixes("/app");// 使用 Redis 消息代理config.setBrokerRegistry().setApplicationDestinationPrefixes("/app").enableStompBrokerRelay("/topic", "/queue").setRelayHost("localhost").setRelayPort(61613);  // 配置Redis(如ActiveMQ)等消息中间件}
}

5. 客户端连接管理

如果你希望在某个用户断开连接时进行一些清理工作(例如清理会话、推送通知等),你可以使用 @OnDisconnect 注解来捕获断开连接事件。

@MessageMapping("/chat.disconnect")
public void handleDisconnect(SessionDisconnectEvent event) {// 用户断开连接时执行的逻辑log.info("用户断开连接,sessionId: " + event.getSessionId());
}

6. WebSocket 消息格式和编码

确保你的客户端和服务端使用相同的消息格式。你可能需要为 WebSocket 消息提供适当的序列化和反序列化器,以确保消息能够正确地从客户端传输到服务端,以及从服务端传输回客户端。

例如,使用 Jackson 或其他库将 Java 对象序列化为 JSON 格式:

@MessageMapping("/chat.sendMessage")
@SendTo("/topic/messages")
public MessageDTO sendMessage(MessageDTO messageDTO) throws Exception {// 消息传输过程中确保使用适当的 JSON 格式return messageDTO;
}

总结

大体上,已经完成了 WebSocket 的配置和处理消息的核心部分,剩下的步骤主要是根据具体业务需求做扩展,如身份验证、消息缓存、广播支持等。如果你的应用规模较大,可能还需要考虑负载均衡和消息队列等高可用性的设计。

后面将继续完善,待更新…

相关文章:

Spring WebSocket 与 STOMP 协议结合实现私聊私信功能

目录 后端pom.xmlConfig配置类Controller类DTO 前端安装相关依赖websocketService.js接口javascripthtmlCSS 效果展示简单测试连接&#xff1a; 报错解决方法1、vue3 使用SockJS报错 ReferenceError: global is not defined 功能补充拓展1. 安全性和身份验证2. 异常处理3. 消息…...

RabbitMQ5-死信队列

目录 死信的概念 死信的来源 死信实战 死信之TTl 死信之最大长度 死信之消息被拒 死信的概念 死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或直接到queue 里了&#xff0c;consumer 从 queue 取出消息进…...

[JavaScript] 面向对象编程

JavaScript 是一种多范式语言&#xff0c;既支持函数式编程&#xff0c;也支持面向对象编程。在 ES6 引入 class 语法后&#xff0c;面向对象编程在 JavaScript 中变得更加易于理解和使用。以下将详细讲解 JavaScript 中的类&#xff08;class&#xff09;、构造函数&#xff0…...

Windows上通过Git Bash激活Anaconda

在Windows上配置完Anaconda后&#xff0c;普遍通过Anaconda Prompt激活虚拟环境并执行Python&#xff0c;如下图所示&#xff1a; 有时需要连续执行多个python脚本时&#xff0c;直接在Anaconda Prompt下可以通过在以下方式&#xff0c;即命令间通过&&连接&#xff0c;…...

XSLT 编辑 XML:深度解析与实际应用

XSLT 编辑 XML&#xff1a;深度解析与实际应用 引言 XML&#xff08;可扩展标记语言&#xff09;和XSLT&#xff08;可扩展样式表语言转换&#xff09;是处理和转换XML数据的重要工具。本文将深入探讨XSLT在编辑XML文档中的应用&#xff0c;包括其基本概念、语法结构、以及实…...

主机监控软件WGCLOUD使用指南 - 如何设置主题背景色

WGCLOUD运维监控系统&#xff0c;从v3.5.7版本开始支持设置不同的主题背景色&#xff0c;如下 更多主题查看说明 如何设置主题背景色 - WGCLOUD...

C语言教程——文件处理(2)

目录 前言 一、顺序读写函数&#xff08;续&#xff09; 1.1fprintf 1.2fscanf 1.3fwrite 1.4fread 二、流和标准流 2.1流 2.2标准流 2.3示例 三、sscanf和sprintf 3.1sprintf 3.2sscanf 四、文件的随机读写 4.1fseek 4.2ftell 4.3rewind 五、文件读取结束的…...

ios打包:uuid与udid

ios的uuid与udid混乱的网上信息 新人开发ios&#xff0c;发现uuid和udid在网上有很多帖子里是混淆的&#xff0c;比如百度下&#xff0c;就会说&#xff1a; 在iOS中使用UUID&#xff08;通用唯一识别码&#xff09;作为永久签名&#xff0c;通常是指生成一个唯一标识&#xf…...

Spring Boot应用中实现基于JWT的登录拦截器,以保证未登录用户无法访问指定的页面

目录 一、配置拦截器进行登录校验 1. 在config层设置拦截器 2. 实现LoginInterceptor拦截器 3. 创建JWT工具类 4. 在登录时创建JWT并存入Cookie 二、配置JWT依赖和环境 1. 添加JWT依赖 2. 配置JWT环境 本篇博客将为大家介绍了如何在Spring Boot应用中实现基于JWT的登录…...

.NET9增强OpenAPI规范,不再内置swagger

ASP.NETCore in .NET 9.0 OpenAPI官方文档ASP.NET Core API 应用中的 OpenAPI 支持概述 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/overview?viewaspnetcore-9.0https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/ope…...

pytest自动化测试 - pytest夹具的基本概念

<< 返回目录 1 pytest自动化测试 - pytest夹具的基本概念 夹具可以为测试用例提供资源(测试数据)、执行预置条件、执行后置条件&#xff0c;夹具可以是函数、类或模块&#xff0c;使用pytest.fixture装饰器进行标记。 1.1 夹具的作用范围 夹具的作用范围&#xff1a; …...

hot100_234. 回文链表

给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;head …...

【超详细】ELK实现日志采集(日志文件、springboot服务项目)进行实时日志采集上报

本文章介绍&#xff0c;Logstash进行自动采集服务器日志文件&#xff0c;并手把手教你如何在springboot项目中配置logstash进行日志自动上报与日志自定义格式输出给logstash。kibana如何进行配置索引模式&#xff0c;可以在kibana中看到采集到的日志 日志流程 logfile-> l…...

谈谈对JavaScript 中的事件冒泡(Event Bubbling)和事件捕获(Event Capturing)的理解

JavaScript 中的事件冒泡&#xff08;Event Bubbling&#xff09;和事件捕获&#xff08;Event Capturing&#xff09;&#xff0c;是浏览器在处理事件时采用的两种机制&#xff0c;它们在事件的传播顺序上有显著区别。这两种机制帮助开发者在事件触发时&#xff0c;能够以不同…...

cherry USB 键盘分析

文章目录 cherry USB 键盘分析描述符结构设备描述符配置描述符集合配置描述符接口 1 描述符HID 描述符端点 IN 描述符接口 2 描述符HID 描述符端点 IN 描述符端点 OUT 描述符字符串描述符语言 ID (字符串索引为 0)厂商字符串(字符串索引为 1)产品字符串(字符串索引为 2)HID 报告…...

IPoIB(IP over InfiniBand)数据接收与发送机制详解

IPoIB&#xff08;IP over InfiniBand&#xff09;是一种在InfiniBand网络上实现IP协议的技术&#xff0c;它允许在InfiniBand网络上传输IP数据包。IPoIB通过将IP数据包封装在InfiniBand的数据包中&#xff0c;实现了在InfiniBand网络上的高效通信。本文将详细分析IPoIB如何接收…...

Spring Boot - 数据库集成04 - 集成Redis

Spring boot集成Redis 文章目录 Spring boot集成Redis一&#xff1a;redis基本集成1&#xff1a;RedisTemplate Jedis1.1&#xff1a;RedisTemplate1.2&#xff1a;实现案例1.2.1&#xff1a;依赖引入和属性配置1.2.2&#xff1a;redisConfig配置1.2.3&#xff1a;基础使用 2&…...

JAVAweb学习日记(八) 请数据库模型MySQL

一、MySQL数据模型 二、SQL语言 三、DDL 详细见SQL学习日记内容 四、DQL-条件查询 五、DQL-分组查询 聚合函数&#xff1a; 分组查询&#xff1a; 六、DQL-分组查询 七、分页查询 八、多表设计-一对多&一对一&多对多 一对多-外键&#xff1a; 一对一&#xff1a; 多…...

网易Android开发面试题200道及参考答案 (下)

说明原码、反码、补码的概念 原码:是一种简单的机器数表示法。对于有符号数,最高位为符号位,0 表示正数,1 表示负数,其余位表示数值的绝对值。比如,对于 8 位二进制数,+5 的原码是 00000101,-5 的原码是 10000101。原码的优点是直观,容易理解,但在进行加减法运算时,…...

16.知识图谱中的本体、实体、属性与关系:区别与联系

文章目录 1. 引言2. 知识图谱的基本概念2.1 本体&#xff08;Ontology&#xff09;2.2 实体&#xff08;Entity&#xff09;2.3 属性&#xff08;Attribute&#xff09;2.4 关系&#xff08;Relationship&#xff09; 3. 本体、实体、属性、关系的区别与联系3.1 区别3.2 联系 4…...

Scratch游戏作品 | 僵尸来袭——生存大战,保卫你的领地!

今天为大家推荐一款刺激十足的Scratch射击生存游戏——《僵尸来袭》&#xff01;在这个充满危机的世界里&#xff0c;僵尸不断向你袭来&#xff0c;利用你的战斗技能与策略道具生存下来&#xff0c;成为最后的幸存者&#xff01;✨ 源码现已在小虎鲸Scratch资源站免费下载&…...

C# 自定义随机字符串生成

目录 简介 调用方式&#xff1a; 详细说明 1.外层方法封装 2.定义字符组合 3.按长度生成 简介 随机字符串&#xff0c;包含数字&#xff0c;字母&#xff0c;特殊符号等&#xff0c;随机拼凑&#xff0c;长度可输入 为了生成效率&#xff0c;长度大于1024的&#xff0c…...

【C++探索之路】STL---string

走进C的世界&#xff0c;也意味着我们对编程世界的认知达到另一个维度&#xff0c;如果你学习过C语言&#xff0c;那你绝对会有不一般的收获&#xff0c;感受到C所带来的码云风暴~ ---------------------------------------begin--------------------------------------- 什么是…...

[LeetCode] 字符串 I — 344#反转字符串 | 541#反转字符串II | 54K替换数字

字符串 基础知识344# 反转字符串541# 反转字符串II54K 替换数字 基础知识 字符串的结尾&#xff1a;空终止字符00 char* name "hello"; // 字符串不可拓展&#xff08;由于是一个固定分配的内存块&#xff09;&#xff0c;有些地方必须加const char name2[5] {h,…...

使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化

使用 Docker 运行 Oracle Database 23ai Free 容器镜像并配置密码与数据持久化 前言环境准备运行 Oracle Database 23ai Free 容器基本命令参数说明示例 注意事项高级配置参数说明 总结 前言 Oracle Database 23ai Free 是 Oracle 提供的免费版数据库&#xff0c;基于 Oracle …...

rust 发包到crates.io/ 操作流程 (十)

第一步github登录 https://crates.io/ 在项目里面login&#xff1a; cargo login ciol4sMwaR61YvzWniodRlssk6RfS4HcZTU --registry crates-io如果不想每次带 这个&#xff0c;就执行 vim ~/.cargo/config.toml 添加下面 [registry] default "crates-io"git a…...

GD32L233RB 驱动数码管

1.数码管有8段A、B、C、D、E、F、G 和 H小数点 以及片选信号&#xff08;DIG&#xff09; DIG用来选择那一位&#xff0c;A-G 用来显示段 静态显示每次只能一次显示单个位 动态显示&#xff08;动态扫描&#xff09;所有的位显示结束要在10ms左右 显示2ms 消光1ms 实…...

MongoDB部署模式

目录 单节点模式&#xff08;Standalone&#xff09; 副本集模式&#xff08;Replica Set&#xff09; 分片集群模式&#xff08;Sharded Cluster&#xff09; MongoDB有多种部署模式&#xff0c;可以根据业务需求选择适合的架构和部署方式。 单节点模式&#xff08;Standa…...

国自然重点项目|代谢影像组学只能预测肺癌靶向耐药的关键技术与应用|基金申请·25-01-25

小罗碎碎念 今天和大家分享一个国自然重点项目&#xff0c;项目执行年限为2019.01 - 2023.12&#xff0c;直接费用为294万。 项目聚焦肺癌靶向治疗中药物疗效预测难题&#xff0c;整合多组学与代谢影像数据展开研究。 在研究过程中&#xff0c;团队建立动物模型获取多维数据&am…...

NFT Insider #166:Nifty Island 推出 AI Agent Playground;Ronin 推出1000万美元资助计划

引言&#xff1a;NFT Insider 由 NFT 收藏组织 WHALE Members、BeepCrypto 联合出品&#xff0c; 浓缩每周 NFT 新闻&#xff0c;为大家带来关于 NFT 最全面、最新鲜、最有价值的讯息。每期周报将从 NFT 市场数据&#xff0c;艺术新闻类&#xff0c;游戏新闻类&#xff0c;虚拟…...