将聊天记录存储到数据库中
上文只讲到在线聊天的操作,并未涉及到数据库操作
需求分析
- 使用FLEX布局设计微信交互界面,要去移动端和PC端通用;
- 具有添加朋友的功能,即用户登录网站后,可以将该网站的用户添加为朋友;
- 用户登录后,系统取出朋友列表显示;并且如果存在离线消息的话,取出显示;
- 用户点击朋友标签后,可以发送消息给朋友;若朋友在线,则直接发送,若朋友不在线,则将消息保存到数据库中;
- 用户与A好友通话过程中,如果收到B好友的消息,则可以显示未浏览的消息数量;
- 用户可以查询与某朋友的聊天历史记录;
Mongodb的schema设计
用户表包含用户名、密码、以及好友三个关键信息,其中好友为引用,指向其他用户表
var userSchema = new Schema({
username: String,
password: String,
imgUrl:String,
friends:[{type:Schema.Types.ObjectId, ref:'User' }],
});
最为关键的聊天记录表为如下格式
var chatPersonSchema = new Schema({
personOne:{
type:Schema.Types.ObjectId,
ref:'User'
},
personTwo:{
type:Schema.Types.ObjectId,
ref:'User'
},
personOneNotRead:Number,
personTwoNotRead:Number,
children: [{
from:{
type:Schema.Types.ObjectId,
ref:'User'
},
to:{
type:Schema.Types.ObjectId,
ref:'User'
},
message:String,
time:{type:Date,default:Date.now()}
}]
})
聊天记录表的设计分析
-
personOne
与perosonTwo
- 意义:personOne和personTwo分别表示参与一对一聊天的两个人
-
解析:参与聊天的双方的身份在不断变换,所以一个包含聊天对象A与聊天对象B的聊天记录不能以
from
和to
作为标识符.
-
personOneNotRead
与personTwoNotRead
-
意义:
personOneNotRead
表示由personOne和personTwo
组成的聊天中personOne
没读的消息数目,personTwoNotRead
类同. - 解析:消息未读是相对的,A给B发消息,那么A的未读消息肯定为0,B就需要根据当时的情况来判断.
-
意义:
-
children
-
意义:该数组存储消息的内容与发送方和接收方,
from
代表发送方,to
代表接收方,message
代表消息. - 解析:每当有新消息产生时将消息存入数组中
-
意义:该数组存储消息的内容与发送方和接收方,
如何将消息存储入数据库
var chatPerson =require('./schema/chatHistory');
socket.on('message',function (obj) {
var id1,id2;
//id1为posterid,id2为receivererid
async.series({
//这里不应该用series函数,而应该用parallel
fuck1:function(done){
User.findOne({username:obj.poster},function(error,doc){
if(!error){
if(doc!=null){
id1 = doc._id;
}
done(null,id1);
}
else{
done(error,null);
}
});
},
fuck2:function(done){
User.findOne({username:obj.receiver},function(error,doc){
if(!error){
if(doc!=null){
id2 = doc._id;
}
done(null,id2);
}
else{
done(error,null);
}
});
},
//fuck1和fuck2都是通过poster和receiver的name来获取对应id
},function(error,result){
if(!error)
{
chatPerson.findOne({$or: [
{ personOne: result.fuck1, personTwo: result.fuck2},
{ personOne: result.fuck2, personTwo: result.fuck1}]}).exec(function (err,doc) {
//因为聊天双方身份不确定,只能用$or来作为查询条件
if(doc==null){
//如果doc查不到,则创建一个对应poster和receiver的聊天对象,并设置未读消息记录为0
var shit = new chatPerson({
personOne:result.fuck1,
personTwo:result.fuck2,
personOneNotRead:0,
personTwoNotRead:0,
})
shit.save(function (err) {
if(err){
console.log('保存失败');
}
chatPerson.update({$or: [
{ personOne: result.fuck1, personTwo: result.fuck2},
{ personOne: result.fuck2, personTwo: result.fuck1}]}, {$addToSet:{'children':
{
from:id1,
to:id2,
message:obj.message,
time:Date.now()
}},$inc: {"personTwoNotRead": 1}},function (err,doc) {
});
//插入消息
console.log('success');
})
}else{
//如果doc已经存在,那就不需要再创建一次
chatPerson.update({$or: [
{ personOne: result.fuck1, personTwo: result.fuck2},
{ personOne: result.fuck2, personTwo: result.fuck1}]}, {$addToSet:{'children':
{
from:id1,
to:id2,
message:obj.message,
time:Date.now()
}} },function (err,doc) {
if(doc.personOne==id2){
chatPerson.update({$or: [
{ personOne: result.fuck1, personTwo: result.fuck2},
{ personOne: result.fuck2, personTwo: result.fuck1}]},{$inc:{'personOneNotRead':1}},
function (err,doc) {
})
}else{
chatPerson.update({$or: [
{ personOne: result.fuck1, personTwo: result.fuck2},
{ personOne: result.fuck2, personTwo: result.fuck1}]},{$inc:{'personTwoNotRead':1}},
function (err,doc) {
})
}
});
}
});
}
else{
console.log('err');
}
});
if(client.hasOwnProperty(obj.receiver)){
client[obj.receiver].emit('receive',obj);
}
socket.emit('send',obj);
})
清理未读消息操作与上类似,只不过是将perosonXXXNotRead设置为0而已
如何将查询到的聊天记录合并入查询的好友信息
聊天记录和用户的信息分属于不同的表,并且无法用ref
方法引用到,所以在实现这个功能的时候遇到了很多困难
exports.findUsrInfo = function (req, cb) {
User.findOne({username:req.session.user.username}).populate({path:'friends',select:"username imgUrl"})
.exec(function (err,doc) {
//通过session中的user的信息,查询到该客户端对应的用户信息
var doc = (doc !== null) ? doc.toObject() : '';
//console.log(doc);
if(err){
cb(true,err);
}else {
//console.log(doc);
var index = 0; //friends下标
async.eachSeries(doc.friends, function (item,callback) {
//eachSeries按顺序遍历执行doc.friends数组中的元素,each则是并发执行,如果函数中的操作带有回调函数,并且该操作依赖于上文提供的数据(例如friends的下标index),则会发生错误
async.waterfall([
//water按顺序依次执行其中的函数,并且上一个函数的结果为下一个函数的参数
function (done) {
chatPerson.findOne({ $or: [{ personOne: doc._id, personTwo: item._id}, { personOne: item._id, personTwo: doc._id}] },
function (err,history) {
done(null, history);
//done函数进入下一个函数
})
},
function (history, done) {
//这两个函数可以合并为一个函数,先findOne再popluate
if(history!=null){
var opts = [
{path:'personOne',select:'username'},
{path:'personTwo',select:'username'},
{path:'children.from',select:'username'},
{path:'children.to',select:'username'}
];
history.populate(opts, function(err, fuckyoubitch) {
//console.log(fuckyoubitch);
if(fuckyoubitch!=null){
doc.friends[index].chatHistory=fuckyoubitch;
//通过下标定位,将聊天记录存入对应的好友信息中
}
++index;
done(null, '');
});
}else{
console.log("dbhelper"+index);
++index;
done(null, '');
}
},
], function (error, result) {
callback();
})
},function (err) {
if(err){
}else{
//console.log(doc);
cb(true,doc);
}
});
}
})
}
题外话:js文件如何获取从服务器端发送到express框架的数据
在视图文件下的布局文件中写下如下代码
<script>
window.scriptData = JSON.stringify({{{scriptData}}});
window.scriptData=eval("("+window.scriptData+")");//转换为json对象
</script>
于是可以在其他的js文件中获取服务器发送过来的数据了:)
网友评论