websocket实现仿微信聊天功能

后端使用的技术是springboot前端是H5+和mui框架。
具体步骤很多,我主要概况一下大概,如果有朋友想要源码欢迎加我qq997355706。

前端最主要的地方有4个:
websocket实现仿微信聊天功能_第1张图片
分别是登录创建与后端websocket的连接,关闭连接和发生错误,和接收消息。
其中open和message又最为重要,里面包括了发送和接收消息。

wsopen: function() {
					console.log("websocket连接已建立...");
					
					var me = app.getUserGlobalInfo();
					//构建ChatMsg
					var chatMsg = new app.ChatMsg(me.id,null,null,null);
					// 构建DateContent	
					var dateContent = new app.DataContent(app.CONNECT,chatMsg,null);
					//发送WebSocket
					CHAT.chat(JSON.stringify(dateContent));
					
					// //每次连接过后,获取用户的未读未签收消息列表
					fetchUnReadMsg();
					
					// //定时发送心跳,要小于后端关闭的时间
					setInterval("CHAT.keepalive()",10000);  
				},

| | |wsmessage: function(e) {
console.log(“接受消息:”+e.data);

				// //转换为DataContent对象
				var dataContent = JSON.parse(e.data);
				// var chatMsg = JSON.parse(e.data);
				
				
				//获取action,判断是否为重新拉取好友/
				var action = dataContent.action;
				if (action === app.PULL_FRIEND) {
					fetchContactList();
					return false;
				}
				
				// //如果不是重新拉取好友列表,则获取聊天消息模型,渲染接收到的聊天记录
				var chatMsg = dataContent.chatMsg;
				var msg = chatMsg.msg;
				var friendUserId = chatMsg.senderId;
				var myId = chatMsg.receiverId;
				// // console.log(msg);
				// // console.log(myId);
				// // console.log(friendUserId);
				
				//调用聊天页面的webview的reciveMsg方法
				// var chatWebview = plus.webview.getWebviewById("chatting-190810CHXGPYWNHH");
				var chatWebview = plus.webview.getWebviewById("chatting-" + friendUserId);
				
				var isRead = true;    //设置消息的默认状态为已读
				
				//当要接受的webview不为空的时候才做渲染
				if (chatWebview != null) {
					//运用其他webview页面的js脚本
					//接受消息
					chatWebview.evalJS("receiveMsg('" + msg + "')");
					// chatWebview.evalJS("receiveMsg('" + e.data + "')");
					//重新调整滚动条
					chatWebview.evalJS("resizeScreen()");
				} else {
					isRead = false;    //chatWebview 聊天页面没有打开,标记消息为未读
				}
				
				//接受到消息之后,对消息记录进行签收
				// console.log(chatMsg.msgId);
				var test = new app.DataContent(app.SIGNED,null,chatMsg.msgId);
				CHAT.chat(JSON.stringify(test)); 	
				
				//保存聊天历史记录到本地缓存,二代表的是朋友的记录
				app.saveUserChatHistory(myId,friendUserId,msg,2); 
				// 保存聊天的快照
				app.saveUserChatSnapshot(myId,friendUserId,msg,isRead);
				// //渲染快照列表进行展示
				loadingChatSnapshot();
				
			},

这是里面的代码,还有其他代码太多就不贴在上面。
里面注意的是保存聊天记录和快照这两部分,这两个部分非常的重要。
然后就是后端chathandler,主要分为了5个步骤:

1.获取客户端发来的消息,将json转化为对象模型
2.判断消息类型,根据不同的类型来处理不同的业务
2.1 当websocket第一次open的时候,初始化channel,把用的channel和userid关联起来
2.2 聊天类型的消息,把聊天记录保存到数据库,同时标记消息的签收状态[未签收]
2.3 签收消息类型,针对具体的消息进行签收,修改数据库中对应消息的签收状态[已签收]
2.4 心跳类型的消息

里面需要注意的是2.1步骤,我们想要聊天是两个人,所以我们需要将各自的channel和userID关联起来,利用channel来判断对方时在线和离线的状态,在通过好友请求的时候我们也可以通过对方的channel获取对方的信息,好实时更新好友的信息。

这是chathandler的代码:

/*

  • 处理消息的handler

  • TextWebSocketFrame:在netty中,是用于为websocket专门处理文本的对象 ,frame是消息的载体
    */
    public class ChatHandler extends SimpleChannelInboundHandler{

    //用户记录和管理所有客户端的channel
    public static ChannelGroup users = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

// //获取客户端传输过来的消息
// String content = msg.text();
// System.out.println(“接收到的数据:”+content);
//
// for(Channel channel : users){
// channel.writeAndFlush(new TextWebSocketFrame("[服务器在:]"+LocalDateTime.now()+“接收到消息,消息为:”+content));
// }

	//下面这个方法,和上面的for循环,一致

// clients.writeAndFlush(new TextWebSocketFrame("[服务器在:]"+LocalDateTime.now()+“接收到消息,消息为:”+content));

// ------------------------------------------------------------------------------------------------------

	//获取客户端传输过来的消息
	String content = msg.text();   
	
	//获取channel
	Channel currentChannel = ctx.channel();   
	
	//1.获取客户端发来的消息,将json转化为对象模型 
	DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);

// System.out.println(dataContent.getAction());
// System.out.println(dataContent.getExtend());
// System.out.println("----------------------");

	Integer action = dataContent.getAction(); 
	
	//2.判断消息类型,根据不同的类型来处理不同的业务
	
	if (action == MsgActionEnum.CONNECT.type) {
		//2.1 当websocket第一次open的时候,初始化channel,把用的channel和userid关联起来
		String senderId = dataContent.getChatMsg().getSenderId();
		UserChannelRel.put(senderId, currentChannel);  
		
		//测试
		for (Channel c:users) {
			System.out.println(c.id().asLongText());
		}
		//测试
		UserChannelRel.output();
		
	}else if(action == MsgActionEnum.CHAT.type) {
		//2.2 聊天类型的消息,把聊天记录保存到数据库,同时标记消息的签收状态[未签收]
		ChatMsg chatMsg = dataContent.getChatMsg();
		String msgText = chatMsg.getMsg();
        String receiverId = chatMsg.getReceiverId();
        String senderId = chatMsg.getSenderId();
        
        //保存到数据库,并且标记为未签收
        //获取userservice
        UserService userService = (UserService) SpringUtil.getBean("userServiceImpl");
        //返回一个msgId,是未签收的状态
        String msgId = userService.saveMsg(chatMsg); 
        //设置MsgId
        chatMsg.setMsgId(msgId);    
		
        //将chatMsg设置在DataContent中,因为后端接收是Datacontent,所以发送也应该是datacontent
        DataContent dataContentMsg = new DataContent();
        dataContentMsg.setChatMsg(chatMsg);
        
        //发送消息
        Channel receiverChannel = UserChannelRel.get(receiverId); 
        if (receiverChannel == null ) {
        	//channel为空代表用户离线,推送消息(JPush,个推,小米推送)
        }else {
        	//当receiverChannel不为空的时候,从ChannelGroup去查找对应的channel是否存在
        	Channel findChannel = users.find(receiverChannel.id());
        	if (findChannel != null) {
        		//用户在线
        		receiverChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(dataContentMsg)));

// receiverChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.objectToJson(chatMsg)));
}else {
//用户离线,推送消息

        	}
        }
        
        
	}else if(action == MsgActionEnum.SIGNED.type) {
		//2.3 签收消息类型,针对具体的消息进行签收,修改数据库中对应消息的签收状态[已签收]
		//获取userservice
        UserService userService = (UserService) SpringUtil.getBean("userServiceImpl");
        
        //扩展字段在signed类型的消息中,代表需要去签收的消息id,逗号间隔。
        String msgIdsStr = dataContent.getExtend();
        String msgIds[] = msgIdsStr.split(",");

// System.out.println(msgIdsStr + " 0000000");
// System.out.println(msgIds[0] + " 000000000 ");

        List msgIdList = new ArrayList<>();
        for (String mid : msgIds) {
        	if(StringUtils.isNoneBlank(mid)) {
        		msgIdList.add(mid);
        	}
        }
        
        //输出msgIDlist
        System.out.println(msgIdList.toString());
        
        //批量签收
        if (msgIdList != null && !msgIdList.isEmpty() && msgIdList.size() > 0) {
        	userService.updateMsgSigned(msgIdList); 
        }
        
	}else if(action == MsgActionEnum.KEEPALIVE.type) {
		//2.4 心跳类型的消息
		System.out.println("收到来自channel为[" + currentChannel + "]的心跳包...");
	}
	
}

/*
 * 当客户端连接服务端之后(打开连接)
 * 获取客户端的channel,并且放到ChannelGroup总进行管理
 */
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

	users.add(ctx.channel());
	
}

@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

	//当触发handlerRemoved,ChannelGroup会自动移除对应客户端的channel

// clients.remove(ctx.channel());
// System.out.println(“客户端断开,channel对应的长id为:”+ ctx.channel().id().asLongText());
// System.out.println(“客户端断开,channel对应的短id为:”+ ctx.channel().id().asShortText());

	String channelId = ctx.channel().id().asShortText();
	System.out.println("客户端被移除,channelId为:" + channelId);
	
	users.remove(ctx.channel());
	
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
	cause.printStackTrace();
	//发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除  
	ctx.channel().close();
	users.remove(ctx.channel());
}

}

然后就是前后端的数据交互,我们在后端创建了dataContent对象和chatmsg对象,datacontent有动作的类型和聊天记录和扩展字段,扩展字段是用于标记后面未读的消息。然后chatmsg主要保存聊天记录。
websocket实现仿微信聊天功能_第2张图片

websocket实现仿微信聊天功能_第3张图片

后端同样创建了这两个对象。
用js使用的构造函数。将对象利用发送消息传到后端。
websocket实现仿微信聊天功能_第4张图片

总结的可能不是太好如果想要更深一步了解,可以加我qq997355706。

你可能感兴趣的:(前端)