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.gzz</groupId>
<artifactId>04-boot-webSocket-stomp</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.3</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2 WebSocketConfig
package com.gzz.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* @author https://www.jianshu.com/u/3bd57d5f1074
* @date 2023-02-28 14:50:00
*/
//https://www.cnblogs.com/dream-flying/articles/13019597.html
@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private ConnectIntercept connectIntercept;
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//设置消息代理的前缀 - '/topic' 被设置的前缀的消息会被转发到消息代理 消息代理再将消息广播给当前连接的客户端 - 群发
// registry.enableSimpleBroker("/topic");
//前端stomp.send("/app/hello",.... 后端@MessageMapping("/hello")加上前缀
// registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//接入点标识 配合前端 Stomp.client("ws://localhost:8080/chat")
registry.addEndpoint("/chat");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(connectIntercept);
}
}
3 ConnectIntercept
package com.gzz.config;
import java.security.Principal;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* @author https://www.jianshu.com/u/3bd57d5f1074
* @date 2023-02-28 14:50:00
*/
@Slf4j
@Component
public class ConnectIntercept implements ChannelInterceptor {
//1、设置拦截器
//2、首次连接的时候,获取其Header信息,利用Header里面的信息进行权限认证
//3、通过认证的用户,使用 accessor.setUser(user); 方法,将登陆信息绑定在该 StompHeaderAccessor 上,在Controller方法上可以获取 StompHeaderAccessor 的相关信息
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
// 1、判断是否首次连接
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// 2、判断用户名和密码
String username = accessor.getLogin();
String password = accessor.getPasscode();
String nickName = accessor.getNativeHeader("nickName").get(0);
accessor.setUser(new Principal() {
@Override
public String getName() {
return username;
}
});
log.info("username={},password={},nickName={}", username, password, nickName);
}
return message;
}
}
4 TestController
package com.gzz.controller;
import java.security.Principal;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.web.bind.annotation.RestController;
import com.gzz.config.Message;
/**
* @author https://www.jianshu.com/u/3bd57d5f1074
* @date 2023-02-28 14:50:00
*/
//@MessageMapping必须在Controller中有效
//以下两个方法作用一致
@RestController
public class TestController {
@MessageMapping("/app/hello") // 接收/app/hello路径发来的消息
@SendToUser("/greetings") // 转发到/topic/greetings
public Message greeting(Principal principal,Message message) {
System.out.println(message);
return message;
}
// @Autowired
// private SimpMessagingTemplate template;
// @MessageMapping("/app/hello") // 接收/app/hello路径发来的消息
// public void greeting(Principal principal, Message message) {
// System.out.println(principal.getName());
// template.convertAndSendToUser(principal.getName(), "/greetings", message);
// }
}
//Spring-messaging(STOMP)@SendTo与@SendToUser的区别
//@SendTo 与 @SendToUser 是Spring的STOMP协议中注解的标签。
//@SendTo会将接收到的消息发送到指定的路由⽬的地,所有订阅该消息的⽤户都能收到,属于⼴播。
//@SendToUser消息⽬的地有UserDestinationMessageHandler来处理,会将消息路由到发送者对应的⽬的地。默认该注解前缀为/user。
//如:⽤户订阅/user/hi ,在@SendToUser('/hi')查找⽬的地时,会将⽬的地的转化为/user/{name}/hi,这个name就是principal的name值,
//该操作是认为⽤户登录并且授权认证,使⽤principal的name作为⽬的地标识。
//发给消息来源的那个⽤户。(就是谁请求给谁,不会发给所有⽤户,区分就是依照principal-name来区分的)。
//此外该注解还有个broadcast属性,表明是否⼴播。就是当有同⼀个⽤户登录多个session时,是否都能收到。取值true/false.
5 Message
package com.gzz.config;
import lombok.Data;
/**
* @author https://www.jianshu.com/u/3bd57d5f1074
* @date 2023-02-28 14:50:00
*/
@Data
public class Message {
/** 昵称 */
private String name;
/** 消息内容 */
private String content;
}
6 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue3.2.45,stomp1.7.1</title>
</head>
<script src="/js/vue.min.js"></script>
<script src="/js/stomp.min.js"></script>
<body>
<div id='app'>
<div>
<label for="name">请输入用户名:</label> <input type="text" v-model="userName" placeholder="用户名">
</div>
<div>
<button type="button" @click="doConnect()" :disabled="status">连接</button>
<button type="button" @click="disconnect()" :disabled="!status">断开</button>
</div>
<div v-show="status">
<div>
<label for="content">请输入聊天内容:</label><br><textarea type="text" v-model="content" rows='10' cols='50'></textarea>
<button type="button" @click="sendMessage()">发送</button>
</div>
<div>
<textarea v-model="conversation" rows='10' cols='50'></textarea>
</div>
</div>
</div>
</body>
<script type="text/javascript">
let stomp=null;
const app = Vue.createApp({
data() {
return { status: false, userName: "gzz", content: null,conversation:"" }
},
methods: {
doConnect() {
let that = this;
let headers = { login: 'gzz', passcode: '12345', 'nickName': this.userName };
stomp = Stomp.client("ws://localhost:8080/chat");
stomp.connect(headers, function (frame) {//连接
that.status = true;
stomp.subscribe('/user/'+that.userName+'/greetings', function (res) {//订阅
that.conversation += res.body + "\r";
});
});
},
disconnect() {//断开
if (stomp != null) stomp.disconnect();
this.status = false;
},
sendMessage() {//发送
stomp.send("/app/hello", {}, JSON.stringify({ 'name': this.userName, 'content': this.content }));
this.content="";
},
}
});
app.mount("#app");
</script>
</html>
7 Application
package com.gzz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
/**
* @author https://www.jianshu.com/u/3bd57d5f1074
* @date 2023-02-28 14:50:00
*/
@EnableWebSocketMessageBroker // 开启WebSocket消息代理
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1677259489403.png
image.png
网友评论