1.简介
前两天博主写了一篇关于前后端分离中前端项目的实现博客前后端分离项目中前端项目的实现其中大部分内容都是参考的《Spring Boot+Vue全栈开发实战》这本书,其实博主在写那篇文章时也是一脸懵逼,就纯属搬运了一下代码,然后对于Vue中插件Vuex以及Vue Rooter 也是不怎么会用(应该说是不会用),利用周末时间将微人事项目中的聊天功能用前后端分离实现了一小下,对Vuex,以及Vue Rooter也有了一定初步的理解与使用,下面就是在实现过程中自己的一些理解。
2.WebScoket
2.1简介
WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。
2.2优势
- 由于WebSocket连接在端口80(ws)或443(wss)上创建,与HTTP使用的端口相同,这样,基本上所有的防火墙都不会阻塞WebSocket连接;
- WebSocket使用HTTP协议进行握手,因此其可以自然的集成到网络浏览器和HTTP服务器中;
- 使用该协议,当消息启动或者到达时,服务端与客户端都可以知道
2.3用途
凡是涉及到即时通信的都可以运用到它:
- 网页上在线聊天
- 多人在线游戏
- 在线股票网站
- 在线即时新闻网站
- 高清视频流
- 应用集群间的通信
- 远程系统/软件状态和性能的实时监控
2.4实战
引入依赖
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
复制代码
我们先通过两个简单的小demo了解一下WebSocket:
2.4.1群聊小demo
首先我们实现一个群聊功能:
群聊实现比较简单,不用考虑接受者,只需要在一个地址进行消息订阅即可,代码部分博主只展示一些关键部分,项目代码链接见文末。
首先定义Spring Security类配置:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("javaboy")
.password("123")
.roles("admin")
.and()
.withUser("sang")
.password("123")
.roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.rememberMe()
.and()
.csrf().disable()
.sessionManagement()
.maximumSessions(1)//最大登陆用户数为1
.maxSessionsPreventsLogin(true);
}
}
复制代码
代码解释:配置两个用户”xiesuper”,”sang”以及为其分配一下权限,关于SpringSecurity如果小伙伴们不太了解的话请参考博主另一篇文章SpringSecurity入门
WebSocket配置类:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue","/topic");
/*registry.setApplicationDestinationPrefixes("/app");*/ //这个是前缀,可以根据需求添加
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/*registry.addEndpoint("/chat").withSockJS();*/
registry.addEndpoint("/ws/endpointChat").withSockJS();
}
}
复制代码
代码解释:
- 我们加了/ws/endpointChat连接点,在网页上我们就可以通过这个链接来和服务器的WebSocket连接了。但是后面还有一句withSockJs,这是什么呢?SockJs是一个WebSocket的通信js库,Spring对这个js库进行了后台的自动支持,也就是说,我们如果使用SockJs,那么我们就不需要对后台进行更多的配置,只需要加上这一句就可以了。
- 第二个方法,configureMessageBroker,大意是设置消息代理,也就是页面上用js来订阅的地址,也是我们服务器往WebSocket端接收js端发送消息的地址。
定义Controller类
@Controller
public class GreetingController {
@Autowired
SimpMessagingTemplate simpMessagingTemplate;
@MessageMapping("/hello")//实现群聊
public void greeting(Message message) {
simpMessagingTemplate.convertAndSend("/topic/greetings", message);//
}
@MessageMapping("/chat")//实现群聊
public void chat(Principal principal, Chat chat){
// 获取发送者信息
chat.setFrom(principal.getName());
simpMessagingTemplate.convertAndSendToUser(chat.getTo(),"/queue/chat",chat);//destination为消息发送出的地址,前端subscribe函数进行消息订阅
}
复制代码
代码解释
- convertAndSend函数第一个是发送消息的地址,第二个为自定义的一个Message类
- convertAndSendToUser函数第一个参数是接收方,第二个参数为发送消息的地址,第三个参数为自定义的一个Chat类
前端实现
前端关键代码chat.html:
stompClient.send('/hello',{},JSON.stringify({'name':$("#name").val(),'content':$("#content").val()}))
复制代码
代码解释:
- 为Controller类greeting函数传参,参数封装在一个Message类中
function connect() {
if (!$("#name").val()) {
return;
}
var socket = new SockJS('/ws/endpointChat');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (success) {
setConnected(true);
stompClient.subscribe('/topic/greetings', function (msg) {//定义的消息
showGreeting(JSON.parse(msg.body));
});
})
}
复制代码
代码解释:
- /ws/endpointChat对应于WebSocket配置类中加入的连接点,subscribe函数为消息订阅,参数一位Controller中我们定义的消息发送地址,第二个参数为回调函数,msg为获取到的信息
2.4.2单聊小demo
对于群聊配置和单聊相似,在上面的Controller类中已给出,然后前端页面和群聊也大同小异:
$("#send").click(function () {
stompClient.send('/chat', {}, JSON.stringify({
'to': $("#to").val(),
'content': $("#content").val()
}))
})
复制代码
function connect() {
var socket = new SockJS('/ws/endpointChat');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (success) {
setConnected(true);
stompClient.subscribe('/user/queue/chat', function (msg) {
showGreeting(JSON.parse(msg.body));
});
})
复制代码
注意此时subscribe函数的第一个参数前加上了user这是Websocket在实现点对点发送的一种规定
演示gif:
OK,下面完成了两个小demo,想来大家对WebSocket使用也是有了一定的了解,下面内容为结合Vue以及其插件vuecli,vuex,vue-router对此聊天室进行一定的整合。
2.5Vue进行界面设计
首先进行项目初始化,cnpm一下vuex,vue-router(vue-router在初始化项目中可以选择是否install)详细参考
项目结构如下:
进行proxyTable配置
proxyTable: {
'/': {
target: 'http://localhost:8089',
changeOrigin: true,
pathRewrite: {
'^/': ''
}
},
'/ws/*': {
target: 'ws://127.0.0.1:8089',
ws: true
}
},
复制代码
路由配置
export default new Router({
routes: [
{
path: '/home',
name: '主页',
component: HelloWorld,
hidden: true,
meta: {
requireAuth: true
},
children: [
{
path: '/chat',
name: '消息',
component: Chat,
hidden: true,
meta: {
keepAlive: false,
requireAuth: true
}
}
]
}
]
})
复制代码
代码解释
- 因为是SPA设计吗,所以需要配置路由,可以看到,我们只配置了一个页面,这个地方小伙伴们可能有点蒙,我们先上界面吧:
我们在地址栏输入/home就会出现以下界面,其中界面是在HelloWorld.vue中设计:
<template>
<div class="hello">
xiesuper的聊天室
<div style="display: flex;align-items: center;margin-right: 7px">
<el-badge style="margin-right: 30px" >
<i class="fa fa-bell-o" @click="goChat" style="cursor: pointer">聊天室</i>
</el-badge>
</div>
<router-view>
</router-view>
</div>
</template>
复制代码
注意router-view:路由匹配到的组件就是在此渲染。
我们在配置路由时children节点配置了/chat的path,作用如下:
我们点击聊天室会调用goChat()函数:
methods:{
goChat(){
this.$router.push({path: '/chat'});
}
}
复制代码
当前url路径改变,然后在router-view标签处渲染组件。
Chat.vue代码
<template>
<div>
<el-tabs v-model="activeName" @tab-click="handleClick" style="margin-top: 10px">
<el-tab-pane label="系统通知" name="notification"><nf></nf></el-tab-pane>
<el-tab-pane label="好友聊天" name="chat"><fc></fc></el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import FriendChat from '@/components/chat/FriendChat'
import Notification from '@/components/chat/Notification'
export default {
data() {
return {
activeName: 'notification'
};
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
}
},
components:{
'fc':FriendChat,
'nf':Notification
}
};
</script>
复制代码
本次我们只是实现好友聊天,系统通知只是为以后开发时提供的一个接口
关键代码实现都在FriendChat.vue,以及store文件夹下的index.js中,基本稍微复杂的代码都有注释,小伙伴可以下载源码学习。
聊天室实现:
关于vuex在使用时遇到一个小坑:
为了在 Vue 组件中访问 this.$store property,你需要为 Vue 实例提供创建好的 store。Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制:
new Vue({
el: '#app',
store: store,
})
复制代码
最后写一下关于window.sessionStorge与window.localStorge区别:
- window.sessionStorge
- 生命周期为关闭浏览器窗口,关闭窗口以后存储的数据消失,数据存储在浏览器内存中。
- 键值对形式存储与使用。
- 同一窗口及页面下数据可以共享。
- window.localStorge
-
生命周期永久生效,除非手动删除,否则关闭浏览器再打开数据还会存在,数据存储于硬盘上。
-
键值对形式存储于使用。
-
在多窗口(页面)(同一浏览器)下数据可共享。