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

websocket + stomp + sockjs学习

文章目录

    • 学习链接
    • 后台代码
      • 引入依赖
      • application.yml
      • WebSocketConfig
      • PrivateController
        • WebSocketService
      • WebSocketEventListener
      • CorsFilter
    • 前端代码
      • Room.vue

学习链接

WebSocket入门教程示例代码,代码地址已fork至本地gitee,原github代码地址,源老外的代码地址

  • [WebSocket入门]手把手搭建WebSocket多人在线聊天室(SpringBoot+WebSocket)
  • [WebSocket]第二章:WebSocket集群分布式改造——实现多人在线聊天室
  • [WebSocket]使用WebSocket实现实时多人答题对战游戏

其它可参考

  • 手把手搭建WebSocket多人在线聊天室(SpringBoot+WebSocket),这个比较详细,排版看上去比较舒服

  • springboot集成websocket小案例 bilibili视频

  • SpringBoot+STOMP 实现聊天室(单聊+多聊)及群发消息详解

  • springboot+websocket构建在线聊天室(群聊+单聊)

  • 基于STOMP协议的WebSocket

  • spring websocket + stomp 实现广播通信和一对一通信 ,这个用法很详细

深入使用

  • SpringBoot——整合WebSocket(STOMP协议) 原创

  • SpringBoot——整合WebSocket(基于STOMP协议)

  • 点对点通信

补充学习

  • 【Springboot WebSocket STOMP使用 1】Springboot最小化配置启用STOMP,并实现浏览器JS通信

  • 【Springboot WebSocket STOMP使用 2】STOMP使用@SendToUser实现用户个人请求-响应

  • WebSocket的那些事(4-Spring中的STOMP支持详解),前面还有3篇

  • SpringBoot Websocket Stomp 实现单设备登录(顶号) ①

  • [WebSocket]之上层协议STOMP

后续使用rabbimtmq作为消息代理实现时,参考的文章

  • Docker容器添加映射端口的两种实现方法,因为需要rabbitmq需要开启rabbitmq_web_stomp插件、rabbitmq_web_stomp_examples插件,开启方式参考下面这个链接,然后需要在docker和主机之间开启端口映射
  • Rabbitmq报错:Connection refused: no further information: /ip:61613,使用rabbitmq作为消息代理,需要让我们的服务连接到rabbitmq,并且mq要开启rabbitmq_web_stomp插件、rabbitmq_web_stomp_examples插件

在这里插入图片描述

后台代码

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.timeless</groupId><artifactId>timeless-chat-websocket</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--<dependency>--><!--    <groupId>com.itheima</groupId>--><!--    <artifactId>pd-tools-swagger2</artifactId>--><!--    <version>1.0-SNAPSHOT</version>--><!--</dependency>--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- RabbitMQ Starter Dependency --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!-- Following additional dependency is required for Full Featured STOMP Broker Relay --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-reactor-netty</artifactId></dependency></dependencies></project>

application.yml

server:port: 8888
spring:application:name: timeless-chat-websocketdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/timeless_chat_websocket?serverTimeZone=UTCusername: rootpassword: rootmvc:pathmatch:# Springboot2.6以后将SpringMVC 默认路径匹配策略从AntPathMatcher 更改为PathPatternParser#matching-strategy: ANT_PATH_MATCHERrabbitmq:host: ${rabbitmq.host}port: ${rabbitmq.port}username: ${rabbitmq.username}password: ${rabbitmq.password}virtual-host: ${rabbitmq.virtual-host}
#pinda:
#  swagger:
#    enabled: true
#    title: timeless文档
#    base-package: com.timeless.controller
mybatis-plus:configuration:log-impl: com.timeless.utils.NoLog

WebSocketConfig

@Configuration
@Slf4j
@EnableWebSocketMessageBroker
@EnableConfigurationProperties(RabbitMQProperties.class)
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {private RabbitMQProperties rabbitMQProperties;public WebSocketConfig(RabbitMQProperties rabbitMQProperties) {this.rabbitMQProperties = rabbitMQProperties;log.info("连接rabbitmq, host: {}", rabbitMQProperties.getHost());}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry// 这个和客户端创建连接时的url有关,后面在客户端的代码中可以看到.addEndpoint("/ws").addInterceptors(new HandshakeInterceptor() {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {log.info("客户端握手即将开始===================【开始】");if (request instanceof ServletServerHttpRequest) {ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;log.info("请求路径: {}", ((ServletServerHttpRequest) request).getServletRequest().getRequestURL());log.info("校验请求头,以验证用户身份:  {}", JsonUtil.obj2Json(servletRequest.getHeaders()));HttpSession session = servletRequest.getServletRequest().getSession();attributes.put("sessionId", session.getId());return true;}log.info("客户端握手结束=================== 【失败】");return false;}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {log.info("客户端握手结束=================== 【成功】");}})// .setAllowedOrigins("http://localhost:8080")// 当传入*时, 使用该方法, 而不要使用setAllowedOrigins("*").setAllowedOriginPatterns("*").withSockJS();}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {// 1. 当客户端发送消息或订阅消息时,url路径开头如果是/app/xxx 时,会先解析stomp协议,然后路由到@controller的@MessageMapping("/xxx")的方法上执行。//    如果不设置,客户端所有发送消息或订阅消息时、都将去匹配@messageMapping。所以最好还是配置上。// 2. 这句表示客户端向服务端发送时的主题上面需要加"/app"作为前缀registry.setApplicationDestinationPrefixes("/app");// 1. 基于内存的消息代理// 2. 声明消息中间件Broker的主题名称,当向这个主题下发送消息时(js: stompclient.send("/topic/target1",{},"hello")),订阅当前主题的客户端都可以收到消息。//    注意:js 客户端如果发送时、直接是/topic/xxx,spring收到消息会直接发送给broker中。//    点对点发送时:enableSimpleBroker 中要配置 /user才可以用: template.convertAndSendToUser("zhangsan","/aaa/hello","111"),否则收不到消息// 3. 这句表示在topic和user这两个域上可以向客户端发消息registry.enableSimpleBroker("/topic", "/user");// 1. 点对点发送前缀// 2. 这句表示给指定用户发送(一对一)的主题前缀是 /userregistry.setUserDestinationPrefix("/user");// Use this for enabling a Full featured broker like RabbitMQ/*// 基于mq的消息代理registry.enableStompBrokerRelay("/topic").setVirtualHost(rabbitMQProperties.getVirtualHost()).setRelayHost(rabbitMQProperties.getHost()).setRelayPort(61613).setClientLogin(rabbitMQProperties.getUsername()).setClientPasscode(rabbitMQProperties.getPassword()).setSystemLogin(rabbitMQProperties.getUsername()).setSystemPasscode(rabbitMQProperties.getPassword()).setSystemHeartbeatSendInterval(5000).setSystemHeartbeatReceiveInterval(5000);*/}
}

PrivateController

@Slf4j
@RestController
public class PrivateController {@Autowiredprivate WebSocketService ws;// 1. 这个注解其实就是用来定义接受客户端发送消息的url(不能是topic开头,如果是topic直接发送给broker了,要用/app/privateChat)//    如果有返回值,则会将返回的内容转换成stomp协议格式发送给broker(主题名:/topic/privateChat)。如果要换主题名可使用@sendTo//    @SubscribeMapping注解和@messageMapping差不多,但不会再把内容发给broker,而是直接将内容响应给客户端,@MessageMapping("/privateChat")public void privateChat(PrivateMessage message) {ws.sendChatMessage(message);}// 客户端向 /app/broadcastMsg 发送消息, 将会使用该方法处理,// 并且因为此方法有返回值, 所以将结果又发送到/topic/broadcastMsg, 因此订阅了/topic/broadcastMsg的客户端将会收到此消息@MessageMapping("/broadcastMsg")@SendTo("/topic/broadcastMsg")public BroadcastMessage broadcastMsg(@Payload BroadcastMessage message,SimpMessageHeaderAccessor headerAccessor) {// 理解为会话添加属性标识headerAccessor.getSessionAttributes().put("extraInfo", message.getFromUsername());message.setContent("广播消息>>> " + message.getContent());return message;}// 客户端向 /app/userMsg 发送消息, 将会使用该方法处理,(谁请求, 则发送给谁, 不会发送给其它的用户)// 并且因为此方法有返回值, 所以将结果又发送到 /user/{username}/singleUserMsg, 其中username@MessageMapping("/userMsg")// broadcast设置为false表示: 将消息只回给发送此消息的会话用户(1个用户可能有多个会话)@SendToUser(value = "/singleUserMsg", broadcast = false)  // ?????????????????????????????????????public BroadcastMessage singleUserMsg(@Payload BroadcastMessage message,SimpMessageHeaderAccessor headerAccessor) {headerAccessor.getSessionAttributes().put("username", message.getFromUsername());message.setContent("用户消息>>> " + message.getContent());return message;}@Autowiredprivate SimpMessagingTemplate template;// 广播推送消息// (向此接口发送请求, 将会向所有的订阅了 /topic/broadcastMsg的客户端发送消息)@RequestMapping("/sendTopicMessage")public void sendTopicMessage(String content) {template.convertAndSend("/topic/broadcastMsg", content);}// 点对点消息@RequestMapping("/sendPointMessage")// (向此接口发送请求, 将会向所有的订阅了 /user/{targetUsername}/singleUserMsg 的客户端发送消息。//  这种方式调用的前提是需要registry.enableSimpleBroker("/topic", "/user");//                      registry.setUserDestinationPrefix("/user");)//  这2个同时配置了才能使用的public void sendQueueMessage(String targetUsername, String content) {this.template.convertAndSendToUser(targetUsername, "/singleUserMsg", content);}}

WebSocketService

@Service
public class WebSocketService {@Autowiredprivate SimpMessagingTemplate template;@Autowiredprivate PrivateMessageService privateMessageService;/*** 简单点对点聊天室*/public void sendChatMessage(PrivateMessage message) {message.setMessage(message.getFromUsername() + " 发送:" + message.getMessage());// 消息存储到数据库boolean save = privateMessageService.save(message);//可以看出template最大的灵活就是我们可以获取前端传来的参数来指定订阅地址, 前面参数是订阅地址,后面参数是消息信息template.convertAndSend("/topic/ServerToClient.private." + message.getToUsername(), message);if(!save){throw new SystemException(AppHttpCodeEnum.SYSTEM_ERROR);}}}

WebSocketEventListener

@Component
public class WebSocketEventListener {@Autowiredprivate SimpMessageSendingOperations messagingTemplate;public static AtomicInteger userNumber = new AtomicInteger(0);@EventListenerpublic void handleWebSocketConnectListener(SessionConnectedEvent event) {userNumber.incrementAndGet();messagingTemplate.convertAndSend("/topic/ServerToClient.showUserNumber", userNumber);System.out.println("我来了哦~");}@EventListenerpublic void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {userNumber.decrementAndGet();messagingTemplate.convertAndSend("/topic/ServerToClient.showUserNumber", userNumber);System.out.println("我走了哦~");}
}

CorsFilter

@WebFilter
public class CorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletResponse response = (HttpServletResponse) res;response.setHeader("Access-Control-Allow-Origin", "*");  response.setHeader("Access-Control-Allow-Methods", "*");  response.setHeader("Access-Control-Max-Age", "3600");  response.setHeader("Access-Control-Allow-Headers", "*");response.setHeader("Access-Control-Allow-Credentials", "true");chain.doFilter(req, res);  }  
}

前端代码

Room.vue

<template><div><h3 style="text-align: center">当前用户:{{ this.username }}</h3><h3 style="text-align: center">在线人数:{{ this.userNumber }}</h3><!--    <h3 style="text-align: center">在线用户:--><!--      <div v-for="user in usernameOnlineList" :key="user">{{ user }}</div>--><!--    </h3>--><div class="container"><div class="left"><h2 style="text-align: center">用户列表</h2><ul><li v-for="user in userList" :key="user.id" :class="{ selected: user.selected }" title="点击选择用户聊天"><div class="user-info"><span @click="selectUser(user)">{{ user.toUsername }}</span><!-- <div class="button-container"><el-buttonv-if="user.isFriend === 0"type="primary"size="mini"@click="sendFriendRequest(user)">申请加好友</el-button><el-buttonv-if="user.isFriend === 1"type="success"@click="sendMessage(user)">好友</el-button><el-button v-if="user.isFriend === 2" type="danger" disabled>申请中</el-button></div> --></div></li></ul></div><div class="right"><div v-if="selectedUser"><h2 style="text-align: center">正在与{{ selectedUser.toUsername }}聊天</h2></div><div v-if="selectedUser"><ul><li v-for="message in messageList[username + selectedUser.toUsername]" :key="message.id">{{ message }}</li></ul></div><div v-if="selectedUser"><div class="message-input"><el-input v-model="selectedUserMessage.message" placeholder="请输入内容" @keyup.enter.native="sendMsg"></el-input><div class="button-container"><el-button type="primary" @click="sendMsg">发送消息</el-button><el-button type="danger" @click="deleteAllMsgs">删除所有消息</el-button></div></div><div class="message-input"><el-input v-model="broadcastMsgContent" placeholder="请输入广播消息内容" @keyup.enter.native="sendMsg"></el-input><div class="button-container"><el-button type="primary" @click="sendBroadcastMsg">发送广播消息</el-button></div></div><div class="message-input"><el-input v-model="userMsgContent" placeholder="请输入userMsg" @keyup.enter.native="sendMsg"></el-input><div class="button-container"><el-button type="primary" @click="sendUserMsg">发送userMsg</el-button></div></div><div class="message-input"><el-input v-model="toTopicMsgContent" placeholder="请输入ToTopicMsg" @keyup.enter.native="sendMsg"></el-input><div class="button-container"><el-button type="primary" @click="sendToTopicMsg">发送ToTopicMsg</el-button></div></div></div></div></div><div><h1 class="bottom" style="text-align: center">好友申请</h1><h2 style="text-align: center; color: rgb(57, 29, 216)">功能开发中......</h2></div></div>
</template><script>
import { getAllUsers, listPrivateMessages, deleteAllMsg } from "@/api";
import SockJS from "sockjs-client";
import Stomp from "stompjs";
import { Message } from "element-ui";export default {name: "Room",data() {return {userList: [],groupList: [],selectedUser: null,message: "",stompClient: null,messageList: {}, // 使用对象来存储每个用户的聊天记录username: "",usernameOnlineList: [],userNumber: 1,selectedUserMessage: {user: null,message: "",},broadcastMsgContent: '',userMsgContent: '',toTopicMsgContent: '',};},methods: {listAllUsers() {getAllUsers(this.username).then((response) => {this.userNumber = ++response.data.userNumber;this.userList = response.data.friends.filter((user) => user.toUsername !== this.username);});},selectUser(user) {if (!this.messageList[this.username + user.toUsername]) {console.log(2222222)this.$set(this.messageList, this.username + user.toUsername, []);}// TODO 展示数据库中存在的信息,也就是聊天记录listPrivateMessages(this.username, user.toUsername).then((response) => {this.$set(this.messageList, this.username + user.toUsername, response.data);});this.selectedUser = user;this.selectedUserMessage.user = user;this.selectedUserMessage.message = ""; // 清空输入框内容this.userList.forEach((u) => {u.selected = false;});user.selected = true;},sendMsg() {if (this.stompClient !== null && this.selectedUserMessage.message !== "") {// 发送私聊消息给服务端this.stompClient.send("/app/privateChat",{},JSON.stringify({fromUsername: this.username,message: this.selectedUserMessage.message,toUsername: this.selectedUserMessage.user.toUsername,}));this.messageList[this.username + this.selectedUserMessage.user.toUsername].push(this.username + " 发送:" + this.selectedUserMessage.message);this.selectedUserMessage.message = ""; // 清空输入框内容} else {Message.info("请输入消息");}},sendBroadcastMsg() {if (this.stompClient !== null) {// 发送私聊消息给服务端this.stompClient.send("/app/broadcastMsg",{},JSON.stringify({fromUsername: this.username,content: this.broadcastMsgContent}));}},sendUserMsg() {if (this.stompClient !== null) {// 发送私聊消息给服务端this.stompClient.send("/app/userMsg",{},JSON.stringify({fromUsername: this.username,content: this.userMsgContent}));}},// 客户端也可以发送 /topic/xx, 这样订阅了 /topic/xx的客户端也会收到消息sendToTopicMsg() {if (this.stompClient !== null) {// 发送私聊消息给服务端this.stompClient.send("/topic/broadcastMsg",{},JSON.stringify({fromUsername: this.username,content: this.toTopicMsgContent}));}},deleteAllMsgs() {if (this.messageList[this.username + this.selectedUserMessage.user.toUsername] == "") {Message.error("当前没有聊天记录");return;}deleteAllMsg(this.username, this.selectedUser.toUsername).then((response) => {this.messageList[this.username + this.selectedUserMessage.user.toUsername] = [];Message.success("删除成功");});},connect() {//建立连接对象(还未发起连接)const socket = new SockJS("/api/ws");// 获取 STOMP 子协议的客户端对象 this.stompClient = Stomp.over(socket);window.stompClient = this.stompClient// 向服务器发起websocket连接并发送CONNECT帧 this.stompClient.connect({},(frame) => { // 连接成功时(服务器响应 CONNECTED 帧)的回调方法console.log("建立连接: " + frame);// 订阅当前个人用户消息this.stompClient.subscribe(`/user/${this.username}/singleUserMsg`, (response) => {console.log('收到当前点对点用户消息: ', response.body);})// 订阅当前个人用户消息2this.stompClient.subscribe(`/user/singleUserMsg`, (response) => {console.log('收到当前点对点用户消息2: ', response.body);})// 订阅广播消息this.stompClient.subscribe(`/topic/broadcastMsg`, (response) => {console.log('收到广播消息: ', response.body);})// 订阅 服务端发送给客户端 的私聊消息//(疑问: 订阅的范围如何限制?当前用户应该不能订阅别的用户吧?//  尝试: 每进来一个用户动态生成这个用户对应的一个标识, 然后, 这个用户订阅当前这个标识, //        其它没用如果想发消息给这个用户, 后台先查询这个用户标识, 然后发消息给这个用户,//        这样这个订阅路径就是动态的, 其它不是好友的用户就无法获取到这个动态生成的用户标识。)this.stompClient.subscribe("/topic/ServerToClient.private." + this.username,(result) => {this.showContent(JSON.parse(result.body).message,JSON.parse(result.body).fromUsername,JSON.parse(result.body).toUsername,)});// 订阅 服务端发送给客户端删除所有聊天内容 的消息this.stompClient.subscribe("/topic/ServerToClient.deleteMsg", (result) => {const res = JSON.parse(result.body);this.messageList[res.toUsername + res.fromUsername] = [];});// 订阅 服务端发送给客户端在线用户数量 的消息this.stompClient.subscribe("/topic/ServerToClient.showUserNumber", (result) => {this.userNumber = result.body;});});},disconnect() {if (this.stompClient !== null) {// 断开连接this.stompClient.disconnect();}console.log("断开连接...");},showContent(body, from, to) {// 处理接收到的消息// 示例代码,根据实际需求进行修改if (!this.messageList[to + from]) {this.$set(this.messageList, to + from, []); // 初始化选定用户的聊天记录数组}this.messageList[to + from].push(body); // 将接收到的消息添加到选定用户的聊天记录数组},},created() {},mounted() {// 从sessionStorage中获取用户名this.username = sessionStorage.getItem("username");console.log('username', this.username);if (!this.username) {this.$router.push('/login')return}this.connect();this.listAllUsers();// console.log(this.username);},beforeDestroy() {this.disconnect();},
};
</script><style scoped>
.container {display: flex;justify-content: space-between;margin: 10px;
}.left,
.middle,
.right {flex: 0.5;margin: 5px;padding: 10px;background-color: lightgray;
}.right {flex: 2;
}.bottom {margin-top: 20px;text-align: center;
}li {cursor: pointer;transition: color 0.3s ease;
}li:hover {color: blue;
}li.selected {color: blue;font-weight: bold;
}.send-button {display: flex;justify-content: flex-end;
}.message-input {display: flex;align-items: center;
}.button-container {margin-left: 10px;/* 调整间距大小 */
}.message-container {display: flex;justify-content: flex-end;
}.button-container {display: flex;justify-content: flex-end;
}.user-info {display: flex;align-items: center;
}.button-container {margin-left: auto;
}
</style>

相关文章:

websocket + stomp + sockjs学习

文章目录 学习链接后台代码引入依赖application.ymlWebSocketConfigPrivateControllerWebSocketService WebSocketEventListenerCorsFilter 前端代码Room.vue 学习链接 WebSocket入门教程示例代码&#xff0c;代码地址已fork至本地gitee&#xff0c;原github代码地址&#xff…...

ApplicationListener , @EventListener 和 CommandLineRunner 启动顺序验证

一. 背景 排查线上问题, 发现一个重要功能的全局锁线程启动延迟很高. 服务启动40分钟之后, 才能拿到锁. 排查之后发现原因是因为代码引入了高优先级的ApplicationListener代码, 导致全局锁线程启动延迟. 二. 结论 启动顺序从高到底依次为: ApplicationListener , EventListe…...

网络编程基础(1)

目录 网络编程解决是跨主机的进程间通讯 1、网络 2、互联网 3、ip地址 &#xff08;1&#xff09;ipv4: &#xff08;2&#xff09;ipV6:1 &#xff08;3&#xff09;IP地址的组成&#xff1a; (4)Linux查看IP地址&#xff1a;ifconfig 4、mac地址 5、ping Ip地址 6…...

Linux驱动开发(Day4)

思维导图&#xff1a; 字符设备驱动分步注册&#xff1a;...

LVS负载均衡群集部署(LVS-NAT模型实例)

一、集群 1.1集群的含义 Cluster&#xff0c;集群、群集,为解决某个特定问题将多台计算机组合起来形成的单个系统。 由多台主机构成&#xff0c;但对外只表现为一个整体。 1.2群集的三种类型 1.2.1负载均衡群集 LB&#xff1a; Load Balancing&#xff0c;负载均衡&#x…...

【仿写tomcat】五、响应静态资源(访问html页面)、路由支持以及多线程改进

访问html页面 如果我们想访问html页面其实就是将本地的html文件以流的方式响应给前端即可&#xff0c;下面我们对HttpResponseServlet这个类做一些改造 package com.tomcatServer.domain;import com.tomcatServer.utils.ScanUtil;import java.io.IOException; import java.io…...

stm32单片机/51单片机蜂鸣器不响(proteus模拟)

蜂鸣器不发生原因就1个&#xff1a;电压不够 所以需要提高蜂鸣器2端的电压&#xff1a;可以采用的方法有&#xff1a; 1提高蜂鸣器电阻&#xff0c;这样根据分压原理&#xff0c;可以提升蜂鸣器2段电压 2更改蜂鸣器的工作电压为更小的值&#xff0c;这个可以通过在proteus内…...

BERT、ERNIE、Grover、XLNet、GPT、MASS、UniLM、ELECTRA、RoBERTa、T5、C4

BERT、ERNIE、Grover、XLNet、GPT、MASS、UniLM、ELECTRA、RoBERTa、T5、C4 ELMOBERTERNIE![在这里插入图片描述](https://img-blog.csdnimg.cn/274e31d0f8274c748d05abe2ec65fc73.png)GroverXLNetGPTMASSUniLMELECTRARoBERTaT5C4ELMO BERT...

主机防护的重要性和方式

01 主机防护的重要性 主机防护是网络安全的重要组成部分。在互联网时代&#xff0c;网络攻击成为了一种常见的威胁&#xff0c;而主机防护则是保护计算机系统免受网络攻击的重要手段。 主机防护可以防范各种网络攻击&#xff0c;如病毒、木马、黑客攻击等&#xff0c;从而保…...

聚观早报 | 抢先体验阿维塔11座舱;本田和讴歌采用NACS充电标准

【聚观365】8月21日消息 抢先体验阿维塔11鸿蒙座舱 本田和讴歌采用特斯拉NACS充电标准 华为秋季新品发布会将于9月12日举行 iQOO Z8即将到来 三星Galaxy S24系列外观或更改 抢先体验阿维塔11鸿蒙座舱 当前&#xff0c;智能座舱成了各大巨头跑马圈地的重要领域。根据毕马威…...

思科计算机网络答案(包含第1~11章节)

第一章 1.在以下哪个场景中推荐使用 WISP? 选择一项: A.城市里的网吧 B.没有有线宽带接入的农村地区的农场 C.任何有多个无线设备的家庭 D.通过有线连接访问 Internet 的大厦公寓 2.一位员工希望以尽可能最安全的方式远程访问公司网络。 下列哪种网络特征将允许员工获得对…...

所见即所得,「Paraverse平行云」助力万间打造智能建造新图景

在城市建设行业中&#xff0c;数字化逐渐成为其主导力量。 新一代信息基础设施建设也迎来了新的里程碑。数据显示&#xff0c;截至今年&#xff0c;我国已全面推进城市信息模型&#xff08;CIM&#xff09;基础平台建设&#xff0c;为城市规划、建设管理提供了多场景应用的强大…...

AI图片处理功能演示

例如&#xff0c;这是一张不错的图片&#xff0c;但是有3只手。 我们可以选择有问题的区域&#xff0c;然后要求 niji 进行重新绘制。 根据我们选择的区域&#xff0c;我们可以以不同的方式修复结果。 创意修复 修复并不仅限于纠正错误。我们可以要求 niji 添加额外的元素&…...

CentOS系统环境搭建(六)——使用docker-compose安装redis

centos系统环境搭建专栏&#x1f517;点击跳转 关于Docker-compose安装请看CentOS系统环境搭建&#xff08;三&#xff09;——Centos7安装Docker&Docker Compose&#xff0c;该文章同样收录于centos系统环境搭建专栏。 Docker-compose安装redis 文章目录 Docker-compose安…...

个人论坛项目测试报告

目录 0.项目概述及部分测试用例展示 以下是部分测试用例&#xff1a; 进行一般的性能测试性能测试 1.摘要及版本修订记录 2.功能介绍 3.测试范围 3.1.功能性 3.2.可靠性 3.3.易用性 4.测试资源 4.1.人员介绍 4.2.测试环境 4.2.测试工具 5.测试策略 5.2.功能测试…...

一起来学shiny把(4)—调控控件进行输出

什么是shiny&#xff1f;Shiny是一个R包&#xff0c;可让您轻松地直接从 R 构建交互式 Web 应用程序&#xff08;应用程序&#xff09;。本系列是个长教程&#xff0c;带你由浅入深学习shiny。 上一节我们在文章《R语言系列教程—–一起来学shiny吧&#xff08;3&#xff09;》…...

VBIC卡管理系统设计与实现

摘要 IC卡管理系统是典型的信息管理系统(MIS),其开发主要包括后台数据库的建立和维护以及前端应用程序的开发两个方面。对于前者要求建立起数据一致性和完整性强、数据安全性好的库。而对于后者则要求应用程序功能完备,易使用等特点。 经过分析,我们使用 MICROSOFT公司的 …...

八种架构演进

日升时奋斗&#xff0c;日落时自省 目录 1、单机架构 2、应用数据分离架构 3、应用服务集群架构 4、读写分离/主从分离架构 5、冷热分离架构 6、垂直分库架构 7、微服务架构 8、容器编排架构 9、小结 1、单机架构 特征&#xff1a;应用服务和数据库服务器公用一台服务…...

商城-学习整理-高级-分布式事务(十九)

目录 一、本地事务1、事务的基本性质2、事务的隔离级别3、事务的传播行为4、SpringBoot 事务关键点 二、分布式事务1、为什么有分布式事务2、CAP 定理与 BASE 理论1、CAP 定理2、面临的问题3、BASE 理论4、强一致性、弱一致性、最终一致性 3、分布式事务几种方案1&#xff09;、…...

Java学习笔记(三):面向对象

文章目录 1.类与对象1.1 定义构造器1.2 定义成员变量1.3 定义方法1.4 static关键字 2. 面向对象的三大特征&#xff1a;封装、继承和多态2.1 封装2.2 继承2.2.1 子类重写父类的方法 2.3 多态 1.类与对象 类&#xff08;class&#xff09;和对象(object, 也被称为实例 instance…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...