前言
本系列文章特点:使用ASP.NET SignalR和LayIM快速入门对接,实现一对一聊天,群聊,添加聊天群组,查找聊天记录等功能。源代码不包含LayIM的源代码,因为官方并没开源属于收费资源,所以得遵从官方的规则,但包含Demo的数据库脚本和改造之后的find.html,源代码在最后一节。
文章目录:
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(一)之效果展示与关键技术简介
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(二)之后台数据库创建
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(三)之LayIM初始化数据
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(四)之ASP.NET SignalR核心功能介绍
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(五)之使用RabbitMQ缓存消息
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(六)之SignalR与MVC结合封装消息发送与接收
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(七)之LayIM与MVC数据交互实现单聊和群聊
LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(八)之改造查找页面实现拉取好友创建群
一、本文的目标与简介
在前面文章已经讲述了集线器以及RabbitMQ相关的技术点,本文就主要针对LayIM的几个主要功能,消息发送,图片文件上传以及消息接收封装三个Action工LayIM前端调用。
二、封装BaseHubController
这个类的作用主要是方便其他继承与它的Controller方便的获取创建的Hub,以实现利用Clients和Groups进行消息处理。代码如下:
public class BaseHubController<T> : Controller where T : Hub
{
public IHubConnectionContext<dynamic> Clients { get; set; }
public IGroupManager Groups { get; set; }
public BaseHubController()
{
var hub = GlobalHost.ConnectionManager.GetHubContext<T>();
Clients = hub.Clients;
Groups = hub.Groups;
}
}
三、LIMController实现消息处理
这个Controller继承BaseHubController,它主要包含以下三个Action,这里会涉及到前文所说的Hub的ConnectionId与数据库用户表主键的对应关系,所以这里首先创建一个安全的Dictionary,前文已经介绍过,就不再介绍这个Dictionary的作用,这里直接贴出代码:
public static class LIMUserTable
{
public static readonly ConcurrentDictionary<int, string> diclist = new ConcurrentDictionary<int, string>();
public static void AddUser(int key, string value)
{
if (diclist.ContainsKey(key))
{
diclist[key] = value;
}
else
{
diclist.TryAdd(key, value);
}
}
public static string GetValueByKey(int key)
{
if (diclist.ContainsKey(key))
{
return diclist[key];
}
else
{
return "";
}
}
}
LIMUserTable有两个方法,一个用来添加数据,一个用来取数据即用作根据用户主键获取当前的ConnectionId。
接下来重点介绍LIMController的三个方法
1.AddConnectionId(string connectionId)
这个方法在前文也提到过,在前端获取到当前用户的ConnectionId时把ConnectionId提交到这个方法里,同时在这个方法里还要做两件事:第一件事就是把当前的ConnectionId添加到用户所有的群组,即所有的群组内用户上线,第二件事:处理ConnectionId接收到的消息,其中涉及到前文提到的RabbitMQReceive自定义的消息监听取出,所以代码如下:
public ActionResult AddConnectionId(string connectionId)
{
try
{
int userid = int.Parse(Session["UserId"].ToString());
LIMUserTable.AddUser(userid, connectionId);//将用户的connectionId存储起来
RabbitMQReceive customer = new RabbitMQReceive();
//开始监听
customer.BindReceiveMqMsg("single_" + userid.ToString());
customer.ReceiveCallback = message =>
{
connectionId = LIMUserTable.GetValueByKey(userid);
Clients.Client(connectionId).receiveMsg(message);
return true;
};
var grouplist = LIMRepository.GetUserRoomList(userid);
foreach (var item in grouplist) //所有的群组上线
{
Groups.Add(connectionId, item.id.ToString());
}
return Json(new ReturnDoEntity { result = true, message = "添加成功" }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(new ReturnDoEntity { result = false, message = ex.Message }, JsonRequestBehavior.AllowGet);
}
}
2.SendMessage(int fid, int tid, MessageDoEntity msg)
这个方法用来发送消息,即消息是谁发给谁,发了什么消息,其中MessageDoEntity是LayIM要求的消息的数据格式,代码如下:
/// <summary>
/// 发送消息对应的实体
/// </summary>
public class MessageDoEntity
{ /// <summary>
/// 消息来源类型
/// 系统消息为ture
/// </summary>
public bool system { get; set; } = false;
/// <summary>
/// 消息来源用户名
/// </summary>
public string username { get; set; }
/// <summary>
/// 消息来源用户头像
/// </summary>
public string avatar { get; set; }
/// <summary>
/// 消息的来源ID(如果是私聊,则是用户id,如果是群聊,则是群组id)
/// </summary>
public string id { get; set; }
/// <summary>
/// 聊天窗口来源类型,从发送消息传递的to里面获取
/// 单人:friend 群组:group
/// </summary>
public string type { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string content { get; set; }
/// <summary>
/// 消息id,可不传。除非你要对消息进行一些操作(如撤回)
/// </summary>
public int cid { get; set; }
/// <summary>
///是否我发送的消息,如果为true,则会显示在右方
/// </summary>
public bool mine { get; set; }
/// <summary>
/// 消息的发送者id(比如群组中的某个消息发送者),可用于自动解决浏览器多窗口时的一些问题
/// </summary>
public string fromid { get; set; }
/// <summary>
/// 服务端时间戳毫秒数。注意:如果你返回的是标准的 unix 时间戳,记得要 *1000,13位
/// </summary>
public long timestamp { get; set; }
/// <summary>
/// 是否显示‘前后消息’
/// </summary>
public string beforemsg { get; set; }
}
LayIM的消息有两种类型即friend和group,根据这两种类型,做不同的处理,代码如下:
/// <summary>
/// 发送消息
/// </summary>
/// <param name="fid">发送者</param>
/// <param name="tid">接收者</param>
/// <param name="msg"></param>
[HttpPost]
public void SendMessage(int fid, int tid, MessageDoEntity msg)
{
DateTime ndt = DateTime.Now;//当前时间
msg.timestamp = CommonHelper.GetTimeSpan(ndt);
List<MessageEntity> meglist = new List<MessageEntity>();
meglist.Add(new MessageEntity()
{
FUserId = fid,
TUserId = tid,
SendTime = ndt,
MessageContent = msg.content
});
string strmsg = CommonHelper.SerializeObject(msg);
if (msg.type == "friend")
{
RabbitMQSend.PushMessage("single_" + tid.ToString(), strmsg);
Task.Run(() => LIMRepository.SaveMessage(meglist));
}
else if (msg.type == "group")
{
///查找当前群组的人
string connectionId = LIMUserTable.GetValueByKey(fid);
///向群组添加消息
Clients.Group(msg.id, connectionId).receiveMsg(strmsg);//消息的来源ID(如果是私聊,则是用户id,如果是群聊,则是群组id)
Task.Run(() => LIMRepository.SaveRoomchat(meglist));
}
}
这里需要注意:本Demo是把“single_”+用户的主键作为消息队列的队列名称,所以取得时候也是按照这个名称取,group的名称也直接就是群组的主键,即对应数据库表LIM.Room的主键RoomId。
3.UploadFileAndImg()
这个Action主要是用来处理LayIM上传的文件和图片,同样LayIM对上传文件的返回数据也有格式要求即本Demo的UploadResultDoEntity实体类模型,代码如下:
/// <summary>
/// 上传文件或者图片返回实体模型
/// </summary>
public class UploadResultDoEntity
{
/// <summary>
/// 0表示成功,其它表示失败
/// </summary>
public int code { get; set; } = 0;
/// <summary>
/// 失败信息
/// </summary>
public string msg { get; set; }
/// <summary>
/// 文件信息
/// </summary>
public FileInfo data { get; set; } = new FileInfo();
}
public class FileInfo
{
/// <summary>
/// 文件地址
/// 如:"http://cdn.xxx.com/upload/file/xxx.zip"
/// </summary>
public string src { get; set; }
/// <summary>
/// 文件名称
/// </summary>
public string name { get; set; }
}
UploadFileAndImg的代码如下:
[ValidateInput(false)]
[HttpPost]
public ActionResult UploadFileAndImg()
{
UploadResultDoEntity uploadResult = new UploadResultDoEntity();
try
{
if (Request.Files.Count > 0)
{
HttpPostedFileBase Portrait = Request.Files[0];
string fileName = Path.GetFileName(Portrait.FileName).Replace("&", "");
string newfilename = Guid.NewGuid().ToString("N") + "-" + fileName;
string serverpath = "/IMFiles/" + DateTime.Now.ToString("yyyy") + "/" + DateTime.Now.ToString("MM");
var sysPath = Server.MapPath(serverpath);
if (!Directory.Exists(sysPath)) Directory.CreateDirectory(sysPath);
string savePath = Path.Combine(sysPath, newfilename);
Portrait.SaveAs(savePath);
uploadResult.data.src = serverpath + "/" + newfilename;
uploadResult.data.name = fileName;
}
}
catch (Exception ex)
{
uploadResult.code = -1;
uploadResult.msg = ex.Message;
}
return Json(uploadResult);
}
四、总结
这一节主要是讲述了如何在MVC中实现和Hub结合实现消息发送与接收,额外的添加了使用RabbitMQ缓存消息,下一节将展示如何使用LayIM和MVC的Action进行数据交互。