云服务器部署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,一经查实,立即删除!

相关文章

数字反向输出

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

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;右键点击一个元素并选…

嵌入式工程师面试笔试总结——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;干扰观测器能够实时补偿外部扰动和…

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

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

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

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

启动前后端分离项目笔记

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

代理池搭建优化-(书接上回,优化改进)

炮台有效炮弹实现 声明 学习视频来自 B 站UP主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 ✍&#x1f3fb;作者…

odoo18中模型的常用字段类型

字段的公共属性: Char 字符类型&#xff0c;对应数据库中varchar类型&#xff0c;除了通用类型外接收另外两个参数&#xff1a; size: 字符长度&#xff0c;超出的长度将被截断 trim: 默认True&#xff0c;是否字段值应该被去空白。 Text 文本类型&#xff0c;对应数据库…

ABAP开发实战——表单打印单位输出问题

在之前的文章中有提到过ABAP开发报表程序时会出现单位显示未转化值&#xff0c;就是说在中文环境下&#xff0c;用户希望看到的单位是“套”&#xff0c;但是报表程序输出的确是“SUI”&#xff0c;这时候需要取数时添加语句进行转化&#xff0c;但是&#xff0c;最近开发表单打…

基于SpringBoot的城镇保障性住房管理系统【附源码】

基于SpringBoot的城镇保障性住房管理系统 效果如下&#xff1a; 系统登录页面 用户管理页面 房源信息管理页面 住房分配管理页面 公示信息页面 系统主页面 用户登陆页面 房源信息页面 研究背景 随着城市化进程的加速&#xff0c;住房问题一直是人们关注的焦点。为了解决低收…

Java文件上传解压

目录结构 工具类 枚举 定义文件类型 public enum FileType {// 未知UNKNOWN,// 压缩文件ZIP, RAR, _7Z, TAR, GZ, TAR_GZ, BZ2, TAR_BZ2,// 位图文件BMP, PNG, JPG, JPEG,// 矢量图文件SVG,// 影音文件AVI, MP4, MP3, AAR, OGG, WAV, WAVE}为了避免文件被修改后缀&#xff0…

IDEA 下载源码很慢,Download Source使用阿里云镜像仓库

参考&#xff1a; IDEA maven本地仓库、中心仓库、远程仓库配置 在观看第三方jar包的api时&#xff0c;有时候需要下载源码看下注释。 这个时候用idea 上的提示的Download Source会发现一直下载不下来。 因此就怀疑用的是apache的maven仓库&#xff0c;不是我们用的 aliyun 镜…

英文版本-带EXCEL函数的数据分析

一、问题&#xff1a; 二、表格内容 三、分析结果 四、具体的操作步骤&#xff1a; 销售工作表公式设计与数据验证 类别&#xff08;Category&#xff09;列公式&#xff1a; 在Category列&#xff08;假设为D列&#xff09;&#xff0c;根据ProductCode在Catalogue工作表中查找…

三层交换机静态路由实验

1、前置知识 2、实验目的 3、实验器材&#xff1a; 3560-23PS交换机2台、主机4台、交叉线1根和直通网线4根。 4、实验规划及拓扑 实验要求&#xff1a; &#xff08;1&#xff09;在交换机A和交换机B上分别划分基于端口的VLAN&#xff1a; 交换机 VLAN 端口成员 交换机…