云服务器部署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,访问
用户端 - 聊天窗口http://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/user
管理员端 - 聊天窗口http://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/admin
至此,部署完成
相关文章:
云服务器部署WebSocket项目
WebSocket是一种在单个TCP连接上进行全双工通信的协议,其设计的目的是在Web浏览器和Web服务器之间进行实时通信(实时Web) WebSocket协议的优点包括: 1. 更高效的网络利用率:与HTTP相比,WebSocket的握手只…...
C#+数据库 实现动态权限设置
将权限信息存储在数据库中,支持动态调整。根据用户所属的角色、特定的功能模块,动态加载权限” 1. 数据库设计 根据这种需求,可以通过以下表设计: 用户表 (Users):存储用户信息。角色表 (Roles):存储角色…...
(原创)Android Studio新老界面UI切换及老版本下载地址
前言 这两天下载了一个新版的Android Studio,发现整个界面都发生了很大改动: 新的界面的一些设置可参考一些博客: Android Studio新版UI常用设置 但是对于一些急着开发的小伙伴来说,没有时间去适应,那么怎么办呢&am…...
Ubuntu24虚拟机-gnome-boxes
推荐使用gnome-boxes, virtualbox构建失败,multipass需要开启防火墙 sudo apt install gnome-boxes创建完毕~...
k8s rainbond centos7/win10 -20241124
参考 https://www.rainbond.com/ 国内一站式云原生平台 对centos7环境支持不太行 [lighthouseVM-16-5-centos ~]$ curl -o install.sh https://get.rainbond.com && bash ./install.sh 2024-11-24 09:56:57 ERROR: Ops! Docker daemon is not running. Start docke…...
SpringBoot+Vue滑雪社区网站设计与实现
【1】系统介绍 研究背景 随着互联网技术的快速发展和冰雪运动的普及,滑雪作为一种受欢迎的冬季运动项目,吸引了越来越多的爱好者。与此同时,社交媒体和在线社区平台的兴起为滑雪爱好者提供了一个交流经验、分享心得、获取信息的重要渠道。滑…...
MySql.2
sql查询语句执行过程 SQL 查询语句的执行过程是一个复杂的过程,涉及多个步骤。以下是典型的关系数据库管理系统 (RDBMS) 中 SQL 查询语句的执行过程概述: 1. 客户端发送查询 用户通过 SQL 客户端或应用程序发送 SQL 查询语句给数据库服务器。 2. …...
算法之区间和题目讲解
题干 难度:简单 题目分析 题目要求算出每个指定区间内元素的总和。 然而,区间在输入的最下面,所以按照暴力破解的思路,我们首先要遍历数组,把它的值都存进去。 然后,遍历下面的区间,从索引a…...
价格分类(神经网络)
# 1.导入依赖包 import timeimport torch import torch.nn as nn import torch.optim as optimfrom torch.utils.data import TensorDataset, DataLoader from sklearn.model_selection import train_test_splitimport numpy as np import pandas as pd import matplotlib.pypl…...
对智能电视直播App的恶意监控
首先我们要指出中国广电总局推出的一个政策性文件是恶意监控的始作俑者,这个广电总局的政策性文件禁止智能电视和电视盒子安装直播软件。应该说这个政策性文件是为了保护特殊利益集团,阻挠技术进步和发展的。 有那么一些电视机和电视盒子的厂商和电信运…...
【JavaEE初阶】多线程初阶下部
文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比(面试题) 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言…...
macOS上进行Ant Design Pro实战教程(一)
由于一个AI项目的前端使用了umi,本教程根据阿里官网上的 《Ant Design 实战教程(beta 版)》来实操一下,我使用macOS操作系统,VS Code 开发环境。 一、开发环境 1、安装nodejs, npm, yarn 官网上建议使用cnpm…...
智能合约运行原理
点个关注吧!! 用一句话来总结,智能合约就像是一个自动售货机:你投入硬币(触发条件),选择商品(执行合约),然后机器就会自动给你商品(执行结果&…...
安卓动态添加View
在安卓应用中,有很多时候需要动态添加View。比如从后台获取商品列表,根据商品数量在页面渲染对应数量的条目,这时候就需要动态添加View。 1.动态添加View的方法 动态添加View有两种方法: 由代码生成子View:这种方式…...
前端预览pdf文件流
需求 后端接口返回pdf文件流,实现新窗口预览pdf。 解决方案 把后端返回的pdf文件流转为blob路径,利用浏览器直接预览。 具体实现步骤 1、引入axios import axios from axios;2、创建预览方法(具体使用时将axios的请求路径替换为你的后端…...
【测试工具JMeter篇】JMeter性能测试入门级教程(一)出炉,测试君请各位收藏了!!!
一、前言 Apache JMeter是纯Java的开源软件,最初由Apache软件基金会的Stefano Mazzocchi开发,旨在加载测试功能行为和测量性能。可以使用JMeter进行性能测试,即针对重负载、多用户和并发流量测试Web应用程序。 我们选择JMeter原因 是否测试过…...
【zookeeper03】消息队列与微服务之zookeeper集群部署
ZooKeeper 集群部署 1.ZooKeeper 集群介绍 ZooKeeper集群用于解决单点和单机性能及数据高可用等问题。 集群结构 Zookeeper集群基于Master/Slave的模型 处于主要地位负责处理写操作)的主机称为Leader节点,处于次要地位主要负责处理读操作的主机称为 follower 节点…...
从 Llama 1 到 3.1:Llama 模型架构演进详解
编者按: 面对 Llama 模型家族的持续更新,您是否想要了解它们之间的关键区别和实际性能表现?本文将探讨 Llama 系列模型的架构演变,梳理了 Llama 模型从 1.0 到 3.1 的完整演进历程,深入剖析了每个版本的技术创新&#…...
UE5肉鸽游戏教程学习
学习地址推荐:UE5肉鸽项目实战教程_哔哩哔哩_bilibili...
Vue3 - 详细实现虚拟列表前端虚拟滚动列表解决方案,vue3长列表优化之虚拟列表,解决列表动态高度不固定高度及图片视频图文异步请求加载问题,虚拟列表DOM大量数据同时加载渲染卡顿太慢及下滑列表闪烁
前言 Vue2 版本,请访问 这篇文章 在 vue3 项目开发中,详解实现虚拟列表高度不固定(不定高)且复杂含有图片视频等复杂虚拟列表教程,决列表每项高度不确定及img图像或视频的加载方案,利用缓冲区技术解决用户浏览时渲染不及时列表闪烁白屏/列表加载闪屏,解vue3实现虚拟列表优…...
英语知识网站开发:Spring Boot框架技巧
摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了英语知识应用网站的开发全过程。通过分析英语知识应用网站管理的不足,创建了一个计算机管理英语知识应用网站的方案。文章介绍了英语知识应用网站的系…...
基于lvgl+ST7735制作一款esp8285的控制面板程序
要在ESP8285上使用LVGL和ST7735创建一个控制面板程序,你需要遵循以下步骤。这个过程包括设置开发环境,连接硬件,编写代码,以及调校和优化。 所需硬件 ESP8285 开发板:像NodeMCU之类的开发板。ST7735 显示屏:通常是1.8英寸或2.0英寸的SPI接口显示屏。电源和连接线:用于连…...
MySQL 索引详解
在数据库的世界中,索引就像是一本巨大书籍的目录,它能够极大地提高数据检索的效率。在 MySQL 中,索引的合理使用对于数据库的性能至关重要。本文将深入探讨 MySQL 索引的各个方面。 一、索引的概念与作用 1. 什么是索引? 索引是一…...
区块链学习笔记(1)--区块、链和共识 区块链技术入门
常见的hash算法: 文件防篡改:MD5比特币挖矿:SHA256证明数据片段:Merkle root文本去重:SimHash 区块 区块(block)由区块头(block header)和交易列表(transac…...
【Android+多线程】IntentService 知识总结:应用场景 / 使用步骤 / 源码分析
定义 IntentService 是 Android中的一个封装类,继承自四大组件之一的Service 功能 处理异步请求 & 实现多线程 应用场景 线程任务 需 按顺序、在后台执行 最常见的场景:离线下载不符合多个数据同时请求的场景:所有的任务都在同一个T…...
Python Tornado框架教程:高性能Web框架的全面解析
Python Tornado框架教程:高性能Web框架的全面解析 引言 在现代Web开发中,选择合适的框架至关重要。Python的Tornado框架因其高性能和非阻塞I/O特性而备受青睐。它特别适合处理大量并发连接的应用,比如聊天应用、实时数据处理和WebSocket服务…...
通过端口测试验证网络安全策略
基于网络安全需求,项目中的主机间可能会有不同的网络安全策略,这当然是好的,但很多时候,在解决网络安全问题的时候,同时引入了新的问题,如k8s集群必须在主机间开放udp端口,否则集群不能正常的运…...
Excel把其中一张工作表导出成一个新的文件
excel导出一张工作表 一个Excel表里有多个工作表,怎么才能导出一个工作表,让其生成新的Excel文件呢? 第一步:首先打开Excel表格,然后选择要导出的工作表的名字,比如“Sheet1”,把鼠标放到“She…...
第四份工作的环境配置
最近在内网中工作,会遇到不少的环境问题. 下面记录一下这个过程中的挑战: 环境:内网,连接不到外网. 如何配置开发环境: 方法0: 在服务器上安装环境. 但是服务器上没有相应的python包.因为python包是从外界获得的.并且,这些python包不能同步更新.所以,在服务器上直接搭建环…...
SpringBoot开发——Maven多模块工程最佳实践及详细示例
文章目录 一、前言二、Maven多模块工程的最佳实践1、项目结构清晰2、依赖管理统一3、插件配置统一4、版本控制一致5、模块间通信简化 三、详细示例1、项目结构2、父模块(parent)的pom.xml文件3、子模块(module-api)的pom.xml文件4…...
营销型网站建设制作/百度搜索广告价格
C# GetHashCode 的实现方式 在项目中,在使用哈希表时,有时会需要Override GetHashCode。这里给出一种普遍的做法: 版本1: 实现一个helper,传递类型T,返回这个类型的hashcode。函数逻辑很直接,只…...
深圳去聋哑做义工申请网站/营销策略是什么
shell中的循环语法 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 一.for循环1.语法格式11 for 变量 in 值1 值2 值3 ... 2 do 3 源代码 4 done 2.语法格式21 for (( 初始值;循环控制条件…...
成都网站建设套餐/网站优化排名软件网
CGLIB浅拷贝BeanCopier的使用和详解 一、bean拷贝工具 bean拷贝工具类比较 常用的bean拷贝工具类当中,主要有Apache提供的beanUtils、Spring提供的beanUtils、Cglib提供的beanCopier,性能上分析如下表所示(该表来自网上的数据) …...
深圳网站制作公司兴田德润信任高/重庆森林电影完整版
2019独角兽企业重金招聘Python工程师标准>>> Web服务器工作原理概述 很多时候我们都想知道,web容器或web服务器(比如Tomcat或者jboss)是怎样工作的?它们是怎样处理来自全世界的http请求的?它们在幕后做了什…...
杭州信贷网站制作/武汉网站排名提升
Win7之家(www.win7china.com):Win7系统优化:如何移动Win7我的文档保存位置如何把Win7用户文件夹移动到E盘?怎么把win7的用户文件夹移动到其他盘?如何把Win7在C盘的用户文件夹转移到其他分区?用户文件夹包含了各种用户数…...
禁止搜索引擎抓取wordpress的目录/怎么下载app到手机上
Graphviz是一个可以对图进行自动布局的绘图工具,由贝尔实验室开源。我们在上次 Python 快速绘制画出漂亮的系统架构图 提到的diagrams,其内部的编排逻辑就用到了这个开源工具包。而今天我们要介绍的项目,就是基于Python和Graphviz开发的&…...