WebIm解决方案:基于WebSocket+JavaWeb
本文引用了不少的链接,若有冒犯,请联系我。另转载请标明出处,我有可能会修改。
有些需要了解的
首先呢,这是第一次写,文笔不是很好,都是有一句说一句,但还是会尽可能的用通俗的话来写;
其次呢,我也不是什么高手,只是将自己的感悟写出来,这个方案纯属是自己设想出来并加以实现的,不是技术教程也不是最好的解决方案,仅供参考;
有些需要掌握的
本解决方案需要有一定的JavaWeb基础以及会常用的一些框架,最主要的是要懂核心WebSocket,里面涉及到的技术不会详细的讲,我会尽可能的给相关的文章提供参考; 解决方案用到的技术:
- 语言
- Java(这个不用说都知道)
- 框架(框架搭建教程)
- Spring Boot(搭建快)****
- Mybatis(大家都用,上面框架搭建教程里有教程)
- 数据库(数据库安装)
- mysql(同上)
- 前端
- 服务器
- Tomcat(简单)
- 通讯
- WebSocket(如果要学的话,可以先做下面方案开始的第一步,然后基于这个来学WebSocket)WebSocket教程
以上用到的,其实都是比较简单易学的,我这里没给出版本,根据自己的需要选择吧;至于为什么用,请看下面方案开始的第七步
方案开始
第一步、搭框架
根据上面所给出的先搭建出一个“健全”的web项目,当然可以用ssm或者ssh,甚至于不用框架普通web项目都行,最好用到maven,否则引包引到头疼,不小心又说了一个东东,啥是maven?;
第二步、建个表
web项目弄好后,先建立几张核心表(我这里不应该用驼峰的),为什么建这些表?又为什么要用这些字段?别急,慢慢看到第七步
- 用户表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL COMMENT '用户名',
`userPwd` varchar(255) NOT NULL DEFAULT '' COMMENT '用户密码',
`realname` varchar(255) DEFAULT '' COMMENT '真实姓名',
`clientId` varchar(255) DEFAULT NULL COMMENT '客户端ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8
- 消息记录表(觉得字段太多,自己精简,不过这些字段在这个方案都用得到)
CREATE TABLE `message_log` (
`id` int(11) NOT NULL COMMENT '主键ID',
`contextId` int(11) DEFAULT NULL COMMENT '上下文ID',
`messageType` varchar(255) DEFAULT NULL COMMENT '消息类型 0=普通文本,1=卡片,2=文章,3=音乐,4=红包,5=图片',
`messageMode` varchar(255) DEFAULT NULL COMMENT '消息模式 0=新增,1=撤回',
`sendTime` int(11) DEFAULT NULL COMMENT '发送时间',
`message` varchar(255) DEFAULT NULL COMMENT '消息内容(根据消息类型格式化)',
`isRead` int(11) DEFAULT NULL COMMENT '是否已读',
`fromId` varchar(255) DEFAULT NULL COMMENT '来源ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- 消息列表(聊天关系表)
CREATE TABLE `chat_user_relation` (
`id` int(11) NOT NULL,
`contextId` varchar(255) NOT NULL COMMENT '上下文ID',
`userId1` int(11) DEFAULT NULL COMMENT '用户ID,用于单聊使用,群聊为空',
`userId2` int(11) DEFAULT NULL COMMENT '用户ID2,用于单聊使用,群聊为空',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
第三步、增删改查
将第二步建立的表,在项目中建立好映射关系,做好增删改查(这个不难吧?);
第四步、前端界面
使用bootstrap+angularjs写一个前端的页面,简单一点的话,就是一个输入框,一个发送按钮,然后可以循环的列表(这里可以用上面给出的websocket给出的教程中聊天室的例子的页面),我个人建议写得好看一点,花一两天的时间搞一搞bootstrap(一两天?不可能吧,抱歉,我就是一两天就可以用得上手了,其实就是复制粘贴),angularjs对于JavaScript不熟的童鞋来说,确实难了点,可以先用jQuery代替,有空再学;
第五步、前后端联调
第三、四步完成后,前后调通,做到什么地步?那就是账号密码登录后,A发送消息到后台存库,并且自己还能从库中查到刚刚发的那条消息返回给前端A,不懂?那我不说了,这里只要求前后端能通;
第六步、上WebSocket
这里就不说那么多了,直接复制上面的websocket教程中的服务端代码,复制一份第四步的代码,写一个websocket的通讯(觉得麻烦,这个可以省略?)
第七步、方案的核心
表设计说明:
-
消息记录表(message_log) 说明
-
contextId
消息记录的上下文标识,与消息列表的contextId关联,这里定义的是字符类型,所以可以自由发挥这个id的规则,最简单的使用UUID; -
messageType
消息类型,这里的消息类型你可以认为是微信的那种,比如普通文本,公众号文章,红包,图片,视频等等这种类型,这个类型将会和下面的message紧紧关联起来; -
messageMode
其实主要就是做撤回的标记,其他可以是更新和删除; -
sendTime
发送时间,由于客户端和服务端会存在时间差,所以这里这个时间有点鸡肋,存在的原因很简单,就是用于排序用的; -
message
消息内容,这里有点复杂,我的实现方式是这里存放json字符串,是的没错,所以当java读取这个字段时,需要根据messageType来格式化对应的实体,便于操作,而前端js来说,这个就更简单了,JSON.parse(message)就好了,另外前端可以做一个类似Android那样的webview,也就是富文本的类型都用这个所谓的webview实现,其他的文本,图片,视频等,可以直接输出(本来就是html); -
isRead
是否已读,这个的存在其实是替代消息队列的离线消息,改成数据库的已读未读; -
fromId
这个在申请添加好友或者临时会话的时候意义重大,重大到可以不用contextId;
-
contextId
-
消息列表(chat_user_relation)说明
-
contextId
打死都不说; -
userId1
用户的ID,当单聊的时候,从这个字段可以直接找到用户的信息,反过来,用户想查自己的消息列表的时候,也可以根据这个查到contextId,只要有上下文ID,那你就可以飞了; -
userId2
和上面的一样 -
延伸
这个中间表在用户的角度来说,不存在,所以可以再做一个表和这个中间表关联,当然也是和contextId关联,那就是消息盒子,换句话说就是对于用户真正意义上的消息列表,这个表可以存放名字(群聊的名字,或对方的名字)、头像(对方的头像)、上次发送的内容、时间,不明白?看一下微信消息列表
-
contextId
通讯机制说明
在这个解决方案里面,websocket不是webim的通讯机制,而是原来http,get、post这些,为什么这么说呢?有什么好处? websocket确实可以实现即时通讯,开发成本低,但是有很多问题,其中一个问题,在做这个webim的时候,发送一个富文本的数据就断开了链接,而据说websocket能支持100万字节,这应该和代码、业务有关系,并且我也没有做websocket链接的维护等等。所以我选择用websocket作为通知使用,也就是说,发送消息,获取消息,历史记录等等还是原来的配方,只是当我或他人发送消息时,会触发websocket通知对方调用获取消息的接口拉取消息,并将消息置为已读,这样能很大程度减少websocket的使用;这么做还有一个好处,就是文件的发送,因为还是用到了以前的上传控件,上传成功后依旧websocket通知一下对方拿就好了;所以看到这里就知道我不是大神了吧,也就是个搬砖的,大神是会用到消息服务器,消息队列等等,我也研究了一个,百万级分布式开源物联网MQTT消息服务器;
前端说明
bootstrap支持响应式布局,当然响应式布局的UI多了去了,可以用其他的;
angularJs用这个是因为数据的双向绑定很方便,在循环数据时,提供排序的功能,指定循环的集合中的某一个字段进行排序,这个用在历史记录很好用,因为历史记录要做分页,我们插入下一页时是要向上插入的,但向上插入也还是要排序的,所以干脆插入到原来的集合重新排序,然后判断分页的节点,用js重新定位到那里就好了,有点像锚点;
消息列表的动态排序用到这个排序功能也很好用,比如指定最后发送时间作为消息排序字段倒序,已经排好序的消息列表,当消息来的时候,只需要修改当前这个会话的时间就会自动排好序,不需要自己去维护;
其次,当我们要扩展到通讯录、朋友圈等等功能的时候,离开了消息列表页但还要接受消息,这时候就要用到路由功能,并将websocket放置到全局的controllerJs中;
另外,分享我自己写的前端例子
H5仿微信界面(没写完)
其他要说的
方案到此也差不多了,这里还可以做一个优化,就是将websocket抽出来做一个服务,因为只是用到了通知功能;
另外,做图片或者文件功能的时候,可以用MongoDB作为文件数据库,MongoDB是非关系型数据库,存放文件的信息之后会的到一个ID,这样在发送图片或者文件的时候,只需要传这个ID,然后再通过接口从MongoDB取就可以了,如图片的src可以这么写src="http://localhost/file/getFileById=MongoDB中的ID"
; 通讯录的话可以再用一个表维护,偷懒可以直接用消息列然后再排个序;