springboot2.0+websocket集成【群發(fā)消息+單對單】(一)

現(xiàn)在用springboot集成websocket變的很方便快捷了家坎,下面簡單寫個小demo疮茄,實現(xiàn)群發(fā)消息,單對單的聊天煌张。主要是功能呐赡,界面將就一下。
參考:
http://tech.lede.com/2017/03/08/qa/websocket+spring/
https://blog.csdn.net/mr_zhuqiang/article/details/46618197

開發(fā)工具是IDEA骏融,2018.2.3


新建項目
默認(rèn)創(chuàng)建springboot的方式
填寫項目簡介
用到的依賴包

依照上圖的流程链嘀,新建一個簡單的工程。
然后在pom文件中添加一些額外用到的插件档玻。

完整的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>websocketdemo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>websocketdemo1</name>
    <description>springboot2.0+websocket的集成,實現(xiàn)群發(fā)消息+單對單消息推送.</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!-- json工具 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>LATEST</version>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <!-- 包含本地的 jar -->
                    <includeSystemScope>true</includeSystemScope>
                    <!--創(chuàng)建成系統(tǒng)服務(wù)在后臺運行-->
                    <executable>true</executable>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <!--打包的時候, 略過test, 不運行-->
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

1. 新建MyHandshake實現(xiàn)HandshakeInterceptor接口

從名字大致上就可以猜測出這個接口的含義怀泊,handshake,Interceptor误趴。
這里主要是實現(xiàn)2個方法霹琼。

//握手之前干啥,常用來注冊用戶信息凉当,綁定 WebSocketSession
beforeHandshake
//握手之后干啥
afterHandshake

完整的實現(xiàn)

package com.example.websocketdemo1.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author linyun
 * @date 2018/9/13 下午3:12
 */
@Slf4j
@Service
public class MyHandshake implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            // 從session中獲取到當(dāng)前登錄的用戶信息. 作為socket的賬號信息. session的的WEBSOCKET_USERNAME信息,在用戶打開頁面的時候設(shè)置.
            String userName = (String) servletRequest.getSession().getAttribute("WEBSOCKET_USERNAME");
            attributes.put("WEBSOCKET_USERNAME", userName);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}

2. 新建MyHandler實現(xiàn)WebSocketHandler接口

主要是負(fù)責(zé)消息的分發(fā)枣申,用戶統(tǒng)計等。

package com.example.websocketdemo1.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author linyun
 * @date 2018/9/13 下午3:26
 */
@Slf4j
@Service
public class MyHandler implements WebSocketHandler {

    /**
     * 為了保存在線用戶信息看杭,在方法中新建一個list存儲一下【實際項目依據(jù)復(fù)雜度忠藤,可以存儲到數(shù)據(jù)庫或者緩存】
     */
    private final static List<WebSocketSession> SESSIONS = Collections.synchronizedList(new ArrayList<>());

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("鏈接成功......");
        SESSIONS.add(session);
        String userName = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
        if (userName != null) {
            JSONObject obj = new JSONObject();
            // 統(tǒng)計一下當(dāng)前登錄系統(tǒng)的用戶有多少個
            obj.put("count", SESSIONS.size());
            users(obj);
            session.sendMessage(new TextMessage(obj.toJSONString()));
        }
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        log.info("處理要發(fā)送的消息");
        JSONObject msg = JSON.parseObject(message.getPayload().toString());
        JSONObject obj = new JSONObject();
        if (msg.getInteger("type") == 1) {
            //給所有人
            obj.put("msg", msg.getString("msg"));
            sendMessageToUsers(new TextMessage(obj.toJSONString()));
        } else {
            //給個人
            String to = msg.getString("to");
            obj.put("msg", msg.getString("msg"));
            sendMessageToUser(to, new TextMessage(obj.toJSONString()));
        }
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (session.isOpen()) {
            session.close();
        }
        log.info("鏈接出錯,關(guān)閉鏈接......");
        SESSIONS.remove(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        log.info("鏈接關(guān)閉......" + closeStatus.toString());
        SESSIONS.remove(session);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 給所有在線用戶發(fā)送消息
     *
     * @param message
     */
    public void sendMessageToUsers(TextMessage message) {
        for (WebSocketSession user : SESSIONS) {
            try {
                if (user.isOpen()) {
                    user.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 給某個用戶發(fā)送消息
     *
     * @param userName
     * @param message
     */
    public void sendMessageToUser(String userName, TextMessage message) {
        for (WebSocketSession user : SESSIONS) {
            if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * 將系統(tǒng)中的用戶傳送到前端
     *
     * @param obj
     */
    private void users(JSONObject obj) {
        List<String> userNames = new ArrayList<>();
        for (WebSocketSession webSocketSession : SESSIONS) {
            userNames.add((String) webSocketSession.getAttributes().get("WEBSOCKET_USERNAME"));
        }
        obj.put("users", userNames);
    }
}

3. 實現(xiàn)WebSocketConfigurer接口

主要的配置文件楼雹,很簡單

package com.example.websocketdemo1.websocket;

import lombok.extern.slf4j.Slf4j;
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 linyun
 * @date 2018/9/13 下午3:41
 */
@Slf4j
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {


    @Autowired
    private MyHandshake handshake;

    @Autowired
    private MyHandler handler;

    /**
     * 實現(xiàn) WebSocketConfigurer 接口模孩,重寫 registerWebSocketHandlers 方法尖阔,這是一個核心實現(xiàn)方法,配置 websocket 入口榨咐,允許訪問的域介却、注冊 Handler、SockJs 支持和攔截器块茁。
     * <p>
     * registry.addHandler()注冊和路由的功能齿坷,當(dāng)客戶端發(fā)起 websocket 連接,把 /path 交給對應(yīng)的 handler 處理龟劲,而不實現(xiàn)具體的業(yè)務(wù)邏輯胃夏,可以理解為收集和任務(wù)分發(fā)中心。
     * <p>
     * addInterceptors昌跌,顧名思義就是為 handler 添加攔截器仰禀,可以在調(diào)用 handler 前后加入我們自己的邏輯代碼。
     * <p>
     * setAllowedOrigins(String[] domains),允許指定的域名或 IP (含端口號)建立長連接蚕愤,如果只允許自家域名訪問答恶,這里輕松設(shè)置。如果不限時使用”*”號萍诱,如果指定了域名悬嗓,則必須要以 http 或 https 開頭。
     *
     * @param registry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //部分 支持websocket 的訪問鏈接,允許跨域
        registry.addHandler(handler, "/echo").addInterceptors(handshake).setAllowedOrigins("*");
        //部分 不支持websocket的訪問鏈接,允許跨域
        registry.addHandler(handler, "/sockjs/echo").addInterceptors(handshake).setAllowedOrigins("*").withSockJS();
    }
}

4. 頁面模擬聊天

模擬用戶登錄裕坊,存儲用戶信息到session中包竹。

package com.example.websocketdemo1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * @author linyun
 * @date 2018/9/13 下午3:45
 */
@Controller
@RequestMapping("/socket")
public class WebSocketController {

    /**
     * 第一個用戶
     *
     * @param request
     * @return
     */
    @RequestMapping("/chat1")
    public String chat1(HttpServletRequest request) {
        // 假設(shè)用戶tom登錄,存儲到session中
        request.getSession().setAttribute("WEBSOCKET_USERNAME", "tom");
        return "chat1";
    }

    /**
     * 第二個用戶登錄
     *
     * @param request
     * @return
     */
    @RequestMapping("/chat2")
    public String chat2(HttpServletRequest request) {
        // 假設(shè)用戶jerry登錄,存儲到session中
        request.getSession().setAttribute("WEBSOCKET_USERNAME", "jerry");
        return "chat2";
    }

    /**
     * 第三個用戶登錄
     *
     * @param request
     * @return
     */
    @RequestMapping("/chat3")
    public String chat3(HttpServletRequest request) {
        // 假設(shè)用戶jack登錄,存儲到session中
        request.getSession().setAttribute("WEBSOCKET_USERNAME", "jack");
        return "chat3";
    }

}

頁面都是一樣的代碼,分別新建3份籍凝,chat1.html周瞎,chat2.html,chat3.html饵蒂。

<!DOCTYPE html>
<html lang="en">
<head>
    <title>測試websocket</title>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <link rel="stylesheet" >
    <link rel="stylesheet" >
</head>
<body>
<div class="container">
    <div class="input-group mb-3">
        <div class="input-group-prepend">
            <label class="input-group-text" for="inputGroupSelect01">用戶</label>
        </div>
        <select class="custom-select" id="inputGroupSelect01">
            <option selected>選擇一個...</option>
        </select>
    </div>
    <div class="input-group mb-3">
        <input type="text" class="form-control">
        <div class="input-group-append">
            <span class="input-group-text" id="btn1">發(fā)送給所有人</span>
        </div>
    </div>
    <div class="input-group mb-3">
        <input type="text" class="form-control">
        <div class="input-group-append">
            <span class="input-group-text" id="btn2">發(fā)送給單人</span>
        </div>
    </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js"></script>
<script language=javascript>
    $(function () {
        var websocket;
        if ('WebSocket' in window) {
            console.log("WebSocket");
            websocket = new WebSocket("ws://localhost:8080/echo");
        } else if ('MozWebSocket' in window) {
            console.log("MozWebSocket");
            websocket = new MozWebSocket("ws://echo");
        } else {
            console.log("SockJS");
            websocket = new SockJS("http://127.0.0.1:8080/sockjs/echo");
        }
        websocket.onopen = function (evnt) {
            console.log("鏈接服務(wù)器成功!", evnt.data);
        };
        websocket.onmessage = function (evnt) {
            console.log('收到消息:', evnt.data);
            var json = JSON.parse(evnt.data);
            if (json.hasOwnProperty('users')) {
                var users = json.users;
                for (var i = 0; i < users.length; i++) {
                    $("#inputGroupSelect01").append('<option value="' + users[i] + '">' + users[i] + '</option>');
                }
            } else {
                //打印消息
                toast(json.msg, 'info')
            }
        };
        websocket.onerror = function (evnt) {
        };
        websocket.onclose = function (evnt) {
            console.log("與服務(wù)器斷開了鏈接!")
        }
        $('#btn2').bind('click', function () {
            if (websocket != null) {
                //根據(jù)勾選的人數(shù)確定是群聊還是單聊
                var value = $(this).parent().parent().find('input').val();
                //得到選擇的用戶
                var name = $("#inputGroupSelect01").find("option:selected").val();
                console.log('選中的用戶', name);
                if (name === '選擇一個...') {
                    toast('請選擇一個用戶', 'warning')
                } else {
                    var object = {
                        to: name,
                        msg: value,
                        type: 2
                    };
                    //將object轉(zhuǎn)成json字符串發(fā)送給服務(wù)端
                    var json = JSON.stringify(object);
                    websocket.send(json);
                }
            } else {
                console.log('未與服務(wù)器鏈接.');
            }
        });
        $('#btn1').bind('click', function () {
            if (websocket != null) {
                //根據(jù)勾選的人數(shù)確定是群聊還是單聊
                var value = $(this).parent().parent().find('input').val();
                var object = {
                    msg: value,
                    type: 1
                };
                //將object轉(zhuǎn)成json字符串發(fā)送給服務(wù)端
                var json = JSON.stringify(object);
                websocket.send(json);
            } else {
                console.log('未與服務(wù)器鏈接.');
            }
        });
    })

    function toast(text, icon) {
        $.toast({
            text: text,
            heading: '新消息',
            icon: icon,
            showHideTransition: 'slide',
            allowToastClose: true,
            hideAfter: 3000,
            stack: 5,
            position: 'top-right',

            bgColor: '#444444',
            textColor: '#eeeeee',
            textAlign: 'left',
            loader: true,
            loaderBg: '#006eff'
        });
    }
</script>
</body>
</html>

5. 頁面的支持

修改配置文件声诸,我比較喜歡yml的。所以重命名配置文件為yml格式

application.yml
spring:
  ####配置 頁面模板的參數(shù)退盯,
  freemarker:
    charset: utf-8
    suffix: .html
    content-type: text/html
    settings:
      ##格式化這個項目中彼乌,頁面數(shù)字的顯示,小數(shù)位數(shù)最多顯示10位
      number_format: 0.##########
  http:
    encoding:
      charset: UTF-8
  #### 配置靜態(tài)資源的地址渊迁。html文件中慰照,可以直接使用/static/目錄
  resources:
    static-locations: "classpath:/"

6.測試

服務(wù)跑起來。
然后瀏覽器打開3個頁面琉朽,分別訪問:

http://localhost:8080/socket/chat1
http://localhost:8080/socket/chat2
http://localhost:8080/socket/chat3

效果如下:


查看窗口的調(diào)試模式

可以看到最先打開的頁面返回的用戶信息只有tom毒租,后面的用戶加入就出現(xiàn)了jerry和jack。
測試發(fā)送一條群消息


群消息效果

下面查看單人消息漓骚,先刷新3個頁面蝌衔,保證都加載了3個用戶信息。發(fā)送的時候選擇另外用戶推送消息蝌蹂,另外2個人是收不到消息的噩斟。


單對單消息

完整項目 git:git@gitlab.com:tulongx/websocketdemo1.git

以上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孤个,一起剝皮案震驚了整個濱河市剃允,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌齐鲤,老刑警劉巖斥废,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異给郊,居然都是意外死亡牡肉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門淆九,熙熙樓的掌柜王于貴愁眉苦臉地迎上來统锤,“玉大人,你說我怎么就攤上這事炭庙∷橇” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵焕蹄,是天一觀的道長逾雄。 經(jīng)常有香客問我,道長腻脏,這世上最難降的妖魔是什么鸦泳? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮迹卢,結(jié)果婚禮上辽故,老公的妹妹穿的比我還像新娘。我一直安慰自己腐碱,他們只是感情好誊垢,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著症见,像睡著了一般喂走。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谋作,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天芋肠,我揣著相機與錄音,去河邊找鬼遵蚜。 笑死帖池,一個胖子當(dāng)著我的面吹牛奈惑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睡汹,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼肴甸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了囚巴?” 一聲冷哼從身側(cè)響起原在,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彤叉,沒想到半個月后庶柿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡秽浇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年浮庐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兼呵。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡兔辅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出击喂,到底是詐尸還是另有隱情维苔,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布懂昂,位于F島的核電站介时,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凌彬。R本人自食惡果不足惜沸柔,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铲敛。 院中可真熱鬧褐澎,春花似錦、人聲如沸伐蒋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽先鱼。三九已至俭正,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焙畔,已是汗流浹背掸读。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人儿惫。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓澡罚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肾请。 傳聞我的和親對象是個殘疾皇子始苇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)筐喳,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架函喉,建立于...
    Hsinwong閱讀 22,313評論 1 92
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • 早起娶親送親 姐姐開始新旅途 吃飯老照片辦簽證 姐弟聚首轉(zhuǎn)路泡腳燒烤 老爸就是喜歡我和姐 凡事牽著掛著 多陪家人吧...
    珍惜眼前始為真閱讀 146評論 0 0
  • 蘭德公司的一份新文件發(fā)現(xiàn)管呵,到2040年梳毙,人工智能有可能取代核威懾的基礎(chǔ)。 據(jù)報道捐下,雖然人工智能控制的世界末日機器被...
    飛豬share閱讀 203評論 0 0