云服务器部署WebSocket项目

WebSocket是一种在单个TCP连接上进行全双工通信的协议,其设计的目的是在Web浏览器和Web服务器之间进行实时通信(实时Web)

WebSocket协议的优点包括:

1. 更高效的网络利用率:与HTTP相比,WebSocket的握手只需要一次,之后客户端和服务器端可以直接交换数据

2. 实时性更高:WebSocket的双向通信能够实现实时通信,无需等待客户端或服务器端的响应

3. 更少的通信量和延迟:WebSocket可以发送二进制数据,而HTTP只能发送文本数据,并且WebSocket的消息头比HTTP更小

项目内容

1.WebSocketConfig

表示这是一个配置类,可以定义 Spring Bean

Spring 会扫描该类并将其中定义的 @Bean 方法返回的对象注册到应用上下文中

@Bean 方法

serverEndpointExporter 方法用来创建并注册一个 ServerEndpointExporter 实例

ServerEndpointExporter 是 Spring 提供的一个类,用于自动注册基于 Java 标准的 WebSocket 端点(由 @ServerEndpoint 注解标注的类)

它负责将 @ServerEndpoint 注解标记的 WebSocket 类注册到容器中

ServerEndpointExporter 的作用:

当应用运行在 Spring Boot 容器中时,ServerEndpointExporter 会扫描所有带有@ServerEndpoint 注解的类,并将其注册为 WebSocket 端点,适用于嵌入式的 Servlet 容器(如 Tomcat),如果使用的是独立的 Servlet 容器(如外部的Tomcat),则不需要配置 ServerEndpointExporter

package com.qcby.chatroom1117.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

2.ChatController

获取在线用户列表:

  • 调用 WebSocketServer.getWebSocketSet() 获取所有在线用户
  • 如果用户的 sid 不是 "admin",则添加到返回列表中

管理员发送消息:

  • 使用 @RequestParam 获取请求中的 sid(目标用户 ID)和 message(消息内容)
  • 调用 WebSocketServer.sendInfo 向指定用户发送消息
package com.qcby.chatroom1117.controller;import com.qcby.chatroom1117.server.WebSocketServer;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;@RestController
@RequestMapping("/api/chat")
public class ChatController {/*** 获取在线用户列表,不包含管理员*/@GetMapping("/online-users")public List<String> getOnlineUsers() {List<String> sidList = new ArrayList<>();for (WebSocketServer server : WebSocketServer.getWebSocketSet()) {//排除管理员if (!server.getSid().equals("admin")) {sidList.add(server.getSid());}}return sidList;}/*** 管理员发送消息给指定用户*/@PostMapping("/send")public void sendMessageToUser(@RequestParam String sid, @RequestParam String message) throws IOException {WebSocketServer.sendInfo(message, sid);}}

3.WebSocketServer

  • @OnOpen: 客户端连接时执行的操作,维护连接集合并记录用户的 sid
  • @OnClose: 客户端断开时从集合中移除,更新在线用户数
  • @OnMessage: 接收客户端消息,解析后发送到指定用户
  • sendMessage: 服务端向客户端单独发送消息
  • sendInfo: 群发或向指定客户端发送消息
  • getOnlineCount: 获取当前在线连接数
  • addOnlineCount & subOnlineCount: 管理在线人数的计数
  • @OnError: 捕获 WebSocket 连接中的异常,记录日志以便排查
package com.qcby.chatroom1117.server;import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;/*** WebSocket 服务端*/
@Component
@Slf4j
@Service
@ServerEndpoint("/api/websocket/{sid}")
public class WebSocketServer {//当前在线连接数private static int onlineCount = 0;//存放每个客户端对应的 WebSocketServer 对象private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();//用户信息private Session session;//当前用户的 sidprivate String sid = "";//JSON解析工具private static final ObjectMapper objectMapper = new ObjectMapper();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {this.session = session;this.sid = sid;webSocketSet.add(this); //加入集合addOnlineCount(); //在线数加1try {sendMessage("conn_success");log.info("有新窗口开始监听: " + sid + ", 当前在线人数为: " + getOnlineCount());} catch (IOException e) {log.error("WebSocket IO Exception", e);}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {webSocketSet.remove(this); //从集合中删除subOnlineCount(); //在线数减1log.info("释放的 sid 为:" + sid);log.info("有一连接关闭!当前在线人数为 " + getOnlineCount());}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message, Session session) {log.info("收到来自窗口 " + sid + " 的信息: " + message);//解析消息中的 targetSidString targetSid;String msgContent;try {Map<String, String> messageMap = objectMapper.readValue(message, Map.class);targetSid = messageMap.get("targetSid");msgContent = messageMap.get("message");} catch (IOException e) {log.error("消息解析失败", e);return;}//构造消息Map<String, String> responseMap = new HashMap<>();responseMap.put("sourceSid", sid);responseMap.put("message", msgContent);String jsonResponse;try {jsonResponse = objectMapper.writeValueAsString(responseMap);} catch (IOException e) {log.error("JSON 序列化失败", e);return;}//按 targetSid 发送消息for (WebSocketServer item : webSocketSet) {try {if (targetSid.equals(item.sid)) {item.sendMessage(jsonResponse);break; //找到目标用户后不再继续发送}} catch (IOException e) {log.error("消息发送失败", e);}}}/*** 判断是否是管理员*/private boolean isAdmin(String sid) {return "admin_sid".equals(sid);}/*** 发生错误时调用的方法*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("发生错误", error);}/*** 实现服务器主动推送*/public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}/*** 群发自定义消息*/public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {log.info("推送消息到窗口 " + sid + ",推送内容: " + message);for (WebSocketServer item : webSocketSet) {try {if (sid == null) {item.sendMessage(message); //推送给所有人} else if (item.sid.equals(sid)) {item.sendMessage(message); //推送给指定 sid}} catch (IOException e) {log.error("推送消息失败", e);}}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {return webSocketSet;}public String getSid() {return this.sid;}
}

 4.admin页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>管理员端 - 聊天窗口</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: Arial, sans-serif;display: flex;height: 100vh;margin: 0;background-color: #f4f7fc;color: #333;}/* 左侧在线用户列表 */#onlineUsersContainer {width: 250px;padding: 20px;background-color: #fff;border-right: 1px solid #ddd;box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);overflow-y: auto;}#onlineUsers {list-style-type: none;padding: 0;margin-top: 20px;}#onlineUsers li {padding: 10px;cursor: pointer;border-radius: 5px;transition: background-color 0.3s ease;}#onlineUsers li:hover {background-color: #e9f1fe;}#onlineUsers li.selected {background-color: #d0e7fe;}/* 右侧聊天窗口 */#chatBox {flex: 1;display: flex;flex-direction: column;padding: 20px;background-color: #fff;}#messages {border: 1px solid #ddd;height: 500px;overflow-y: scroll;margin-bottom: 20px;padding: 15px;background-color: #f9f9f9;border-radius: 10px;box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);}.message {padding: 10px;margin: 8px 0;border-radius: 10px;max-width: 80%;line-height: 1.6;word-wrap: break-word;}.message-right {background-color: #dcf8c6;text-align: right;margin-left: auto;}.message-left {background-color: #f1f0f0;text-align: left;margin-right: auto;}#messageInput {width: 80%;padding: 12px;border-radius: 25px;border: 1px solid #ccc;margin-right: 10px;font-size: 16px;transition: border-color 0.3s ease;}#messageInput:focus {border-color: #007bff;outline: none;}button {padding: 12px 20px;border-radius: 25px;border: 1px solid #007bff;background-color: #007bff;color: white;cursor: pointer;font-size: 16px;transition: background-color 0.3s ease;}button:hover {background-color: #0056b3;}h3 {font-size: 18px;color: #333;margin-bottom: 20px;}#onlineUsers li.unread {font-weight: bold;color: red;}@media (max-width: 768px) {#onlineUsersContainer {width: 100%;padding: 15px;}#chatBox {padding: 15px;}#messageInput {width: calc(100% - 100px);}button {width: 80px;}}</style>
</head>
<body>
<div id="onlineUsersContainer"><h3>在线用户</h3><ul id="onlineUsers"></ul>
</div>
<div id="chatBox"><h3>聊天窗口</h3><div id="messages"></div><div style="display: flex;"><input id="messageInput" type="text" placeholder="请输入消息"><button onclick="sendMessage()">发送</button></div>
</div><script>let websocket;const sid = "admin";let currentUserSid = null; //当前聊天对象的sidlet chatHistory = {}; //用于存储每个用户的聊天记录//页面加载时初始化window.onload = () => {connectWebSocket();getOnlineUsers(); //页面加载时刷新在线用户列表restoreSelectedUser(); //恢复选中的用户};function connectWebSocket() {websocket = new WebSocket("ws://localhost:8080/api/websocket/" + sid);websocket.onopen = () => {console.log("连接成功,管理员ID:" + sid);};websocket.onmessage = (event) => {try {let data;if (event.data.startsWith("{") && event.data.endsWith("}")) {data = JSON.parse(event.data); // 如果是有效的 JSON 格式,进行解析} else {// 如果是无效的 JSON(比如 "conn_success" 这样的字符串),进行处理console.log("接收到非 JSON 消息:", event.data);return;}const { sourceSid, message } = data;if (sourceSid) {//初始化聊天记录存储if (!chatHistory[sourceSid]) {chatHistory[sourceSid] = [];}//存储对方的消息chatHistory[sourceSid].push({ sender: 'left', message });//如果消息来源是当前聊天对象,更新聊天窗口if (sourceSid === currentUserSid) {displayMessages();} else {//消息来源不是当前对象,提示未读消息notifyUnreadMessage(sourceSid);}}} catch (e) {console.error("解析消息失败", e);}};websocket.onclose = () => {console.log("连接关闭");};websocket.onerror = (error) => {console.error("WebSocket发生错误", error);};}function notifyUnreadMessage(userSid) {const userListItems = document.querySelectorAll("#onlineUsers li");userListItems.forEach(item => {if (item.textContent === userSid) {item.classList.add("unread"); //添加未读消息样式}});}//清除未读消息提示function clearUnreadMessage(userSid) {const userListItems = document.querySelectorAll("#onlineUsers li");userListItems.forEach(item => {if (item.textContent === userSid) {item.classList.remove("unread");}});}function sendMessage() {const message = document.getElementById("messageInput").value;if (!currentUserSid) {alert("请选择一个用户进行聊天!");return;}if (message.trim() !== "") {websocket.send(JSON.stringify({ targetSid: currentUserSid, message }));chatHistory[currentUserSid] = chatHistory[currentUserSid] || [];chatHistory[currentUserSid].push({ sender: 'right', message });document.getElementById("messageInput").value = '';displayMessages();}}//显示当前用户的聊天记录function displayMessages() {const messagesDiv = document.getElementById("messages");messagesDiv.innerHTML = "";if (currentUserSid && chatHistory[currentUserSid]) {chatHistory[currentUserSid].forEach(msg => {const messageDiv = document.createElement("div");messageDiv.classList.add("message", msg.sender === 'right' ? "message-right" : "message-left");messageDiv.textContent = msg.message;messagesDiv.appendChild(messageDiv);});}scrollToBottom();}function scrollToBottom() {const messagesDiv = document.getElementById("messages");messagesDiv.scrollTop = messagesDiv.scrollHeight;}//获取在线用户列表(不包括管理员)function getOnlineUsers() {fetch("/api/chat/online-users").then(response => response.json()).then(users => {const userList = document.getElementById("onlineUsers");userList.innerHTML = "";users.forEach(user => {if (user !== "admin") {const li = document.createElement("li");li.textContent = user;li.onclick = () => selectUser(user, li);userList.appendChild(li);}});});}//选择用户进行聊天function selectUser(user, liElement) {//清除所有选中状态const userListItems = document.querySelectorAll("#onlineUsers li");userListItems.forEach(item => item.classList.remove("selected"));//高亮显示当前选中的用户liElement.classList.add("selected");if (currentUserSid !== user) {currentUserSid = user;//清除未读消息提示clearUnreadMessage(user);//显示与该用户的聊天记录displayMessages();//保存当前选中的用户localStorage.setItem('selectedUserSid', user);}scrollToBottom();}//恢复选中的用户function restoreSelectedUser() {const savedUserSid = localStorage.getItem('selectedUserSid');if (savedUserSid) {currentUserSid = savedUserSid;const userListItems = document.querySelectorAll("#onlineUsers li");userListItems.forEach(item => {if (item.textContent === savedUserSid) {item.classList.add("selected");}});displayMessages();}}
</script>
</body>
</html>

5.user页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>用户端 - 聊天窗口</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: Arial, sans-serif;background-color: #f0f4f8;display: flex;justify-content: center;align-items: center;min-height: 100vh;}#chatBox {position: fixed;bottom: 10px;right: 10px;width: 400px;height: 500px;background-color: #ffffff;border-radius: 8px;padding: 20px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);background: linear-gradient(to top right, #f9f9f9, #e9eff7);display: flex;flex-direction: column;max-height: 80vh;}#chatBox h3 {font-size: 20px;margin-bottom: 15px;color: #333;text-align: center;}#messages {flex: 1;border: 1px solid #ddd;padding: 15px;overflow-y: auto;background-color: #f9f9f9;border-radius: 8px;margin-bottom: 15px;font-size: 14px;color: #333;line-height: 1.5;box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1);}.message {padding: 10px;margin: 5px 0;border-radius: 8px;max-width: 80%;word-wrap: break-word;}.message-right {background-color: #dcf8c6;text-align: right;margin-left: auto;}.message-left {background-color: #f1f0f0;text-align: left;margin-right: auto;}#inputWrapper {display: flex;width: 100%;}#messageInput {width: calc(100% - 80px);padding: 12px;border-radius: 25px;border: 1px solid #ccc;margin-right: 10px;font-size: 16px;transition: border-color 0.3s ease;}#messageInput:focus {border-color: #007bff;outline: none;}button {padding: 12px 20px;border-radius: 25px;border: 1px solid #007bff;background-color: #007bff;color: white;cursor: pointer;font-size: 16px;transition: background-color 0.3s ease;width: 60px;display: inline-flex;align-items: center;justify-content: center;}button:hover {background-color: #0056b3;}@media (max-width: 768px) {#chatBox {width: 100%;bottom: 20px;padding: 15px;}#messageInput {width: calc(100% - 100px);}button {width: 70px;padding: 10px;}}</style><script>let websocket;const sid = Math.random().toString(36).substring(2, 15); //用户端的sidconst isAdmin = false; //这是用户端,管理员标识为falsefunction connectWebSocket() {websocket = new WebSocket("ws://localhost:8080/api/websocket/" + sid);websocket.onopen = () => {console.log("连接成功,用户ID:" + sid);document.getElementById("messages").innerHTML += `<div class="message-left">连接成功,您的ID是:${sid}</div>`;};websocket.onmessage = (event) => {try {let data;// 检查消息是否是有效的 JSONif (event.data && event.data.startsWith("{")) {data = JSON.parse(event.data);const { targetSid, message, sourceSid } = data;// 确保消息是发送给当前用户的if (sourceSid === "admin" || targetSid === sid) {document.getElementById("messages").innerHTML += `<div class="message-left">${message}</div>`;scrollToBottom();}} else {// 如果不是 JSON 格式,可以直接处理其他类型的消息document.getElementById("messages").innerHTML += `<div class="message-left">${event.data}</div>`;scrollToBottom();}} catch (e) {console.error("解析消息失败", e);}};websocket.onclose = () => {console.log("连接关闭");};websocket.onerror = (error) => {console.error("WebSocket发生错误", error);};}function sendMessage() {const message = document.getElementById("messageInput").value;const targetSid = "admin"; //目标为管理员if (message.trim() !== "") {websocket.send(JSON.stringify({ targetSid, message }));document.getElementById("messages").innerHTML += `<div class="message-right">${message}</div>`;document.getElementById("messageInput").value = '';scrollToBottom();}}function scrollToBottom() {const messagesDiv = document.getElementById("messages");messagesDiv.scrollTop = messagesDiv.scrollHeight;}connectWebSocket();</script>
</head>
<body>
<div id="chatBox"><h3>用户聊天窗口</h3><div id="messages"></div><div id="inputWrapper"><input id="messageInput" type="text" placeholder="请输入消息"><button onclick="sendMessage()">发送</button></div>
</div>
</body>
</html>

项目部署

1.准备云服务器

2.在服务器上安装jdk

(1)yum install -y java-1.8.0-openjdk-devel.x86_64

(2)输入java -version查看已安装的jdk版本

3.在服务器上安装tomcat

(1)sudo wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.75/bin/apache-tomcat-9.0.75.tar.gz

(2)解压后进入到文件目录,启动

3.修改项目

(1)修改pom文件

添加打包方式:

添加tomcat和websocket依赖:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope> <!-- 提示该依赖已由外部服务器提供 --></dependency><dependency><groupId>javax.websocket</groupId><artifactId>javax.websocket-api</artifactId><version>1.1</version></dependency>

添加插件:

            <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><configuration><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin>

(2)修改启动类

package com.qcby.chatroom1117;import javafx.application.Application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplication
public class ChatRoom1117Application extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication.run(ChatRoom1117Application.class, args);}@Override  //这个表示使用外部的tomcat容器protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {// 注意这里要指向原先用main方法执行的启动类return builder.sources(ChatRoom1117Application.class);}}

(3)修改前端代码

4.打包

先执行clean,再执行install

5.上传war包到tomcat文件夹的webapp目录下

6.重新启动tomcat,访问

用户端 - 聊天窗口icon-default.png?t=O83Ahttp://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/user

管理员端 - 聊天窗口icon-default.png?t=O83Ahttp://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/admin

 

至此,部署完成

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

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

相关文章

二十:HTML Form表单提交时的协议格式

在Web开发中,HTML表单(form)是与用户交互的一个重要组件,常用于收集用户输入并将数据发送到服务器进行处理。HTML表单的提交是通过HTTP协议进行的,而表单的提交格式是由表单的method、action、enctype等属性决定的。在本文中,我们将详细探讨HTML表单在提交数据时的协议格…

数字反向输出

数字反向输出 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 小明听到广播里的数字后&#xff0c;总喜欢反着念给妈妈听。请聪明的你将小明听到的数字反向输出。 输入 输入为一个整型的四位数n 输出 …

libigl 邻接矩阵

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 libigl使用一种邻接矩阵来描述各个点之间的关系,这样做非常的直观,方便使用。 二、实现代码 #include <Eigen/Core> #include <Eigen/Geometry> #include <igl/read_triangle_mesh.h>

2024-11-19 kron积

若A[a11 a12; a21 a22]; B[b11 b12; b21 b22]; 则C[a11*b11 a12*b11 a21*b11 a22*b11; a11*b12 a12*b12 a21*b12 a22*b12; a11*b21 a12*b21 a21*b21 a22*b21; a11*b22 a12*b22 a21*b22 a22*b22] 用MATLAB实现 方法1&#xff1a; A [a11 a12; a21 a22]; B [b11 b12; b21 b22]…

Java多态的优势和弊端

1. public class text {public static void main(String[] args) {animal dnew dog();d.eat();// dog a (dog) d;//类似强制转换//a.lookhome();/* if(d instanceof dog){dog a(dog)d;a.lookhome();}else if(d instanceof cat){cat c(cat) d;c.work();}else{System.out.print…

Chrome 浏览器 131 版本开发者工具(DevTools)更新内容

Chrome 浏览器 131 版本开发者工具&#xff08;DevTools&#xff09;更新内容 一、使用 Gemini 调试 CSS Chrome DevTools 现在推出了一个新的实验性 AI 辅助面板&#xff0c;可以与 Gemini 聊天并获得帮助来调试 CSS。 在 Elements 面板中&#xff0c;右键点击一个元素并选…

按出生日期排序(结构体专题)

题目描述 送人玫瑰手有余香&#xff0c;小明希望自己能带给他人快乐&#xff0c;于是小明在每个好友生日的时候发去一份生日祝福。小明希望将自己的通讯录按好友的生日排序排序&#xff0c;这样就查看起来方便多了&#xff0c;也避免错过好友的生日。为了小明的美好愿望&#x…

嵌入式工程师面试笔试总结——day1

第一章、进程与线程 1、什么是进程、线程&#xff0c;有什么区别&#xff1f; 进程是资源&#xff08; CPU 、内存等&#xff09;分配的基本单位&#xff0c;线程是 CPU 调度和分配的基本单位&#xff08;程序执行的最小单 位&#xff09;。同一时间&#xff0c;如果CPU 是单…

数据库表设计范式

华子目录 MYSQL库表设计&#xff1a;范式第一范式&#xff08;1NF&#xff09;第二范式&#xff08;2NF&#xff09;第三范式&#xff08;3NF&#xff09;三范式小结巴斯-科德范式&#xff08;BCNF&#xff09;第四范式&#xff08;4NF&#xff09;第五范式&#xff08;5NF&…

提成制是什么?如何高效管理提成制?

提成工资制即将企业盈利按照一定的比例在企业和员工之间分成的方式&#xff0c;这种方式具有一定的激励性。实行提成制首先要确定合适的提成指标&#xff0c;一般是按照业务量或销售额提成&#xff0c;即多卖多得。 对于提成制来说&#xff0c;确定合适的提成方式和比例是非常重…

VPN技术-IPSec VPN概述学习笔记

企业对网络安全性的需求日益提升&#xff0c;而传统的TCPЛIP协议缺乏有效的安全认证和保密机制。IPSec(Internet Protocol Security)作为一种开放标准的安全框架结构&#xff0c;可以用来保证IP数据报文在网络上传输的机密性、完整性和防重放。 IPsec VPN&#xff08;Interne…

基于干扰观测器的 PD 控制

基于干扰观测器的 PD 控制 1. 基本概念 干扰观测器&#xff08;Disturbance Observer, DOB&#xff09; 是一种用于估计系统中未建模动态或外部干扰的工具&#xff0c;通过补偿干扰&#xff0c;提高系统控制性能。结合 PD 控制器&#xff0c;干扰观测器能够实时补偿外部扰动和…

缓存工具类编写

缓存工具类编写 一般操作 在外面日常开发中&#xff0c;经常会有为了减少数据库压力&#xff0c;而将数据保存到缓存中并设置一个过期时间的操作。日常代码如下&#xff1a; Autowired private RedisTemplate<String, String> redisTemplate;public Object queryDataW…

STM32hal库创建+LED控制演示+中断概念

1 如何使用STM32CubeMX创建ZET6项目 引言 大家好&#xff0c;今天我们将一起学习如何使用STM32CubeMX来创建一个基于ZET6的项目。如果你已经有一定的基础&#xff0c;那么这篇文章将帮助你快速回顾和深入理解创建项目的步骤。 准备工作 在开始之前&#xff0c;请确保你已经…

[371]基于springboot的高校实习管理系统

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统高校实习管理系统信息管理难度大&#xff0c;容错率低&am…

基于无线传感器网络的无线温湿度采集系统(附详细使用教程+完整代码+原理图+完整课设报告)

&#x1f38a;项目专栏&#xff1a;【Zigbee课程设计系列文章】&#xff08;附详细使用教程完整代码原理图完整课设报告&#xff09; 前言 &#x1f451;由于无线传感器网络&#xff08;也即是Zigbee&#xff09;作为&#x1f310;物联网工程的一门必修专业课&#xff0c;具有…

Go-RPC关键指标分析与企业实践

1.稳定性-保障策略 熔断&#xff1a;保护调用方 限流&#xff1a;保护被调用方 超时控制&#xff1a;避免浪费 2.稳定性-请求成功率&#xff08;用重复发送 负载均衡&#xff09; 3.稳定性-长尾请求&#xff08;用备份请求&#xff09; 4.稳定性-注册中间件 易用性&#xff1a…

启动前后端分离项目笔记

一、项目 首先可以在各大开源软件拿取一个项目&#xff0c;以下项目是在gitee上获取 二、准备工作 配置JDK环境&#xff0c;node.js环境&#xff0c;安装vue脚手架工具以及maven环境 三、前端项目启动 在前端目录下安装依赖 npm install 如果报错可能是因为权限不够&#…

非线性控制器设计原理

非线性控制器设计原理 非线性控制器设计旨在解决非线性系统的控制问题&#xff0c;克服传统线性控制器在处理非线性现象&#xff08;如饱和、死区、耦合、时变性等&#xff09;时的不足。其核心在于利用非线性数学工具和设计方法&#xff0c;使控制系统在非线性条件下具备良好…

android 使用SQLiteOpenHelper 如何优化数据库的性能

一、数据库设计优化 (Schema Design): 这是性能优化的基础。一个精心设计的数据库结构可以显著提高查询速度和减少存储空间。 范式化 (Normalization): 遵循数据库范式&#xff0c;特别是第一范式、第二范式和第三范式&#xff0c;可以消除数据冗余。冗余数据不仅浪费存储空间…