构建离线消息获取流程

在 “开发简单Android聊天软件(6)” 中,完成了完成消息接收和加载,构建一个完整的聊天流程。
但是我们只完成了一半,完成存量历史记录展示,和即时聊天的接受处理,和页面的实时刷新。目前我们来讲关于聊天记录的另一部分,离线消息数据获取。

一、服务端改造,存储离线聊天数据

1、首先我们要弄清楚一点:什么是在线?什么是离线?在线离线在程序中,实际应该由socket长链接作为代表。手机端和服务器的长链接挂上了,说明用户在线。长链接断开了,说明用户离线了。

手机端和服务端的socket就在这个时候是核心作用。回忆一下,在开发简单Android聊天软件(2)中,关于服务端长链接的ChatHandler类。

public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {public static HashMap<ChannelHandlerContext,String> client=new HashMap<String, ChannelHandlerContext>();@Overridepublic void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {System.out.println(msg.text());if(msg.text().substring(0,1).equals("u")){   //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+"  u_*** "+client.size());}else {System.out.println("收到消息"+msg.text());if(client.get(ctx) != null){ //判断消息是否来自于已经绑定上线的用户,判断通过hashmap查询user_id不能为空Gson gson = new Gson();Im_msg_content im_msg_content = gson.fromJson(String.valueOf(msg.text()), Im_msg_content.class);   //收到的是聊天消息String recipient_id = im_msg_content.getRecipient_id;  //获取消息接收方user_id:u_00002ChannelHandlerContext recipient_ctx;for(ChannelHandlerContext getCtx:client.keySet()){            //hashmap通过userid 获取ctxif(client.get(getCtx).equals(recipient_id)){recipient_ctx= getCtx;                                   //拿到u_00002的ctxsendMessage(recipient_ctx,msg.text())            //调用sendMesage方法,发送消息}}}else{ System.out.println("未绑定用户id的长链接发出的消息,不予处理:"+msg.text());}}}//客户端连接@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {     }//客户端断开@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {client.remove(ctx)}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {e.printStackTrace();}//给指定链接发消息public void SendMessage(ChannelHandlerContext ctx String message) {ctx.channel().writeAndFlush(new TextWebSocketFrame(message));}}

梳理一下核心逻辑。手机端长链接与服务器接上的那一刻,是handlerAdded()方法触发执行了。

然后手机端根据我们之前写好的逻辑,会主动发送一条,文本为自己用户id的消息,此时channelRead0()触发执行。

最后手机端关闭app,长链接断掉,此时handlerRemoved()触发执行。

理明白后,我们就可以过长链接的方法,来给长链接做一个简单的记录。也就是代码中定义的HashMap<ChannelHandlerContext,String> client

我们将在这个hashmap中,把“记录长链接ctx”和“删除长链接ctx”的两个动作,视为用户app的在线和离线。所以我们在channelRead0()中,识别到app端,用userid发消息,视为用户长链接挂载成功。给定义的hashmap添加ctx记录。为了方便知道是哪个用户上线,所以key为ctx,value为userid,行程对应关系。

 if(msg.text().substring(0,1).equals("u")){   //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+"  u_*** "+client.size());}else{System.out.println("收到消息"+msg.text());....}

同样的,我们也在handlerRemoved(),用户下线的地方,调用remove,清除长链接记录。在后续判断中,我们只需要判断用户userid是否存在与map中,则可以判断APP端是否在线。

@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {client.remove(ctx)
}

2、接下来,开始做离线消息存储。逻辑大概分两步:1、判断消息属于离线消息,2、保存离线消息。

逻辑在 client.get(getCtx).equals(recipient_id)) 判断的 else 部分。

           if(client.get(ctx) != null){ //判断消息是否来自于已经绑定上线的用户,判断通过hashmap查询user_id不能为空Gson gson = new Gson();Im_msg_content im_msg_content = gson.fromJson(String.valueOf(msg.text()), Im_msg_content.class);   //收到的是聊天消息String recipient_id = im_msg_content.getRecipient_id;  //获取消息接收方user_id:u_00002ChannelHandlerContext recipient_ctx;for(ChannelHandlerContext getCtx:client.keySet()){            //hashmap通过userid 获取ctxif(client.get(getCtx).equals(recipient_id)){recipient_ctx= getCtx;                                   //拿到u_00002的ctxsendMessage(recipient_ctx,msg.text())            //调用sendMesage方法,发送消息}else{ //在hashmap中找不到接收用户recipient_id:u_00002所需要的ctx,则判断用户u_00002未登录app端,为离线状态addOfflineMsg(im_msg_content);  //存储离线消息//此处使用addOfflineMsg()的方法,对应方法实现通过jdbc调用insert ,将数据放入mysql数据库。//其中涉及springboot项目配置使用mysql,数据库操作dao类等。//全部展开的话与本节主题偏离甚远,所以本次就略过了。//如果后续大家反馈需要的话,我就在后面章节补充此部分代码和逻辑}}}else{ System.out.println("未绑定用户id的长链接发出的消息,不予处理:"+msg.text());}

至此,发离线消息部分就全部完成了,后面是接受用户,从离线恢复到上线,其离线期间,消息的接收逻。辑

3、离线消息的接收,也是涉及服务端的改造。让我们回到 channelRead0() 中长链接判断用户上线的逻辑。

        if(msg.text().substring(0,1).equals("u")){   //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+"  u_*** "+client.size());}

之前,我们通过用户发的消息是否是u开头的用户名,作为用户在app端上线的标志。

这样判断的原因,之前我们也明确过:因为我们在android侧,将长链接挂上之后,会立马执行发送用户id的文本。所以是可以通过这个判断的。

我们的离线消息获取的逻辑,也将会写在这里。

        if(msg.text().substring(0,1).equals("u")){   //判断传来是不是u开头,是则认为发送的为用户名client.put(ctx,msg.text());//绑定上线用户user_id和ctxSystem.out.println(msg.text()+"  u_*** "+client.size());//在此继续List<Im_msg_content> offlinelist = new ArrayList<>();//queryOfflineContentByRecipientId: 通过userid搜索属于该用户的离线消息//该逻辑也是涉及mysql,查询方法,实现逻辑略过offlinelist = queryOfflineContentByRecipientId(msg.text().toString());  if (offlinelist.size() > 0) {  //若存在离线消息记录System.out.println(user_id + " 有" + offlinelist.size() + "条离线消息");for (Im_msg_content i : offlinelist) {int mid = i.getMid();sendMessage(ctx, new Gson().toJson(i));  //引用Gson序列化,将对象转为json格式,发送//deleteOfflineByMid: mysql操作,通过where mid = ? 条件,将发送成功的离线数据进行删除deleteOfflineByMid(mid);}} else {System.out.println(user_id + " 无离线消息");}}

这样改造完成之后,离线消息的接收就完成实现了。

(1)用户启动android app
(2)android socket自动触发连接成功
(3)socket 默认发出第一条消息:userid
(4)服务端收到 userid,判断用户登录成功。随后进行查询该用户的离线记录。然后通过长链接依次向android端发送mysql存下的离线消息。
(5)android通过长链接接收方法,收到离线消息,进行存储,同时刷新消息窗口页面

三、总结

本次我们也完成了两个部分的内容:
1、用户B不在线时,此期间收到的消息,都会进行存储。(理论上,无论是否在线,服务端都应该存储消息,供用户远程同步)
2、用户B上线后,查询离线期间,所有离线消息,然后发送到手机端,手机端通过“开发简单Android聊天软件(6)”中的逻辑进行刷新。

关于其中操作mysql数据库的那三个方法,我没有展开。

springboot如何操作数据库,网上教程应该不少。我把大致需要的表结构放在下面,大家可以各显神通,自行实现。

DROP DATABASE IF EXISTS `app`;
CREATE DATABASE IF NOT EXISTS `app`
USE `app`;/*用户表 */
DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user` (`id` int(11) NOT NULL,`user_id` varchar(50) NOT NULL,`user_nickname` varchar(50) NOT NULL,`user_password` varchar(50) NOT NULL,`user_name` varchar(50) NOT NULL,`user_mail` varchar(50) NOT NULL,`user_phone` varchar(50) DEFAULT NULL,`user_token` varchar(50) DEFAULT NULL,`status` varchar(50) NOT NULL COMMENT '0:管理员,1:注册用户'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*离线消息表 */
DROP TABLE IF EXISTS `im_msg_content_offline`;
CREATE TABLE IF NOT EXISTS `im_msg_content_offline` (`mid` int(11) NOT NULL COMMENT '表主键',`cid` varchar(50) NOT NULL COMMENT '会话id',`id` int(11) NOT NULL COMMENT '个人会话id',`content` varchar(1000) NOT NULL,`sender_id` varchar(50) NOT NULL,`recipient_id` varchar(50) NOT NULL,`msg_type` int(11) NOT NULL,`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`mid`,`cid`),INDEX `recipient_id` (`recipient_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*消息记录表 */
/*全量消息记录,用于用户远程同步聊天记录,1000w数据量以下无需分表 */
DROP TABLE IF EXISTS `im_msg_content`;
CREATE TABLE IF NOT EXISTS `im_msg_content` (`mid` int(11) NOT NULL COMMENT '表主键',`cid` varchar(50) NOT NULL COMMENT '会话id',`id` int(11) NOT NULL COMMENT '个人会话id',`content` varchar(1000) NOT NULL,`sender_id` varchar(50) NOT NULL,`recipient_id` varchar(50) NOT NULL,`msg_type` int(11) NOT NULL,`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`mid`,`cid`)INDEX `create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

开发简单Android聊天软件(7)相关推荐

  1. 开发简单Android聊天软件(6)

    构建完整消息接收加载流程 在 "开发简单Android聊天软件(5)" 中,完成了会话窗口的绘制,以及消息发送.现在我们来完成消息接收和加载,构建一个完整的聊天流程. 消息加载,那 ...

  2. 开发简单Android聊天软件(1)

    总体介绍 开篇 大概思路 一. 客户端主要依赖 二.包引用完成后,创建wsClient类. 三.连接成功后,就可以在对于业务逻辑调用以下方法开始发送消息 开篇 本人是一位开发新人,将自己的开发学习过程 ...

  3. 开发简单Android聊天软件(2)

    服务端实现大概思路 服务端需要创建socket端口供客户端(android)链接,为了能实现消息的转发,还需要记录一下长链接与用户的对应关系.总体大概思路是,服务端端口创建完成上线后,每次收到客户端的 ...

  4. Android聊天软件的开发(四)--通讯录

    一,好友排序    好友排序是按照昵称拼音进行A-Z排序.效果如下图:      对好友昵称进行排序,需要先将首字转换为ASCII码,然后根据ASCII码得到对应的拼音,最后根据拼音进行A-Z排序.点 ...

  5. Android聊天软件的开发(六)--表情

    表情用于聊天对话的输入,实现的原理主要是:在EditText或TextView中,使用SpannableString,将特定字符串替换为图片. 首先,我们可以规定,表情的字符串为[**],图片名称为s ...

  6. Android聊天软件的开发(七)--聊天通信

    聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服务器在启动时开启聊天服务线程 可以通过ServletContextListener监听Servlet的初始化和销毁,来开启和关闭聊天 ...

  7. Android聊天软件的开发--聊天通信

    Android聊天软件的开发(七)--聊天通信 2014-06-20 23:17:49CSDN-vaintwyt-点击数:338  聊天通信通过Socket实现,大概的框架如下图: 通信流程: 1.服 ...

  8. Android聊天软件的开发(二)--数据库

    一,服务器数据库    服务器端的数据库是MySQL,使用Hibernate实现数据的增删改查.主要存储的数据有:用户信息,好友列表.             其中,好友列表中的friend_list ...

  9. Android聊天软件的开发(三)--网络连接

    一,服务器网络接口    服务器网络接口通过Servlet实现,可以获得客户端提交的数据,对数据进行查询存储操作,以及返回结果数据给客户端.客户端可以通过HTTP协议直接访问网络接口.    HTTP ...

最新文章

  1. 一看就懂!【英雄联盟锐雯】与 Python 详解设计模式之门面模式
  2. React入门看这篇就够了
  3. 数组的最后一位的下一位为什么是0?
  4. php xml 添加节点 出问题,PHP往XML中添加节点的方法
  5. 关于如何安装cocoapods
  6. python自动化输入文本_快速掌握Python Selenium Web自动化:)四、使用Selenium在网页上进行操作...
  7. fiddler4安装教程以及手机下载证书时报错 no root certificate was found解决方法
  8. React Native集成阿里云推送----广播推送
  9. java 共享类,Java 技术,IBM 风格: 类共享
  10. webSpider----request
  11. 联想z5 android pie,联想z5成功吃上安卓pie
  12. [Linux](小白须知)超级用户、普通用户、系统管理员怎么区分
  13. 2020新冠疫情下如何防范恶意软件攻击避免数据泄露?
  14. ping6 之 unknown host 解决方法
  15. 各种界面库介绍及比较
  16. 焦深(depth of focus)
  17. The 19th Zhejiang Provincial Collegiate Programming Contest
  18. 【综述】NL2SQL (二) WikiSQL
  19. 谷歌浏览器中文乱码问题
  20. Jedis 使用详解(官方原版)

热门文章

  1. 不看公式自己复原魔方
  2. 黑龙江第三方软件测试机构 CMA/CNAS双资质
  3. 过滤链FilterChain,请求与响应--chain.doFilter()代码前后
  4. oracle函数怎么遍历二维数组,oracle存储过程之游标查询,一维和二维数组,循环,条件判断例子...
  5. 龙格现象及分段线性插值
  6. 第3章--Linux内核及内核编程
  7. 动态链接库和静态链接库
  8. MSAA,SSAA,CSAA,CFAA...如何正确地为您的游戏设置抗锯齿模式
  9. TCAM CAM 说明 原理 结构 Verilog 硬件实现
  10. ElementUI tree超出显示省略号