看起来挺简单,细节还是很多的,好,接上一篇,我们已经成功连接singalR服务器了,那么剩下的内容呢,就是一步一步实现聊天功能。

  我们先看看缺什么东西

  1. 点击好友弹框之后,要给服务器发消息,进入组Group.Group原理在上一篇已经介绍了,这里不再赘述。
  2. 点击发送消息到后台,后台在传送回来
  3. 将htmlappend到相应元素上,demo已经实现了,我们把代码拿过来用就可以了
  4. 模拟用户登录,点击发送聊天

  在做上述工作之前,还是要做许多准备工作的。我们分析一下界面元素

  

  好的,可以看到,一个消息里面有消息发送时间(addtime),用户名(username),用户头像(userphoto),用户消息体(msgcontent),除此之外还需要用户id,聊天id,以及组名(groupname).以此我先在后台建立模型。

  

namespace LayIM.Model
{
public enum CSMessageType
{
System = ,//系统消息,出错,参数错误等消息
Custom = //普通消息,对话,或者群组消息
}
}
namespace LayIM.Model
{
public class CSChatMessage
{
public CSChatMessage() {
addtime = DateTime.Now.ToString("HH:mm:ss");
}
/// <summary>
/// 消息来源
/// </summary>
public CSUser fromuser { get; set; }
public CSUser touser { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string msg { get; set; }
/// <summary>
/// 消息发送时间
/// </summary>
public string addtime { get; set; }
/// <summary>
/// 消息类型
/// </summary>
public CSMessageType msgtype { get; set; } public object other { get; set; }
}
}
namespace LayIM.Model
{
public class SingalRUser
{
protected string _groupName { get; set; }
private string _connectionId { get; set; }
/// <summary>
/// 用户当前所在组
/// </summary>
public string groupname
{
get
{
return this._groupName;
}
}
/// <summary>
/// 用户当前所在connectionid
/// </summary>
public string connectionid
{
get
{
return this._connectionId;
}
}
public SingalRUser(string groupName, string connectionId)
{
_groupName = groupName;
_connectionId = connectionId;
}
public SingalRUser() { }
}
/// <summary>
/// 用户Model
/// </summary>
public class CSUser : SingalRUser
{
public CSUser(string groupName, string connectionId) :
base(groupName, connectionId)
{
}
/// <summary>
/// 用户id
/// </summary>
public int userid { get; set; }
/// <summary>
/// 用户昵称
/// </summary>
public string username { get; set; }
/// <summary>
/// 用户头像
/// </summary>
public string photo { get; set; }
}
}

  ok,很简单的几个model,CSUser为用户,CSChatMessage为消息体。那么,如果想让两个用户联通,我们需要得到他们所在的组,即经常说到的 userID1+userID2,生成组名代码如下:(主要保证两个用户的组名唯一性就可,方法随意)

    /// <summary>
/// 根据两个用户ID得到对应的组织名称
/// </summary>
/// <param name="sendid">发送人(主动联系人)</param>
/// <param name="receiveid">接收人(被动联系人)</param>
/// <returns></returns>
public static string GetGroupName(string sendid, string receiveid)
{
/*
排序的目的就是为了保证,无论谁连接服务器,都能得到正确的组织ID
*/
int compareResult = string.Compare(sendid, receiveid);
if (compareResult > ) {
//重新排序 如果sendid>receiveid
return string.Format("G{0}{1}", receiveid, sendid);
}
return string.Format("G{0}{1}", sendid, receiveid);
}

现在groupName也有了,我们回到 CustomServiceHub 类中来。添加用户加入组的方法,这个方法什么时候调用呢,就是当你点击某个用户头像弹出聊天框的时候调用。

/// <summary>
/// 人对人聊天 连接服务器
/// </summary>
/// <param name="sendid">发送人</param>
/// <param name="receiveid">接收人</param>
/// <returns></returns>
public Task ClientToClient(string sendid, string receiveid)
{
if (sendid == null || receiveid == null) { throw new ArgumentNullException("sendid or receiveid can't be null"); }
//获取组名
string groupName = MessageUtils.GetGroupName(sendid, receiveid);
//将当前用户添加到此组织内
Groups.Add(CurrentUserConnectionId, groupName);
//构建系统连接成功消息
var msg = MessageUtils.GetSystemMessage(groupName, MessageConfig.ClientToClientConnectedSucceed, new { currentid = sendid, receiveid = receiveid });
//将消息推送到当前组 (A和B聊天的组) 同样调用receiveMessage方法
return Clients.Caller.receiveMessage(msg);
}

  里面有些代码是我封装的,大体看清思路就可以了。下面去读一下layim.js里的源代码,找到弹出用户窗口那一段。

 //弹出聊天窗
config.chatings = 0;
node.list.on('click', '.xxim_childnode', function () {
var othis = $(this);
//当前登录用户id
var currentid = config.user.id;
//取得被点击的用户id
var receiveid = othis.data('id');
//调用signalR封装的方法,连接服务器,将发送人id,接收人id传给后台,当前用户加入组
csClient.server.ctoc(currentid, receiveid);
xxim.popchatbox(othis);
});

  在看一下csClient到底做了什么

 ctoc: function (sid, rid) {
//调用hub的clientToClient方法
if (!chat.isConnected(rid)) {
//如果没有连接过,进行连接
console.log("用户 " + rid + "没有连接过...");
_this.proxy.proxyCS.server.clientToClient(sid, rid);
} else {
console.log("用户 " + rid + "已经连接过了,不需要连接了...");
}
},

  这里呢,我另外加了个js对象缓存,防止每次点击都要重复连接数据库,当然,页面刷新之后缓存消失,需要重新 连。到这里我们点击一下,看看效果。

  好,从上图可以看到,服务器返回了成功的消息,并且,groupname也是按照顺序生成的。这个消息有什么用呢,其实对于客户端是没有什么效果的,如果想要提示用户连接成功或者提示对方是否在线可以用到,这里我不在扩展,只是为了打印看是否连接成功,当连接成功之后呢,用户就会存在组 G1000010003中了,这时候你发消息如果对面没有连接的话,他是看不见的。连接成功之后,就要做发消息功能了。继续回到 CustomServiceHub 类,添加发送消息方法:

/// <summary>
/// 发送消息 ,服务器接收的是CSChatMessage实体,他包含发送人,接收人,消息内容等信息
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public Task ClientSendMsgToClient(CSChatMessage msg)
{
var groupName = MessageUtils.GetGroupName(msg.fromuser.userid.ToString(), msg.touser.userid.ToString());
/*
中间处理一下消息直接转发给(A,B所在组织,即聊天窗口)
*/
msg.msgtype = CSMessageType.Custom;//消息类型为普通消息
return Clients.Group(groupName).receiveMessage(msg);
}

  可以看到,同样是用到了receiveMessage方法,不过这里呢,调用的Clients.Group(groupName)也就是说,发送的这条消息职能在这个组内的人才能看到,那么组里就两个人,是不是就实现了1对1 聊天呢,离线留言也支持哦。消息发送成功之后,其实不管对方在不在线,我们都可以做一下本地处理,为了演示消息发送效果,我们不用本地js在发送的时候直接拼接到页面上,而是client端接收到消息体之后再处理,这样会看出消息延时效果。(扩展:假如发送的消息很慢的话,就可以在消息体旁边加一个等待的小菊花,提示发送成功,失败等。)好,我直接将layim里模拟消息处理的代码拿出来了,我们看详细代码。

   handleCustomMsg: function (result) {
var log = {};
//接收人
var keys = 'one' + result.touser.userid;
//发送人
var keys1 = 'one' + result.fromuser.userid;
//这里一定要注意,这个keys是会变的,也就是说,如果只取一个的话,会造成 log.imarea[0]为undefined的情况,至于为什么会变,看看代码好好思考一下吧
log.imarea = $('#layim_area' + keys);//layim_areaone0
if (!log.imarea.length) {
log.imarea = $('#layim_area' + keys1);//layim_areaone0
}
//拼接html模板
log.html = function (param, type) {
return '<li class="' + (type === 'me' ? 'layim_chateme' : '') + '">'
+ '<div class="layim_chatuser">'
+ function () {
if (type === 'me') {
return '<span class="layim_chattime">' + param.time + '</span>'
+ '<span class="layim_chatname">' + param.name + '</span>'
+ '<img src="' + param.face + '" >';
} else {
return '<img src="' + param.face + '" >'
+ '<span class="layim_chatname">' + param.name + '</span>'
+ '<span class="layim_chattime">' + param.time + '</span>';
}
}()
+ '</div>'
+ '<div class="layim_chatsay">' + param.content + '<em class="layim_zero"></em></div>'
+ '</li>';
};
//上述代码还是layim里的代码,只不过拼接html的时候,参数采用signalR返回的参数
var type = result.fromuser.userid == currentUser.id ? "me" : "";//如果发送人的id==当前用户的id,那么这条消息类型为me
//拼接html 直接调用layim里的代码
log.imarea.append(log.html({
time: result.addtime,
name: result.fromuser.username,
face: result.fromuser.photo,
content: result.msg
}, type));
//滚动条处理
log.imarea.scrollTop(log.imarea[0].scrollHeight);
},

  好了, 代码也都处理完了,这里呢有个小插曲,我们怎么确定当前用户是谁呢?由于我写的是死数据,所以我就采用随机生成的方法,然后将用户保存到 localStorage里面了,这样当用户再次打开页面,还是会取到第一次的用户,这里呢不多做介绍了。

 /*
获取随机一个用户
当用户第一次登陆就获取,然后存到本地localStorage中模拟用户,之后再登录就直接从缓存里面取
*/
function getRandomUser() {
var userKey = "SIGNALR_USER";
var user = local.get(userKey);
if (user) { return JSON.parse(user);}
var userids = [];
var usernames = ["痴玉", "书筠", "诗冬", "飞枫", "盼玉", "靖菡", "宛雁", "之卉", "凡晴", "书枫", "沛梦"];
var userphotos = [];
//添加id,用户头像数组
for (var i = 0; i < 9; i++) {
userids.push(10000 + i);
userphotos.push("/photos/00" + i.toString() + ".jpg");
}
//取一个random值,自动生成当前用户
var random = Math.random().toString().substr(3, 1);
if (random > 8) { random = 8; }
var user = {
name: usernames[random],
photo: userphotos[random],
id:userids[random]
};
local.set(userKey, JSON.stringify(user));
return user;
}
/*本地存储*/
var local = {
get: function (key) {
return localStorage.getItem(key);
},
set: function (key, value) {
localStorage.setItem(key, value);
}
}

  当然里面有好多需要注意的细节没有给大家讲,具体的可以看详细代码,思路基本已经出来了。我在重复一遍吧:第一,点击用户,连接服务器,当前用户加入对应的组。第二,发送消息,调用server端的方法,将消息发送出去后,在推送到组里面去,第三,客户端接收到消息之后,加到html页面上,就这么简单。还有一个细节,注意消息在左边还是右边。

  演示一下吧:模拟第一个用户登录。(谷歌浏览器)

  好,很好听的名字:飞枫,id为10003,下面第二个用户登录,(QQ浏览器)

  id为10001,名字为 书筠。那么我们先用第一个用户点击  书筠 头像 打开聊天窗口,然后在用第二个用户点击 飞枫头像打开聊天窗口(由于没有做历史记录,所以离线留言功能暂时不支持,只支持在线)

  打开之后,唉,单身的我只能模拟两个人聊天玩了。。

到此为止呢,1v1聊天就到一段落,仅支持。。。文本,还不知道输入script有没有处理。。另外还有一个bug,就是窗口多开的话,应该 信息可能会乱,不是因为 发送乱了,而是,里面有可能有重复的ID导致信息赋html错误,我没测,但是我猜测是这样的。非常感谢“贤心”大神的web前端通讯框架。本篇到此结束,喜欢的同学点个赞吧。多多转发哦。下篇预告:最终章-修改1v1聊天bug,添加图片表情,附件传送功能。群聊功能实现。

GitHub地址:

https://github.com/fanpan26/LayIM/tree/master

ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天的更多相关文章

  1. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天   看起来挺简单,细节还是很多的,好,接上一篇,我们已经成功连接singalR服务器 ...

  2. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据   最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前 ...

  3. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(二) 实现聊天室连接

    上一篇已经简单介绍了layim WebUI即时通讯组件和获取数据的后台方法.现在要讨论的是SingalR的内容,之前都是直接贴代码.那么在贴代码之前先分析一下业务模型,顺便简单讲一下SingalR里的 ...

  4. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能

    休息了两天,还是决定把这个尾巴给收了.本篇是最后一篇,也算是草草收尾吧.今天要加上表情功能和群聊.基本上就差不多了,其他功能,读者可以自行扩展或者优化.至于我写的代码方面,自己也没去重构.好的,我们开 ...

  5. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前端组件配合后台完成即时聊天等功能.当然用到的技术就是ASP.NET SingalR框架.本人不会c ...

  6. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(五) 补充:历史记录 和 消息提醒

    有开发者提问怎么做历史记录功能和即使不打开聊天窗口有消息提醒功能.简单抽时间写了点代码.不过只是基本思路,具体细节没有实现. 正如前几篇博客中提到的,读取历史记录什么时候读取呢?按照常理,应该是打开聊 ...

  7. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(八)之 聊天记录入队(列)

    本篇也算是个番外篇了,跟之前几篇关系不算大.之前一篇  RabbitMQ .NET Client 实战实验 里有介绍过今天要用的内容. 做了一下小更改,就是在用户聊天的时候,消息记录不直接进入数据库, ...

  8. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(七)之 图文,附件消息(2016-05-05 12:13)

    上一篇介绍了加好友的流程,这里不再赘述,不过之前的聊天只能发送普通文字,那么本篇就教你如何实现发送附件和图片消息.我们先对功能进行分析: 发送图片,附件,需要实现上传图片和附件的功能. textare ...

  9. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(六)之 好友申请、同意、拒绝

    不知道距离上一篇多久没有写了,可能是因为忙(lan)的关系吧.废话不多说,今天要介绍的不算什么新知识,主要是逻辑上的一些东西.什么逻辑呢,加好友,发送好友申请,对方审批通过,拒绝.(很遗憾,对方审批通 ...

随机推荐

  1. linux无敌权限之chattr

    chattr 修改文件或者目录的文件属性,添加超级权限 -R 递归修改文件机子目录 -V 显示详细修改的内容 + 在原有的基础上追加参数 — 减参数 = 跟新为指定参数 a append 设定改参数后 ...

  2. flask综合整理2

    session功能 首先我们知道session可以理解是一把钥匙,它存在在服务器上,其实在flask中也有session 1.系统自带的session from flask import sessio ...

  3. VS中为什么不同的项目类型属性查看和设置的界面不一样

    在VS中,存在ATL.MFC.Win32.CLR.常规等等各种工程模板,这些工程模板对应于开发不同类型的应用,比如要开发com,你应该选ATL:开发最原始的通过API代用操作系统的应用,应该用Win3 ...

  4. [转]浅谈微信小程序

    本文转自:http://www.cnblogs.com/liziyou/p/6340159.html 微信小程序 1.什么是小程序 小程序是指微信公众号平台小程序,小程序可以在微信内被便捷的获取和转播 ...

  5. [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...

  6. win10下MySQL 5.7.20解压版安装步骤

    1.从官网下载MySQL5.7.20解压版64位:https://dev.mysql.com/downloads/file/?id=473309. 2.解压(我的解压路径为:E:\mysql-5.7. ...

  7. javaSystem.out.println()输出byte[]和char[]异常的问题

    javaSystem.out.println()输出byte[]和char[]异常的问题 今天 突然有人问我他写的byte[]和char[],在用System.out.println()输出的时候所得 ...

  8. ZROJ#397. 【18提高7】模仿游戏(爆搜)

    题意 题目链接 Sol 考试的时候调了1.5h没调出来我真是菜爆了... 读完题目后不难发现,每次约束的条件相当于是\(b[((x[i] + i) % N + (i / N) % N) % N] = ...

  9. 转载《学习HTML5 canvas遇到的问题》

    学习HTML5 canvas遇到的问题 1. 非零环绕原则(nonzZero rule) 非零环绕原则是canvas在进行填充的时候是否要进行填充的判断依据. 在判断填充的区域拉一条线出来,拉到图形的 ...

  10. C++之不带指针类的设计——Boolean

    经典的类设计分类 带指针类 不带指针类 Header文件的布局 #ifndef __COMPLEX__ #define __COMPLEX__ #include <iostream.h> ...