[spring6: @EnableWebSocket]-源碼解析

注解

EnableWebSocket

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebSocketConfiguration.class)
public @interface EnableWebSocket {}

DelegatingWebSocketConfiguration

@Configuration(proxyBeanMethods = false)
public class DelegatingWebSocketConfiguration extends WebSocketConfigurationSupport {private final List<WebSocketConfigurer> configurers = new ArrayList<>();@Autowired(required = false)public void setConfigurers(List<WebSocketConfigurer> configurers) {if (!CollectionUtils.isEmpty(configurers)) {this.configurers.addAll(configurers);}}@Overrideprotected void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {for (WebSocketConfigurer configurer : this.configurers) {configurer.registerWebSocketHandlers(registry);}}
}

WebSocketConfigurationSupport

public class WebSocketConfigurationSupport {@Nullableprivate ServletWebSocketHandlerRegistry handlerRegistry;@Beanpublic HandlerMapping webSocketHandlerMapping(@Qualifier("defaultSockJsSchedulerContainer") DefaultSockJsSchedulerContainer schedulerContainer) {ServletWebSocketHandlerRegistry registry = initHandlerRegistry();if (registry.requiresTaskScheduler()) {TaskScheduler scheduler = schedulerContainer.getScheduler();Assert.notNull(scheduler, "TaskScheduler is required but not initialized");registry.setTaskScheduler(scheduler);}return registry.getHandlerMapping();}private ServletWebSocketHandlerRegistry initHandlerRegistry() {if (this.handlerRegistry == null) {this.handlerRegistry = new ServletWebSocketHandlerRegistry();registerWebSocketHandlers(this.handlerRegistry);}return this.handlerRegistry;}protected void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {}@BeanDefaultSockJsSchedulerContainer defaultSockJsSchedulerContainer() {return (initHandlerRegistry().requiresTaskScheduler() ?new DefaultSockJsSchedulerContainer(initDefaultSockJsScheduler()) :new DefaultSockJsSchedulerContainer(null));}private ThreadPoolTaskScheduler initDefaultSockJsScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();scheduler.setThreadNamePrefix("SockJS-");scheduler.setPoolSize(Runtime.getRuntime().availableProcessors());scheduler.setRemoveOnCancelPolicy(true);return scheduler;}static class DefaultSockJsSchedulerContainer implements InitializingBean, DisposableBean {@Nullableprivate final ThreadPoolTaskScheduler scheduler;DefaultSockJsSchedulerContainer(@Nullable ThreadPoolTaskScheduler scheduler) {this.scheduler = scheduler;}@Nullablepublic ThreadPoolTaskScheduler getScheduler() {return this.scheduler;}@Overridepublic void afterPropertiesSet() throws Exception {if (this.scheduler != null) {this.scheduler.afterPropertiesSet();}}@Overridepublic void destroy() throws Exception {if (this.scheduler != null) {this.scheduler.destroy();}}}
}

WebSocketConfigurer

public interface WebSocketConfigurer {void registerWebSocketHandlers(WebSocketHandlerRegistry registry);}

WebSocketHandlerRegistry

public interface WebSocketHandlerRegistry {WebSocketHandlerRegistration addHandler(WebSocketHandler webSocketHandler, String... paths);}

ServletWebSocketHandlerRegistry

public class ServletWebSocketHandlerRegistry implements WebSocketHandlerRegistry {private final List<ServletWebSocketHandlerRegistration> registrations = new ArrayList<>(4);private int order = 1;public ServletWebSocketHandlerRegistry() {}@Overridepublic WebSocketHandlerRegistration addHandler(WebSocketHandler handler, String... paths) {ServletWebSocketHandlerRegistration registration = new ServletWebSocketHandlerRegistration();registration.addHandler(handler, paths);this.registrations.add(registration);return registration;}public AbstractHandlerMapping getHandlerMapping() {Map<String, Object> urlMap = new LinkedHashMap<>();for (ServletWebSocketHandlerRegistration registration : this.registrations) {MultiValueMap<HttpRequestHandler, String> mappings = registration.getMappings();mappings.forEach((httpHandler, patterns) -> {for (String pattern : patterns) {urlMap.put(pattern, httpHandler);}});}WebSocketHandlerMapping hm = new WebSocketHandlerMapping();hm.setUrlMap(urlMap);hm.setOrder(this.order);if (this.urlPathHelper != null) {hm.setUrlPathHelper(this.urlPathHelper);}return hm;}
}

請求

推薦閱讀:[spring6: DispatcherServlet.doDispatch]-源碼分析

WebSocketHandlerMapping

public class WebSocketHandlerMapping extends SimpleUrlHandlerMapping implements SmartLifecycle {private boolean webSocketUpgradeMatch;@Nullableprivate Integer phase;private volatile boolean running;@Override@Nullableprotected Object getHandlerInternal(HttpServletRequest request) throws Exception {// new HandlerExecutionChain(WebSocketHttpRequestHandler)Object handler = super.getHandlerInternal(request);return (matchWebSocketUpgrade(handler, request) ? handler : null);}private boolean matchWebSocketUpgrade(@Nullable Object handler, HttpServletRequest request) {handler = (handler instanceof HandlerExecutionChain chain ? chain.getHandler() : handler);if (this.webSocketUpgradeMatch && handler instanceof WebSocketHttpRequestHandler) {String header = request.getHeader(HttpHeaders.UPGRADE);return (HttpMethod.GET.matches(request.getMethod()) &&header != null && header.equalsIgnoreCase("websocket"));}return true;}}

HttpRequestHandlerAdapter

public class HttpRequestHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return (handler instanceof HttpRequestHandler);}@Override@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {((HttpRequestHandler) handler).handleRequest(request, response);return null;}@Override@SuppressWarnings("deprecation")public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified lastModified) {return lastModified.getLastModified(request);}return -1L;}}

WebSocketHttpRequestHandler

public class WebSocketHttpRequestHandler implements HttpRequestHandler, Lifecycle, ServletContextAware {private static final Log logger = LogFactory.getLog(WebSocketHttpRequestHandler.class);private final WebSocketHandler wsHandler;private final HandshakeHandler handshakeHandler;private final List<HandshakeInterceptor> interceptors = new ArrayList<>();private volatile boolean running;public WebSocketHttpRequestHandler(WebSocketHandler wsHandler, HandshakeHandler handshakeHandler) {Assert.notNull(wsHandler, "wsHandler must not be null");Assert.notNull(handshakeHandler, "handshakeHandler must not be null");this.wsHandler = decorate(wsHandler);this.handshakeHandler = handshakeHandler;}protected WebSocketHandler decorate(WebSocketHandler handler) {return new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(handler));}@Overridepublic void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);ServerHttpResponse response = new ServletServerHttpResponse(servletResponse);HandshakeInterceptorChain chain = new HandshakeInterceptorChain(this.interceptors, this.wsHandler);HandshakeFailureException failure = null;try {if (logger.isDebugEnabled()) {logger.debug(servletRequest.getMethod() + " " + servletRequest.getRequestURI());}Map<String, Object> attributes = new HashMap<>();if (!chain.applyBeforeHandshake(request, response, attributes)) {return;}// 握手流程,校驗請求頭、協議版本、Origin 等信息后,底層調用StandardWebSocketUpgradeStrategy完成協議升級this.handshakeHandler.doHandshake(request, response, this.wsHandler, attributes);chain.applyAfterHandshake(request, response, null);}catch (HandshakeFailureException ex) {failure = ex;}catch (Exception ex) {failure = new HandshakeFailureException("Uncaught failure for request " + request.getURI() + " - " + ex.getMessage(), ex);}finally {if (failure != null) {chain.applyAfterHandshake(request, response, failure);response.close();throw failure;}response.close();}}
}

StandardWebSocketUpgradeStrategy

基于標準 Java WebSocket API,實現了通過 Servlet 容器(如 Tomcat)將 HTTP 請求升級為 WebSocket 連接的核心邏輯。

public class StandardWebSocketUpgradeStrategy extends AbstractStandardUpgradeStrategy {private static final String[] SUPPORTED_VERSIONS = new String[] {"13"};@Overridepublic String[] getSupportedVersions() {return SUPPORTED_VERSIONS;}// endpoint: StandardWebSocketHandlerAdapter@Overrideprotected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,@Nullable String selectedProtocol, List<Extension> selectedExtensions, Endpoint endpoint)throws HandshakeFailureException {HttpServletRequest servletRequest = getHttpServletRequest(request);HttpServletResponse servletResponse = getHttpServletResponse(response);StringBuffer requestUrl = servletRequest.getRequestURL();String path = servletRequest.getRequestURI();  // shouldn't matterMap<String, String> pathParams = Collections.<String, String> emptyMap();ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint);endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol));endpointConfig.setExtensions(selectedExtensions);try {upgradeHttpToWebSocket(servletRequest, servletResponse, endpointConfig, pathParams);}catch (Exception ex) {throw new HandshakeFailureException("Servlet request failed to upgrade to WebSocket: " + requestUrl, ex);}}protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response,ServerEndpointConfig endpointConfig, Map<String,String> pathParams) throws Exception {getContainer(request).upgradeHttpToWebSocket(request, response, endpointConfig, pathParams);}}

StandardWebSocketHandlerAdapter

/*** Adapts a {@link WebSocketHandler} to the standard WebSocket for Java API.** @author Rossen Stoyanchev* @since 4.0*/
public class StandardWebSocketHandlerAdapter extends Endpoint {private final Log logger = LogFactory.getLog(StandardWebSocketHandlerAdapter.class);private final WebSocketHandler handler;private final StandardWebSocketSession wsSession;public StandardWebSocketHandlerAdapter(WebSocketHandler handler, StandardWebSocketSession wsSession) {Assert.notNull(handler, "WebSocketHandler must not be null");Assert.notNull(wsSession, "WebSocketSession must not be null");this.handler = handler;this.wsSession = wsSession;}@Overridepublic void onOpen(final jakarta.websocket.Session session, EndpointConfig config) {this.wsSession.initializeNativeSession(session);// The following inner classes need to remain since lambdas would not retain their// declared generic types (which need to be seen by the underlying WebSocket engine)if (this.handler.supportsPartialMessages()) {session.addMessageHandler(new MessageHandler.Partial<String>() {@Overridepublic void onMessage(String message, boolean isLast) {handleTextMessage(session, message, isLast);}});session.addMessageHandler(new MessageHandler.Partial<ByteBuffer>() {@Overridepublic void onMessage(ByteBuffer message, boolean isLast) {handleBinaryMessage(session, message, isLast);}});}else {session.addMessageHandler(new MessageHandler.Whole<String>() {@Overridepublic void onMessage(String message) {handleTextMessage(session, message, true);}});session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {@Overridepublic void onMessage(ByteBuffer message) {handleBinaryMessage(session, message, true);}});}session.addMessageHandler(new MessageHandler.Whole<jakarta.websocket.PongMessage>() {@Overridepublic void onMessage(jakarta.websocket.PongMessage message) {handlePongMessage(session, message.getApplicationData());}});try {this.handler.afterConnectionEstablished(this.wsSession);}catch (Exception ex) {ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);}}private void handleTextMessage(jakarta.websocket.Session session, String payload, boolean isLast) {TextMessage textMessage = new TextMessage(payload, isLast);try {this.handler.handleMessage(this.wsSession, textMessage);}catch (Exception ex) {ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);}}private void handleBinaryMessage(jakarta.websocket.Session session, ByteBuffer payload, boolean isLast) {BinaryMessage binaryMessage = new BinaryMessage(payload, isLast);try {this.handler.handleMessage(this.wsSession, binaryMessage);}catch (Exception ex) {ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);}}private void handlePongMessage(jakarta.websocket.Session session, ByteBuffer payload) {PongMessage pongMessage = new PongMessage(payload);try {this.handler.handleMessage(this.wsSession, pongMessage);}catch (Exception ex) {ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);}}@Overridepublic void onClose(jakarta.websocket.Session session, CloseReason reason) {CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase());try {this.handler.afterConnectionClosed(this.wsSession, closeStatus);}catch (Exception ex) {if (logger.isWarnEnabled()) {logger.warn("Unhandled on-close exception for " + this.wsSession, ex);}}}@Overridepublic void onError(jakarta.websocket.Session session, Throwable exception) {try {this.handler.handleTransportError(this.wsSession, exception);}catch (Exception ex) {ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);}}}

WebSocketHandler

public interface WebSocketHandler {void afterConnectionEstablished(WebSocketSession session) throws Exception;void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;boolean supportsPartialMessages();}

實戰

頁面

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><title>WebSocket 測試頁面</title><style>body { font-family: Arial, sans-serif; margin: 20px; }#messages { border: 1px solid #ccc; height: 200px; overflow-y: scroll; padding: 10px; }#inputArea { margin-top: 10px; }</style>
</head>
<body><h2>WebSocket 測試客戶端</h2><label for="wsUrl">WebSocket 地址:</label><input type="text" id="wsUrl" size="40" value="ws://localhost:8080/demo" /><button id="connectBtn">連接</button><button id="disconnectBtn" disabled>斷開連接</button><div id="status">狀態:未連接</div><div id="messages"></div><div id="inputArea"><input type="text" id="messageInput" size="40" placeholder="輸入消息..." /><button id="sendBtn" disabled>發送</button></div><script>let websocket;const statusEl = document.getElementById('status');const messagesEl = document.getElementById('messages');const connectBtn = document.getElementById('connectBtn');const disconnectBtn = document.getElementById('disconnectBtn');const sendBtn = document.getElementById('sendBtn');const messageInput = document.getElementById('messageInput');const wsUrlInput = document.getElementById('wsUrl');function appendMessage(msg) {const p = document.createElement('p');p.textContent = msg;messagesEl.appendChild(p);messagesEl.scrollTop = messagesEl.scrollHeight; // 自動滾動到底部}connectBtn.onclick = () => {const url = wsUrlInput.value;websocket = new WebSocket(url);websocket.onopen = () => {statusEl.textContent = '狀態:已連接';appendMessage('已連接到 ' + url);connectBtn.disabled = true;disconnectBtn.disabled = false;sendBtn.disabled = false;};websocket.onmessage = (event) => {appendMessage('收到消息: ' + event.data);};websocket.onerror = (event) => {appendMessage('發生錯誤');console.error('WebSocket 錯誤:', event);};websocket.onclose = () => {statusEl.textContent = '狀態:已斷開';appendMessage('連接已關閉');connectBtn.disabled = false;disconnectBtn.disabled = true;sendBtn.disabled = true;};};disconnectBtn.onclick = () => {if (websocket) {websocket.close();}};sendBtn.onclick = () => {const msg = messageInput.value;if (msg && websocket && websocket.readyState === WebSocket.OPEN) {websocket.send(msg);appendMessage('發送消息: ' + msg);messageInput.value = '';}};// 支持回車發送消息messageInput.addEventListener('keypress', function(event) {if (event.key === 'Enter') {sendBtn.click();event.preventDefault();}});
</script>
</body>
</html>

配置

package xyz.idoly.demo;import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new DemoHandler(), "/demo").setAllowedOrigins("*");}private static class DemoHandler extends TextWebSocketHandler {@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {System.out.println("連接建立:" + session.getId());session.sendMessage(new TextMessage("歡迎連接 WebSocket 服務"));}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println("收到消息:" + message.getPayload());session.sendMessage(new TextMessage("服務器收到: " + message.getPayload()));}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("連接關閉:" + session.getId());}}
}

效果

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90471.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90471.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90471.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Nacos 封裝與 Docker 部署實踐

Nacos 封裝與 Docker 部署指南 0 準備工作 核心概念? 命名空間&#xff1a;用于隔離不同環境&#xff08;如 dev、test、prod&#xff09;或業務線&#xff0c;默認命名空間為public。? 數據 ID&#xff1a;配置集的唯一標識&#xff0c;命名規則推薦為{服務名}-{profile}.{擴…

Vue2——4

組件的樣式沖突 scoped默認情況&#xff1a;寫在組件中的樣式會 全局生效 → 因此很容易造成多個組件之間的樣式沖突問題。1. 全局樣式: 默認組件中的樣式會作用到全局2. 局部樣式: 可以給組件加上 scoped 屬性, 可以讓樣式只作用于當前組件原理&#xff1a;當前組件內標簽都被…

30天打好數模基礎-邏輯回歸講解

案例代碼實現一、代碼說明本案例針對信用卡欺詐檢測二分類問題&#xff0c;完整實現邏輯回歸的數據生成→預處理→模型訓練→評估→閾值調整→決策邊界可視化流程。數據生成&#xff1a;模擬1000條交易數據&#xff0c;其中欺詐樣本占20%&#xff08;類不平衡&#xff09;&…

CDH yarn 重啟后RM兩個備

yarn rmadmin -transitionToActive --forcemanual rm1 cd /opt/cloudera/parcels/CDH/lib/zookeeper/bin/ ./zkCli.sh -server IT-CDH-Node01:2181 查看是否存在殘留的ActiveBreadCrumb節點 ls /yarn-leader-election/yarnRM #若輸出只有[ActiveBreadCrumb]&#xff08;正常應…

HTML5音頻技術及Web Audio API深入解析

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;音頻處理在IT行業中的多媒體、游戲開發、在線教育和音樂制作等應用領域中至關重要。本文詳細探討了HTML5中的 <audio> 標簽和Web Audio API等技術&#xff0c;涉及音頻的嵌入、播放、控制以及優化。特別…

每日面試題13:垃圾回收器什么時候STW?

STW是什么&#xff1f;——深入理解JVM垃圾回收中的"Stop-The-World"在Java程序運行過程中&#xff0c;JVM會通過垃圾回收&#xff08;GC&#xff09;自動管理內存&#xff0c;釋放不再使用的對象以騰出空間。但你是否遇到過程序突然卡頓的情況&#xff1f;這可能與G…

【系統全面】常用SQL語句大全

一、基本查詢語句 查詢所有數據&#xff1a; SELECT * FROM 表名;查詢特定列&#xff1a; SELECT 列名1, 列名2 FROM 表名;條件查詢&#xff1a; SELECT * FROM 表名 WHERE 條件;模糊查詢&#xff1a; SELECT * FROM 表名 WHERE 列名 LIKE 模式%;排序查詢&#xff1a; SELECT *…

Spring之SSM整合流程詳解(Spring+SpringMVC+MyBatis)

Spring之SSM整合流程詳解-SpringSpringMVCMyBatis一、SSM整合的核心思路二、環境準備與依賴配置2.1 開發環境2.2 Maven依賴&#xff08;pom.xml&#xff09;三、整合配置文件&#xff08;核心步驟&#xff09;3.1 數據庫配置&#xff08;db.properties&#xff09;3.2 Spring核…

C++STL系列之set和map系列

前言 set和map都是關聯式容器&#xff0c;stl中樹形結構的有四種&#xff0c;set&#xff0c;map&#xff0c;multiset,multimap.本次主要是講他們的模擬實現和用法。 一、set、map、multiset、multimap set set的中文意思是集合&#xff0c;集合就說明不允許重復的元素 1……

Linux 磁盤掛載,查看uuid

lsblk -o NAME,FSTYPE,LABEL,UUID,MOUNTPOINT,SIZEsudo ntfsfix /dev/nvme1n1p1sudo mount -o remount,rw /dev/nvme1n1p1 /media/yake/Datasudo ntfsfix /dev/sda2sudo mount -o remount,rw /dev/sda2 /media/yake/MyData

【AJAX】XMLHttpRequest、Promise 與 axios的關系

目錄 一、AJAX原理 —— XMLHttpRequest 1.1 使用XMLHttpRequest 二、 XMLHttpRequest - 查詢參數 &#xff08;就是往服務器后面拼接要查詢的字符串&#xff09; 三、 地區查詢 四、 XMLHttpRequest - 數據提交 五、 認識Promise 5.1 為什么 JavaScript 需要異步&#…

C++中的stack和queue

C中的stack和queue 前言 這一節的內容對于stack和queue的使用介紹會比較少&#xff0c;主要是因為stack和queue的使用十分簡單&#xff0c;而且他們的功能主要也是在做題的時候才會顯現。這一欄目暫時不會寫關于做題的內容&#xff0c;后續我會額外開一個做題日記的欄目的。 這…

Spring Bean生命周期七步曲:定義、實例化、初始化、使用、銷毀

各位小猿&#xff0c;程序員小猿開發筆記&#xff0c;希望大家共同進步。 引言 1.整體流程圖 2.各階段分析 1??定義階段 1.1 定位資源 Spring 掃描 Component、Service、Controller 等注解的類或解析 XML/Java Config 中的 Bean 定義 1.2定義 BeanDefinition 解析類信息…

API安全監測工具:數字經濟的免疫哨兵

&#x1f4a5; 企業的三重致命威脅 1. 漏洞潛伏的定時炸彈 某支付平臺未檢測出API的批量數據泄露漏洞&#xff0c;導致230萬用戶信息被盜&#xff0c;面臨GDPR 1.8億歐元罰單&#xff08;IBM X-Force 2024報告&#xff09;。傳統掃描器對邏輯漏洞漏檢率超40%&#xff08;OWASP基…

Matplotlib詳細教程(基礎介紹,參數調整,繪圖教程)

目錄 一、初識Matploblib 1.1 安裝 Matplotlib 1.2、Matplotlib 的兩種接口風格 1.3、Figure 和 Axes 的深度理解 1.4 設置畫布大小 1.5 設置網格線 1.6 設置坐標軸 1.7 設置刻度和標簽 1.8 添加圖例和標題 1.9 設置中文顯示 1.10 調整子圖布局 二、常用繪圖教程 2…

Redis高可用架構演進面試筆記

1. 主從復制架構 核心概念Redis單節點并發能力有限&#xff0c;通過主從集群實現讀寫分離提升性能&#xff1a; Master節點&#xff1a;負責寫操作Slave節點&#xff1a;負責讀操作&#xff0c;從主節點同步數據 主從同步流程 全量同步&#xff08;首次同步&#xff09;建立連接…

無人機保養指南

定期清潔無人機在使用后容易積累灰塵、沙礫等雜物&#xff0c;需及時清潔。使用軟毛刷或壓縮空氣清除電機、螺旋槳和機身縫隙中的雜質。避免使用濕布直接擦拭電子元件&#xff0c;防止短路。電池維護鋰電池是無人機的核心部件&#xff0c;需避免過度放電或充電。長期存放時應保…

vlm MiniCPM 學習部署實戰

目錄 開源地址&#xff1a; 模型repo下載&#xff1a; 單圖片demo&#xff1a; 多圖推理demo&#xff1a; 論文學習筆記&#xff1a; 部署完整教程&#xff1a; 微調教程&#xff1a; 部署&#xff0c;微調教程&#xff0c;視頻實測 BitCPM4 技術報告 創意&#xff1…

92套畢業相冊PPT模版

致青春某大學同學聚會PPT模版&#xff0c;那些年我們一起走過的歲月PPT模版&#xff0c;某學院某班同學聯誼會PPT模版&#xff0c;匆匆那年PPT模版&#xff0c;青春的紀念冊PPT模版&#xff0c;梔子花開PPT模版&#xff0c;畢業紀念冊PPT模版。 92套畢業相冊PPT模版&#xff1…

爬蟲基礎概念

網絡爬蟲概述 概念 網絡爬蟲&#xff08;Web Crawler&#xff09;&#xff0c;也稱為網絡蜘蛛&#xff08;Web Spider&#xff09;或機器人&#xff08;Bot&#xff09;&#xff0c;是一種自動化程序&#xff0c;用于系統地瀏覽互聯網并收集網頁信息。它模擬人類瀏覽器行為&…