ReviewController.java


package com.example.controller;

import com.example.websocket.WebSocketProcess;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import java.time.LocalDateTime;

@RestController
@RequestMapping("review")
public class ReviewController {
    @Autowired
    private WebSocketProcess websocketProcess;

    @GetMapping("save/{clientID}")
    public String saveReview(@PathVariable("clientID") Integer clientID)  {
        Integer replyUserId = 90;// katy
        String replyUsername ="Katy";

        String reviewText = "展览的介绍非常不错,快去看吧!!!";

        String message ="用户"+replyUsername +" 在"+ LocalDateTime.now() +" 回复了您的评论,评论内容是" +reviewText;

        websocketProcess.sendMsg(clientID,message);
        return "successfully";

    }
}


WebSocketConfig.java


package com.example.websocket;

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


@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}


WebSocketProcess.java


package com.example.websocket;




/**
 * 该类封装了 客户端与服务器端的Websocket 通讯的
 *  (1) 连接对象的管理   ConcurrentHashMap<Long, WebSocketProcess>
 *  (2) 事件监听      @OnOpen ,  @OnMessage,  @OnClose , @OnError
 *  (3) 服务器向 (所有/单个)客户端 发送消息
 */




//这个代码示例展示了一个简单的WebSocket服务实现,它依赖于Java WebSocket API和Spring框架的一些特性。接下来,我会详细解释为什么这个服务能够实现其功能,特别是@OnOpen、@OnMessage、@OnClose和@OnError这些注解的作用。
//
//WebSocket基本原理
//WebSocket是一种网络通信协议,允许服务器与客户端之间建立持久的连接,并进行全双工通信。这意味着服务器和客户端可以同时发送和接收消息,而不需要像传统的HTTP请求那样,每次通信都需要重新建立连接。
//
//注解解释
//@OnOpen
//
//当一个WebSocket连接成功建立时,@OnOpen注解的方法会被调用。在这个方法中,通常会执行一些初始化操作,比如保存这个连接的信息,以便后续使用。
//在示例中,@OnOpen方法将新的WebSocket连接(即WebSocketProcess对象自身,因为它实现了@ServerEndpoint)存储到ConcurrentHashMap中,以客户端ID为键。这样,服务器就可以跟踪所有活动的WebSocket连接,并能够根据需要将消息发送给特定的客户端。
//@OnMessage
//
//当服务器从WebSocket连接接收到消息时,@OnMessage注解的方法会被调用。这个方法可以处理从客户端发送过来的数据。
//在代码中,@OnMessage方法可能只是简单地接收并打印消息,或者进行其他形式的处理(尽管示例中并未详细展示)。这是WebSocket通信中处理实时数据交换的关键部分。
//@OnClose
//
//当WebSocket连接关闭时(无论是客户端还是服务器发起的关闭),@OnClose注解的方法会被调用。这个方法通常用于清理资源,比如从连接跟踪集合中移除已关闭的连接。
//在代码中,这个方法确保当某个客户端断开连接时,其信息会从ConcurrentHashMap中删除,从而保持连接管理的准确性。
//@OnError
//
//如果在WebSocket通信过程中出现错误,@OnError注解的方法会被调用。这个方法为开发者提供了一个处理异常和错误的机制。
//在示例中,这个方法可能只是简单地打印出错误信息,但在实际应用中,你可能需要执行更复杂的错误处理和恢复逻辑。
//Spring和WebSocket的结合
//Spring框架通过@ServerEndpoint注解和ServerEndpointExporter类为WebSocket提供了强大的支持。@ServerEndpoint注解用于标记一个类作为WebSocket端点,而ServerEndpointExporter则负责在Spring容器中自动注册这些端点。
//
//通过结合Spring的依赖注入和WebSocket的实时通信能力,代码示例实现了一个功能强大的WebSocket服务,能够处理多客户端连接、消息接收和发送等核心功能。
//
//总结
//这个代码示例通过巧妙地结合Java WebSocket API和Spring框架的特性,实现了一个简单但功能完备的WebSocket服务。这个服务能够管理多个并发的WebSocket连接,处理来自客户端的消息,并能够通过REST接口向特定或所有客户端发送消息,展示了WebSocket在实时通信方面的强大能力。






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.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 1. manage client and sever socket object(concurrentHashMap)
 * 2. event trigger :
 *      receive client connect : onopen
 *      receive message from client : onmessage
 *      client socket close :onclose
 *
 * 3. server  send message to client
 */
@Component
@ServerEndpoint(value = "/testWebSocket/{id}")
public class WebSocketProcess {

    private static ConcurrentHashMap<Integer,WebSocketProcess> map = new ConcurrentHashMap();

    private Session session;

    @OnOpen
    public void onOpen(Session session, @PathParam("id") Integer clientId){
        this.session = session;
        map.put(clientId,this);
        System.out.println("server get a socket from client :"  + clientId);
    }

    //    receive message from client : onmessage
    @OnMessage
    public void onMessage(String message, @PathParam("id") Integer clientId){
        System.out.println("server get message from client id:" + clientId+", and message is :" + message);
    }

    @OnClose
    public void onClose(Session session, @PathParam("id") Integer clientId){
        map.remove(clientId);
    }


    //    server  send message to client
    public  void sendMsg(Integer clientId,String message)  {
        WebSocketProcess socket =  map.get(clientId);
        if(socket!=null){
            if(socket.session.isOpen()){
                try {
                    socket.session.getBasicRemote().sendText(message);
                    System.out.println("server has send message to  client :"+clientId +", and message is:"+ message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                System.out.println("this client "+clientId +" socket has closed");
            }

        }else{
            System.out.println("this client "+clientId +" socket has exit");
        }
    }


    public  void sendMsg(String message) throws IOException {
        Set<Map.Entry<Integer, WebSocketProcess>> entrySet = map.entrySet();
        for(Map.Entry<Integer, WebSocketProcess> entry: entrySet ){
            Integer clientId = entry.getKey();
            WebSocketProcess socket = entry.getValue();

            if(socket!=null){
                if(socket.session.isOpen()){
                    socket.session.getBasicRemote().sendText(message);
                }else{
                    System.out.println("this client "+clientId +" socket has closed");
                }

            }else{
                System.out.println("this client "+clientId +" socket has exit");
            }
        }




    }

}



ServletInitializer.java



package com.example;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WebsocketApplication.class);
    }

}


WebsocketApplication.java



package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebsocketApplication {

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

}


readme


在生产环境中,可能需要更复杂的错误处理和日志记录机制。此外,考虑到安全性,可能还需要实施WebSocket连接的身份验证和授权。



WebSocket服务端:使用WebSocketProcess类来处理WebSocket连接、接收和发送消息。这个类使用了@ServerEndpoint注解来定义WebSocket端点,并通过@OnOpen@OnMessage@OnClose@OnError注解来处理WebSocket事件。

REST控制器:ReviewController类是一个REST控制器,它提供了一个RESTful API(save/{clientID})来模拟用户提交评论,并通过WebSocket向特定客户端发送通知。

配置:WebSocketConfig类配置了ServerEndpointExporter,这是一个Spring Bean,用于注册所有带有@ServerEndpoint注解的类作为WebSocket端点。

前端页面:提供了一个简单的HTML页面,该页面使用JavaScriptWebSocket来与服务器建立连接,并接收服务器发送的消息。
Maven POM文件:定义了项目依赖和构建配置。




WebSocketProcess@Component@ServerEndpoint:这两个注解表明这个类是一个Spring组件和一个WebSocket端点。
ConcurrentHashMap<Integer, WebSocketProcess>:用于管理WebSocket连接。
@OnOpen 方法:当WebSocket连接打开时调用,将连接保存到map中。
@OnMessage 方法:当从客户端接收到消息时调用(但当前实现中未使用消息内容)。
@OnClose 方法:当WebSocket连接关闭时调用,从map中移除连接。
sendMsg 方法:用于向特定客户端或所有客户端发送消息。
ReviewController@RestController@RequestMapping("review"):定义了一个REST控制器,并指定了基础URL路径。
saveReview 方法:提供了一个RESTful API,用于模拟用户提交评论,并通过WebSocket向特定客户端发送通知。
前端页面
JavaScript代码使用new WebSocket("ws://localhost:8080/app/testWebSocket/"+clientID)WebSocket服务端建立连接。
定义了onopen、onmessage和onclose事件处理器来处理WebSocket事件。
Maven POM文件
定义了Spring Boot的父POM和项目的基本信息。
添加了必要的依赖,如spring-boot-starter-websocket、spring-boot-starter-web等。
配置了Maven构建插件。



index.html


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>
<h2>Home Page</h2>

<div id="content"></div>

<script>

  $(function (){
    let ws ;
    if('WebSocket' in window){
      console.log("this broswer is  support websocket.")
      let clientID = Math.ceil(Math.random()*100);

      <!-- build webscoket to server  -->
      ws = new WebSocket("ws://localhost:8080/app/testWebSocket/"+clientID);


      ws.onopen = function (){
        $("#content").append("client id= "+clientID +"has connect to server")
      }

      // receive message from server
      ws.onmessage = function (event){
        var message = event.data;
        $("#content").append("<br>");
        $("#content").append("client id= "+clientID +"receive message from server, content is :" + message)
      }

      ws.onclose = function (){
        console.log("client id= "+clientID +"has closed, disconnect to server")
      }



    }else{
      console.log("this browser is not support websocket.")
    }
  })


</script>
</body>
</html>



application.yaml


server:
  servlet:
    context-path: /app





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 https://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.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>websocket</name>
    <description>websocket</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</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-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>
            </plugin>
        </plugins>
    </build>

</project>




05-09 06:51