栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Websocket+SpringBoot+Vuex实现点对点聊天系统

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Websocket+SpringBoot+Vuex实现点对点聊天系统

概述

本篇文章主要记录下我是怎么在项目中实现点对点聊天功能的。关于Websocket和Stomp的概念就不再赘述,直接上代码。

后端代码 Maven
		
            org.springframework.boot
            spring-boot-starter-websocket
        

		
            org.projectlombok
            lombok
        
        
		
		
            org.springframework.boot
            spring-boot-starter-security
        
配置类

configureClientInboundChannel()相当于拦截器,是拦截Websocket连接发送消息前执行的,这里可以用来对每次sendMessage的用户做权限验证。如果项目里无需验证可以忽略重写此方法。

@Slf4j
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// 	以下注入的都是为了SpringSecurity鉴权的
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        
        registry.addEndpoint("/ws/pets").setAllowedOriginPatterns("*").withSockJS();
    }

    
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message preSend(Message message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                //判断是否为连接,如果是,需要获取token,并且设置用户对象
                if (StompCommand.CONNECT.equals(accessor.getCommand())){
                    String token = accessor.getFirstNativeHeader("Auth-Token");
                    if (!StringUtils.isEmpty(token)){
                        String authToken = token.substring(tokenHead.length());
                        String username = jwtTokenUtil.getUsernameByToken(authToken);
                        //token中存在用户名
                        if (!StringUtils.isEmpty(username)){
                            //登录
                            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                            //验证token是否有效
                            if (jwtTokenUtil.validateToken(authToken,userDetails)){
                                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                                accessor.setUser(authenticationToken);
                            }
                        }
                    }
                }
                return message;
            }
        });
    }

    
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //配置代理域,可以配置多个,配置代理目的地前缀,可以在配置域上向客户端推送消息
        registry.enableSimpleBroker("/broadcast","/message");
//        //设置服务端接收消息的前缀,只有下面注册的前缀的消息才会接收
//        registry.setApplicationDestinationPrefixes("/app");
    }

}

消息实体

定义双方之间发送消息的对象。

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class ChatMsg {
	//发送者唯一标识
    private String from;
    //接收方唯一标识
    private String to;
    //内容
    private String content;

	//发送时间
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime date;
    //发送者用户名
    private String fromNickName;
}
Controller代码

SimpMessagingTemplate是SpringBoot为我们提供发送消息用的统一模板类。
@MessageMapping可以理解为@GetMappring


@Slf4j
@RestController
public class WebSocketController {
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

	//Authentication是SpringSecurity提供的全局对象,用来获取登录成功的User,若没用到 SpringSecurity可删除此形参
    @MessageMapping("/sendMsg")
    public void handleMsg(Authentication authentication, ChatMsg chatMsg){
        MyUserDetails userDetails = (MyUserDetails)authentication.getPrincipal();
        //获取发送者的用户名
        User user = userDetails.getUser();
        
        chatMsg.setFrom(user.getUsername());
        chatMsg.setFromNickName(user.getNickName());
        
        log.info("用户[{}]发送消息=========={}",user.getNickName(), chatMsg);
        simpMessagingTemplate.convertAndSendToUser(chatMsg.getTo(),"/message/chat",chatMsg);
    }

}

写到这里后端的Websocket基础框架就搭好了,接下来写前端代码测试一下能不能实现单聊。

Vue前端代码 Vuex

我把用户聊天模块的数据全部交给Vuex的一个模块来管理,从而实现多个页面之间数据的共享。
目录结构

chat.js就是我们放聊天数据和聊天用户数据的地方。

chat.js

别忘了npm install stomp,具体版本如下

	"sockjs-client": "^1.5.1",
    "stompjs": "^2.3.3",
import Stomp from 'stompjs'
import SockJS from 'sockjs-client'
import { Notify } from 'vant';
import Vue from "vue";
import {getUsers} from "../../api/chat";
import store from "../index";

const chat = {
  namespaced: true,
  state: {
    sessions: []//会话,是一个map,储存聊天信息
    currentAdmin: JSON.parse(window.localStorage.getItem('user')),//当前用户
    admins: [],//所有聊天对象
    currentSession: null,//当前聊天对象
    sockJs: null,
    stomp: null,
  },
  mutations:{
    //改变当前聊天会话session
    changeCurrentSession(state, currentSession) {
      state.currentSession = currentSession;
      console.log('当前聊天用户:' + state.currentSession.username)
    },
    //添加一条消息进session
    addMessage(state, msg) {
      //会话key的定义 自己的username+’#‘+对方的username
      const sessionKey = state.currentAdmin.username + '#' + msg.to;
      //找到该会话,如果会话从未创建就初始化,然后把message push进去
      let mss = state.sessions[sessionKey];
      if (!mss) {
        Vue.set(state.sessions, sessionKey, []);
      }
      state.sessions[sessionKey].push({
        content: msg.content,
        date: new Date(),
        self: !msg.notSelf//是否是自己发的消息
      })
      console.log(state.sessions)
    },
    //设置所有聊天用户
    INIT_ADMIN(state, data) {
      state.admins = data;
    }
  },
  actions: {
    //连接websocket
    connect(context) {
      context.dispatch('initChatUsers')//获取所有聊天用户
      const { state } = context
      //连接wbsocket
      let socket = new SockJS('/ws/pets')
      state.stomp = Stomp.over(socket);
      const token = store.state.user.token;
      //连接携带鉴权token
      state.stomp.connect({'Auth-Token': token}, success => {
        //订阅聊天消息,注意加上默认前缀/user,这点在后端代码已经指出,点对点通信的默认前缀
        state.stomp.subscribe('/user/message/chat', msg => {
          let receiveMsg = JSON.parse(msg.body);
          //当前不在消息页面或者正在和另一个人聊天,消息提示
          if (!state.currentSession || receiveMsg.from != state.currentSession.username){
            Notify({type: 'primary',message: receiveMsg.fromNickName+'发来了信息'})
          }
          //接收到的消息设为不是自己发的
          receiveMsg.notSelf = true;
          receiveMsg.to = receiveMsg.from;
          //收到的别人的消息放进session
          context.commit('addMessage', receiveMsg);
        })
      }, error => {
      })

      //监听窗口关闭
      window.onbeforeunload = function (event) {
        socket.close()
      }
    },

    //自己发送消息
    sendMessage({ commit, state}, msgObj){
      state.stomp.send('/sendMsg', {}, JSON.stringify(msgObj));
      //自己发送的消息添加进session
      commit('addMessage',msgObj);
    },
    //初始化所有聊天用户,向后端请求数据
    initChatUsers(context) {
      getUsers().then(res=>{
        if (res.data){
          context.commit('INIT_ADMIN', res.data.users);
        }
      })
    }
  }
}


export default chat
getters.js

getter的chatMessages是取出某一个会话的聊天记录的

const getters = {
  token: state => state.user.token,
  user: state => state.user.userInfo,
  chatMessages: state => {
    return state.chat.sessions[state.chat.currentAdmin.username+'#'+state.chat.currentSession.username]
  }
}
export default getters
HTML

页面部分html、样式什么的太多了,这里只给出重要的代码。

联系人列表页面


聊天页面


聊天页面就一个方法,就是发送聊天消息的方法


测试


控制台出现这些log就说明连接websocket并订阅成功了。

总结

初步搭建已经完成了,但还有很多问题有待解决。
(1)消息持久化,聊天记录放在vuex一刷新页面就丢失了,应该在后端落库。
(2)用户在线状态,以及用户上下线的实时性提示
(3)消息的“已读”,“未读”功能怎么实现
(4)群聊怎么实现

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/845342.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号