国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

SpringBoot進(jìn)階教程(七十七)WebSocket

這篇具有很好參考價(jià)值的文章主要介紹了SpringBoot進(jìn)階教程(七十七)WebSocket。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在WebSocket API中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

v原理

很多網(wǎng)站為了實(shí)現(xiàn)推送技術(shù),所用的技術(shù)都是輪詢。輪詢是在特定的時(shí)間間隔(如每1秒),由瀏覽器對(duì)服務(wù)器發(fā)出HTTP請(qǐng)求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器。這種傳統(tǒng)的模式帶來(lái)很明顯的缺點(diǎn),即瀏覽器需要不斷的向服務(wù)器發(fā)出請(qǐng)求,然而HTTP請(qǐng)求可能包含較長(zhǎng)的頭部,其中真正有效的數(shù)據(jù)可能只是很小的一部分,顯然這樣會(huì)浪費(fèi)很多的帶寬等資源。

而比較新的技術(shù)去做輪詢的效果是Comet。這種技術(shù)雖然可以雙向通信,但依然需要反復(fù)發(fā)出請(qǐng)求。而且在Comet中,普遍采用的長(zhǎng)鏈接,也會(huì)消耗服務(wù)器資源。

在這種情況下,HTML5定義了WebSocket協(xié)議,能更好的節(jié)省服務(wù)器資源和帶寬,并且能夠更實(shí)時(shí)地進(jìn)行通訊。

v架構(gòu)搭建

添加maven引用

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

配置應(yīng)用屬性

server.port=8300
spring.thymeleaf.mode=HTML
spring.thymeleaf.cache=true
spring.thymeleaf.prefix=classpath:/web/
spring.thymeleaf.encoding: UTF-8
spring.thymeleaf.suffix: .html
spring.thymeleaf.check-template-location: true
spring.thymeleaf.template-resolver-order: 1

添加WebSocketConfig

package com.test.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 chen bo
 * @Date 2023/10
 * @Des
 */
@Configuration
public class WebSocketConfig {
    /**
     * bean注冊(cè):會(huì)自動(dòng)掃描帶有@ServerEndpoint注解聲明的Websocket Endpoint(端點(diǎn)),注冊(cè)成為Websocket bean。
     * 要注意,如果項(xiàng)目使用外置的servlet容器,而不是直接使用springboot內(nèi)置容器的話,就不要注入ServerEndpointExporter,因?yàn)樗鼘⒂扇萜髯约禾峁┖凸芾怼?     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

添加WebSocket核心類

因?yàn)閃ebSocket是類似客戶端服務(wù)端的形式(采用ws協(xié)議),那么這里的WebSocketServer其實(shí)就相當(dāng)于一個(gè)ws協(xié)議的Controller

直接@ServerEndpoint("/imserver/{userId}") 、@Component啟用即可,然后在里面實(shí)現(xiàn)@OnOpen開啟連接,@onClose關(guān)閉連接,@onMessage接收消息等方法。

新建一個(gè)ConcurrentHashMap用于接收當(dāng)前userId的WebSocket或者Session信息,方便IM之間對(duì)userId進(jìn)行推送消息。單機(jī)版實(shí)現(xiàn)到這里就可以。集群版(多個(gè)ws節(jié)點(diǎn))還需要借助 MySQL或者 Redis等進(jìn)行訂閱廣播方式處理,改造對(duì)應(yīng)的 sendMessage方法即可。

package com.test.util;

import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.gson.JsonObject;

/**
 * WebSocket的操作類
 * html頁(yè)面與之關(guān)聯(lián)的接口
 * var reqUrl = "http://localhost:8300/websocket/" + cid;
 * socket = new WebSocket(reqUrl.replace("http", "ws"));
 */
@Component
@Slf4j
@ServerEndpoint("/websocket/{sid}")
public class WebSocketServer {

    /**
     * 靜態(tài)變量,用來(lái)記錄當(dāng)前在線連接數(shù),線程安全的類。
     */
    private static AtomicInteger onlineSessionClientCount = new AtomicInteger(0);

    /**
     * 存放所有在線的客戶端
     */
    private static Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>();

    /**
     * 連接sid和連接會(huì)話
     */
    private String sid;
    private Session session;

    /**
     * 連接建立成功調(diào)用的方法。由前端<code>new WebSocket</code>觸發(fā)
     *
     * @param sid     每次頁(yè)面建立連接時(shí)傳入到服務(wù)端的id,比如用戶id等??梢宰远x。
     * @param session 與某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(fā)送消息
     */
    @OnOpen
    public void onOpen(@PathParam("sid") String sid, Session session) {
        /**
         * session.getId():當(dāng)前session會(huì)話會(huì)自動(dòng)生成一個(gè)id,從0開始累加的。
         */
        log.info("連接建立中 ==> session_id = {}, sid = {}", session.getId(), sid);
        //加入 Map中。將頁(yè)面的sid和session綁定或者session.getId()與session
        //onlineSessionIdClientMap.put(session.getId(), session);
        onlineSessionClientMap.put(sid, session);

        //在線數(shù)加1
        onlineSessionClientCount.incrementAndGet();
        this.sid = sid;
        this.session = session;
        sendToOne(sid, "上線了");
        log.info("連接建立成功,當(dāng)前在線數(shù)為:{} ==> 開始監(jiān)聽新連接:session_id = {}, sid = {},。", onlineSessionClientCount, session.getId(), sid);
    }

    /**
     * 連接關(guān)閉調(diào)用的方法。由前端<code>socket.close()</code>觸發(fā)
     *
     * @param sid
     * @param session
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid, Session session) {
        //onlineSessionIdClientMap.remove(session.getId());
        // 從 Map中移除
        onlineSessionClientMap.remove(sid);

        //在線數(shù)減1
        onlineSessionClientCount.decrementAndGet();
        log.info("連接關(guān)閉成功,當(dāng)前在線數(shù)為:{} ==> 關(guān)閉該連接信息:session_id = {}, sid = {},。", onlineSessionClientCount, session.getId(), sid);
    }

    /**
     * 收到客戶端消息后調(diào)用的方法。由前端<code>socket.send</code>觸發(fā)
     * * 當(dāng)服務(wù)端執(zhí)行toSession.getAsyncRemote().sendText(xxx)后,前端的socket.onmessage得到監(jiān)聽。
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        /**
         * html界面?zhèn)鬟f來(lái)得數(shù)據(jù)格式,可以自定義.
         * {"sid":"user","message":"hello websocket"}
         */
        JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject();
        String toSid = jsonObject.get("sid").getAsString();
        String msg = jsonObject.get("message").getAsString();
        log.info("服務(wù)端收到客戶端消息 ==> fromSid = {}, toSid = {}, message = {}", sid, toSid, message);

        /**
         * 模擬約定:如果未指定sid信息,則群發(fā),否則就單獨(dú)發(fā)送
         */
        if (toSid == null || toSid == "" || "".equalsIgnoreCase(toSid)) {
            sendToAll(msg);
        } else {
            sendToOne(toSid, msg);
        }
    }

    /**
     * 發(fā)生錯(cuò)誤調(diào)用的方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket發(fā)生錯(cuò)誤,錯(cuò)誤信息為:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 群發(fā)消息
     *
     * @param message 消息
     */
    private void sendToAll(String message) {
        // 遍歷在線map集合
        onlineSessionClientMap.forEach((onlineSid, toSession) -> {
            // 排除掉自己
            if (!sid.equalsIgnoreCase(onlineSid)) {
                log.info("服務(wù)端給客戶端群發(fā)消息 ==> sid = {}, toSid = {}, message = {}", sid, onlineSid, message);
                toSession.getAsyncRemote().sendText(message);
            }
        });
    }

    /**
     * 指定發(fā)送消息
     *
     * @param toSid
     * @param message
     */
    private void sendToOne(String toSid, String message) {
        // 通過(guò)sid查詢map中是否存在
        Session toSession = onlineSessionClientMap.get(toSid);
        if (toSession == null) {
            log.error("服務(wù)端給客戶端發(fā)送消息 ==> toSid = {} 不存在, message = {}", toSid, message);
            return;
        }
        // 異步發(fā)送
        log.info("服務(wù)端給客戶端發(fā)送消息 ==> toSid = {}, message = {}", toSid, message);
        toSession.getAsyncRemote().sendText(message);
        /*
        // 同步發(fā)送
        try {
            toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("發(fā)送消息失敗,WebSocket IO異常");
            e.printStackTrace();
        }*/
    }

}

添加controller

package com.test.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;

/**
 * @Author chen bo
 * @Date 2023/10
 * @Des
 */
@Controller
public class HomeController {
    /**
     * 跳轉(zhuǎn)到websocketDemo.html頁(yè)面,攜帶自定義的cid信息。
     * http://localhost:8300/demo/toWebSocketDemo/user
     *
     * @param cid
     * @param model
     * @return
     */
    @GetMapping("/demo/toWebSocketDemo/{cid}")
    public String toWebSocketDemo(@PathVariable String cid, Model model) {
        model.addAttribute("cid", cid);
        return "index";
    }

    @GetMapping("hello")
    @ResponseBody
    public String hi(HttpServletResponse response) {
        return "Hi";
    }
}

添加html

注意:html文件添加在application.properties配置的對(duì)應(yīng)目錄中。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>聊天窗口</title>
</head>
<body>
<div>
我的用戶名:
<input type="text" th:value="${cid}" readonly="readonly" id="cid"/>
</div>
<div id="chat-windows" style="width: 400px; height: 300px;overflow: scroll;border: blue 1px solid;"></div>
<div>收消息人用戶名:<input id="toUserId" name="toUserId" type="text"></div>
<div>輸入你要說(shuō)的話:<input id="contentText" name="contentText" type="text"></div>
<div>
    <button type="button" onclick="sendMessage()">發(fā)送消息</button>
</div>
</body>

<script type="text/javascript">
    var socket;
    if (typeof (WebSocket) == "undefined") {
        alert("您的瀏覽器不支持WebSocket");
    } else {
        console.log("您的瀏覽器支持WebSocket");
        //實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口  建立連接

        var cid = document.getElementById("cid").value;
        console.log("cid-->" + cid);
        var reqUrl = "http://localhost:8300/websocket/" + cid;
        socket = new WebSocket(reqUrl.replace("http", "ws"));
        //打開事件
        socket.onopen = function () {
            console.log("Socket 已打開");
            //socket.send("這是來(lái)自客戶端的消息" + location.href + new Date());
        };
        //獲得消息事件
        socket.onmessage = function (msg) {
            console.log("onmessage--" + msg.data);
            //發(fā)現(xiàn)消息進(jìn)入    開始處理前端觸發(fā)邏輯
            var chatWindows = document.getElementById("chat-windows");
            var pElement = document.createElement('p')
            pElement.innerText = msg.data;
            chatWindows.appendChild(pElement);
        };
        //關(guān)閉事件
        socket.onclose = function () {
            console.log("Socket已關(guān)閉");
        };
        //發(fā)生了錯(cuò)誤事件
        socket.onerror = function () {
            alert("Socket發(fā)生了錯(cuò)誤");
            //此時(shí)可以嘗試刷新頁(yè)面
        }
        //離開頁(yè)面時(shí),關(guān)閉socket
        //jquery1.8中已經(jīng)被廢棄,3.0中已經(jīng)移除
        // $(window).unload(function(){
        //     socket.close();
        //});
    }

    function sendMessage() {
        if (typeof (WebSocket) == "undefined") {
            alert("您的瀏覽器不支持WebSocket");
        } else {
            var toUserId = document.getElementById('toUserId').value;
            var contentText = document.getElementById('cid').value + ":" + document.getElementById('contentText').value;
            var msg = '{"sid":"' + toUserId + '","message":"' + contentText + '"}';
            console.log(msg);
            var chatWindows = document.getElementById("chat-windows");
            var chatWindows = document.getElementById("chat-windows");
            var pElement = document.createElement('p');
            pElement.innerText = "我:" + document.getElementById('contentText').value;
            chatWindows.appendChild(pElement);
            socket.send(msg);
        }
    }

</script>
</html>

1對(duì)1模擬演練

啟動(dòng)項(xiàng)目后,在瀏覽器訪問(wèn)http://localhost:8300/demo/toWebSocketDemo/{cid} 跳轉(zhuǎn)到對(duì)應(yīng)頁(yè)面,其中cid是用戶名。

為了便于1對(duì)1測(cè)試,這里我們啟動(dòng)兩個(gè)瀏覽器窗口。

http://localhost:8300/demo/toWebSocketDemo/陽(yáng)光男孩

http://localhost:8300/demo/toWebSocketDemo/水晶女孩

按照要求輸入對(duì)方用戶信息之后,便可以輸入你要說(shuō)的話,暢快聊起來(lái)了。

效果圖如下:

SpringBoot進(jìn)階教程(七十七)WebSocket

當(dāng)然,如果收消息人用戶名是自己的話,也可以自己給自己發(fā)送數(shù)據(jù)的。

群發(fā)模擬演練

為了便于群發(fā)測(cè)試,這里我們啟動(dòng)3個(gè)瀏覽器窗口。

http://localhost:8300/demo/toWebSocketDemo/陽(yáng)光男孩

http://localhost:8300/demo/toWebSocketDemo/水晶女孩

http://localhost:8300/demo/toWebSocketDemo/路人A

由于sendToAll方法中定義群發(fā)的條件為:當(dāng)不指定 toUserid時(shí),則為群發(fā)。

效果圖如下:

SpringBoot進(jìn)階教程(七十七)WebSocket

項(xiàng)目架構(gòu)圖如下:

SpringBoot進(jìn)階教程(七十七)WebSocket

v源碼地址

https://github.com/toutouge/javademosecond

其他參考/學(xué)習(xí)資料:

  • https://www.cnblogs.com/xswz/p/10314351.html
  • https://www.cnblogs.com/xuwenjin/p/12664650.html
  • https://blog.csdn.net/qq_42402854/article/details/130948270
  • https://www.cnblogs.com/zhangxinhua/p/11341292.html


作  者:請(qǐng)叫我頭頭哥
出  處:http://www.cnblogs.com/toutou/
關(guān)于作者:專注于基礎(chǔ)平臺(tái)的項(xiàng)目開發(fā)。如有問(wèn)題或建議,請(qǐng)多多賜教!
版權(quán)聲明:本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文鏈接。
特此聲明:所有評(píng)論和私信都會(huì)在第一時(shí)間回復(fù)。也歡迎園子的大大們指正錯(cuò)誤,共同進(jìn)步?;蛘咧苯铀叫盼?
聲援博主:如果您覺得文章對(duì)您有幫助,可以點(diǎn)擊文章右下角【推薦】一下。您的鼓勵(lì)是作者堅(jiān)持原創(chuàng)和持續(xù)寫作的最大動(dòng)力!
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-711960.html

到了這里,關(guān)于SpringBoot進(jìn)階教程(七十七)WebSocket的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • aardio教程二) 進(jìn)階語(yǔ)法

    aardio中除了基礎(chǔ)數(shù)據(jù)類型外,其他的復(fù)合對(duì)象都是table(例如類和名字空間等)。table可以用來(lái)存放aardio的任何對(duì)象,包括另一個(gè)table。 在其他語(yǔ)言中的字典、列表、數(shù)組、集合映射等,在aardio中都使用table來(lái)實(shí)現(xiàn)。 創(chuàng)建字典 創(chuàng)建數(shù)組 數(shù)組和字典可以混用 使用class定義類

    2024年03月18日
    瀏覽(99)
  • kotlin教程4:函數(shù)進(jìn)階

    kotlin教程:編程基礎(chǔ)??數(shù)據(jù)結(jié)構(gòu)??面向?qū)ο?kotlin 的函數(shù)定義非常靈活,既可以按照順序傳參,也可以通過(guò)參數(shù)名傳參,而且可以設(shè)置參數(shù)默認(rèn)值,這些在基礎(chǔ)教程中已經(jīng)講過(guò)了。 此外, kotlin 中用 vararg 修飾的參數(shù),為長(zhǎng)度可變的參數(shù)列表 遞歸是一種常用的編程技巧,就像

    2024年02月05日
    瀏覽(92)
  • Lua 進(jìn)階 · 教程筆記

    Lua 進(jìn)階 · 教程筆記

    筆記的內(nèi)容出自 Bilibili 上的視頻:Lua教程-進(jìn)階部分 - 4K超清【不定期更新】 筆記主要用于供筆者個(gè)人或讀者回顧知識(shí)點(diǎn),如有紕漏,煩請(qǐng)指出 : ) 國(guó)內(nèi)的大佬 云風(fēng) 翻譯了 Lua 的 Api 參考手冊(cè):傳送門【】 以后讀者在練習(xí)或者開發(fā)途中可以在參考手冊(cè)里查看 Lua 提供的 Api。

    2024年01月24日
    瀏覽(97)
  • SQL Server進(jìn)階教程讀書筆記

    最近把SQL Server進(jìn)階教程重新讀了一遍,順便整理了一下書本中的知識(shí)點(diǎn) CASE WHEN ????????? 高手使用select做分支,新手用where和having做分支 ????????? 要寫ELSE,要寫END,避免未匹配上得到NULL ????????? check到底怎么用?? ????????????????在SQL Server中,s

    2024年02月09日
    瀏覽(89)
  • git 進(jìn)階系列教程--add

    git 進(jìn)階系列教程--add

    功能介紹 將工作區(qū)(working directory)中的內(nèi)容放入暫存區(qū)(staging area) 追蹤文件(解釋:剛被添加到工作區(qū)的文件處于未跟蹤狀態(tài)(Untracked files),該命令會(huì)將新添加的文件放入暫存區(qū),并且文件將處于已跟蹤狀態(tài)) 命令解析 添加工作區(qū)所有文件進(jìn)入暫存區(qū) 最后的點(diǎn)可以理解為正則里的

    2024年02月06日
    瀏覽(98)
  • git 進(jìn)階系列教程--push

    功能介紹 將本地倉(cāng)庫(kù)中的內(nèi)容同步到遠(yuǎn)程倉(cāng)庫(kù) 指令解析 這個(gè)命令就上將本地倉(cāng)庫(kù)中的文件同步到遠(yuǎn)程。是平時(shí)用到push最多的指令。它其實(shí)是一種簡(jiǎn)寫方式。省略了遠(yuǎn)程倉(cāng)庫(kù)的地址,本地分支。 這個(gè)命令是比較全的一條指令。意思是推送本地master分支到遠(yuǎn)程(origin)的master分支

    2024年02月14日
    瀏覽(126)
  • git 進(jìn)階系列教程--pull

    功能 從遠(yuǎn)程倉(cāng)庫(kù)拉取最新代碼到本地 代碼解析 將遠(yuǎn)程倉(cāng)庫(kù)代碼拉取到本地。當(dāng)然這個(gè)命令的前提是你本地代碼與遠(yuǎn)程代碼有鏈接,無(wú)論是你upstream也好git clone也罷。而且是默認(rèn)拉取遠(yuǎn)程代碼中與該分支有關(guān)聯(lián)的分支與本地該分支合并。同時(shí),git pull不止是拉取代碼,同時(shí)會(huì)將

    2024年02月11日
    瀏覽(302)
  • Docker基礎(chǔ)教程 - 14 Docker進(jìn)階

    請(qǐng)查看 Docker 進(jìn)階教程 。

    2024年03月17日
    瀏覽(96)
  • JavaScript高級(jí)教程(javascript實(shí)戰(zhàn)進(jìn)階)

    JavaScript高級(jí)教程(javascript實(shí)戰(zhàn)進(jìn)階)

    ? ? 分類: 基本數(shù)據(jù)(值)類型 String:任意字符串 Number:任意的數(shù)字 boolean:true/false undefined:undefined null:null 對(duì)象(引用)類型 Object:任意對(duì)象 Function:一種特別的對(duì)象(可以執(zhí)行) Array:一種特別的對(duì)象(數(shù)值下標(biāo),內(nèi)部數(shù)據(jù)是有序的) 判斷: typeof:可以判斷undef

    2024年02月07日
    瀏覽(105)
  • matplotlib進(jìn)階教程:如何逐步美化一個(gè)折線圖

    matplotlib進(jìn)階教程:如何逐步美化一個(gè)折線圖

    大家好,今天分享一個(gè)非常有趣的 Python 教程,如何美化一個(gè) matplotlib 折線圖, 喜歡記得收藏、關(guān)注、點(diǎn)贊。 注:數(shù)據(jù)、完整代碼、技術(shù)交流文末獲取 1. 導(dǎo)入包 2. 獲得數(shù)據(jù) 數(shù)據(jù)長(zhǎng)得是這樣的: 3. 對(duì)數(shù)據(jù)做一些預(yù)處理 按照需要,對(duì)數(shù)據(jù)再做一些預(yù)處理,代碼及效果如下:

    2024年02月03日
    瀏覽(983)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包