web版聊天功能简单实现
一、问题
核心点:如何找到要发送的人?
要完成一个功能我觉得首先要分析该功能的逻辑及技术难点,而不是盲目的直接就撸代码,这样非常浪费时间。个人觉得web版聊天功能没什么实际应用场景,以前看过中国移动好像有过这种东西,所以就简单实现了下
解决:使用缓存存储当前聊天状态
public class SignalRMessageGroups { public string ConnectionId { get; set; } public long UserId { get; set; } public string GroupName { get; set; } public static List<SignalRMessageGroups> UserGroups = new List<SignalRMessageGroups>(); }
将当前聊天信息存储在内存中,当然你也可以持久化到其它地方,思路是一样的
二、具体实现代码
使用SignalR进行通讯,具体逻辑不描述(注释都有),因为是在自己的项目实现的,所以只显示部分代码,非常简单的东西,可能js和css写起来麻烦些
Hub代码:
[Authorize] public class ChatHub: Hub { private readonly IOaChatService _chatService; public ChatHub(IOaChatService chatService) { this._chatService = chatService; } public override async Task OnConnectedAsync() { await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception ex) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, "ChatHubGroup"); var curUser = SignalRMessageGroups.UserGroups.FirstOrDefault(m => m.ConnectionId == Context.ConnectionId && m.GroupName == "ChatHubGroup"); if (curUser != null) { SignalRMessageGroups.UserGroups.Remove(curUser); } await base.OnDisconnectedAsync(ex); } /// <summary> /// 信息发送 /// </summary> /// <param name="receiver">接收人</param> /// <param name="sender">发送人</param> /// <param name="message"></param> /// <returns></returns> public async Task SendMessage(long receiver,long sender, string message) { //判断接收的人是否在线 var receiveUser = SignalRMessageGroups.UserGroups.FirstOrDefault(m => m.UserId == receiver && m.GroupName == "ChatHubGroup"); if (receiveUser != null) { await Clients.Client(receiveUser.ConnectionId).SendAsync("ReceiveChater", new { sender, message, time = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") }); await _chatService.InsertAsync(new Model.OaChat { Receiver = receiver, Sender = sender, Message = message }); } else { //发送邮件/短信提醒 } } public async Task InitMessage(long userid) { await Groups.AddToGroupAsync(Context.ConnectionId, "ChatHubGroup"); var curUser = SignalRMessageGroups.UserGroups.FirstOrDefault(m => m.UserId == userid && m.GroupName == "ChatHubGroup"); if (curUser != null) { SignalRMessageGroups.UserGroups.Remove(curUser); } SignalRMessageGroups.UserGroups.Add(new SignalRMessageGroups { ConnectionId = Context.ConnectionId, GroupName = "ChatHubGroup", UserId = userid }); //刷新在线用户列表 await Clients.All.SendAsync("RefreshOnliner", userid); } }
接口(获取在线人员)
[Authorize] [Route("api/Chat/[action]")] [Produces("application/json")] [ApiController] public class ChatController : ControllerBase { private ISystemService _systemService; private readonly IOaChatService _chatService; private IHubContext<ChatHub> _hubContext; public ChatController(IServiceProvider serviceProvider, ISystemService systemService,IOaChatService chatService) { _hubContext = serviceProvider.GetService<IHubContext<ChatHub>>(); this._systemService = systemService; this._chatService = chatService; } /// <summary> /// 获取全部聊天用户 /// </summary> /// <returns></returns> [HttpPost] public async Task<List<ChatUserViewModel>> GetChatUserAsync([FromBody]List<long> chattinguserids) { //获取用户 var allUsers = await _systemService.GetAllUserAsync(); List<ChatUserViewModel> chatUsers = allUsers.Select(m => new ChatUserViewModel { UserId = m.UserId, UserName = m.UserName, HeadImg = m.HeadImg, CreateTime = m.CreateTime, IsChatting = 0, IsOnline = 0 }).ToList(); var userids = SignalRMessageGroups.UserGroups.Where(m => m.GroupName == "ChatHubGroup").Select(m => m.UserId); foreach (var item in chatUsers) { if (userids.Contains(item.UserId)) { item.IsOnline = 1; } if (chattinguserids.HasItems() && chattinguserids.Contains(item.UserId)) { item.IsChatting = 1; } } return chatUsers.OrderByDescending(m => m.IsChatting).ThenByDescending(m => m.IsOnline).ThenBy(m => m.CreateTime).ToList(); ; } [HttpGet] public async Task<List<ChatUserListDto>> GetChatListAsync([FromQuery]ChatUserListSearchDto model) { return await _chatService.GetChatListAsync(model); } }
页面代码(css、js代码较多)
@{ ViewData["Title"] = "聊天"; Layout = "~/Views/Shared/_LayoutJQ.cshtml"; UserIdentity user = ViewBag.User; } @inject MsSystem.Web.Infrastructure.TokenClient tokenClient @inject Microsoft.Extensions.Configuration.IConfiguration configuration @section css{ <style> body { background:#fff !important; } .ibox-content{ border:none !important; } .chat-main { display: inline-block; background: #eee; border-radius: 3px; } .chat-main-left { width: 200px; float: left; overflow-y: auto; display: none; border: 1px solid #ddd; box-shadow: 0px 0px 10px #ddd; border-right: none; } .chat-main-right { width: 600px; float: left; height: 550px; border: 1px solid #ddd; box-shadow: 0px 0px 10px #ddd; border-radius: 3px; } .chat-users{ margin-left:0px; height:550px; } .chat-user { cursor: pointer; margin: 10px; border-radius: 3px; } .chat-user.active { background: #fff; border-radius: 4px; } .currentUser { background: #eee; font-size: 12px; text-align: left; line-height: 60px; height: 60px; color: #333; font-weight: bold; opacity: .8; } .currentUser img{ width:45px; height:45px; margin-left:10px; } .message-count { background: red; color: #fff; width: 15px; height: 15px; text-align: center; border-radius: 50%; display: none; margin: 2px; line-height: 15px; } .chatim { position: absolute; right: 0px; background: #fff; width: 260px; border-radius: 3px; bottom: 0px; } .chatim.active { box-shadow: 0px 0px 10px #eee; border: 1px solid #eee; } .chatim-title { height: 55px; line-height: 55px; background: #eee; padding-left: 10px; } .chatim-body { height: 450px; overflow-y: auto; } .chatim-userbox { margin: 10px; } .chatim-userbox-category{ cursor:pointer; } .chatim-userbox-online a{ color:#333; } .chatim-userbox-notonline a{ color:#ddd; } .chatim ul { list-style: none; margin: 0px; padding: 0px; } .chatim ul li{ cursor:pointer; margin:10px; } .chat-user:not(.active):hover { background: #fff; } .chat-user a{ margin-left:10px; } .chat-user-name { text-align: left; } .chat-user-name .chat-close { width: 16px; height: 16px; line-height: 16px; border-radius: 50%; background: #ddd; color: #fff; font-size: 12px; margin-left: 30px; position: relative; text-align: center; float: right; display:none; } .chat-user-name:hover .chat-close { display: inline-block; background: #FF0000; } .chatim-user img { width: 35px; height: 35px; } .message-input{ border-right:none; border-left:none; } #sendMessage { position: relative; top: -35px; float: right; border-radius: 0px; width: 70px; } .chatim-title-username{ font-size:16px; font-weight:bold; margin:0px 10px; } .chatim-title img{ width:40px; height:40px; margin-top:-3px; } .chatim-user span{ margin:0px 10px; } .chatim-userbox-category span { width: 12px; display: inline-block; font-size: 14px; } .chatim-footer{ text-align:center; padding-bottom:10px; } .chatim-footer a { font-size: 18px; color: #fff; cursor: pointer; width: 38px; height: 38px; display: inline-block; line-height: 38px; border-radius: 50%; background: #0e9aef; box-shadow: 0px 0px 5px #0e9aef; text-align: center; } .more-msg { font-size: 12px; color: #4ea9e9; } .more-msg:hover{ text-decoration:underline; color: #4ea9e9; } </style> } @section scripts{ <script src="/lib/signalr/dist/browser/signalr.min.js"></script> <script> $(function () { var chattinguserids = []; var currentUserid = "@user.UserId"; const connection = new signalR.HubConnectionBuilder() .withUrl("http://localhost:5000/hub/oa/chatHub", { transport: signalR.HttpTransportType.LongPolling, accessTokenFactory: () => { return "Authorization", getToken(); } }).build(); connection.start().then(function () { connection.invoke('InitMessage', currentUserid); }).catch(function (err) { return console.error(err.toString()); }); //获取聊天信息 connection.on("ReceiveChater", function (data) { //判断是否已打开 var _target = $('.users-list .chat-user[data-userid=' + data.sender + ']'); var userlength = $('.users-list .chat-user').length; if (_target[0] == undefined) { var issactive = userlength == 0 ? true : false; var username = $('.chatim-body a[data-userid=' + data.sender + ']').text(); var img = $('.chatim-body a[data-userid=' + data.sender + ']').parent().prev().attr('src'); addLeftUser(data.sender, username, img, issactive); resetDiscussionTitle(data.sender, username, img); } var chatuser = $('.users-list .chat-user[data-userid=' + data.sender + ']'); var msgcount = chatuser.find('.message-count').text(); chatuser.find('.message-count').show(); if (msgcount == '') { chatuser.find('.message-count').text(1); } else { chatuser.find('.message-count').text(parseInt(msgcount) + 1); } var headimg = chatuser.find('img').attr('src'); var username = chatuser.find('.chat-user-name a').text(); var html = ''; html += '<div class="chat-message chat-left">'; html += '<img class="message-avatar" src="' + headimg +'" alt="">'; html += '<div class="message">'; html += '<div><a class="message-author">' + username +'</a>'; html += '<span class="message-date">' + data.time+'</span></div>'; html += '<span class="message-content">'; html += data.message; html += '</span>'; html += '</div>'; html += '</div>'; addOrShowDiscussion(data.sender, false); var relBox = $('.chat-discussion[data-touser=' + data.sender + ']'); relBox.append(html); srcollBottom(relBox); }); //获取在线人列表 connection.on("RefreshOnliner", data => { axios.post('/OA/Chat/GetChatUserAsync', chattinguserids).then(function (response) { var data = response.data; $('.chatim-userbox ul').empty(); $.each(data, function (index, item) { var html = '<li>'; html += '<div class="chatim-user">'; html += '<img src="' + item.HeadImg+'" />'; html += '<span><a data-userid="' + item.UserId + '">' + item.UserName + '</a></span>'; html += '</div>'; html += '</li>'; if (item.IsOnline == 1) { $('.chatim-userbox-online ul').append(html); } else { $('.chatim-userbox-notonline ul').append(html); } }); }); }); function getToken() { return '@tokenClient.GetToken().Result'; } document.getElementById('sendMessage').addEventListener('click', function (e) { e.preventDefault(); sendMessage(); }); $('textarea[name=message]').on('keydown', function (e) { if (e.keyCode != 13) { return; } else { event.preventDefault(); e.returnValue = false; sendMessage(); } }); $('.users-list').on('click', '.chat-user', function () { $('.users-list .chat-user').removeClass('active'); $(this).addClass('active'); var currentDom = $(this).find('.chat-user-name a'); var userid = currentDom.attr('data-userid'); var imgsrc = $(this).find('img').attr('src'); resetDiscussionTitle(userid, currentDom.text(), imgsrc); addOrShowDiscussion(userid, true); resetUserMessageCount(userid); addOnlines(userid); $('.chat-message-form').show(); }); function sendMessage() { var userid = $('#ToUserId').val(); var message = $('textarea[name=message]').val(); if (message == '' || message.trim() == '') { return; } connection.invoke('SendMessage', userid, currentUserid, message); $('textarea[name=message]').val(''); var time = $.getCurrentTime(); var html = ''; html += '<div class="chat-message chat-right">'; html += '<img class="message-avatar" src="@user.HeadImg" alt="">'; html += '<div class="message">'; html += '<div><a class="message-author">@user.UserName</a>'; html += '<span class="message-date">' + time + '</span></div>'; html += '<span class="message-content">'; html += message; html += '</span>'; html += '</div>'; html += '</div>'; var chatdis = $('.chat-discussion[data-touser=' + userid + ']'); chatdis.append(html); resetUserMessageCount(userid); srcollBottom(chatdis); } function srcollBottom(container) { var scrollToContainer = container.find('.chat-message :last'); container.animate({ scrollTop: scrollToContainer.offset().top - container.offset().top + container.scrollTop() }, 800); } function resetUserMessageCount(userid) { var chatuser = $('.users-list .chat-user[data-userid=' + userid + ']'); chatuser.find('.message-count').text('').hide(); } function addOnlines(userid) { var flag = false; for (var i = 0; i < chattinguserids.length; i++) { if (chattinguserids[i] == userid) { flag = true; } } if (!flag) { chattinguserids.push(userid); } } function showChatBox(userid) { var length = $('.users-list .chat-user').length; if (length <= 1) { $('.chat-main-left').show(); } addOrShowDiscussion(userid, true); } function addOrShowDiscussion(userid, toTop) { var disc = $('.chat-discussion[data-touser=' + userid + ']'); if (disc.length == 1) { $('.chat-discussion').hide(); disc.show(); } else { var html = ''; if ($('.chat-discussion').length == 1 || toTop == true) { $('.chat-discussion').hide(); html = '<div class="chat-discussion" data-pageindex="0" data-touser="' + userid + '"><a class="more-msg"><i class="fa fa-clock-o"></i>查看更多消息</a></div>'; } else { html = '<div class="chat-discussion" style="display:none" data-pageindex="0" data-touser="' + userid + '"><a class="more-msg"><i class="fa fa-clock-o"></i>查看更多消息</a></div>'; } $('.chat-discussion:last').after(html) } $('.chat-message-form').show(); } function resetDiscussionTitle(userid, username,imgsrc) { $('#ToUserId').val(userid); $('.currentUser span').text(username); $('.currentUser img').attr('src', imgsrc); } function addLeftUser(userid, username, img,isactive) { var html = ''; if (isactive) { $('.chat-user').removeClass('active'); html += '<div class="chat-user active" data-userid="' + userid + '">'; } else { html += '<div class="chat-user" data-userid="' + userid + '">'; } html += '<span class="pull-right message-count"></span>'; html += '<img class="chat-avatar" src="' + img + '" alt="">'; html += '<div class="chat-user-name"><a data-userid="' + userid + '">' + username + '</a><span class="chat-close"><i class="fa fa-close"></i></span>'; html += '</div>'; html += '</div>'; $('.users-list').append(html); $('.chat-main-left').show(); } $('.chatim-userbox-category').on('click', function () { var type = $(this).attr('data-type'); if (type == 1) { $(this).attr('data-type', 0); $(this).find('span i').attr('class', 'fa fa-angle-right'); $(this).next().hide(); } else { $(this).attr('data-type', 1); $(this).find('span i').attr('class', 'fa fa-angle-down'); $(this).next().show(); } }); $('.chatim-userbox-online ul').on('dblclick', 'li', function () { var userid = $(this).find('a').attr('data-userid'); var username = $(this).find('a').text(); var _target = $('.users-list .chat-user[data-userid=' + userid + ']'); if (_target[0] == undefined) { var img = $(this).find('img').attr('src'); addLeftUser(userid, username, img, true); showChatBox(userid); } else { $('.users-list .chat-user').removeClass('active'); _target.addClass('active'); addOrShowDiscussion(userid, true); } var imgsrc = $(this).find('img').attr('src'); resetDiscussionTitle(userid, username, imgsrc); }); $('.users-list').on('click', '.chat-close', function (e) { e.stopPropagation(); var currentUser = $(this).parent().parent(); //当前会话只有一个情况 var uleg = $('.users-list .chat-user').length; if (uleg == 1) { currentUser.remove(); $('.chat-discussion').hide(); $('.chat-discussion:first').show(); resetDiscussionTitle('', '','/uploadfile/342bd59b-edf4-48cf-aa27-d13e5a0b70df.jpeg'); $('.chat-main-left,.chat-message-form').hide(); } else { var prevUser = currentUser.prev(); if (prevUser.length == 1) { var userid = prevUser.find('a').attr('data-userid'); var username = prevUser.find('a').text(); if (currentUser.hasClass('active')) { prevUser.addClass('active'); } addOrShowDiscussion(userid); var imgsrc = prevUser.find('img').attr('src'); resetDiscussionTitle(userid, username, imgsrc) } else { var nextUser = currentUser.next(); if (nextUser.length == 1) { var nextUserId = nextUser.find('a').attr('data-userid'); var nextUsername = nextUser.find('a').text(); if (currentUser.hasClass('active')) { nextUser.addClass('active'); } addOrShowDiscussion(nextUserId); var imgsrc = nextUser.find('img').attr('src'); resetDiscussionTitle(nextUserId, nextUsername, imgsrc) } } currentUser.remove(); } }); $('.chatim-footer').on('click', 'a', function () { var type = $(this).attr('data-type'); if (type == 1) { $(this).attr('data-type', 0); $('.chatim-title,.chatim-body').fadeOut(); $(this).find('i').attr('class', 'fa fa-comments'); $('.chatim').removeClass('active'); } else { $(this).attr('data-type', 1); $('.chatim-title,.chatim-body').fadeIn(); $(this).find('i').attr('class', 'fa fa-close'); $('.chatim').addClass('active'); } }); $('.chat-main-right').on('click', 'a.more-msg', function () { var _this = $(this); var pageindex = _this.parent().attr('data-pageindex'); var receiver = _this.parent().attr('data-touser'); var url = '/OA/Chat/GetChatListAsync?PageIndex=' + pageindex + '&Receiver=' + receiver + ''; axios.get(url).then(function (response) { var data = response.data; if (data.length > 0) { var ToUserId = $('#ToUserId').val(); var currentDis = $('.chat-discussion[data-touser=' + ToUserId + ']'); currentDis.find('a.more-msg').hide(); var html = '<a class="more-msg"><i class="fa fa-clock-o"></i>查看更多消息</a>'; $.each(data, function (index, item) { var headimg = ''; var username = ''; if (item.Sender == '@user.UserId') { headimg = '@user.HeadImg'; username = '@user.UserName'; html += '<div class="chat-message chat-right">'; } else { headimg = $('.currentUser img').attr('src'); username = $('.currentUser span').text(); html += '<div class="chat-message chat-left">'; } var time = $.unixToDate(item.CreateTime, true); html += '<img class="message-avatar" src="' + headimg + '" alt="">'; html += '<div class="message">'; html += '<div><a class="message-author">' + username + '</a>'; html += '<span class="message-date">' + time + '</span></div>'; html += '<span class="message-content">'; html += item.Message; html += '</span>'; html += '</div>'; html += '</div>'; }); var addpageindex = pageindex == 0 ? 1 : pageindex; if (addpageindex == 1) { currentDis.empty(); } currentDis.prepend(html); currentDis.attr('data-pageindex', parseInt(addpageindex) + 1); } else { _this.hide(); } }); }); }); </script> } <div class="wrapper-content"> <div class="row"> <div class="ibox"> <div class="ibox-content"> <div class="row" style="margin:0px auto;text-align: center;"> <div class="chat-main"> <div class="chat-main-left"> <div class="chat-users"> <div class="users-list"></div> </div> </div> <div class="chat-main-right"> <div class="currentUser"> <img src="/uploadfile/342bd59b-edf4-48cf-aa27-d13e5a0b70df.jpeg" /> <span></span> <input type="hidden" id="ToUserId" value="" /> </div> <div class="chat-discussion" data-touser=""> <h3> WEB 在线聊天系统 </h3> </div> <div class="chat-message-form" style="display:none"> <div class="form-group"> <textarea class="form-control message-input" name="message"></textarea> </div> <button class="btn btn-primary" id="sendMessage">发送</button> </div> </div> <div class="clear"></div> </div> </div> <div class="chatim active"> <div class="chatim-title"> <img src="@user.HeadImg" /> <span class="chatim-title-username"> @user.UserName </span> <span class="green">在线</span> </div> <div class="chatim-body"> <div class="chatim-userbox chatim-userbox-online"> <div class="chatim-userbox-category" data-type="1"> <span><i class="fa fa-angle-down"></i></span>在线用户 </div> <ul></ul> </div> <div class="chatim-userbox chatim-userbox-notonline"> <div class="chatim-userbox-category" data-type="0"> <span><i class="fa fa-angle-right"></i></span>未在线用户 </div> <ul style="display:none"></ul> </div> </div> <div class="chatim-footer"> <a data-type="1"><i class="fa fa-close"></i></a> </div> </div> </div> </div> </div> </div>
数据库设计,就一张表
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for oa_chat -- ---------------------------- DROP TABLE IF EXISTS `oa_chat`; CREATE TABLE `oa_chat` ( `Id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `Sender` bigint(20) NOT NULL COMMENT '发送方', `Message` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '消息', `Receiver` bigint(20) NOT NULL COMMENT '接收方', `CreateTime` bigint(20) NOT NULL COMMENT '创建时间', PRIMARY KEY (`Id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 156 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
效果如下:
双击在线用户发送并发送信息
用户接收到消息
部分代码地址:
也可以clone 下项目运行查看效果,docker功能已完成,可直接运行
作者:王家大人
出处:http://wms01.cnblogs.com
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。