java中springboot專案整合socketIo實現實時推送
今天在這裡跟大家分享一下springboot專案整合socketIo實現實時推送功能。不多說什麼直接上程式碼,然後慢慢講解。
第一步專案中準備socketIo的執行環境
<!--socketio--> <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>1.7.11</version> </dependency>
第二步socketIo的執行類,和啟動類。
@Configuration public class NettySocketConfig { private String ipWin; private String ipLinxu; private int port; @Bean public SocketIOServer socketIOServer() throws Exception{ ipWin = (String)YmlUtil.getValue("socketIo.win"); ipLinxu = (String)YmlUtil.getValue("socketIo.linxu"); port = (Integer) YmlUtil.getValue("socketIo.port"); com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); String os = System.getProperty("os.name"); if(os.toLowerCase().startsWith("win")){ //在本地window環境測試時用localhost config.setHostname(ipWin); } else { //部署到你的遠端伺服器正式釋出環境時用伺服器公網ip config.setHostname(ipLinxu); } // 埠,任意 config.setPort(port); config.setMaxFramePayloadLength(1024 * 1024); config.setMaxHttpContentLength(1024 * 1024); //該處進行身份驗證h config.setAuthorizationListener(handshakeData -> { //http://localhost:8081?username=test&password=test //例如果使用上面的連結進行connect,可以使用如下程式碼獲取使用者密碼資訊 //String username = data.getSingleUrlParam("username"); //String password = data.getSingleUrlParam("password"); return true; }); final SocketIOServer server = new SocketIOServer(config); return server; } @Bean public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) { return new SpringAnnotationScanner(socketServer); } }
讀取配置檔案的util類,用於讀取yml檔案中的配置
public class YmlUtil { /** * key:檔名索引 * value:配置檔案內容 */ private static Map<String, LinkedHashMap> ymls = new HashMap<>(); /** * string:當前執行緒需要查詢的檔名 */ private static ThreadLocal<String> nowFileName = new ThreadLocal<>(); /** * 載入配置檔案 * @param fileName */ public static void loadYml(String fileName) { nowFileName.set(fileName); if (!ymls.containsKey(fileName)) { ymls.put(fileName, new Yaml().loadAs(YmlUtil.class.getResourceAsStream("/" + fileName), LinkedHashMap.class)); } } public static Object getValueByKey(String key) throws Exception { // 首先將key進行拆分 String[] keys = key.split("[.]"); // 將配置檔案進行復制 Map ymlInfo = (Map) ymls.get(nowFileName.get()).clone(); for (int i = 0; i < keys.length; i++) { Object value = ymlInfo.get(keys[i]); if (i < keys.length - 1) { ymlInfo = (Map) value; } else if (value == null) { throw new Exception("key不存在"); } else { return value; } } throw new RuntimeException("不可能到這裡的..."); } public static Object getValue(String key) throws Exception { // 首先載入配置檔案 loadYml("application.yml"); return getValueByKey(key); } }
socketIo啟動類
@Component @Order(value=1) public class ServerRunner implements CommandLineRunner { private final SocketIOServer server; @Autowired public ServerRunner(SocketIOServer server) { this.server = server; } @Override public void run(String... args) throws Exception { server.start(); System.out.println("socket.io啟動成功!"); } }
socketIo的前後端互動
@Component public class SocketIoServer { public static SocketIOServer socketIoServer; @Autowired public SocketIoServer(SocketIOServer server) { this.socketIoServer = server; } @OnConnect public void onConnect(SocketIOClient client) { // TODO Auto-generated method stub String sa = client.getRemoteAddress().toString(); String clientIp = sa.substring(1, sa.indexOf(":"));// 獲取裝置ip System.out.println(clientIp + "-------------------------" + "客戶端已連線"); Map<String, List<String>> params = client.getHandshakeData().getUrlParams(); SocketIoServerMapUtil.put(clientIp, client); } @OnDisconnect public void onDisconnect(SocketIOClient client) { // TODO Auto-generated method stub String sa = client.getRemoteAddress().toString(); String clientIp = sa.substring(1, sa.indexOf(":"));// 獲取裝置ip System.out.println(clientIp + "-------------------------" + "客戶端已斷開連線"); SocketIoServerMapUtil.remove(clientIp); } @OnEvent(value = "text") public void onEvent(SocketIOClient client, AckRequest ackRequest, String data) { // TODO Auto-generated method stub // 客戶端推送advert_info事件時,onData接受資料,這裡是string型別的json資料,還可以為Byte[],object其他型別 String sa = client.getRemoteAddress().toString(); String clientIp = sa.substring(1, sa.indexOf(":"));// 獲取客戶端連線的ip Map<String, List<String>> params = client.getHandshakeData().getUrlParams();// 獲取客戶端url引數 System.out.println(clientIp + ":客戶端:************" + data); JSONObject gpsData = (JSONObject) JSONObject.parse(data); String userIds = gpsData.get("userName") + ""; String taskIds = gpsData.get("password") + ""; client.sendEvent("text1", "後臺得到了資料"); } }
存放前端連線的裝置ip SocketIoServerMapUtil 類
public class SocketIoServerMapUtil { public static ConcurrentMap<String, SocketIOClient> webSocketMap = new ConcurrentHashMap<>(); public static void put(String key, SocketIOClient SocketIOClient) { webSocketMap.put(key, SocketIOClient); } public static SocketIOClient get(String key) { return webSocketMap.get(key); } public static void remove(String key) { webSocketMap.remove(key); } public static Collection<SocketIOClient> getValues() { return webSocketMap.values(); } public static ConcurrentMap<String, SocketIOClient> getWebSocketMap() { return webSocketMap; } }
前端程式碼,這裡需要我們前端專案中引進socket.io.js這個類。我這邊介紹vue引入socket:npm install --save socket.io,不是vue環境的直接去下載一個js檔案。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="./socket.io.js"></script> </head> <body> <script> //建立socket var socket = io('http://localhost:9092');//用於連線後端的服務 /* 建立自定義事件 'news' 作用:接受服務端 socket.emit('news', 資料); 發出的資料 */ socket.on('connect', function () { socket.emit('text', JSON.stringify({userName:"caiyy",passWord:"123456"})); }); socket.on('text1', function (data) { //輸出服務端響應了資料 console.log(data); }); </script> </body> </html>
現在所有的程式碼已經都準備好了,開始講解詳細步驟了。
開始匯入socketIo的jar包,由於springboot存在一個啟動類,我們這邊的socketIo的配置雖然可以放在springboot的啟動類中,但是一般情況下還是提取起來到一個類中。
public static void main(String[] args) { SpringApplication.run(EventApplication.class,args); }
這裡定義的是NettySocketConfig類。這個類中是socketIo的配置項,ipWin是window系統的ip,一般也就是本機測試的ip127.0.0.1;ipLinxu是linxu環境的ip,一般是我們正式環境的ip;port是socket的埠號,一般是9092,這些都是可以在application.yml中配置的,下面就是我在yml中的配置。
#網路ip socketIo: win: localhost linxu: 各自linxu環境的ip port: 9092
然後就是配置socketIo的啟動項即ServerRunner類。@Order(value=1)這個配置很重要,
這個標記包含一個value屬性。屬性接受整形值。如:1,2 等等。值越小擁有越高的優先順序。就是因為我們springboot自帶一個啟動類,所以在這裡配置啟動的value值為1.就是在springboot啟動之後在啟動socketIo.啟動完成配置也搞定了 ,現在就是要進行前後端的互動了。
@OnConnect用於監聽客戶端連線資訊的,
@OnDisconnect使用者監聽客戶端斷開資訊的。
@OnEvent(value = "text")使用者後端監聽前端的請求事件的。value值就是前端請求的唯一標識,前端攜帶這個請求的唯一標識進行請求後臺,然後後臺監聽到這個請求,然後進行一系列操作。
client.sendEvent("text1", "後臺得到了資料");用於後端響應前端資料。text1就是後臺給前端的唯一標識,前端通過這個唯一標識來篩選後端給的資料是否是這個自己這個連線中所需要的。來確保訊息不會發送給錯誤的前端連線者。
當後臺服務啟動之後,通過瀏覽器訪問我們寫的那個頁面,後臺就是看到想對應的ip連到後臺服務當中,前端也會相對應的打印出“後臺得到了資料”。
現在就在講一箇中間寫了的一個沒用用到的util類,SocketIoServerMapUtil 類。
這個類使用者儲存前端正在存於連線的ip,我們可以用這個類進行集體推送訊息。只要前端連線到我們這個服務,在SocketIoServer裡面的監聽裡面就用到了這個類的put方法。SocketIoServerMapUtil.put(clientIp, client);存入了客戶端的ip。當我們的程式中某一個步驟啟動了,要觸發到集體推送訊息的時候,我們可以在這個步驟中新增一段程式碼:
for (SocketIOClient client: SocketIoServerMapUtil.getValues()) { client.sendEvent("quntui", "新年快樂"); }
然後在前端程式碼需要接受這個群推的登入者中加入以下程式碼就可以得到這個訊息:
socket.on('quntui', function (data) { //輸出服務端響應了資料 alert(data); });
本次分享到此結束。。。歡迎大家留言評論和互相交流。
原文作者技術部落格:https://www.jianshu.com/u/ac4daaeecdfe
95後前端妹子一枚,愛閱讀,愛交友,將工作中遇到的問題記錄在這裡,希望給每一個看到的你能帶來一點幫助。