yun2win官网:www.yun2win.com
数据服务器源码下载地址:https://github.com/yun2win/yun2win-sdk-server
简介
即时通讯云后台由数据服务器和推送服务器组成,其中数据服务器由开发者从Github获得源码,自行部署,保证开发者对其 数据的绝对控制。验证系统使用了OAtuth2.0,对外接口是RESTful Api。如果您只是配置部署后台,请转至服务器部署。
开发准备
先从Github下载源码,目前后台源码是nodejs版本, 可以使用Webstorm、Visual Studio或其它IDE打开源码。
下面看看源码结构
+
//这里所有业务代码 | -- app //业务核心代码 | -- core //实体,可以持久化的 | -- model //使用的一些工具模块,目前有AES加密库,DB操作库,邮件操作库,汉字拼音库 | -- plugins //用提供给router的入口,目前已经慢慢由Core的代码代替 | -- service //附件存储临时位置,开发者可以另行定义,后面会讲到 | -- atts //路由,所有RESTful Api的入口都是这里实现 | -- router //一些基础的测试 | -- test //放一些静态文件 | -- public //主要是404和error时的展示模板 | -- views | -- app.js //启动入口,使用node server.js启动本程序 | -- server.js
类结构介绍
实体结构请看下图:
简单介绍一下各类的意义
User
。用户类Users
。User的集合,管理User的生命周期UserConversation
。用户会话类,目前支持单聊&群聊类型UserConversations
。UserConversation的集合,管理其的生命周期UserSession
。群组类,即是对Session的收藏UserSessions
。UserSession的集合,管理其的生命周期Contact
。联系人类。Contacts
。Contact的集合,管理其的生命周期Session
。会话类,所有会话,包括单聊,群聊都以此为单位Sessions
。Session的集合,管理其的生命周期SessionMember
。会话成员类,即是所有参与此会话的成员SessionMembers
。SessionMember的集合,管理其的生命周期Message
。消息类,聊天产生的信息,支持各种类型Messages
。Message的集合,管理其的生命周期
createdAt
和
updatedAt
两个属性,分别是
加入时间
和
最后修改时间
。
这里有两个核心类,分别是User
和Session
。用户包括联系人,群组及用户会话;Session包括成员和消息。 从UML图可以看出,其它类均围绕这两个类展开。
同步机制
同步机制是保证客户端和服务器数据一致的重要机制,目前分五种集合:
- 用户会话
范围:用户
。 - 联系人
范围:用户
。 - 群组
范围:用户
。 - SessionMember
范围:Session
。 - Message
范围:Session
。
基本原理
同步机制的原理很简单,目前同步机制是以数据集合为单位的。每个数据集合同步流程为:
- 获得本地时间戳(如果没有,定为1900年1月1日 0时0分0秒)
- 以此时间戳访问业务服务器取得数据
- 将取得数据更新至本地(有三种情况:新增的,修改的,删除的)
- 取此数据最大的updatedAt时间为新的本地时间戳,保存这个时间戳.
- 判断数据是否同步完,如果没有,重回第1步
而服务端每更改数据,均要更新其updatedAt属性,如此,客户端便可以最少的流量,最少的请求获得完整数据。有了此同步机制, 各数据搜索也可以直接搜索本地数据,不用再浪费网络请求资源。
这里要特别指出,为了让各客户端能同步到被删除的指令,所有的数据删除并不是真正意义上的删除,而改其栏位isDelete,当其为 true时就表示他已经被删除了。
用户
用户即是即时通讯的帐户体系,先看app/core/user.js
的结构:
var User=function(users,entity){ this.users=users; this.entity=entity; this.id=entity.id; this.userConversations=new UserConversations(this); //用户会话集合 this.contacts=new Contacts(this); //联系人集合 //各项属性 for(var index in this.entity) this[index]=this.entity[index]; };
其中各项属性如下(参考app/model/user.js
):
var Obj=db.define("User",{ email:db.String, name:db.String, password:db.String, role:db.String, phone:db.String, jobTitle:db.String, //职位 address:db.String, status:db.String, //用户状态,有效(active),封禁(inactive) avatarUrl:db.String, isDelete:db.Boolean //是否已删除 });
代码上app/core/user.js
引用了app/model/user.js
,前者负责实现User各项功能调用,后者只是持久定义。 本程序基本所有类都遵循代码逻辑处理和数据持久分开。
帐户对接
如您的业务系统已有帐户体系,您可以有两种方式来完成帐户对接
1、同步帐户体系
调用RESTful API将您业务系统的帐户体系全部注册一遍
2、改写用户接口
只要重写用户的以下几个方法即可:
- 获取:
app/core/users.js
的方法get
- 注册:
app/core/users.js
的方法register
- 删除:
app/core/users.js
的方法delete
- 保存:
app/core/user.js
的方法store
用户会话
用户会话是此用户交流过的会话收藏列表,主要显示会话最新消息
和用户未读消息数
。用户会话 应该是客户端访问最频繁的数据集合,为了增加服务负载,用户会话均读取入内存,将性能做到最优。
查看下其结构
var Obj=db.define("UserConversation",{ ownerId:db.String, //所属者Id,此时是用户Id targetId:db.String, name:db.String, type:db.String, //['p2p','group'] top:db.Boolean, //置顶 avatarUrl:db.String, isDelete:db.Boolean });
用户会话有以下几点需要注意的地方:
- 所有用户会话均由服务端创建,客户端只需建立
Session
和SessionMmember
,当发消息时会自动创建对应的会话 - 用户会话的
用户未读消息数
并未持久化,故服务器重启后此值会重置0
- 用户会话的
updatedAt
也是最新消息的updatedAt
- 用户会话的targetId属性分两种情况:
type=='p2p'
时是对方UserId
;type=='group'
时是SessionId
- 用户会话的名称头像分两种情况:
type=='p2p'
时是对方的名称头像
,非最新,需要客户端处理获得最新名称头像;type=='group'
时是Session的名称头像
,一直保持最新。
获得最新用户会话
UserConversations.prototype.getList=function(clientTime,cb){ //支持单参数访问 if(clientTime && !cb) { cb = clientTime; clientTime=new Date(1900,1,1); } var that=this; var lastConvrs=[]; var tobj=this; thenjs() .then(function(cont){ //先查看是否已载入过用户会话列表 if(that.list) return cont(null,that.list); var clientId=that.user.users.client.id; var userid=that.user.id; //没有就去取出最新有更新100条 //此处限定了100条是为了避免一次载入大量的用户会话使服务内存爆了。 UserConversationModel.getLatest(clientId,userid,100,function(error,objs){ if(that.list) return cont(null,that.list); that.list=[]; for(var i=0;i<objs.length;i++){ that.list.push(new="" userconversation(that,objs[i]));="" }="" cont(null,that.list);="" });="" })="" 找出一段时间(配置里默认设定为1小时)内有更新的会话="" .then(function(cont){="" var="" timeslip="new" date();="" timeslip.sethours(timeslip.gethours()-config.session.userconvrkeepalive);="" tobj.timeslip="timeslip;" for(var="" i="0;i<that.list.length;i++){" obj="that.list[i];" if(obj.updatedat="">clientTime) lastConvrs.push(obj); } cont(); }) //更新每一个用户会话 .each(lastConvrs,function(cont,uc){ if(uc.updatedAt>tobj.timeslip || !uc.lastMessage) return uc.refresh(cont); cont(); }) //排序返回 .then(function(cont,list){ lastConvrs.sort(function(a,b