【websocket】spring boot 集成 websocket 的四種方式

来源:https://www.cnblogs.com/kiwifly/archive/2019/10/23/11729304.html
-Advertisement-
Play Games

集成 websocket 的四種方案 1. 原生註解 pom.xml WebSocketConfig 說明: 這個配置類很簡單,通過這個配置 spring boot 才能去掃描後面的關於 websocket 的註解 WsServerEndpoint 說明 這裡有幾個註解需要註意一下,首先是他們的包都 ...


集成 websocket 的四種方案

1. 原生註解

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocketConfig

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-18 15:45 buhao
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpoint() {
        return new ServerEndpointExporter();
    }
}
說明:

這個配置類很簡單,通過這個配置 spring boot 才能去掃描後面的關於 websocket 的註解

WsServerEndpoint

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.ws;

import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author buhao
 * @version WsServerEndpoint.java, v 0.1 2019-10-18 16:06 buhao
 */
@ServerEndpoint("/myWs")
@Component
public class WsServerEndpoint {

    /**
     * 連接成功
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        System.out.println("連接成功");
    }

    /**
     * 連接關閉
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        System.out.println("連接關閉");
    }

    /**
     * 接收到消息
     *
     * @param text
     */
    @OnMessage
    public String onMsg(String text) throws IOException {
        return "servet 發送:" + text;
    }
}
說明

這裡有幾個註解需要註意一下,首先是他們的包都在 javax.websocket 下。並不是 spring 提供的,而 jdk 自帶的,下麵是他們的具體作用。

  1.  @ServerEndpoint
  2. 通過這個 spring boot 就可以知道你暴露出去的 ws 應用的路徑,有點類似我們經常用的@RequestMapping。比如你的啟動埠是8080,而這個註解的值是ws,那我們就可以通過 ws://127.0.0.1:8080/ws 來連接你的應用
  3.  @OnOpen
  4. 當 websocket 建立連接成功後會觸發這個註解修飾的方法,註意它有一個 Session 參數
  5. @OnClose
  6. 當 websocket 建立的連接斷開後會觸發這個註解修飾的方法,註意它有一個 Session 參數
  7. @OnMessage
  8. 當客戶端發送消息到服務端時,會觸發這個註解修改的方法,它有一個 String 入參表明客戶端傳入的值
  9. @OnError
  10. 當 websocket 建立連接時出現異常會觸發這個註解修飾的方法,註意它有一個 Session 參數

另外一點就是服務端如何發送消息給客戶端,服務端發送消息必須通過上面說的 Session 類,通常是在@OnOpen 方法中,當連接成功後把 session 存入 Map 的 value,key 是與 session 對應的用戶標識,當要發送的時候通過 key 獲得 session 再發送,這裡可以通過 **session.getBasicRemote_().sendText(_)** 來對客戶端發送消息。

2. Spring封裝

pom.xml

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

HttpAuthHandler

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.handler;

import cn.coder4j.study.example.websocket.config.WsSessionManager;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.time.LocalDateTime;

/**
 * @author buhao
 * @version MyWSHandler.java, v 0.1 2019-10-17 17:10 buhao
 */
@Component
public class HttpAuthHandler extends TextWebSocketHandler {

    /**
     * socket 建立成功事件
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Object token = session.getAttributes().get("token");
        if (token != null) {
            // 用戶連接成功,放入線上用戶緩存
            WsSessionManager.add(token.toString(), session);
        } else {
            throw new RuntimeException("用戶登錄已經失效!");
        }
    }

    /**
     * 接收消息事件
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 獲得客戶端傳來的消息
        String payload = message.getPayload();
        Object token = session.getAttributes().get("token");
        System.out.println("server 接收到 " + token + " 發送的 " + payload);
        session.sendMessage(new TextMessage("server 發送給 " + token + " 消息 " + payload + " " + LocalDateTime.now().toString()));
    }

    /**
     * socket 斷開連接時
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Object token = session.getAttributes().get("token");
        if (token != null) {
            // 用戶退出,移除緩存
            WsSessionManager.remove(token.toString());
        }
    }


}
說明

通過繼承 TextWebSocketHandler 類並覆蓋相應方法,可以對 websocket 的事件進行處理,這裡可以同原生註解的那幾個註解連起來看

  1. afterConnectionEstablished 方法是在 socket 連接成功後被觸發,同原生註解里的 @OnOpen 功能
  2. afterConnectionClosed  方法是在 socket 連接關閉後被觸發,同原生註解里的 @OnClose 功能
  3. handleTextMessage 方法是在客戶端發送信息時觸發,同原生註解里的 @OnMessage 功能

    WsSessionManager

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author buhao
 * @version WsSessionManager.java, v 0.1 2019-10-22 10:24 buhao
 */
@Slf4j
public class WsSessionManager {
    /**
     * 保存連接 session 的地方
     */
    private static ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>();

    /**
     * 添加 session
     *
     * @param key
     */
    public static void add(String key, WebSocketSession session) {
        // 添加 session
        SESSION_POOL.put(key, session);
    }

    /**
     * 刪除 session,會返回刪除的 session
     *
     * @param key
     * @return
     */
    public static WebSocketSession remove(String key) {
        // 刪除 session
        return SESSION_POOL.remove(key);
    }

    /**
     * 刪除並同步關閉連接
     *
     * @param key
     */
    public static void removeAndClose(String key) {
        WebSocketSession session = remove(key);
        if (session != null) {
            try {
                // 關閉連接
                session.close();
            } catch (IOException e) {
                // todo: 關閉出現異常處理
                e.printStackTrace();
            }
        }
    }

    /**
     * 獲得 session
     *
     * @param key
     * @return
     */
    public static WebSocketSession get(String key) {
        // 獲得 session
        return SESSION_POOL.get(key);
    }
}
說明

這裡簡單通過 ConcurrentHashMap 來實現了一個 session 池,用來保存已經登錄的 web socket 的  session。前文提過,服務端發送消息給客戶端必須要通過這個 session。

MyInterceptor

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.HashMap;
import java.util.Map;

/**
 * @author buhao
 * @version MyInterceptor.java, v 0.1 2019-10-17 19:21 buhao
 */
@Component
public class MyInterceptor implements HandshakeInterceptor {

    /**
     * 握手前
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        System.out.println("握手開始");
        // 獲得請求參數
        HashMap<String, String> paramMap = HttpUtil.decodeParamMap(request.getURI().getQuery(), "utf-8");
        String uid = paramMap.get("token");
        if (StrUtil.isNotBlank(uid)) {
            // 放入屬性域
            attributes.put("token", uid);
            System.out.println("用戶 token " + uid + " 握手成功!");
            return true;
        }
        System.out.println("用戶登錄已失效");
        return false;
    }

    /**
     * 握手後
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param exception
     */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        System.out.println("握手完成");
    }

}
說明

通過實現 HandshakeInterceptor 介面來定義握手攔截器,註意這裡與上面 Handler 的事件是不同的,這裡是建立握手時的事件,分為握手前與握手後,而  Handler 的事件是在握手成功後的基礎上建立 socket 的連接。所以在如果把認證放在這個步驟相對來說最節省伺服器資源。它主要有兩個方法 beforeHandshake 與 afterHandshake ,顧名思義一個在握手前觸發,一個在握手後觸發。

WebSocketConfig

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import cn.coder4j.study.example.websocket.handler.HttpAuthHandler;
import cn.coder4j.study.example.websocket.interceptor.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-17 15:43 buhao
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private HttpAuthHandler httpAuthHandler;
    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry
                .addHandler(httpAuthHandler, "myWS")
                .addInterceptors(myInterceptor)
                .setAllowedOrigins("*");
    }
}
說明

通過實現 WebSocketConfigurer 類並覆蓋相應的方法進行 websocket 的配置。我們主要覆蓋 registerWebSocketHandlers 這個方法。通過向 WebSocketHandlerRegistry 設置不同參數來進行配置。其中 addHandler 方法添加我們上面的寫的 ws 的  handler 處理類,第二個參數是你暴露出的 ws 路徑。addInterceptors 添加我們寫的握手過濾器。**setAllowedOrigins("*") **這個是關閉跨域校驗,方便本地調試,線上推薦打開。

3. TIO

pom.xml

 <dependency>
     <groupId>org.t-io</groupId>
     <artifactId>tio-websocket-spring-boot-starter</artifactId>
     <version>3.5.5.v20191010-RELEASE</version>
</dependency>

application.xml

tio:
  websocket:
    server:
      port: 8989
說明

這裡只配置了 ws 的啟動埠,還有很多配置,可以通過結尾給的鏈接去尋找

MyHandler

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.handler;

import org.springframework.stereotype.Component;
import org.tio.core.ChannelContext;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.server.handler.IWsMsgHandler;

/**
 * @author buhao
 * @version MyHandler.java, v 0.1 2019-10-21 14:39 buhao
 */
@Component
public class MyHandler implements IWsMsgHandler {
    /**
     * 握手
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        return httpResponse;
    }

    /**
     * 握手成功
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @throws Exception
     */
    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        System.out.println("握手成功");
    }

    /**
     * 接收二進位文件
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        return null;
    }

    /**
     * 斷開連接
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        System.out.println("關閉連接");
        return null;
    }

    /**
     * 接收消息
     *
     * @param wsRequest
     * @param s
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {
        System.out.println("接收文本消息:" + s);
        return "success";
    }
}
說明

這個同上個例子中的 handler 很像,也是通過實現介面覆蓋方法來進行事件處理,實現的介面是IWsMsgHandler,它的方法功能如下

  1. handshake
  2. 在握手的時候觸發
  3. onAfterHandshaked
  4. 在握手成功後觸發
  5. onBytes
  6. 客戶端發送二進位消息觸發
  7. onClose
  8. 客戶端關閉連接時觸發
  9. onText
  10. 客戶端發送文本消息觸發

StudyWebsocketExampleApplication

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */

package cn.coder4j.study.example.websocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.tio.websocket.starter.EnableTioWebSocketServer;

@SpringBootApplication
@EnableTioWebSocketServer
public class StudyWebsocketExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudyWebsocketExampleApplication.class, args);
    }
}
說明

這個類的名稱不重要,它其實是你的 spring boot 啟動類,只要記得加上@EnableTioWebSocketServer註解就可以了

STOMP

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocketConfig

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-21 16:32 buhao
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 配置客戶端嘗試連接地址
        registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 設置廣播節點
        registry.enableSimpleBroker("/topic", "/user");
        // 客戶端向服務端發送消息需有/app 首碼
        registry.setApplicationDestinationPrefixes("/app");
        // 指定用戶發送(一對一)的首碼 /user/
        registry.setUserDestinationPrefix("/user/");
    }
}
說明
  1. 通過實現 WebSocketMessageBrokerConfigurer 介面和加上@EnableWebSocketMessageBroker來進行 stomp 的配置與註解掃描。
  2. 其中覆蓋 registerStompEndpoints 方法來設置暴露的 stomp 的路徑,其它一些跨域、客戶端之類的設置。
  3. 覆蓋 configureMessageBroker 方法來進行節點的配置。
  4. 其中 enableSimpleBroker 配置的廣播節點,也就是服務端發送消息,客戶端訂閱就能接收消息的節點。
  5. 覆蓋setApplicationDestinationPrefixes 方法,設置客戶端向服務端發送消息的節點。
  6. 覆蓋 setUserDestinationPrefix 方法,設置一對一通信的節點。

    WSController

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.controller;

import cn.coder4j.study.example.websocket.model.RequestMessage;
import cn.coder4j.study.example.websocket.model.ResponseMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author buhao
 * @version WSController.java, v 0.1 2019-10-21 17:22 buhao
 */
@Controller
public class WSController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/hello")
    @SendTo("/topic/hello")
    public ResponseMessage hello(RequestMessage requestMessage) {
        System.out.println("接收消息:" + requestMessage);
        return new ResponseMessage("服務端接收到你發的:" + requestMessage);
    }

    @GetMapping("/sendMsgByUser")
    public @ResponseBody
    Object sendMsgByUser(String token, String msg) {
        simpMessagingTemplate.convertAndSendToUser(token, "/msg", msg);
        return "success";
    }

    @GetMapping("/sendMsgByAll")
    public @ResponseBody
    Object sendMsgByAll(String msg) {
        simpMessagingTemplate.convertAndSend("/topic", msg);
        return "success";
    }

    @GetMapping("/test")
    public String test() {
        return "test-stomp.html";
    }
}
說明
  1. 通過 @MessageMapping 來暴露節點路徑,有點類似 @RequestMapping。註意這裡雖然寫的是 hello ,但是我們客戶端調用的真正地址是** /app/hello。 因為我們在上面的 config 里配置了registry.setApplicationDestinationPrefixes("/app")**。
  2. @SendTo這個註解會把返回值的內容發送給訂閱了 /topic/hello 的客戶端,與之類似的還有一個@SendToUser 只不過他是發送給用戶端一對一通信的。這兩個註解一般是應答時響應的,如果服務端主動發送消息可以通過 simpMessagingTemplate類的convertAndSend方法。註意 simpMessagingTemplate.convertAndSendToUser(token, "/msg", msg) ,聯繫到我們上文配置的 registry.setUserDestinationPrefix("/user/"),這裡客戶端訂閱的是/user/{token}/msg,千萬不要搞錯。

    Session 共用的問題

    上面反覆提到一個問題就是,服務端如果要主動發送消息給客戶端一定要用到 session。而大家都知道的是 session 這個東西是不跨 jvm 的。如果有多台伺服器,在 http 請求的情況下,我們可以通過把 session 放入緩存中間件中來共用解決這個問題,通過 spring session 幾條配置就解決了。但是 web socket  不可以。他的 session 是不能序列化的,當然這樣設計的目的不是為了為難你,而是出於對 http 與 web socket 請求的差異導致的。
    目前網上找到的最簡單方案就是通過 redis 訂閱廣播的形式,主要代碼跟第二種方式差不多,你要在本地放個 map 保存請求的 session。也就是說每台伺服器都會保存與他連接的 session 於本地。然後發消息的地方要修改,並不是現在這樣直接發送,而通過 redis 的訂閱機制。伺服器要發消息的時候,你通過 redis 廣播這條消息,所有訂閱的服務端都會收到這個消息,然後本地嘗試發送。最後肯定只有有這個對應用戶 session 的那台才能發送出去。

    如何選擇

  3. 如果你在使用 tio,那推薦使用 tio 的集成。因為它已經實現了很多功能,包括上面說的通過 redis 的 session 共用,只要加幾個配置就可以了。但是 tio 是半開源,文檔是需要收費的。如果沒有使用,那就忘了他。
  4. 如果你的業務要求比較靈活多變,推薦使用前兩種,更推薦第二種 Spring 封裝的形式。
  5. 如果只是簡單的伺服器雙向通信,推薦 stomp 的形式,因為他更容易規範使用。

    其它

  6. websocket 線上驗證

寫完服務端代碼後想調試,但是不會前端代碼怎麼辦,點這裡,這是一個線上的 websocket 客戶端,功能完全夠我們調試了。

  1. stomp 驗證

這個沒找到線上版的,但是網上有很多 demo 可以下載到本地進行調試,也可以通過後文的連接找到。

  1. 另外由於篇幅有限,並不能放上所有代碼,但是測試代碼全都上傳 gitlab,保證可以正常運行,可以在 這裡 找到

參考鏈接

  1. SpringBoot 系統 - 集成 WebSocket 實時通信
  2. WebSocket 的故事(二)—— Spring 中如何利用 STOMP 快速構建 WebSocket 廣播式消息模式
  3. SpringBoot集成WebSocket【基於純H5】進行點對點[一對一]和廣播[一對多]實時推送
  4. Spring Framework 參考文檔(WebSocket STOMP)
  5. Spring Boot中使用WebSocket總結(一):幾種實現方式詳解
  6. Spring Boot 系列 - WebSocket 簡單使用
  7. tio-websocket-spring-boot-starter

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 模塊在python編程中的地位舉足輕重,熟練運用模塊可以大大減少代碼量,以最少的代碼實現複雜的功能。 下麵介紹一下在python編程中如何導入模塊: (1)import 模塊名:直接導入,這裡導入模塊中的所有與函數; 這裡的模塊也可以是自己編寫的腳本名稱,如: (2) from 模塊名 import ...
  • 原文地址: "梁桂釗的博客" 博客地址: "http://blog.720ui.com" 歡迎關註公眾號:「服務端思維」。一群同頻者,一起成長,一起精進,打破認知的局限性。 今天,探討一個有趣的話題:我們可以通過 Git 來實現項目版本控制;通過 Jenkins 進行持續集成,那麼對於資料庫層面,我 ...
  • 集合類 Collection介面 定義的是所有單列集合中共性方法 創建對象使用多態 Collection<String> coll = new ArrayList<>() add() 把給定的對象添加到當前集合中,返回一個boolean值 remove() 在集合中刪除指定的對象,返回一個boole ...
  • 一、json數據 python數據類型與json對應關係表如下: 以上就是關於json的所有數據類型。 什麼是json? 定義: 講json對象,不得不提到JS對象: 合格的json對象: 不合格的json對象: python數據類型和json的轉換 JavaScript數據類型和json的轉換 p ...
  • 前面的章節,講解了[Spring Boot集成Spring Cache]( https://blog.csdn.net/Simple_Yangger/article/details/102693316),Spring Cache已經完成了多種Cache的實現,包括EhCache、RedisCache ...
  • 前面的章節,講解了[Spring Boot集成Spring Cache]( https://blog.csdn.net/Simple_Yangger/article/details/102693316),Spring Cache已經完成了多種Cache的實現,包括EhCache、RedisCache ...
  • 在python腳本中我們經常看到如下的代碼: # hello.py def hello(): print("hello world!") def test(): hello() if __name__ == '__main__': test() 通常,一個python文件有兩種使用方法: (1)直接 ...
  • 繼承Thread類 實現Runnable介面 匿名內部類的兩種寫法 基於java.util.concurrent.Callable工具類的實現,帶返回值 基於java.util.Timer工具類的實現 基於java.util.concurrent.Executors工具類,基於線程池的實現 更多信息 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...