SpringBoot+Vue+WebSocket實現消息推送

在給導師做項目的時候,APP端需要通過後端服務推送消息,該消息與userId綁定。

(1)服務端實現

實現的話首先說下服務端,基於微服務,將該模塊設計成單獨模塊,部署端口8100。

首先引入maven依賴(根據自己的需求,只要包含websocket即可)

<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>cn.edu.bjfu</groupId>
		<artifactId>fdcp</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>fdcp-provider-websocket</artifactId>
	<dependencies>
		<dependency><!-- 引入自己定義的api通用包 -->
			<groupId>cn.edu.bjfu</groupId>
			<artifactId>fdcp-api</artifactId>
			<version>${project.version}</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jetty</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-test</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
		</dependency>
		<!--整合redis -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<!-- 修改後立即生效,熱部署 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>springloaded</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<!-- 將微服務provider側註冊進eureka -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>
		<dependency>
			<!-- SpringBoot健康監控 -->
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

		<!-- websocket -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

實現原理如下:

(1)先配置一個ws的配置類WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker("/topic");
		config.setApplicationDestinationPrefixes("/app");
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/fdcp").setAllowedOrigins("*").withSockJS();;
	}

	@Override
	public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void configureClientInboundChannel(ChannelRegistration registration) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void configureClientOutboundChannel(ChannelRegistration registration) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
		// TODO Auto-generated method stub
		return false;
	}
}

(2)連接ws監聽類STOMPConnectEventListener,需實現監聽接口:ApplicationListener,開啓ws連接。客戶端將userId傳入進來,服務端將該userId保存到map中,key爲userId,value爲sessionId。該map是一個全局變量,保存在springboot啓動類WebSocketApp中。

【注】此時可確定客戶端連接的地址爲:http://localhost:8100/fdcp

@Component
public class STOMPConnectEventListener implements ApplicationListener<SessionConnectEvent> {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Override
    public void onApplicationEvent(SessionConnectEvent event) {
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
        String userId = sha.getNativeHeader("userId").get(0);
        String sessionId = sha.getSessionId();
        //System.out.println("STOMPConnectEventListener........"+userId+"-"+sessionId);    
        WebSocketApp.userMap.put(userId, sessionId);
    }
}

啓動類:

@EnableScheduling//開啓定時任務
@EnableAsync//開啓異步
@SpringBootApplication
@EnableEurekaClient
public class WebSocketApp {
	public static Map<String, String> userMap = new ConcurrentHashMap<>();//userId:sessionId
	public static void main(String[] args) {
		SpringApplication.run(WebSocketApp.class, args);
	}
}

(3)開啓定時任務:每15min監聽一次消息,該消息的是通過客戶端傳入的userId獲取redis中的數據,當獲取完之後清空該redis的key。(2)中提到,userId保存在一個全局map中,可以獲取到。獲取的value是一個存儲的json字符串,格式大致是這樣:

{

"userId": "a56sd1a6s51dxzcs5",

"islook": 0,

"message": "推送消息內容",

"createtime": "2020-05-15 19:57:29",

"messageId": "de91d3f80d0841219caeb9298f3fc09a",

"title": "消息標題"

}

定時任務類AppPushTask:

@Component
public class AppPushTask {
	@Autowired
	private SimpMessagingTemplate messagingTemplate;
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	private static final String appPushPrefix = "APP_PUSH_MESSAGE";//消息推送app中redis的前綴

	@Scheduled(fixedRate = 1000*60*15)//15分鐘執行一次
	public void callback() throws Exception {
		
		Map<String, String> userMap = WebSocketApp.userMap;
		
		for(Entry<String, String> entry : userMap.entrySet()) {
			String userId = entry.getKey();
			String key = appPushPrefix+":"+userId;
			String message = stringRedisTemplate.opsForValue().get(key);
			if(message != null) {
				messagingTemplate.convertAndSend("/topic/callback", message);
				stringRedisTemplate.delete(key);
			}else {
				//不執行任何操作
			}
		}
		
		
	}

}

服務端的大致代碼結構如下:

(2)客戶端

首先需要安裝sockJS和StompJS

cnpm install sockjs-client --save
cnpm install stompjs --save
cnpm install net --save

直接貼代碼:

<template>
  <div>
    <button @click="connect">連接ws</button><br/>
    消息標題:<span v-text="title"></span><br/>
    消息內容:<span v-html="message"></span>
  </div>
</template>
<script>
import SockJS from "sockjs-client";
import Stomp from "stompjs";
export default {
  data() {
    return {
      messageId: "", //推送消息的id
      userId: "1", //當前該設備用戶id(消息推送接收者)
      title: "", //消息標題
      message: "", //消息
      stompClient: null//stomp
    };
  },
   methods:{
      connect(){         
         let socket=new SockJS('http://localhost:8100/fdcp')
         this.stompClient = Stomp.over(socket)
         this.stompClient.connect({"userId": this.userId }, this.onConnected)   
      },
      onConnected(frame) {
           this.stompClient.subscribe('/topic/callback', this.callback)
      },
      callback(msg){
            let body= JSON.parse(msg.body) 
            //console.log(body)
            this.message = body.message;
            this.title = body.title;   
      },
  }
};
</script>

最終實現的效果如下,點擊“連接ws”之後會與服務端建立連接,之後服務端向客戶端推送數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章