springboot vue sse消息推送,封裝系統公共消息推送前后端方法

概述

1、封裝springboot全局的消息推送接口;
注:1)由于原生HTML5 EventSource 不支持添加header,所以要把連接創建接口加入身份驗證白名單,并在接口內添加自己校驗token2)后臺需定時心跳,保證鏈接的存活
2、封裝前端公共的消息推動存儲方法:保證整個系統只有1個消息鏈接
組件可根據傳遞指定的業務類型,展示制定的消息
3、注意sse連接建立接口,需要單獨指定nginx配置,防止nginx默認配置導致的推送鏈接中斷
4、分布式系統,該后臺接口改動介紹

測試效果

如下圖
在這里插入圖片描述

1 后端接口的實現

controller有3個方法
1、sse鏈接建立
2、給已連接的指定用戶推送消息(用戶在線才能收到,不在線消息丟下:可根據您的業務再做具體代碼編寫)
3、給所有已建立的用戶廣播消息
注:本文章采用:有心跳 → 用 0L 永久連接,服務器資源受控,客戶端也能保持連接
也可采用:無心跳 → 建議設置 30~60 秒超時,客戶端需要重連:適合連接數量非常多,或者不頻繁推送的場景

1.1 推送服務Service

SseService 接口

package com.server.common.notice.service;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;public interface SseService {/*** 建立連接** @param clientId 連接id,這里是用戶id* @return see 服務器事件響應*/SseEmitter connect(String clientId);/*** 給指定 連接,發送消息** @param clientId 連接id* @param type     消息類型* @param data     數據*/void sendMessage(String clientId, String type, Object data);/*** 廣播消息** @param type 類型* @param data 數據*/void broadcast(String type, Object data);
}

SseService 接口實現
注意:鏈接建立邏輯不要做改動,若直接根據clientId 移除和關閉,可能造成競態刪除”錯誤對象

package com.server.common.notice.service.impl;import com.server.common.notice.service.SseService;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;@Service
public class SseServiceImpl implements SseService {private final Map<String, SseEmitter> clients = new ConcurrentHashMap<>();@Overridepublic SseEmitter connect(String clientId) {// 1) 先移除舊連接,避免 onCompletion 把后續的新連接誤刪SseEmitter old = clients.remove(clientId);if (old != null) {try {old.complete();} catch (Exception ignore) {}}// 2) 建立新連接(可改成有超時,比如 15min;配合心跳更穩)SseEmitter emitter = new SseEmitter(0L);clients.put(clientId, emitter);// 3) 回調里做“條件刪除”:僅當 Map 中的值就是當前這個 emitter 時才刪除Runnable cleanup = () -> clients.remove(clientId, emitter);emitter.onCompletion(cleanup);emitter.onTimeout(cleanup);emitter.onError(ex -> cleanup.run());// 初始事件try {emitter.send(SseEmitter.event().name("INIT").data("connected"));} catch (Exception e) {try {emitter.completeWithError(e);} catch (Exception ignore) {}}return emitter;}@Overridepublic void sendMessage(String clientId, String type, Object data) {SseEmitter emitter = clients.get(clientId);if (emitter == null) return;try {emitter.send(SseEmitter.event().name("MESSAGE").data(Map.of("id", UUID.randomUUID().toString(),"type", type,"data", data)));} catch (Exception e) {clients.remove(clientId, emitter);try {emitter.completeWithError(e);} catch (Exception ignore) {}}}@Overridepublic void broadcast(String type, Object data) {for (String clientId : clients.keySet()) {sendMessage(clientId, type, data);}}
}

1.2 推送服務Controller

package com.server.common.notice.controller;import cn.hutool.core.util.ObjectUtil;
import com.server.common.notice.service.SseService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.time.Instant;@Slf4j
@RestController
@RequestMapping("/common/stream")
public class SseController {@Resourceprivate SseService sseService;/*** 建立 SSE 連接** @param clientId 連接id* @return sse 事件*/@GetMapping("/connect/{clientId}")public SseEmitter connect(@PathVariable String clientId, @RequestParam String token) {// todo 編寫自己的token校驗,且不加刷新token邏輯,否則系統永不掉線return sseService.connect(clientId);}/*** 給指定用戶推送(僅測試用)** @param clientId 連接id* @param type     類型* @param data     數據*/@PostMapping("/push/{clientId}")public void push(@PathVariable String clientId,@RequestParam String type,@RequestBody Object data) {sseService.sendMessage(clientId, type, data);}/*** 廣播推送(僅測試用)** @param type 類型* @param data 數據*/@PostMapping("/broadcast")public void broadcast(@RequestParam String type,@RequestBody Object data) {sseService.broadcast(type, data);}
}

1.3 定時心跳,保證鏈接不中斷

package com.server.common.notice.schedule;import com.server.common.notice.service.SseService;
import jakarta.annotation.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class SseHeartbeatTask {@Resourceprivate SseService sseService;/*** 每30秒執行一次,給sse鏈接事件,發送一次廣播*/@Scheduled(fixedRate = 30000)public void sendHeartbeat() {sseService.broadcast("HEARTBEAT", "ping");}
}

2 前端公共組件封裝

核心:
1、使用公共變量存儲前端唯一的:eventSource ,不重復建立連接
2、pinia 類定義了前端唯一的sse事件監聽器,且存儲所有消息
3、封裝消息展示組件固定化流程,防止鏈接重復創建
包含:sseStore.js、PushMessage.vue

2.1 sseStore.js

注:其中鏈接地址的前綴需要你根據實際業務調整:例如:import.meta.env.VITE_BASE_API
屬性 messages:存儲的消息列表:格式:[{id:唯一標識,type:消息類型,data:消息內容}]
屬性 connected:是夠鏈接建立成功
方法:
1)initSse 初始化鏈接,傳遞用戶id 和 用戶的token
2)removeMessage:存儲中刪除指定消息

import {defineStore} from 'pinia';
import {ref} from 'vue';let eventSource = null; // 全局唯一 SSE 連接export const useSseStore = defineStore('sse', () => {const messages = ref([]);const connected = ref(false);function initSse(clientId, token) {if (eventSource) return; // 已存在連接eventSource = new EventSource(`/api/common/stream/connect/${clientId}?token=${token}`);eventSource.addEventListener('INIT', () => {connected.value = true;console.log('SSE connected');});eventSource.addEventListener('MESSAGE', event => {const msg = JSON.parse(event.data);messages.value.push(msg);if (messages.value.length > 500) messages.value.shift(); // 限制緩存});eventSource.addEventListener('HEARTBEAT', () => {// 可更新心跳狀態});eventSource.onerror = () => {connected.value = false;console.warn('SSE error, will auto-reconnect');};}function removeMessage(id) {messages.value = messages.value.filter(msg => msg.id !== id);}return {messages, connected, initSse, removeMessage};
});

2.2 PushMessage.vue

注:onMounted的參數需要你根據實際業務傳遞,ui展示也需要您根據業務調整

<template><div class="push-messages"><h4>{{ title }},鏈接狀態:{{ sseStore.connected }}</h4><ul><li v-for="msg in filteredMessages" :key="msg.id">{{ msg.data }}<button @click="remove(msg.id)">刪除</button></li></ul><p v-if="!filteredMessages.length">暫無消息</p></div>
</template><script setup>
import {computed, onMounted} from 'vue';
import {useSseStore} from '@/pinia/sseStore.js';const props = defineProps({// 消息類型moduleType: {type: String, required: true},// 組件標題title: {type: String, default: '消息推送'},// 最大緩存數量maxCache: {type: Number, default: 50}
});const sseStore = useSseStore();// 過濾指定模塊消息
const filteredMessages = computed(() => {return sseStore.messages.filter(msg => msg.type === props.moduleType).slice(-props.maxCache);
});function remove(id) {sseStore.removeMessage(id);
}// 組件掛載時調用 initSse
onMounted(() => {// todo 這里需要根據你的實際情況:傳遞用戶id 和需要校驗的tokensseStore.initSse()
})
</script><style scoped>
.push-messages {border: 1px solid #ddd;padding: 10px;max-height: 300px;overflow-y: auto;
}.push-messages ul {list-style: none;padding: 0;margin: 0;
}.push-messages li {display: flex;justify-content: space-between;padding: 4px 0;border-bottom: 1px dashed #eee;
}.push-messages button {background: #f5f5f5;border: 1px solid #ccc;padding: 2px 5px;cursor: pointer;
}
</style>

3 前端組件測試

包含:1 sseApi.js 本質就是網路請求公共提取
2 SseMessageTest.vue,測試頁面

3.1 sseApi.js

這里引用的utils/request會自動添加header 頭

import request from '@/utils/request'export default {// 給用發送消息sendToUser(clientId, type, data) {return request({url: `/common/stream/push/${clientId}?type=${type}`,method: 'post',data: data,headers: {'Content-Type': 'application/json',  // 根據需求設置}})},//broadcast(type, data) {return request({url: `/common/stream/broadcast?type=${type}`,method: 'post',data: data,headers: {'Content-Type': 'application/json',  // 根據需求設置}})},
}

3.1 SseMessageTest.vue

下列:common.getUserIdByToken() 為我這獲取當前登陸用戶id的前端方法,請根據實際業務進行替換

<template><el-card class="sse-message-test" shadow="hover"><template #header><span>📡 SSE 消息測試,用戶:{{ common.getUserIdByToken() }}</span></template><el-divider content-position="left">1 給指定用戶發消息</el-divider><!-- 給指定用戶發消息 --><el-form :inline="true" :model="formSingle" class="form-block"><el-form-item label="用戶ID"><el-input v-model="formSingle.clientId" placeholder="請輸入用戶ID" style="width: 200px"/></el-form-item><el-form-item label="類型"><el-input v-model="formSingle.type" placeholder="如 chat/order" style="width: 160px"/></el-form-item><el-form-item label="消息"><el-input v-model="formSingle.data" placeholder="請輸入消息內容" style="width: 260px"/></el-form-item><el-form-item><el-button type="primary" @click="sendToUser">發送給用戶</el-button></el-form-item></el-form><el-divider/><el-divider content-position="left">2 給指所有用戶廣播消息</el-divider><!-- 廣播消息 --><el-form :inline="true" :model="formBroadcast" class="form-block"><el-form-item label="類型"><el-input v-model="formBroadcast.type" placeholder="如 notice/chat" style="width: 160px"/></el-form-item><el-form-item label="消息"><el-input v-model="formBroadcast.data" placeholder="請輸入廣播內容" style="width: 260px"/></el-form-item><el-form-item><el-button type="success" @click="broadcast">廣播所有人</el-button></el-form-item></el-form><el-divider content-position="left">3 收到的指定消息</el-divider><push-message module-type="chat"/><el-divider content-position="left">4 收到的廣播消息</el-divider><el-divider content="廣播信息"/><push-message module-type="notice"/></el-card>
</template><script setup>
import {reactive} from 'vue'
import {ElMessage} from 'element-plus'
import sseApi from '@/api/sys/sseApi.js'
import common from "@/utils/common.js";import PushMessage from "@/components/message/PushMessage.vue";// 單用戶消息
const formSingle = reactive({clientId: common.getUserIdByToken(),type: 'chat',data: ''
})// 廣播消息
const formBroadcast = reactive({type: 'notice',data: ''
})// 給指定用戶發消息
async function sendToUser() {if (!formSingle.clientId || !formSingle.data) {return ElMessage.warning('請填寫用戶ID和消息內容')}try {await sseApi.sendToUser(formSingle.clientId, formSingle.type, formSingle.data)ElMessage.success(`已向 ${formSingle.clientId} 發送消息`)} catch (e) {ElMessage.error('發送失敗')}
}// 廣播所有人
async function broadcast() {if (!formBroadcast.data) {return ElMessage.warning('請輸入廣播內容')}try {await sseApi.broadcast(formBroadcast.type, formBroadcast.data)ElMessage.success('廣播成功')} catch (e) {console.log('廣播失敗', e)ElMessage.error('廣播失敗:')}
}
</script><style scoped>
.sse-message-test {margin: 20px;
}.form-block {margin-bottom: 15px;
}
</style>

3 nginx部署改動

單獨添加

       # SSE 專用location /api/common/stream/connect/ {proxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;# SSE 專用配置proxy_http_version 1.1;        # SSE 必須 HTTP/1.1proxy_set_header Connection ''; # 保持長連接chunked_transfer_encoding off; # 保證消息實時# 本機上運行的后端接口proxy_pass http://127.0.0.1:8080/common/stream/connect/;}

完整的配置nginx的配置,這邊nginx安裝是啟用了壓縮
詳見:https://blog.csdn.net/qq_26408545/article/details/133685624?spm=1001.2014.3001.5502

# Nginx 進程數,一般設置為和 CPU 核數一樣,可設置 auto
worker_processes  auto;#error_log  logs/error.log; # Nginx 的錯誤日志存放目錄
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;#pid        logs/nginx.pid; # Nginx 服務啟動時的 pid 存放位置events {# 根據操作系統自動選擇:建議指定事件驅動模型,避免 Nginx 誤判環境use epoll;# 每個進程允許最大并發數# 小規模的服務器:512或1024,中等規模的服務器:2048或4096,大規模的服務器:8192或更高# 考慮到內存占用和CPU的利用率,一般建議不要將worker_connections設置得過高worker_connections  2048;# 默認:off,高并發下建議開,讓 worker 每次盡量多 accept 新連接multi_accept on;# 默認:on,避免多個 worker 同時搶占 accept,減少驚群現象accept_mutex on;
}http {include       mime.types;# 文件擴展名與類型映射表default_type  application/octet-stream;# 默認文件類型# 設置日志模式#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '#                  '$status $body_bytes_sent "$http_referer" '#                  '"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main; # Nginx訪問日志存放位置sendfile        on;# 開啟高效傳輸模式#tcp_nopush     on;# 減少網絡報文段的數量keepalive_timeout  65;# 保持連接的時間,也叫超時時間,單位秒gzip on;#表示開啟壓縮功能gzip_static on;#靜態文件壓縮開啟# 設置壓縮的最低文件大小(默認值是 20 字節)gzip_min_length 5k;# 設置為 1KB 或更大,避免對小文件壓縮# 設置使用的壓縮算法(一般是 gzip)gzip_comp_level 7;# 范圍是 1-9,數字越大壓縮率越高,但占用 CPU 更多# 開啟對特定文件類型的壓縮(不建議壓縮緊湊格式:圖片)gzip_types text/plain text/css application/javascript application/json application/xml text/xml application/xml+rss text/javascript application/font-woff2 application/font-woff application/font-otf;# 不壓縮的 MIME 類型gzip_disable "msie6";# 禁止壓縮 IE6 瀏覽器# 壓縮緩存控制gzip_vary on;# 設置響應頭 `Vary: Accept-Encoding`# 壓縮后文件傳輸gzip_buffers 16 8k;# 設定緩沖區大小#認證后臺server {listen       80; # 88 ssl 本服務監聽的端口號server_name  localhost; # 主機名稱client_max_body_size 600m;client_body_buffer_size 128k;proxy_connect_timeout 600;proxy_read_timeout 600;proxy_send_timeout 600;proxy_buffer_size 64k;proxy_buffers   4 32k;proxy_busy_buffers_size 64k;proxy_temp_file_write_size 64k;# 首頁 index.html — 禁止緩存,強烈推薦location = /index.html {root /opt/sm-crypto/process-center-web/dist;add_header Cache-Control "no-cache, no-store, must-revalidate";add_header Pragma "no-cache";add_header Expires "0";try_files $uri =404;}# 靜態資源 /assets/,緩存7天,不帶immutable,允許刷新更新location /assets/ {root /opt/sm-crypto/process-center-web/dist;expires 7d;add_header Cache-Control "public";}location / {# root 規定了通過監聽的端口號訪問的文件目錄root  /opt/sm-crypto/process-center-web/dist;# 配置資源重新跳轉,防止刷新后頁面丟失try_files $uri $uri/  /index.html;# index 規定了該目錄下指定哪個文件index  index.html index.htm;}# SSE 專用location /api/common/stream/connect/ {proxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;# proxy_http_version 1.1;        # SSE 必須 HTTP/1.1proxy_set_header Connection ''; # 保持長連接chunked_transfer_encoding off; # 保證消息實時# 本機上運行的后端接口proxy_pass http://127.0.0.1:8080/common/stream/connect/;}# 配置后端接口的跨域代理# 對于路徑為 "api 的接口,幫助他跳轉到指定的地址location /api/ {proxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header REMOTE-HOST $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;# 本機上運行的后端接口proxy_pass http://127.0.0.1:8080/; 	}location /status{stub_status on;}}}

4 分布式系統,該后臺接口改動介紹

實現有多重可問AI,下面只是實現方案之一

基于 消息隊列(推薦)

  1. 核心思路
    • 每臺應用實例只維護自己的 SSE 連接
    • 推送消息通過 消息中間件(Redis Pub/Sub、Kafka、RabbitMQ 等)廣播到所有節點
    • 每臺節點收到消息后,將其推送給本節點內存里的 SSE 客戶端
  2. 流程示意
客戶端(EventSource)|v節點 A --------> Redis Pub/Sub ---------> 節點 B|                                   |v                                   vSseEmitter                           SseEmitter
  1. 優點
    • 高可用,自動擴展節點
    • 節點之間解耦
    • 消息順序可控(Kafka 支持順序)
  2. 實現舉例(Redis Pub/Sub)
// 發布消息
redisTemplate.convertAndSend("sse:channel", msg);// 訂閱消息
@EventListener
public void onMessage(Message msg) {sseService.broadcast(msg.getType(), msg.getData());
}

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

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

相關文章

LeetCode 每日一題 2025/9/1-2025/9/7

記錄了初步解題思路 以及本地實現代碼&#xff1b;并不一定為最優 也希望大家能一起探討 一起進步 目錄9/1 1792. 最大平均通過率9/2 3025. 人員站位的方案數 I9/3 3027. 人員站位的方案數 II9/4 3516. 找到最近的人9/5 2749. 得到整數零需要執行的最少操作數9/6 3495. 使數組元…

小迪安全v2023學習筆記(八十講)—— 中間件安全WPS分析WeblogicJenkinsJettyCVE

文章目錄前記服務攻防——第八十天中間件安全&HW2023-WPS分析&Weblogic&Jetty&Jenkins&CVE應用WPS - HW2023-RCE&復現&上線CS介紹漏洞復現中間件 - Weblogic-CVE&反序列化&RCE介紹利用中間件 - Jenkins-CVE&RCE執行介紹漏洞復現CVE-20…

各webshell管理工具流量分析

哥斯拉哥斯拉是一個基于流量、HTTP全加密的webshell管理工具 特點 1.內置了3種Payload以及6種加密器&#xff0c;6種支持腳本后綴&#xff0c;20個內置插件 2.基于java&#xff0c;可以跨平臺使用 3.可以自己生成webshell&#xff0c;根據管理來生成一些payload&#xff0c;然后…

pytest(1):fixture從入門到精通

pytest&#xff08;1&#xff09;&#xff1a;fixture從入門到精通前言1. Fixture 是什么&#xff1f;為什么我們需要它&#xff1f;2. 快速上手&#xff1a;第一個 Fixture 與基本用法3. 作用域 (Scope)&#xff1a;控制 Fixture 的生命周期4. 資源管理&#xff1a;Setup/Tear…

Java17 LTS 新特性用例

基于 Java 17 LTS 的 實用示例 以下是基于 Java 17 LTS 的 30 個實用示例,涵蓋語言新特性、API 改進及常見場景。所有代碼均兼容 Java 17 語法規范。 文本塊(Text Blocks) String json = """{"name": "Java 17","type": &qu…

SpringBoot-Web開發-內容協商——多端內容適配內容協商原理HttpMessageConverter

其它篇章&#xff1a; 一&#xff1a;SpringBoot3-日志——日志原理&日志格式&日志級別&日志分組&文件輸出&文件歸檔&滾動切割 二&#xff1a;SpringBoot3-Web開發-靜態資源——WebMvcAutoConfiguration原理&資源映射&資源緩存&歡迎頁&…

Spring MVC 類型轉換與參數綁定:從架構到實戰

在 Spring MVC 開發中&#xff0c;“前端請求數據” 與 “后端 Java 對象” 的格式差異是高頻痛點 —— 比如前端傳的String類型日期&#xff08;2025-09-08&#xff09;要轉成后端的LocalDate&#xff0c;或者字符串male要轉成GenderEnum.MALE枚舉。Spring 并非通過零散工具解…

Spark提交任務的資源配置和優化

Spark 提交任務時主要可調的資源配置參數包括 Driver 資源&#xff08;內存、CPU&#xff09;、Executor 資源&#xff08;數量、內存、CPU&#xff09;以及 集群管理相關參數。配置和優化時一般結合集群硬件資源、數據規模、作業類型和作業復雜度&#xff08;SQL / 機器學習&a…

機器學習06——支持向量機(SVM核心思想與求解、核函數、軟間隔與正則化、支持向量回歸、核方法)

上一章&#xff1a;機器學習05——多分類學習與類別不平衡 下一章&#xff1a;機器學習07——貝葉斯分類器 機器學習實戰項目&#xff1a;【從 0 到 1 落地】機器學習實操項目目錄&#xff1a;覆蓋入門到進階&#xff0c;大學生就業 / 競賽必備 文章目錄一、間隔與支持向量&…

AI集群全鏈路監控:從GPU微架構指標到業務Metric關聯

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;H卡級別算力&#xff0c;80G大顯存&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生更享專屬優惠。 引言&#xff1a;AI算力時代的監控挑戰 隨著深度學習模型規模的指…

K8s Ingress Annotations參數使用指南

Kubernetes Ingress Annotations 是與特定 Ingress 控制器&#xff08;如 Nginx、Traefik、HAProxy 等&#xff09;配合使用&#xff0c;用于擴展和定制 Ingress 資源行為的關鍵配置項。它們通常以鍵值對的形式添加在 Ingress 資源的 metadata部分。Ingress Annotations參數速查…

CodeBuddy Code深度實戰:從零構建智能電商推薦系統的完整開發歷程

項目背景與挑戰作為一名有著多年全棧開發經驗的技術人員&#xff0c;我最近接手了一個具有挑戰性的項目&#xff1a;為某中型服裝電商平臺開發一套智能商品推薦系統。該系統需要在2個月內完成&#xff0c;包含以下核心功能&#xff1a;前端&#xff1a;React TypeScript構建的…

Day 19: 算法基礎與面試理論精通 - 從思想理解到策略掌握的完整體系

Day 19: 算法基礎與面試理論精通 - 從思想理解到策略掌握的完整體系 ?? 課程概述 核心目標:深度理解算法設計思想和核心原理,掌握面試高頻算法概念,建立完整的算法知識體系 學習重點: ? 核心數據結構的本質理解和應用場景分析 ? 經典算法設計模式的思想精髓和解題策…

AI與AR融合:重塑石化與能源巡檢的未來

在石化企業和新能源電站的巡檢工作中&#xff0c;傳統模式正被一場技術革命所顛覆。AI與AR&#xff08; www.teamhelper.cn &#xff09;的深度融合&#xff0c;不僅提升了巡檢效率&#xff0c;更將巡檢工作從被動響應轉變為預測預防&#xff0c;開啟了智能運維的新篇章。一、透…

滴滴二面(準備二)

手寫防抖函數并清晰闡述其價值&#xff0c;確實是前端面試的常見考點。下面我將為你直接呈現防抖函數的代碼&#xff0c;并重點結合滴滴的業務場景進行解釋&#xff0c;幫助你向面試官展示思考深度。 這是防抖函數的一個基本實現&#xff0c;附帶注釋以便理解&#xff1a; func…

Kubernetes(四):Service

目錄 一、定義Service 1.1 typeClusterIP 1.2 typeNodePort 1.3 typeLoadBalancer 1.4 typeExternalName 1.5 無標簽選擇器的Service 1.6 Headless Service 二、Kubernetes的服務發現 2.1 環境變量方式 2.2 DNS方式 Kubernetes 中 Service 是 將運行在一個或一組 Pod 上的應用…

在 Python 中實現觀察者模式的具體步驟是什么?

在 Python 中實現觀察者模式可以遵循以下具體步驟&#xff0c;這些步驟清晰地劃分了角色和交互流程&#xff1a; 步驟 1&#xff1a;定義主題&#xff08;Subject&#xff09;基類 主題是被觀察的對象&#xff0c;負責管理觀察者和發送通知。需實現以下核心方法&#xff1a; 存…

分布式方案 一 分布式鎖的四大實現方式

Java分布式鎖實現方式詳解 什么是分布式鎖 基于數據庫的分布式鎖基于Redis的分布式鎖基于ZooKeeper的分布式鎖基于Etcd的分布式鎖 各種實現方式對比最佳實踐建議多節點/線程調用結果展示 基于數據庫的分布式鎖 - 多線程測試基于Redis的分布式鎖 - 多節點測試基于ZooKeeper的分…

基于Room+RESTful的雙權限Android開機時間監控方案

概述 以下是使用Kotlin實現的商業級Android開機時間記錄功能&#xff0c;包含現代Android開發最佳實踐。 系統架構 組件設計 // BootReceiver - 接收開機廣播 class BootReceiver : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent?) {if (int…

水庫大壩安全監測系統的作用

水庫大壩作為重要的水利基礎設施&#xff0c;承擔著防洪、供水、發電、灌溉等多重功能&#xff0c;其安全性直接關系到人民生命財產安全和社會經濟發展。然而&#xff0c;由于自然環境變化、材料老化、荷載作用以及人為因素的影響&#xff0c;大壩在長期運行過程中可能出現裂縫…