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

news2025/3/7 8:56:04

目录

    • 后端
      • 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 {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue", "/topic","/user");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public 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 {

    @Autowired
    private UserMessagesMapper userMessagesMapper;

    @Autowired
    private 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 转换为 String
        String 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(), // 使用当前时间戳作为唯一 ID
      senderId: userInfo.value.id, // Example sender
      receiverId: receiverIdAnswer.value, // Example receiver
      content: 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">
        <div
          v-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>
            <div
                class="message-bubble message-green"
                v-html="message.content"
              ></div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div style="border: 1px solid #ccc">
      <Toolbar
        style="border-bottom: 1px solid #ccc"
        :editor="editorRef"
        :defaultConfig="toolbarConfigPrivateMessages"
        mode="default"
      />
      <Editor
        style="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 {
    @Override
    public 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 的配置和处理消息的核心部分,剩下的步骤主要是根据具体业务需求做扩展,如身份验证、消息缓存、广播支持等。如果你的应用规模较大,可能还需要考虑负载均衡和消息队列等高可用性的设计。

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

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

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

相关文章

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;…

主机监控软件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…

.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…

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…

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; 多…

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--------------------------------------- 什么是…

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…

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;虚拟…

Word 中实现方框内点击自动打 √ ☑

注&#xff1a; 本文为 “Word 中方框内点击打 √ ☑ / 打 ☒” 相关文章合辑。 对第一篇增加了打叉部分&#xff0c;第二篇为第一篇中方法 5 “控件” 实现的详解。 在 Word 方框内打 √ 的 6 种技巧 2020-03-09 12:38 使用 Word 制作一些调查表、检查表等&#xff0c;通常…