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

websocket + stomp + sockjs學(xué)習(xí)

這篇具有很好參考價(jià)值的文章主要介紹了websocket + stomp + sockjs學(xué)習(xí)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

學(xué)習(xí)鏈接

Spring WebSocket整合Stomp源碼詳解 PDF版本

Spring & SpringBoot官方文檔資料

  • spring5.1.9官方文檔關(guān)于websocket的介紹
  • spring5.3.29官方文檔關(guān)于websocket的介紹

WebSocket入門教程示例代碼,代碼地址已fork至本地gitee,原github代碼地址,源老外的代碼地址

  • [WebSocket入門]手把手搭建WebSocket多人在線聊天室(SpringBoot+WebSocket)
  • [WebSocket]第二章:WebSocket集群分布式改造——實(shí)現(xiàn)多人在線聊天室
  • [WebSocket]使用WebSocket實(shí)現(xiàn)實(shí)時(shí)多人答題對(duì)戰(zhàn)游戲

其它可參考

  • 手把手搭建WebSocket多人在線聊天室(SpringBoot+WebSocket),這個(gè)比較詳細(xì),排版看上去比較舒服

  • springboot集成websocket小案例 bilibili視頻

  • SpringBoot+STOMP 實(shí)現(xiàn)聊天室(單聊+多聊)及群發(fā)消息詳解

  • springboot+websocket構(gòu)建在線聊天室(群聊+單聊)

  • 基于STOMP協(xié)議的WebSocket

  • spring websocket + stomp 實(shí)現(xiàn)廣播通信和一對(duì)一通信 ,這個(gè)用法很詳細(xì)

深入使用

  • SpringBoot——整合WebSocket(STOMP協(xié)議) 原創(chuàng)

  • SpringBoot——整合WebSocket(基于STOMP協(xié)議)

  • 點(diǎn)對(duì)點(diǎn)通信

補(bǔ)充學(xué)習(xí)

  • 【Springboot WebSocket STOMP使用 1】Springboot最小化配置啟用STOMP,并實(shí)現(xiàn)瀏覽器JS通信

  • 【Springboot WebSocket STOMP使用 2】STOMP使用@SendToUser實(shí)現(xiàn)用戶個(gè)人請(qǐng)求-響應(yīng)

  • WebSocket的那些事(4-Spring中的STOMP支持詳解),前面還有3篇

  • SpringBoot Websocket Stomp 實(shí)現(xiàn)單設(shè)備登錄(頂號(hào)) ①

  • [WebSocket]之上層協(xié)議STOMP

  • SpringBoot 集成 STOMP 實(shí)現(xiàn)一對(duì)一聊天的兩種方法

  • SpringBoot 配置基于 wss 和 STOMP 的 WebSocket

  • STOMP 客戶端 API 整理

  • Spring websocket+Stomp+SockJS 實(shí)現(xiàn)實(shí)時(shí)通信 詳解

  • spring+socket+stomp 實(shí)現(xiàn)消息推送

  • Spring Websocket+Stomp 防踩坑實(shí)戰(zhàn)

  • 【Springboot實(shí)例】WebSocket即時(shí)聊天室設(shè)計(jì)與實(shí)現(xiàn)

  • Stomp on Spring WebSocket項(xiàng)目源碼分析

  • (二) WebSocket客戶端/服務(wù)端代碼

  • Springboot項(xiàng)目整合WebSocket源碼分析

  • Spring websocket+Stomp+SockJS 實(shí)現(xiàn)實(shí)時(shí)通信 詳解

后續(xù)使用rabbimtmq作為消息代理實(shí)現(xiàn)時(shí),參考的文章

  • Docker容器添加映射端口的兩種實(shí)現(xiàn)方法,因?yàn)樾枰猺abbitmq需要開啟rabbitmq_web_stomp插件、rabbitmq_web_stomp_examples插件,開啟方式參考下面這個(gè)鏈接,然后需要在docker和主機(jī)之間開啟端口映射
  • Rabbitmq報(bào)錯(cuò):Connection refused: no further information: /ip:61613,使用rabbitmq作為消息代理,需要讓我們的服務(wù)連接到rabbitmq,并且mq要開啟rabbitmq_web_stomp插件、rabbitmq_web_stomp_examples插件

websocket + stomp + sockjs學(xué)習(xí),# websocket,websocket,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

websocket + stomp + sockjs學(xué)習(xí),# websocket,websocket,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

websocket + stomp + sockjs學(xué)習(xí),# websocket,websocket,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

websocket + stomp + sockjs學(xué)習(xí),# websocket,websocket,學(xué)習(xí),網(wǎng)絡(luò)協(xié)議

示例1

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.timeless</groupId>
    <artifactId>timeless-chat-websocket</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
    </parent>

    <dependencies>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--<dependency>-->
        <!--    <groupId>com.itheima</groupId>-->
        <!--    <artifactId>pd-tools-swagger2</artifactId>-->
        <!--    <version>1.0-SNAPSHOT</version>-->
        <!--</dependency>-->

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

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

        <!-- RabbitMQ Starter Dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <!-- Following additional dependency is required for Full Featured STOMP Broker Relay -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-reactor-netty</artifactId>
        </dependency>



    </dependencies>

</project>

application.yml

server:
  port: 8888
spring:
  application:
    name: timeless-chat-websocket
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/timeless_chat_websocket?serverTimeZone=UTC
    username: root
    password: root
  mvc:
    pathmatch:
      # Springboot2.6以后將SpringMVC 默認(rèn)路徑匹配策略從AntPathMatcher 更改為PathPatternParser
      #
      matching-strategy: ANT_PATH_MATCHER
  rabbitmq:
    host: ${rabbitmq.host}
    port: ${rabbitmq.port}
    username: ${rabbitmq.username}
    password: ${rabbitmq.password}
    virtual-host: ${rabbitmq.virtual-host}
#pinda:
#  swagger:
#    enabled: true
#    title: timeless文檔
#    base-package: com.timeless.controller
mybatis-plus:
  configuration:
    log-impl: com.timeless.utils.NoLog

WebSocketConfig

@Configuration
@Slf4j
@EnableWebSocketMessageBroker
@EnableConfigurationProperties(RabbitMQProperties.class)
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private RabbitMQProperties rabbitMQProperties;

    public WebSocketConfig(RabbitMQProperties rabbitMQProperties) {
        this.rabbitMQProperties = rabbitMQProperties;
        log.info("連接rabbitmq, host: {}", rabbitMQProperties.getHost());
    }


    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry
                // 這個(gè)和客戶端創(chuàng)建連接時(shí)的url有關(guān),后面在客戶端的代碼中可以看到
                .addEndpoint("/ws")
                .addInterceptors(new HandshakeInterceptor() {
                    @Override
                    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                        log.info("客戶端握手即將開始===================【開始】");
                        if (request instanceof ServletServerHttpRequest) {
                            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                            log.info("請(qǐng)求路徑: {}", ((ServletServerHttpRequest) request).getServletRequest().getRequestURL());
                            log.info("校驗(yàn)請(qǐng)求頭,以驗(yàn)證用戶身份:  {}", JsonUtil.obj2Json(servletRequest.getHeaders()));
                            HttpSession session = servletRequest.getServletRequest().getSession();
                            attributes.put("sessionId", session.getId());
                            return true;
                        }
                        log.info("客戶端握手結(jié)束=================== 【失敗】");
                        return false;
                    }

                    @Override
                    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
                        log.info("客戶端握手結(jié)束=================== 【成功】");
                    }
                })
                // .setAllowedOrigins("http://localhost:8080")
                // 當(dāng)傳入*時(shí), 使用該方法, 而不要使用setAllowedOrigins("*")
                .setAllowedOriginPatterns("*")
                .withSockJS()
        ;
    }


    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        // 1. 當(dāng)客戶端發(fā)送消息或訂閱消息時(shí),url路徑開頭如果是/app/xxx 時(shí),會(huì)先解析stomp協(xié)議,然后路由到@controller的@MessageMapping("/xxx")的方法上執(zhí)行。
        //    如果不設(shè)置,客戶端所有發(fā)送消息或訂閱消息時(shí)、都將去匹配@messageMapping。所以最好還是配置上。
        // 2. 這句表示客戶端向服務(wù)端發(fā)送時(shí)的主題上面需要加"/app"作為前綴
        registry.setApplicationDestinationPrefixes("/app");

        // 1. 基于內(nèi)存的消息代理
        // 2. 聲明消息中間件Broker的主題名稱,當(dāng)向這個(gè)主題下發(fā)送消息時(shí)(js: stompclient.send("/topic/target1",{},"hello")),訂閱當(dāng)前主題的客戶端都可以收到消息。
        //    注意:js 客戶端如果發(fā)送時(shí)、直接是/topic/xxx,spring收到消息會(huì)直接發(fā)送給broker中。
        //    點(diǎn)對(duì)點(diǎn)發(fā)送時(shí):enableSimpleBroker 中要配置 /user才可以用: template.convertAndSendToUser("zhangsan","/aaa/hello","111"),否則收不到消息
        // 3. 這句表示在topic和user這兩個(gè)域上可以向客戶端發(fā)消息
        registry.enableSimpleBroker("/topic", "/user");

        // 1. 點(diǎn)對(duì)點(diǎn)發(fā)送前綴
        // 2. 這句表示給指定用戶發(fā)送(一對(duì)一)的主題前綴是 /user
        registry.setUserDestinationPrefix("/user");

        // Use this for enabling a Full featured broker like RabbitMQ
        /*
        // 基于mq的消息代理
        registry.enableStompBrokerRelay("/topic")
                .setVirtualHost(rabbitMQProperties.getVirtualHost())
                .setRelayHost(rabbitMQProperties.getHost())
                .setRelayPort(61613)
                .setClientLogin(rabbitMQProperties.getUsername())
                .setClientPasscode(rabbitMQProperties.getPassword())
                .setSystemLogin(rabbitMQProperties.getUsername())
                .setSystemPasscode(rabbitMQProperties.getPassword())
                .setSystemHeartbeatSendInterval(5000)
                .setSystemHeartbeatReceiveInterval(5000);
        */

    }
}

PrivateController

@Slf4j
@RestController
public class PrivateController {

    @Autowired
    private WebSocketService ws;

    // 1. 這個(gè)注解其實(shí)就是用來定義接受客戶端發(fā)送消息的url(不能是topic開頭,如果是topic直接發(fā)送給broker了,要用/app/privateChat)
    //    如果有返回值,則會(huì)將返回的內(nèi)容轉(zhuǎn)換成stomp協(xié)議格式發(fā)送給broker(主題名:/topic/privateChat)。如果要換主題名可使用@sendTo
    //    @SubscribeMapping注解和@messageMapping差不多,但不會(huì)再把內(nèi)容發(fā)給broker,而是直接將內(nèi)容響應(yīng)給客戶端,
    @MessageMapping("/privateChat")
    public void privateChat(PrivateMessage message) {
        // 使用發(fā)布訂閱的方式變相的實(shí)現(xiàn)私聊(并不是真正意義上的點(diǎn)對(duì)點(diǎn))
        ws.sendChatMessage(message);
    }

	/**
     * 問候信息處理
     * <p>{@link MessageMapping}方法的返回值會(huì)被轉(zhuǎn)發(fā)到Broker對(duì)應(yīng)的主題中</p>
     * <p>比如向/app/greetings發(fā)送的消息,其響應(yīng)會(huì)被轉(zhuǎn)發(fā)到/topic/greetings主題中</p>
     */
    @MessageMapping("/greetings")
    public String greetings(String content) {
        return String.format("Server response: %s", content);
    }


    // 客戶端向 /app/broadcastMsg 發(fā)送消息, 將會(huì)使用該方法處理,
    // 并且因?yàn)榇朔椒ㄓ蟹祷刂? 所以將結(jié)果又發(fā)送到/topic/broadcastMsg, 因此訂閱了/topic/broadcastMsg的客戶端將會(huì)收到此消息
    @MessageMapping("/broadcastMsg")
    @SendTo("/topic/broadcastMsg")
    public BroadcastMessage broadcastMsg(@Payload BroadcastMessage message,
                                         SimpMessageHeaderAccessor headerAccessor) {
        // 理解為會(huì)話添加屬性標(biāo)識(shí)
        headerAccessor.getSessionAttributes().put("extraInfo", message.getFromUsername());
        message.setContent("廣播消息>>> " + message.getContent());
        return message;
    }

    @Autowired
    private SimpMessagingTemplate template;

    // 廣播推送消息
    // (向此接口發(fā)送請(qǐng)求, 將會(huì)向所有的訂閱了 /topic/broadcastMsg的客戶端發(fā)送消息)
    @RequestMapping("/sendTopicMessage")
    public void sendTopicMessage(String content) {
        template.convertAndSend("/topic/broadcastMsg", content);
    }

    // 點(diǎn)對(duì)點(diǎn)消息
    @RequestMapping("/sendPointMessage")
    // (向此接口發(fā)送請(qǐng)求, 將會(huì)向所有的訂閱了 /user/{targetUsername}/singleUserMsg 的客戶端發(fā)送消息。
    //  這種方式調(diào)用的前提是需要registry.enableSimpleBroker("/topic", "/user"); 里面指定/user的前綴時(shí)才能使用的
    // (但覺得這并不是真正意義上的點(diǎn)對(duì)點(diǎn),因?yàn)橹灰锌蛻舳擞嗛喠诉@個(gè)/user/{targetUsername}/singleUserMsg主題, 就能收到這個(gè)主題下的消息,
    public void sendQueueMessage(String targetUsername, String content) {
        this.template.convertAndSendToUser(targetUsername, "/singleUserMsg", content);
    }

}

WebSocketService

@Service
public class WebSocketService {


    @Autowired
    private SimpMessagingTemplate template;

    @Autowired
    private PrivateMessageService privateMessageService;

    /**
     * 簡(jiǎn)單點(diǎn)對(duì)點(diǎn)聊天室(使用發(fā)布訂閱的方式變相的實(shí)現(xiàn)私聊(并不是真正意義上的點(diǎn)對(duì)點(diǎn)))
     */
    public void sendChatMessage(PrivateMessage message) {
        message.setMessage(message.getFromUsername() + " 發(fā)送:" + message.getMessage());
        // 消息存儲(chǔ)到數(shù)據(jù)庫
        boolean save = privateMessageService.save(message);
        //可以看出template最大的靈活就是我們可以獲取前端傳來的參數(shù)來指定訂閱地址, 前面參數(shù)是訂閱地址,后面參數(shù)是消息信息
        template.convertAndSend("/topic/ServerToClient.private." + message.getToUsername(), message);
        if(!save){
            throw new SystemException(AppHttpCodeEnum.SYSTEM_ERROR);
        }
    }

}

WebSocketEventListener

也可以實(shí)現(xiàn)ApplicationListener<T>接口,泛型T,即為感興趣的事件類型。支持如下事件監(jiān)聽:

  • SessionConnectedEvent
  • SessionConnectEvent
  • SessionDisconnectEvent
  • SessionSubscribeEvent
  • SessionUnsubscribeEvent
@Component
public class WebSocketEventListener {

    @Autowired
    private SimpMessageSendingOperations messagingTemplate;

    public static AtomicInteger userNumber = new AtomicInteger(0);

    @EventListener
    public void handleWebSocketConnectListener(SessionConnectedEvent event) {
        userNumber.incrementAndGet();
        messagingTemplate.convertAndSend("/topic/ServerToClient.showUserNumber", userNumber);
        System.out.println("我來了哦~");
    }

    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
        userNumber.decrementAndGet();
        messagingTemplate.convertAndSend("/topic/ServerToClient.showUserNumber", userNumber);
        System.out.println("我走了哦~");
    }
}

CorsFilter

@WebFilter
public class CorsFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");  
        response.setHeader("Access-Control-Allow-Methods", "*");  
        response.setHeader("Access-Control-Max-Age", "3600");  
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        chain.doFilter(req, res);  
    }  
}

package.json

{
  "name": "timeless-chat-websocket-front",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.17.1",
    "core-js": "^3.8.3",
    "element-ui": "^2.15.3",
    "net": "^1.0.2",
    "nprogress": "^0.2.0",
    "sockjs-client": "^1.6.1",
    "stompjs": "^2.3.3",
    "vue": "^2.6.14",
    "vue-router": "^3.5.3"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "vue-template-compiler": "^2.6.14"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "@babel/eslint-parser"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

Room.vue

<template>
  <div>
    <h3 style="text-align: center">當(dāng)前用戶:{{ this.username }}</h3>
    <h3 style="text-align: center">在線人數(shù):{{ this.userNumber }}</h3>
    <!--    <h3 style="text-align: center">在線用戶:-->
    <!--      <div v-for="user in usernameOnlineList" :key="user">{{ user }}</div>-->
    <!--    </h3>-->
    <div class="container">
      <div class="left">
        <h2 style="text-align: center">用戶列表</h2>
        <ul>
          <li v-for="user in userList" :key="user.id" :class="{ selected: user.selected }" title="點(diǎn)擊選擇用戶聊天">
            <div class="user-info">
              <span @click="selectUser(user)">
                {{ user.toUsername }}
              </span>
              <!-- <div class="button-container">
                <el-button
                  v-if="user.isFriend === 0"
                  type="primary"
                  size="mini"
                  @click="sendFriendRequest(user)"
                >
                  申請(qǐng)加好友
                </el-button>
                <el-button
                  v-if="user.isFriend === 1"
                  type="success"
                  @click="sendMessage(user)"
                >
                  好友
                </el-button>
                <el-button v-if="user.isFriend === 2" type="danger" disabled>
                  申請(qǐng)中
                </el-button>
              </div> -->
            </div>
          </li>
        </ul>
      </div>
      <div class="right">
        <div v-if="selectedUser">
          <h2 style="text-align: center">
            正在與{{ selectedUser.toUsername }}聊天
          </h2>
        </div>
        <div v-if="selectedUser">
          <ul>
            <li v-for="message in messageList[username + selectedUser.toUsername]" :key="message.id">
              {{ message }}
            </li>
          </ul>
        </div>
        <div v-if="selectedUser">
          <div class="message-input">
            <el-input v-model="selectedUserMessage.message" placeholder="請(qǐng)輸入內(nèi)容" @keyup.enter.native="sendMsg"></el-input>
            <div class="button-container">
              <el-button type="primary" @click="sendMsg">發(fā)送消息</el-button>
              <el-button type="danger" @click="deleteAllMsgs">刪除所有消息</el-button>
            </div>
          </div>
          <div class="message-input">
            <el-input v-model="broadcastMsgContent" placeholder="請(qǐng)輸入廣播消息內(nèi)容" @keyup.enter.native="sendMsg"></el-input>
            <div class="button-container">
              <el-button type="primary" @click="sendBroadcastMsg">發(fā)送廣播消息</el-button>
            </div>
          </div>
          
          <div class="message-input">
            <el-input v-model="toTopicMsgContent" placeholder="請(qǐng)輸入ToTopicMsg" @keyup.enter.native="sendMsg"></el-input>
            <div class="button-container">
              <el-button type="primary" @click="sendToTopicMsg">發(fā)送ToTopicMsg</el-button>
            </div>
          </div>
          <div class="message-input">
            <el-input v-model="greetingsMsgContent" placeholder="請(qǐng)輸入greetings消息" @keyup.enter.native="sendMsg"></el-input>
            <div class="button-container">
              <el-button type="primary" @click="sendGreetingsMsg">發(fā)送GreetingsMsg</el-button>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div>
      <h1 class="bottom" style="text-align: center">好友申請(qǐng)</h1>

      <h2 style="text-align: center; color: rgb(57, 29, 216)">
        功能開發(fā)中......
      </h2>
    </div>
  </div>
</template>

<script>
import { getAllUsers, listPrivateMessages, deleteAllMsg } from "@/api";
import SockJS from "sockjs-client";
import Stomp from "stompjs";
import { Message } from "element-ui";

export default {
  name: "Room",
  data() {
    return {
      userList: [],
      groupList: [],
      selectedUser: null,
      message: "",
      stompClient: null,
      messageList: {}, // 使用對(duì)象來存儲(chǔ)每個(gè)用戶的聊天記錄
      username: "",
      usernameOnlineList: [],
      userNumber: 1,
      selectedUserMessage: {
        user: null,
        message: "",
      },
      broadcastMsgContent: '',
      userMsgContent: '',
      toTopicMsgContent: '',
      greetingsMsgContent:'',
    };
  },
  methods: {

    listAllUsers() {
      getAllUsers(this.username).then((response) => {
        this.userNumber = ++response.data.userNumber;
        this.userList = response.data.friends.filter(
          (user) => user.toUsername !== this.username
        );
      });
    },

    selectUser(user) {

      if (!this.messageList[this.username + user.toUsername]) {
        console.log(2222222)
        this.$set(this.messageList, this.username + user.toUsername, []);
      }

      // TODO 展示數(shù)據(jù)庫中存在的信息,也就是聊天記錄
      listPrivateMessages(this.username, user.toUsername).then((response) => {
        this.$set(this.messageList, this.username + user.toUsername, response.data);
      });

      this.selectedUser = user;
      this.selectedUserMessage.user = user;
      this.selectedUserMessage.message = ""; // 清空輸入框內(nèi)容
      this.userList.forEach((u) => {
        u.selected = false;
      });
      user.selected = true;
    },

    sendMsg() {
      if (this.stompClient !== null && this.selectedUserMessage.message !== "") {

        // 發(fā)送私聊消息給服務(wù)端
        this.stompClient.send(
          "/app/privateChat",
          {},
          JSON.stringify({
            fromUsername: this.username,
            message: this.selectedUserMessage.message,
            toUsername: this.selectedUserMessage.user.toUsername,
          })
        );

        this.messageList[this.username + this.selectedUserMessage.user.toUsername].push(
          this.username + " 發(fā)送:" + this.selectedUserMessage.message
        );

        this.selectedUserMessage.message = ""; // 清空輸入框內(nèi)容
      } else {
        Message.info("請(qǐng)輸入消息");
      }
    },

    sendBroadcastMsg() {
      if (this.stompClient !== null) {
        // 發(fā)送私聊消息給服務(wù)端
        this.stompClient.send(
          "/app/broadcastMsg",
          {},
          JSON.stringify({
            fromUsername: this.username,
            content: this.broadcastMsgContent
          })
        );
      }
    },
    
    // 客戶端也可以發(fā)送 /topic/xx, 這樣訂閱了 /topic/xx的客戶端也會(huì)收到消息
    sendToTopicMsg() {
      if (this.stompClient !== null) {
        // 發(fā)送私聊消息給服務(wù)端
        this.stompClient.send(
          "/topic/broadcastMsg",
          {},
          JSON.stringify({
            fromUsername: this.username,
            content: this.toTopicMsgContent
          })
        );
      }
    },
    // 客戶端發(fā)送/app/greetings消息, 將會(huì)被@MessageMapping處理, 驗(yàn)證方法的返回值將會(huì)被發(fā)送到/topic/greetings主題
    sendGreetingsMsg() {
      if (this.stompClient !== null) {
        // 發(fā)送私聊消息給服務(wù)端
        this.stompClient.send(
          "/app/greetings",
          {},
          JSON.stringify({
            fromUsername: this.username,
            content: this.greetingsMsgContent,
          })
        );
      }
    },
    deleteAllMsgs() {
      if (this.messageList[this.username + this.selectedUserMessage.user.toUsername] == "") {
        Message.error("當(dāng)前沒有聊天記錄");
        return;
      }
      deleteAllMsg(this.username, this.selectedUser.toUsername).then(
        (response) => {
          this.messageList[this.username + this.selectedUserMessage.user.toUsername] = [];
          Message.success("刪除成功");
        }
      );
    },
    connect() {

      //建立連接對(duì)象(還未發(fā)起連接)
      const socket = new SockJS("/api/ws");

      // 獲取 STOMP 子協(xié)議的客戶端對(duì)象 
      this.stompClient = Stomp.over(socket);
      window.stompClient = this.stompClient

      // 向服務(wù)器發(fā)起websocket連接并發(fā)送CONNECT幀 
      this.stompClient.connect(
        {},
        (frame) => { // 連接成功時(shí)(服務(wù)器響應(yīng) CONNECTED 幀)的回調(diào)方法

          console.log("建立連接: " + frame);

          // 訂閱當(dāng)前個(gè)人用戶消息
          this.stompClient.subscribe(`/user/${this.username}/singleUserMsg`, (response) => {
            console.log('收到當(dāng)前點(diǎn)對(duì)點(diǎn)用戶消息: ', response.body);
          })

          // 訂閱當(dāng)前個(gè)人用戶消息2
          this.stompClient.subscribe(`/user/singleUserMsg`, (response) => {
            console.log('收到當(dāng)前點(diǎn)對(duì)點(diǎn)用戶消息2: ', response.body);
          })

          // 訂閱廣播消息
          this.stompClient.subscribe(`/topic/broadcastMsg`, (response) => {
            console.log('收到廣播消息: ', response.body);
          })

          // 訂閱/topic/greetings消息
          this.stompClient.subscribe(`/topic/greetings`, (response) => {
            console.log('收到greetings消息: ', response.body);
          })

          // 訂閱 服務(wù)端發(fā)送給客戶端 的私聊消息
          //(疑問: 訂閱的范圍如何限制?當(dāng)前用戶應(yīng)該不能訂閱別的用戶吧?
          //  嘗試: 每進(jìn)來一個(gè)用戶動(dòng)態(tài)生成這個(gè)用戶對(duì)應(yīng)的一個(gè)標(biāo)識(shí), 然后, 這個(gè)用戶訂閱當(dāng)前這個(gè)標(biāo)識(shí), 
          //        其它沒用如果想發(fā)消息給這個(gè)用戶, 后臺(tái)先查詢這個(gè)用戶標(biāo)識(shí), 然后發(fā)消息給這個(gè)用戶,
          //        這樣這個(gè)訂閱路徑就是動(dòng)態(tài)的, 其它不是好友的用戶就無法獲取到這個(gè)動(dòng)態(tài)生成的用戶標(biāo)識(shí)。)
          this.stompClient.subscribe(
            "/topic/ServerToClient.private." + this.username,
            (result) => {
              this.showContent(
                JSON.parse(result.body).message,
                JSON.parse(result.body).fromUsername,
                JSON.parse(result.body).toUsername,
              )
            });

          // 訂閱 服務(wù)端發(fā)送給客戶端刪除所有聊天內(nèi)容 的消息
          this.stompClient.subscribe("/topic/ServerToClient.deleteMsg", (result) => {
            const res = JSON.parse(result.body);
            this.messageList[res.toUsername + res.fromUsername] = [];
          });

          // 訂閱 服務(wù)端發(fā)送給客戶端在線用戶數(shù)量 的消息
          this.stompClient.subscribe("/topic/ServerToClient.showUserNumber", (result) => {
            this.userNumber = result.body;
          });

        });
    },

    disconnect() {
      if (this.stompClient !== null) {
        // 斷開連接
        this.stompClient.disconnect();
      }
      console.log("斷開連接...");
    },

    showContent(body, from, to) {
      // 處理接收到的消息
      // 示例代碼,根據(jù)實(shí)際需求進(jìn)行修改
      if (!this.messageList[to + from]) {
        this.$set(this.messageList, to + from, []); // 初始化選定用戶的聊天記錄數(shù)組
      }
      this.messageList[to + from].push(body); // 將接收到的消息添加到選定用戶的聊天記錄數(shù)組
    },
  },
  created() {
  },
  mounted() {
    // 從sessionStorage中獲取用戶名
    this.username = sessionStorage.getItem("username");
    console.log('username', this.username);
    if (!this.username) {
      this.$router.push('/login')
      return
    }
    this.connect();
    this.listAllUsers();
    // console.log(this.username);
  },
  beforeDestroy() {
    this.disconnect();
  },
};
</script>

<style scoped>
.container {
  display: flex;
  justify-content: space-between;
  margin: 10px;
}

.left,
.middle,
.right {
  flex: 0.5;
  margin: 5px;
  padding: 10px;
  background-color: lightgray;
}

.right {
  flex: 2;
}

.bottom {
  margin-top: 20px;
  text-align: center;
}

li {
  cursor: pointer;
  transition: color 0.3s ease;
}

li:hover {
  color: blue;
}

li.selected {
  color: blue;
  font-weight: bold;
}

.send-button {
  display: flex;
  justify-content: flex-end;
}

.message-input {
  display: flex;
  align-items: center;
}

.button-container {
  margin-left: 10px;
  /* 調(diào)整間距大小 */
}

.message-container {
  display: flex;
  justify-content: flex-end;
}

.button-container {
  display: flex;
  justify-content: flex-end;
}

.user-info {
  display: flex;
  align-items: center;
}

.button-container {
  margin-left: auto;
}
</style>

示例2

引入依賴

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
    </parent>

    <groupId>com.zzhua</groupId>
    <artifactId>ws-demo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <!-- RabbitMQ Starter Dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>

    </dependencies>

</project>

WebsocketMessageBrokerConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebsocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry
                .addEndpoint("/websocket") // WebSocket握手端口
                .addInterceptors(new HttpSessionHandshakeInterceptor())
                .addInterceptors(new HandshakeInterceptor() { 
                    @Override
                    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                    	// 此處可作認(rèn)證, 因?yàn)橹挥形帐殖晒χ? 才會(huì)建立websocket連接
                        return true;
                    }

                    @Override
                    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

                    }
                })
                
                .setHandshakeHandler(new DefaultHandshakeHandler(){ // 設(shè)置默認(rèn)的握手處理器(可重寫其中的方法,比如determineUser)
                    @Override
                    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
                    	// 此處可作認(rèn)證, 因?yàn)橹挥形帐殖晒χ? 才會(huì)建立websocket連接
                    	// 這里可以返回Principal對(duì)象, Principal#
                        return super.determineUser(request, wsHandler, attributes);
                    }
                })
                .setAllowedOriginPatterns("*") // 設(shè)置跨域
                .withSockJS(); // 開啟SockJS回退機(jī)制
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // 攔截器配置
        registration
                .interceptors(new UserAuthenticationChannelInterceptor());
    }

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
        // 這里我們?cè)O(shè)置入站消息最大為8K
        registry
                .setMessageSizeLimit(8 * 1024);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry
                .setApplicationDestinationPrefixes("/app") // 發(fā)送到服務(wù)端目的地前綴
                .enableSimpleBroker("/topic");// 開啟簡(jiǎn)單消息代理,指定消息訂閱前綴
    }

}

StompAuthenticatedUser

@Data
@AllArgsConstructor
@NoArgsConstructor
public class StompAuthenticatedUser implements Principal {

	/**
	 * 用戶唯一ID
	 */
	private String userId;

	/**
	 * 用戶昵稱
	 */
	private String nickName;

	/**
	 * 用于指定用戶消息推送的標(biāo)識(shí)
	 * @return
	 */
	@Override
	public String getName() {
		return this.userId;
	}

}

UserAuthenticationChannelInterceptor

@Slf4j
public class UserAuthenticationChannelInterceptor implements ChannelInterceptor {

	private static final String USER_ID = "User-ID";
	private static final String USER_NAME = "User-Name";

	@Override
	public Message<?> preSend(Message<?> message, MessageChannel channel) {
	
		StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
		
		// 如果是連接請(qǐng)求,記錄userId
		if (StompCommand.CONNECT.equals(accessor.getCommand())) {
		
			// 此處可以拿到stomp客戶端請(qǐng)求連接時(shí),傳入的請(qǐng)求頭
			String userID = accessor.getFirstNativeHeader(USER_ID);
			
			String username = accessor.getFirstNativeHeader(USER_NAME);

			log.info("Stomp User-Related headers found, userID: {}, username:{}", userID, username);
			
			/*
			  為什么保存認(rèn)證信息使用 setUser 方法?
			  
				- 該方法表示會(huì)話的擁有者,即存儲(chǔ)該會(huì)話擁有者信息。
				  每次建立連接都會(huì)創(chuàng)建一個(gè) WebSocketSession 會(huì)話信息類,在該會(huì)話進(jìn)行消息傳遞每次都會(huì)把 SessionId ,SessionAttributes 和 
				  Principal(即我們setUser()保存的信息) 賦值到 Message 中,而 Principal 就是專門存儲(chǔ)身份認(rèn)證信息的。
			
				- SessionId: 初始隨機(jī)分配的,用于確定唯一的會(huì)話
				  SessionAttributes: 用于給 WebSocketSession 設(shè)置一些額外記錄屬性,結(jié)構(gòu)是 Map
				  Principal: 用于設(shè)置 WebSocketSession 的身份認(rèn)證信息
			*/

			accessor.setUser(new StompAuthenticatedUser(userID, username));
			
			// 如果沒有權(quán)限, 則直接在此處拋出異常即可
		}

		// 此處也可以根據(jù)stomp客戶端不同的命令作權(quán)限控制, 比如是否有權(quán)限訂閱某個(gè)路徑

		

		return message;
	}

	    @Override
    public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
        log.info("ChannelInterceptor#postSend: {}", message);
    }

    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
        log.info("ChannelInterceptor#afterSendCompletion: {}", message);
    }

    @Override
    public boolean preReceive(MessageChannel channel) {
        log.info("ChannelInterceptor#preReceive: {}", channel);
        return true;
    }

    @Override
    public Message<?> postReceive(Message<?> message, MessageChannel channel) {
        log.info("ChannelInterceptor#preReceive: {}", message);
        return message;
    }

    @Override
    public void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) {
        log.info("ChannelInterceptor#afterReceiveCompletion: {}", message);
    }

}

StompSessionEventListener

@Slf4j
@Component
public class StompSessionEventListener implements ApplicationListener<AbstractSubProtocolEvent> {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @Override
    public void onApplicationEvent(AbstractSubProtocolEvent event) {

        log.debug("監(jiān)聽到事件: {}", event);

        if (event instanceof SessionConnectEvent) {
            handleSessionConnect((SessionConnectEvent)event);
        } else if (event instanceof SessionConnectedEvent) {
            handleSessionConnected((SessionConnectedEvent)event);
        } else if (event instanceof SessionDisconnectEvent) {
            handleSessionDisconnect((SessionDisconnectEvent)event);
        } else if (event instanceof SessionSubscribeEvent) {
            handleSessionSubscribe((SessionSubscribeEvent)event);
        } else if (event instanceof SessionUnsubscribeEvent) {
            handleSessionUnSubscribe((SessionUnsubscribeEvent)event);
        }
    }

    private void handleSessionUnSubscribe(SessionUnsubscribeEvent event) {
        log.info("{}取消訂閱{}", event.getUser(),StompHeaderAccessor.wrap(event.getMessage()).getDestination());
    }

    private void handleSessionSubscribe(SessionSubscribeEvent event) {
        log.info("{}訂閱了{(lán)}", event.getUser(), StompHeaderAccessor.wrap(event.getMessage()).getDestination());
    }

    private void handleSessionDisconnect(SessionDisconnectEvent event) {
        log.info("{}下線了", event.getUser());
        simpMessagingTemplate.convertAndSend(
                "/topic/chat/group",
                new WebSocketMsgVO(event.getUser() + "下線了")
        );
    }

    private void handleSessionConnected(SessionConnectedEvent event) {
        log.info("{}上線了", event.getUser());
        simpMessagingTemplate.convertAndSend(
                "/topic/chat/group",
                new WebSocketMsgVO(event.getUser() + "上線了")
        );
    }

    private void handleSessionConnect(SessionConnectEvent event) {
        log.info("{}請(qǐng)求建立stomp連接", event.getUser());
    }

}

ChatController

@Slf4j
@Controller
@EnableScheduling
@RequiredArgsConstructor
public class ChatController {

	private final SimpUserRegistry simpUserRegistry;
	
	private final SimpMessagingTemplate simpMessagingTemplate;

	@GetMapping("/page/chat")
	public ModelAndView turnToChatPage() {
		return new ModelAndView("chat");
	}

	/**
	 * 群聊消息處理
	 * 這里我們通過@SendTo注解指定消息目的地為"/topic/chat/group",如果不加該注解則會(huì)自動(dòng)發(fā)送到"/topic" + "/chat/group"
	 * @param webSocketMsgDTO 請(qǐng)求參數(shù),消息處理器會(huì)自動(dòng)將JSON字符串轉(zhuǎn)換為對(duì)象
	 * @return 消息內(nèi)容,方法返回值將會(huì)廣播給所有訂閱"/topic/chat/group"的客戶端
	 */
	@MessageMapping("/chat/group")
	@SendTo("/topic/chat/group")
	public WebSocketMsgVO groupChat(WebSocketMsgDTO webSocketMsgDTO) {
	
		log.info("Group chat message received: {}", JSONObject.toJSONString(webSocketMsgDTO));
		
		String content = String.format("來自[%s]的群聊消息: %s", webSocketMsgDTO.getName(), webSocketMsgDTO.getContent());
		
		return WebSocketMsgVO.builder().content(content).build();
	}

	/**
	 * 私聊消息處理(客戶端向/app/chat/private發(fā)送消息, 會(huì)觸發(fā)該方法, 該方法的返回值將只會(huì)發(fā)送給當(dāng)前用戶自己)
	 * 這里我們通過@SendToUser注解指定消息目的地為"/topic/chat/private",發(fā)送目的地默認(rèn)會(huì)拼接上"/user/"前綴
	 * 實(shí)際發(fā)送目的地為"/user/topic/chat/private"
	 * @param webSocketMsgDTO 請(qǐng)求參數(shù),消息處理器會(huì)自動(dòng)將JSON字符串轉(zhuǎn)換為對(duì)象
	 * @return 消息內(nèi)容,方法返回值將會(huì)基于SessionID單播給指定用戶
	 */
	@MessageMapping("/chat/private")
	@SendToUser("/topic/chat/private")
	public WebSocketMsgVO privateChat(WebSocketMsgDTO webSocketMsgDTO,
                                      Principal principal,
                                      Message message,
                                      MessageHeaderAccessor messageHeaderAccessor) {
		log.info("Private chat message received: {}, ", JSONObject.toJSONString(webSocketMsgDTO), principal);
		log.info("Private chat message  principal:{}", principal);
		log.info("Private chat message  message:{}", message);
		log.info("Private chat message  messageHeaderAccessor:{}", messageHeaderAccessor);
        StompHeaderAccessor stompHeaderAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (stompHeaderAccessor != null) {
            log.info("Private chat message  sessionId:{}", stompHeaderAccessor.getSessionId());           
        }
        log.info("Private chat message  sessionId:{}", StompHeaderAccessor.wrap(message).getSessionId());
        String content = "私聊消息回復(fù):" + webSocketMsgDTO.getContent();
		return WebSocketMsgVO.builder().content(content).build();
	}

	/**
	 * 后臺(tái)發(fā)送消息給指定的用戶
	 * 條件: 1. 客戶端訂閱stompClient.subscribe(`/user/topic/chat/toPrivate`,message=>{...})
	 *      2. 請(qǐng)求http://localhost:9090/toPrivateChat?userId=1693137128824&content=halo111接口即可
	 *         (其中, userId為UserAuthenticationChannelInterceptor 在連接時(shí)記錄的userId)
	 */
	@GetMapping("/toPrivateChat")
    @ResponseBody
    public String toPrivateChat(String userId,String content) {
        messagingTemplate.convertAndSendToUser(userId, "/topic/chat/toPrivate", content);
        return "ok";
    }

	/**
     * 發(fā)送(廣播)消息到/topic/chat/group
     */
    @GetMapping("/toPublicChat")
    @ResponseBody
    public String toPublicChat(String content) {
	    messagingTemplate.convertAndSend("/topic/chat/group",new WebSocketMsgVO(content));
        return "ok";
    }

	/**
	 * 定時(shí)消息推送,這里我們會(huì)列舉所有在線的用戶,然后單播給指定用戶。
	 * 通過SimpMessagingTemplate實(shí)例可以在任何地方推送消息。
	 */
	@Scheduled(fixedRate = 10 * 1000)
	public void pushMessageAtFixedRate() {
	
		log.info("當(dāng)前在線人數(shù): {}", simpUserRegistry.getUserCount());
		
		if (simpUserRegistry.getUserCount() <= 0) {
			return;
		}

		// 這里的Principal為StompAuthenticatedUser實(shí)例
		Set<StompAuthenticatedUser> users = simpUserRegistry.getUsers().stream()
			.map(simpUser -> StompAuthenticatedUser.class.cast(simpUser.getPrincipal()))
			.collect(Collectors.toSet());

		users.forEach(authenticatedUser -> {
		
			String userId = authenticatedUser.getUserId();
			
			String nickName = authenticatedUser.getNickName();
			
			WebSocketMsgVO webSocketMsgVO = new WebSocketMsgVO();
			
			webSocketMsgVO.setContent(String.format("定時(shí)推送的私聊消息, 接收人: %s, 時(shí)間: %s", nickName, LocalDateTime.now()));

			log.info("開始推送消息給指定用戶, userId: {}, 消息內(nèi)容:{}", userId, JSONObject.toJSONString(webSocketMsgVO));
			
			simpMessagingTemplate.convertAndSendToUser(userId, "/topic/chat/push", webSocketMsgVO);
			
		});
	}

}

WebSocketMsgDTO

@Data
public class WebSocketMsgDTO {

	private String name;

	private String content;
}

WebSocketMsgVO

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WebSocketMsgVO {

	private String content;
}

chat.html

引入下面3個(gè)js文件(可以使用cdn,此處我把它們下載到了本地,放在了resources/static/js下)文章來源地址http://www.zghlxwxcb.cn/news/detail-661330.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>greeting</title>
    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script>-->
    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>-->
    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>-->
    <script src="/js/sockjs.min.js"></script>
    <script src="/js/stomp.min.js"></script>
    <script src="/js/jquery.min.js"></script>
    <style>
        #mainWrapper {
            width: 600px;
            margin: auto;
        }
    </style>
</head>
<body>
<div id="mainWrapper">
    <div>
        <label for="username" style="margin-right: 5px">姓名:</label><input id="username" type="text"/>
    </div>
    <div id="msgWrapper">
        <p style="vertical-align: top">發(fā)送的消息:</p>
        <textarea id="msgSent" style="width: 600px;height: 100px"></textarea>
        <p style="vertical-align: top">收到的群聊消息:</p>
        <textarea id="groupMsgReceived" style="width: 600px;height: 100px"></textarea>
        <p style="vertical-align: top">收到的私聊消息:</p>
        <textarea id="privateMsgReceived" style="width: 600px;height: 200px"></textarea>
    </div>
    <div style="margin-top: 5px;">
        <button onclick="connect()">連接</button>
        <button onclick="sendGroupMessage()">發(fā)送群聊消息</button>
        <button onclick="sendPrivateMessage()">發(fā)送私聊消息</button>
        <button onclick="disconnect()">斷開連接</button>
    </div>
</div>
<script type="text/javascript">
    $(() => {
        $('#msgSent').val('');
        $("#groupMsgReceived").val('');
        $("#privateMsgReceived").val('');
    });

    let stompClient = null;


    // 連接服務(wù)器
    const connect = () => {
        const header = {"User-ID": new Date().getTime().toString(), "User-Name": $('#username').val()};
        const ws = new SockJS('http://localhost:8080/websocket');
        stompClient = Stomp.over(ws);
        // 連接時(shí), 傳入請(qǐng)求頭
        stompClient.connect(header, () => subscribeTopic());
    }

    // 訂閱主題
    const subscribeTopic = () => {
        alert("連接成功!");

        // 訂閱廣播消息
        stompClient.subscribe('/topic/chat/group', function (message) {
                console.log(`Group message received : ${message.body}`);
                const resp = JSON.parse(message.body);
                const previousMsg = $("#groupMsgReceived").val();
                $("#groupMsgReceived").val(`${previousMsg}${resp.content}\n`);
            }
        );
        // 訂閱單播消息
        stompClient.subscribe('/user/topic/chat/private', message => {
                console.log(`Private message received : ${message.body}`);
                const resp = JSON.parse(message.body);
                const previousMsg = $("#privateMsgReceived").val();
                $("#privateMsgReceived").val(`${previousMsg}${resp.content}\n`);
            }
        );
        // 訂閱定時(shí)推送的單播消息
        stompClient.subscribe(`/user/topic/chat/push`, message => {
                console.log(`Private message received : ${message.body}`);
                const resp = JSON.parse(message.body);
                const previousMsg = $("#privateMsgReceived").val();
                $("#privateMsgReceived").val(`${previousMsg}${resp.content}\n`);
            }
        );

		// 訂閱發(fā)送到當(dāng)前用戶的消息
        stompClient.subscribe(`/user/topic/chat/toPrivate`, message => {
                console.log(`ToPrivate message received : ${message.body}`);
                console.log(message.body)
            }
        );
    };

    // 斷連
    const disconnect = () => {
        stompClient.disconnect(() => {
            $("#msgReceived").val('Disconnected from WebSocket server');
        });
    }

    // 發(fā)送群聊消息
    const sendGroupMessage = () => {
        const msg = {name: $('#username').val(), content: $('#msgSent').val()};
        stompClient.send('/app/chat/group', {}, JSON.stringify(msg));
    }

    // 發(fā)送私聊消息
    const sendPrivateMessage = () => {
        const msg = {name: $('#username').val(), content: $('#msgSent').val()};
        stompClient.send('/app/chat/private', {}, JSON.stringify(msg));
    }
</script>
</body>
</html>

StompApp 啟動(dòng)類

@SpringBootApplication
public class StompApp {

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

}

application.yml配置文件

server:
  port: 8080
spring:
  thymeleaf:
    enabled: true
    prefix: classpath:/templates/
    cache: false

到了這里,關(guān)于websocket + stomp + sockjs學(xué)習(xí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(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)文章

  • 整合 WebSocket 基于 STOMP 協(xié)議實(shí)現(xiàn)廣播

    整合 WebSocket 基于 STOMP 協(xié)議實(shí)現(xiàn)廣播

    SpringBoot 實(shí)戰(zhàn) (十六) | 整合 WebSocket 基于 STOMP 協(xié)議實(shí)現(xiàn)廣播 如題,今天介紹的是 SpringBoot 整合 WebSocket 實(shí)現(xiàn)廣播消息。 什么是 WebSocket ? WebSocket 為瀏覽器和服務(wù)器提供了雙工異步通信的功能,即瀏覽器可以向服務(wù)器發(fā)送信息,反之也成立。 WebSocket 是通過一個(gè) socket 來實(shí)現(xiàn)雙

    2024年01月21日
    瀏覽(20)
  • flutter開發(fā)實(shí)戰(zhàn)-長(zhǎng)鏈接WebSocket使用stomp協(xié)議stomp_dart_client

    flutter開發(fā)實(shí)戰(zhàn)-長(zhǎng)鏈接WebSocket使用stomp協(xié)議stomp_dart_client

    flutter開發(fā)實(shí)戰(zhàn)-長(zhǎng)鏈接WebSocket使用stomp協(xié)議stomp_dart_client 在app中經(jīng)常會(huì)使用長(zhǎng)連接進(jìn)行消息通信,這里記錄一下基于websocket使用stomp協(xié)議的使用。 1.1 stomp介紹 stomp,Streaming Text Orientated Message Protocol,是流文本定向消息協(xié)議,是一種為MOM(Message Oriented Middleware,面向消息的中間件

    2024年02月13日
    瀏覽(27)
  • Springboot 整合 WebSocket ,使用STOMP協(xié)議 ,前后端整合實(shí)戰(zhàn) (一)(1)

    Springboot 整合 WebSocket ,使用STOMP協(xié)議 ,前后端整合實(shí)戰(zhàn) (一)(1)

    server: port: 9908 3.WebSocketConfig.java 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.springfra

    2024年04月25日
    瀏覽(26)
  • Springboot 整合 WebSocket ,使用STOMP協(xié)議+Redis 解決負(fù)載場(chǎng)景問題

    Springboot 整合 WebSocket ,使用STOMP協(xié)議+Redis 解決負(fù)載場(chǎng)景問題

    ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); template.setValueSerializer(jacksonSeial); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template

    2024年04月14日
    瀏覽(49)
  • 基于STOMP協(xié)議的WebSocket消息代理和相關(guān)的安全握手處理器以及消息攔截器

    這段代碼是一個(gè)Spring配置類 WsChatConfig ,基于STOMP協(xié)議的WebSocket消息代理和相關(guān)的安全握手處理器以及消息攔截器。這個(gè)類通過實(shí)現(xiàn) WebSocketMessageBrokerConfigurer 接口來定義WebSocket通信的路由、安全握手以及消息攔截的邏輯。 核心方法詳解 configureMessageBroker(MessageBrokerRegistry confi

    2024年04月26日
    瀏覽(64)
  • Spring Boot 3 + Vue 3 整合 WebSocket (STOMP協(xié)議) 實(shí)現(xiàn)廣播和點(diǎn)對(duì)點(diǎn)實(shí)時(shí)消息

    Spring Boot 3 + Vue 3 整合 WebSocket (STOMP協(xié)議) 實(shí)現(xiàn)廣播和點(diǎn)對(duì)點(diǎn)實(shí)時(shí)消息

    ?? 作者主頁: 有來技術(shù) ?? 開源項(xiàng)目: youlai-mall ?? vue3-element-admin ?? youlai-boot ?? 倉庫主頁: Gitee ?? Github ?? GitCode ?? 歡迎點(diǎn)贊 ?? 收藏 ?留言 ?? 如有錯(cuò)誤敬請(qǐng)糾正! WebSocket是一種在Web瀏覽器與Web服務(wù)器之間建立雙向通信的協(xié)議,而Spring Boot提供了便捷的WebSocket支持

    2024年02月02日
    瀏覽(18)
  • WebSocket—STOMP詳解(官方原版)

    WebSocket—STOMP詳解(官方原版)

    WebSocket協(xié)議定義了兩種類型的消息(文本和二進(jìn)制),但其內(nèi)容未作定義。該協(xié)議定義了一種機(jī)制,供客戶端和服務(wù)器協(xié)商在WebSocket之上使用的子協(xié)議(即更高級(jí)別的消息傳遞協(xié)議),以定義各自可以發(fā)送何種消息、格式是什么、每個(gè)消息的內(nèi)容等等。子協(xié)議的使用是可選的

    2024年02月04日
    瀏覽(21)
  • WebSocket(三) -- 使用websocket+stomp實(shí)現(xiàn)群聊功能

    WebSocket(三) -- 使用websocket+stomp實(shí)現(xiàn)群聊功能

    SpringBoot+websocket的實(shí)現(xiàn)其實(shí)不難,你可以使用原生的實(shí)現(xiàn),也就是websocket本身的OnOpen、OnClosed等等這樣的注解來實(shí)現(xiàn),以及對(duì)WebSocketHandler的實(shí)現(xiàn),類似于netty的那種使用方式,而且原生的還提供了對(duì)websocket的監(jiān)聽,服務(wù)端能更好的控制及統(tǒng)計(jì)(即上文實(shí)現(xiàn)的方式)。 但是,真

    2023年04月08日
    瀏覽(20)
  • SpringBoot + WebSocket+STOMP指定推送消息

    SpringBoot + WebSocket+STOMP指定推送消息

    前些天發(fā)現(xiàn)了一個(gè)巨牛的人工智能學(xué)習(xí)網(wǎng)站,通俗易懂,風(fēng)趣幽默,忍不住分享一下給大家。點(diǎn)擊跳轉(zhuǎn)到網(wǎng)站。 本文將簡(jiǎn)單的描述SpringBoot + WebSocket+STOMP指定推送消息場(chǎng)景,不包含信息安全加密等,請(qǐng)勿用在生產(chǎn)環(huán)境。 JDK:11+ Maven: 3.5+ SpringBoot: 2.6+ stompjs@7.0.0 STOMP 是面向簡(jiǎn)

    2024年02月14日
    瀏覽(23)
  • WebSocket的那些事(3-STOMP實(shí)操篇)

    WebSocket的那些事(3-STOMP實(shí)操篇)

    上節(jié)中我們?cè)?WebSocket的那些事(2-實(shí)操篇)中簡(jiǎn)單介紹了Spring中對(duì)于 WebSocket 的封裝,并實(shí)現(xiàn)一個(gè)簡(jiǎn)單的服務(wù)端,這節(jié)我們將會(huì)結(jié)合 STOMP 子協(xié)議實(shí)現(xiàn) WebSocket 通信。 WebSocket協(xié)議定義了兩種消息類型(文本類型和二進(jìn)制類型),但是消息內(nèi)容卻是未定義的,下面我們介紹一下

    2024年02月07日
    瀏覽(20)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

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

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包