- 支持私聊、群聊
- 支持分布式多个服务端通信
- 支持文本消息、文件消息、语音消息(感谢ft115637850的PR)
###演示
进入CookIM所在目录,运行以下命令,以docker-compose方式启动CookIM集群,该集群启动了三个容器:mongo、cookim1、cookim2
$ git clone https://github.com/cookeem/CookIM.git
$ cd CookIM
$ sudo docker-compose up -d
Creating mongo
Creating cookim1
Creating cookim2
成功启动集群后,浏览器分别访问以下网址,对应不同的CookIM服务
可以通过修改docker-compose.yml文件增加CookIM服务节点,例如增加第三个节点cookim3:
cookim3:
image: cookeem/cookim
container_name: cookim3
hostname: cookim3
environment:
HOST_NAME: cookim3
WEB_PORT: 8080
AKKA_PORT: 2551
SEED_NODES: cookim1:2551
MONGO_URI: mongodb://mongo:27017/local
ports:
- "8082:8080"
depends_on:
- mongo
- cookim1
查看cookim1容器日志输出
$ sudo docker logs -f cookim1
进入cookim1容器进行调试
$ sudo docker exec -ti cookim1 bash
$ sudo docker-compose stop
$ sudo docker-compose rm -f
- JDK 8+
- Scala 2.11+
- SBT 0.13.15
- MongoDB 2.6 - 3.4
git clone https://github.com/cookeem/CookIM.git
cd CookIM
配置文件位于conf/application.conf
,请务必配置mongodb的uri配置
mongodb {
dbname = "cookim"
uri = "mongodb://mongo:27017/local"
}
对CookIM进行打包fatjar,打包后文件位于target/scala-2.11/CookIM-assembly-0.2.0-SNAPSHOT.jar
sbt clean assembly
CookIM的数据保存在MongoDB中,启动CookIM前务必先启动MongoDB
a. 调试方式启动服务:
$ sbt "run-main com.cookeem.chat.CookIM -h localhost -w 8080 -a 2551 -s localhost:2551"
b. 打包编译:
$ sbt assembly
c. 产品方式启动服务:
$ java -classpath "target/scala-2.11/CookIM-assembly-0.2.4-SNAPSHOT.jar" com.cookeem.chat.CookIM -h localhost -w 8080 -a 2551 -s localhost:2551
以上命令启动了一个监听8080端口的WEB服务,akka system的监听端口为2551
参数说明:
-a -h [-m ] [-n] -s -w -a,--akka-port akka cluster node port -h,--host-name current web service external host name -m,--mongo-uri mongodb connection uri, example: mongodb://localhost:27017/local -n,--nat is nat network or in docker -s,--seed-nodes akka cluster seed nodes, seperate with comma, example: localhost:2551,localhost:2552 -w,--web-port web service port
打开另外一个终端,启动另一个CookIM服务,测试服务间的消息通讯功能。
a. 调试方式启动服务:
$ sbt "run-main com.cookeem.chat.CookIM -h localhost -w 8081 -a 2552 -s localhost:2551"
b. 产品方式启动服务:
$ java -classpath "target/scala-2.11/CookIM-assembly-0.2.0-SNAPSHOT.jar" com.cookeem.chat.CookIM -h localhost -w 8081 -a 2552 -s localhost:2551
以上命令启动了一个监听8081端口的WEB服务,akka system的监听端口为2552
该演示启动了两个CookIM服务,访问地址分别为8080端口以及8081端口,用户通过两个浏览器分别访问不同的的CookIM服务,用户在浏览器中通过websocket发送消息到akka集群,akka集群通过分布式的消息订阅与发布,把消息推送到集群中相应的节点,实现消息在不同服务间的分布式通讯。
CookIM服务由三部分组成,基础原理如下:
- akka http:用于提供web服务,浏览器通过websocket连接akka http来访问分布式聊天应用;
- akka stream:akka http在接收websocket发送的消息之后(消息包括文本消息:TextMessage以及二进制文件消息:BinaryMessage),把消息放到chatService流中进行流式处理。websocket消息中包含JWT(Javascript web token),如果JWT校验不通过,chatService流会直接返回reject消息;如果JWT校验通过,chatService流会把消息发送到ChatSessionActor中;
- akka cluster:akka stream把用户消息发送到akka cluster,CookIM使用到akka cluster的DistributedPubSub,当用户进入会话的时候,订阅(Subscribe)对应的会话;当用户向会话发送消息的时候,会把消息发布(Publish)到订阅的actor中,此时,群聊中的用户就可以收到消息。
- akka http在接收到websocket发送的消息之后,会把消息发送到chatService流里边进行处理,这里使用到akka stream graph:
- websocket发送的消息体包含JWT,flowFromWS用于接收websocket消息,并把消息里边的JWT进行解码,验证有效性;
- 对于JWT校验失败的消息,会经过filterFailure进行过滤;对于JWT校验成功的消息,会经过filterSuccess进行过滤;
- builder.materializedValue为akka stream的物化值,在akka stream创建的时候,会自动向connectedWs发送消息,connectedWs把消息转换成UserOnline消息,通过chatSinkActor发送给ChatSessionActor;
- chatActorSink向chatSessionActor发送消息,在akka stream结束的时候,向down stream发送UserOffline消息;
- chatSource用于接收从ChatSessionActor中回送的消息,并且把消息发送给flowAcceptBack;
- flowAcceptBack提供keepAlive,保证连接不中断;
- flowReject和flowAcceptBack的消息最后统一通过flowBackWs处理成websocket形式的Message通过websocket回送给用户;
- users: 用户表
*login(登录邮箱)
nickname(昵称)
password(密码SHA1)
gender(性别:未知:0,男生:1,女生:2)
avatar(头像,绝对路径,/upload/avatar/201610/26/xxxx.JPG)
lastLogin(最后登录时间,timstamp)
loginCount(登录次数)
sessionsStatus(用户相关的会话状态列表)
[{sessionid: 会话id, newCount: 未读的新消息数量}]
friends(用户的好友列表:[好友uuid])
dateline(注册时间,timstamp)
- sessions: 会话表(记录所有群聊私聊的会话信息)
*createuid(创建者的uid)
*ouid(接收者的uid,只有当私聊的时候才有效)
sessionIcon(会话的icon,对于群聊有效)
sessionType(会话类型:0:私聊,1:群聊)
publicType(可见类型:0:不公开邀请才能加入,1:公开)
sessionName(群描述)
dateline(创建日期,timestamp)
usersStatus(会话对应的用户uuid数组)
[{uid: 用户uuid, online: 是否在线(true:在线,false:离线}]
lastMsgid(最新发送的消息id)
lastUpdate(最后更新时间,timstamp)
- messages: 消息表(记录会话中的消息记录)
*uid(消息发送者的uid)
*sessionid(所在的会话id)
msgType(消息类型:)
content(消息内容)
fileInfo(文件内容)
{
filePath(文件路径)
fileName(文件名)
fileType(文件mimetype)
fileSize(文件大小)
fileThumb(缩略图)
}
*dateline(创建日期,timestamp)
- onlines:(在线用户表)
*id(唯一标识)
*uid(在线用户uid)
dateline(更新时间戳)
- notifications:(接收通知表)
noticeType:通知类型("joinFriend", "removeFriend", "inviteSession")
senduid:操作方uid
*recvuid:接收方uid
sessionid:对应的sessionid
isRead:是否已读(0:未读,1:已读)
dateline(更新时间戳)
有两个websocket信道:ws-push和ws-chat
ws-push向用户下发消息提醒,当用户不在会话中,可以提醒用户有哪些会话有新消息
/ws-push channel
上行消息,用于订阅推送消息:
{ userToken: "xxx" }
下行消息:
acceptMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "accept", content: "xxx", dateline: "xxx" }
rejectMsg: { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "reject", content: "xxx", dateline: "xxx" }
keepAlive: { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "keepalive", content: "", dateline: "xxx" }
textMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "text", content: "xxx", dateline: "xxx" }
fileMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "file", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx", dateline: "xxx" }
onlineMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "online", content: "xxx", dateline: "xxx" }
offlineMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "offline", content: "xxx", dateline: "xxx" }
joinSessionMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "join", content: "xxx", dateline: "xxx" }
leaveSessionMsg:{ uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "leave", content: "xxx", dateline: "xxx" }
noticeMsg: { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "xxx", sessionIcon: "xxx", msgType: "system", content: "xxx", dateline: "xxx" }
下行到浏览器消息格式:
pushMsg: {
uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "xxx",
content: "xxx", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx",
dateline: "xxx"
}
ws-chat为用户在会话中的聊天信道,用户在会话中发送消息以及接收消息用
/ws-chat channel
上行消息:
onlineMsg: { userToken: "xxx", sessionToken: "xxx", msgType:"online", content:"" }
textMsg: { userToken: "xxx", sessionToken: "xxx", msgType:"text", content:"xxx" }
fileMsg: { userToken: "xxx", sessionToken: "xxx", msgType:"file", fileName:"xxx", fileSize: 999, fileType: "xxx" }<#BinaryInfo#>binary_file_array_buffer
下行消息:
rejectMsg: { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "reject", content: "xxx", dateline: "xxx" }
keepAlive: { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "keepalive", content: "", dateline: "xxx" }
textMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "text", content: "xxx", dateline: "xxx" }
fileMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "file", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx", dateline: "xxx" }
onlineMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "online", content: "xxx", dateline: "xxx" }
offlineMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "offline", content: "xxx", dateline: "xxx" }
joinSessionMsg:{ uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "join", content: "xxx", dateline: "xxx" }
leaveSessionMsg:{ uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "leave", content: "xxx", dateline: "xxx" }
noticeMsg: { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "xxx", sessionIcon: "xxx", msgType: "system", content: "xxx", dateline: "xxx" }
下行到浏览器消息格式:
chatMsg: {
uid: "xxx", nickname: "xxx", avatar: "xxx", msgType: "xxx",
content: "xxx", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx",
dateline: "xxx"
}
- CookIM支持MongoDB 3.4.4
- 更新akka版本为2.5.2
- 更新容器启动方式,只保留docker-compose方式启动集群
- 支持发送语音消息,chrome和firefox以及最新的safari11支持
- 支持命令行设置mongodb连接参数设置
- 更新docker启动方式