当前位置: 首页 > 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…...

电商项目part02 电商后台多数据源

电商后台项目需要访问的数据源 多数据源方法&#xff08;读写分离&#xff09; 方法1&#xff1a;jdk自带的dynamicdatasource 方法2&#xff1a;Mybatis 方式 方法3&#xff1a;dynamicdatasource框架 <!--Druid连接池--><dependency><groupId>com.aliba…...

【C# 基础精讲】LINQ 基础

LINQ&#xff08;Language Integrated Query&#xff09;是一项强大的C#语言特性&#xff0c;它使数据查询和操作变得更加简洁、灵活和可读性强。通过使用LINQ&#xff0c;您可以使用类似SQL的语法来查询各种数据源&#xff0c;如集合、数组、数据库等。本文将介绍LINQ的基础概…...

ChatGPT成为工作工具,具体都应用在哪些地方?

Verified Market Research估计&#xff0c;到2030年&#xff0c;人工智能写作辅助软件市场将达到约65亿美元&#xff0c;复合年增长率为27%。生成式人工智能的浪潮正在席卷世界各地的营销部门。 Botco对美国1000名工作人员进行的调查发现&#xff0c;73%的人表示他们会利用生成…...

Shader学习(三)(片元着色器)

1、在片元着色器处理漫反射 // Upgrade NOTE: replaced _World2Object with unity_WorldToObjectShader "Custom/specularfragement" {properties{_sp("Specular",color) (1,1,1,1)_shiness("Shiness",range(1,64)) 8}SubShader{pass {tags{&…...

谷歌推出首款量子弹性 FIDO2 安全密钥

谷歌在本周二宣布推出首个量子弹性 FIDO2 安全密钥&#xff0c;作为其 OpenSK 安全密钥计划的一部分。 Elie Bursztein和Fabian Kaczmarczyck表示&#xff1a;这一开源硬件优化的实现采用了一种新颖的ECC/Dilithium混合签名模式&#xff0c;它结合了ECC抵御标准攻击的安全性和…...

前端常用的三种加密方式(MD5、base64、sha.js)

作为一名优秀的前端开发工程狮&#xff0c;保障用户的信息安全、密码义不容辞&#xff0c;废话不多说&#xff0c;由我来介绍三种日常开发中经常用到的加密方式。 一、MD5加密 介绍&#xff1a; MD5中文含义为信息-摘要算法5&#xff0c;就是一种信息摘要加密算法&#xff0c…...

alpine镜像时区设置

alpine镜像是一个完整的操作系统镜像&#xff0c;因为其小巧、功能完备的特点&#xff0c;非常适合作为容器的基础镜像。 如ubuntu、centos镜像动辄几百M的体积下&#xff0c;只有5M的alpine简直是一股清流。 当然alpine在维持体积小的情况下&#xff0c;必然牺牲一些东西。比…...

Java导入Excel,保留日期格式为文本格式

Java 读取Excel文件&#xff0c;防止日期格式变为数字 import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.*;import java.io.FileInputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date;public cla…...

uploadifive php上传进度条插件 解决动态传参数问题

uploadifive默认只能在加载时,静态传递参数.如果想让用户输入参数.再动态读取.传到后端,是没有直接的办法的 效果图 但我们可以通过settings.formData 来修改配置的方式传参数,完整演示 <form id"file_form{$v.id}" style"display:none"><hr/>…...

Lombok生成的Getter和Setter的名称对于“eMail”或“xAxis”等属性存在大小写转换异常

问题 最新开发中&#xff0c;遇到一个字段映射问题。我们先看问题案例&#xff1a; 明明代码中第二个字母是大写&#xff0c;结果测试接口时发现变成了小写字母。 分析 通过网上查询发现&#xff0c;这属于Lombok的bug。而且早在2015年就有人在GitHub上提出了issues。 Names o…...

数据库跟网站内容/网站模版

1. UISearchDisplayController.searchResultsTableView 的frame指定只有在 didShowSearchResultsTableView委托调用之后&#xff0c;反正我觉得系统会改动它的大小位置&#xff0c;所以我不得不写了一个重新定位它的frame&#xff0c;来覆盖系统的默认设置。 2. UISearchDis…...

seo网站建设是什么意思/朋友圈广告推广平台

VisualVM是一个以监控、显示本地或者远程服务器JVM工作情况&#xff0c;进行性能调优的工具。借助VisualVM&#xff0c;我们可以实现对JVM内存各个子池、CPU、垃圾收集器等方面进行监控&#xff0c;从而发现程序代码中潜在的泄露点和配置问题。 远程监控Linux JVM有两种连接方式…...

外贸自建站如何收款/网站优化要做哪些

保存照片和视频到系统相册显示- http://blog.csdn.net/chendong_/article/details/52290329 Android 7.0 之拍照与图片裁剪适配-http://blog.csdn.net/yyh352091626/article/details/54908624 拍照、相册及裁剪的终极实现&#xff08;一&#xff09;——拍照及裁剪功能实现- ht…...

旅游网站结构图/百度查重软件

近期学习 javafx 自己编写了一款软件 &#xff08;网上尽管也有类似功能的软件&#xff0c;可是界面不够美观&#xff0c;功能比較单一&#xff0c;或者操作比較复杂&#xff09; 软件官方网址&#xff1a;http://pcm.chujianyun.com 注&#xff1a;安装说明、使用说明、关于等…...

网站开发方案 文档/搜索引擎营销的实现方法有哪些

总第216篇/张俊红预测是时间序列相关知识中比较重要的一个应用场景。我们在前面说过时间序列数据(上)&#xff0c;时间序列可以分为平稳时间序列与非平稳时间序列两种。今天这一篇就主要介绍下《平稳时间序列》预测相关的方法。所谓平稳时间序列&#xff0c;就是随着时间的推移…...

邢台哪个公司做网站好/免费留电话号码的广告

1&#xff0c; 解压&#xff1a;gzip -d aix5-bfagent-7.1.1.4-0-0007.tar.gz tar -xvf aix5-bfagent-7.1.1.4-0-0007.tar 2&#xff0c;传文件&#xff0c;最好使用WinSCP但是选择使用ftp而不是SFTP 或者SCP 3&#xff0c;查看目录大小&#xff0c;因为RAFW…...