从上一篇文章我们知道了怎么配置中心和处理跨域的问题,这篇文章主要学习怎么向指定客户端发送消息,模拟了1对1聊天
如何配置中心服务,请移至上一篇
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
namespace Common.SignalR
{
/// <summary>
///
/// </summary>
/// <param name="Id">连接ID</param>
/// <param name="User">用户名</param>
/// <param name="Message">消息</param>
public record TransData(string Id, string User, string Message);
public interface IChatClient
{
Task ReceiveMessage(TransData data);
}
/// <summary>
/// https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-6.0
/// </summary>
public class CustomHub : Hub<IChatClient>
{
//用户集
private readonly static Dictionary<string, string> _connections = new();
private readonly string systemid = "system";
private readonly string systemname = "system";
#region 发送消息
/// <summary>
/// 以个人名义向所有客户端发送消息
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendToAll(string message)
{
string cid = GetConnectionId();
await Clients.All.ReceiveMessage(new(cid, _connections[cid], message));
}
/// <summary>
/// 以系统名义向所有客户端发送消息
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendSysToAll(string message) => await Clients.All.ReceiveMessage(new(systemid, systemname, message));
/// <summary>
/// 发送消息给指定用户(个人)
/// </summary>
/// <param name="id"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendToOne(string id, string message)
{
string cid = GetConnectionId();
await Clients.Client(id).ReceiveMessage(new(cid, _connections[cid], message));
}
/// <summary>
/// 发送消息给指定用户(系统)
/// </summary>
/// <param name="id"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendSysToOne(string id, string message) => await Clients.Client(id).ReceiveMessage(new(systemid, systemname, message));
/// <summary>
/// 发送群组消息(个人)
/// </summary>
/// <param name="group"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendToGroup(string group, string message)
{
string cid = GetConnectionId();
await Clients.Group(group).ReceiveMessage(new(cid, _connections[cid], message));
}
/// <summary>
/// 发送群组消息(系统)
/// </summary>
/// <param name="group"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendSysToGroup(string group, string message) => await Clients.Group(group).ReceiveMessage(new(systemid, systemname, message));
#endregion
#region SignalR用户
/// <summary>
/// 获取连接的唯一 ID(由 SignalR 分配)。 每个连接都有一个连接 ID
/// </summary>
/// <returns></returns>
public string GetConnectionId()
{
return Context.ConnectionId;
}
#endregion
#region SignalR群组
/// <summary>
/// 主动加入群组
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
public async Task AddToGroup(string group)
{
string cid = GetConnectionId();
await Groups.AddToGroupAsync(cid, group);
await SendSysToGroup(group, $@"欢迎{_connections[cid]}加入");
}
/// <summary>
/// 被动加入群组
/// </summary>
/// <param name="group"></param>
/// <param name="id"></param>
/// <returns></returns>
public async Task AddToGrouped(string group, string id)
{
string cid = GetConnectionId();
await Groups.AddToGroupAsync(id, group);
await SendSysToGroup(group, $@"欢迎{_connections[cid]}加入");
}
#endregion
#region 临时用户操作
/// <summary>
/// 添加到在线用户集
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public async Task AddUser(string name)
{
string cid = GetConnectionId();
if (!_connections.ContainsKey(cid))
{
await Task.Run(() => _connections.Add(cid, name));
await SendSysToAll("relst");
}
}
/// <summary>
/// 获取在线用户
/// </summary>
/// <returns></returns>
public object GetUser()
{
string cid = GetConnectionId();
return _connections.Where(t => !t.Key.Equals(cid));
}
#endregion
#region 重写连接断开钩子
/// <summary>
/// 重写链接钩子
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
/// <summary>
/// 重写断开链接钩子
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public override async Task OnDisconnectedAsync(Exception? exception)
{
string cid = GetConnectionId();
_connections.Remove(cid);
await SendSysToAll("relst");
await base.OnDisconnectedAsync(exception);
}
#endregion
}
}
客户端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
html {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.box {
margin: 0 auto;
height: 600px;
width: 256px;
background-color: aqua;
}
.box>div {
background-color: antiquewhite;
padding: 10px;
border: 1px solid #aaaaff;
}
.box>div:hover {
background-color: beige;
}
.msg {
margin: 0 auto;
height: 400px;
width: 500px;
background-color: aqua;
overflow: hidden;
left: -20px;
}
.t {
max-height: 70%;
height: 70%;
width: 100%;
background-color: #ffaa00;
overflow-y: scroll;
}
.t>div {
margin: 6px;
font-size: 14px;
}
.b {
margin-top: 1px;
max-height: 30%;
height: 29%;
width: 100%;
background-color: #55aaff;
}
textarea {
width: 96%;
height: 94%;
}
.self {
text-align: right;
}
.name {
color: #1a38ff;
}
.hide {
display: none;
}
.myname {
margin: 0 auto;
}
.dian {
position: absolute;
color: white;
font-size: 17px;
background-color: red;
/*height: 24px;改前*/
min-height: 15px;
/*改后新增的代码*/
min-width: 15px;
/*改后新增的代码*/
line-height: 15px;
right: 40%;
text-align: center;
-webkit-border-radius: 15px;
border-radius: 15px;
padding: 2px;
}
</style>
</head>
<body>
<div id="app">
<div class="myname">{{user.name}}</div>
<div class="box" v-show="!showbox">
<div v-for="item in userlst" @dblclick="trun(item)">
<div v-if="item.state" class="dian"> {{item.state}} </div>
{{item.name}}
</div>
</div>
<div class="msg" v-show="showbox">
<div>
<button title="返回" @click="trun()">
< </button>
{{nowuser.name}}
</div>
<div class="t" id="t">
<div v-for="item in nowmsglst" :class="{'self':item.id==user.id}">
<div class="name" v-if="item.id==user.id">{{user.name}}:</div>
<div class="name" v-else>{{nowuser.name}}</div>
<div class="conent">{{item.msg}}</div>
</div>
</div>
<div class="b">
<textarea id="smg" @keyup.enter="sendmsg()" v-model="nowmsg"></textarea>
</div>
</div>
</div>
</div>
<script src="vue.js"></script>
<script src="signalr.min.js"></script>
<script src="common.js"></script>
<script>
var app = new Vue({
el: '#app',
data() {
return {
//链接
connection: null,
showbox: false,
nowmsg: '',
user: {
id: '1',
name: '111'
},
//用户列表
userlst: [],
//与用户的聊天记录
msglst: [{
id: '',
msg: [{
}]
}],
//当前聊天对象
nowuser: {
id: 0,
name: '123'
},
//当前聊天记录
nowmsglst: []
}
},
created: function() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://192.168.5.73:6123/chatHub")
.build();
//如果连接断开,尝试重新连接
this.connection.onclose(async () => {
await this.start();
});
// 开始连接.
this.start();
//ReceiveMessage 与后端对应 ,接收中心发来的消息
this.connection.on("ReceiveMessage", (res) => {
console.log(res, "收到消息");
switch (res.message) {
case 'relst': //中心通知有新用户加入
console.log("更新在线列表");
this.getUserLst();
break;
default: //默认接收消息处理
//得到当前发送者的消息集位置
let inx = this.msglst.findIndex(t => {
return t.id == res.id
});
//如果不存在,则添加进消息集
//否则在指定用户消息及添加消息
if (inx == -1) {
this.msglst.push({
id: res.id,
msg: [{
id: res.id,
msg: res.message
}]
})
} else {
this.msglst[inx].msg.push({
id: res.id,
msg: res.message
})
}
//消息加1
this.editState(res.id, true);
//如果为当前聊天用户,赋值并自动滑到底部
if (this.nowuser.id == res.id) {
nowmsglst.push({
id: res.id,
msg: res.message
})
this.editState(res.id, false);
this.scrollTop();
};
break;
}
});
},
methods: {
//连接方法,如果连接失败,5s后重新连接
start: async function() {
try {
await this.connection.start();
this.user.name =getName(); //得到当前用户名
this.user.id = this.getmyid(); //得到当前用户ID
this.login(); //主动加入到用户列表
console.log("连接成功.");
} catch (err) {
console.log(err);
setTimeout(this.start, 5000);
}
},
//跳转
trun: async function(d) {
if (d) {
this.nowuser = d;
this.editState(d.id, false);
}
this.showbox = !this.showbox;
let res = await this.msglst.findIndex(t => {
return t.id == this.nowuser.id
});
if (res >= 0) {
this.nowmsglst = this.msglst[res].msg;
} else {
this.nowmsglst = [{
id: '',
msg: ''
}];
}
},
//登入并获取用户列表
login: function() {
this.connection.invoke("AddUser", this.user.name);
},
//获取在线用户列表
getUserLst: function() {
this.connection.invoke("GetUser").then(res => {
console.log(res, "getuser");
this.createlst(res);
});
},
//获取个人ID
getmyid: function() {
this.connection.invoke("GetConnectionId").then(res => {
return res;
});
},
//发送消息
sendmsg: async function() {
this.connection.send("SendToOne", this.nowuser.id, this.nowmsg)
var obj = {
id: this.user.id,
msg: this.nowmsg
};
this.nowmsglst.push(obj);
let res = await this.msglst.findIndex(t => {
return t.id == this.nowuser.id
});
if (res == -1) {
this.msglst.push({
id: this.nowuser.id,
msg: this.nowmsglst
})
} else {
this.msglst[res].msg = this.nowmsglst
}
this.scrollTop();
this.nowmsg = '';
},
//重构上线用户列表
createlst: function(lst) {
this.userlst = [];
for (d in lst) {
this.userlst.push({
id: lst[d].key,
name: lst[d].value,
state: 0
})
}
},
//指定用户清空消息
editState: async function(cid, isadd) {
let inx = await this.userlst.findIndex(t => {
return t.id == cid
});
console.log(inx, 123)
if (isadd) {
this.userlst[inx].state = Number(this.userlst[inx].state) + 1;
} else {
this.userlst[inx].state = 0;
}
},
//重置滚动条
scrollTop: function() {
let t = document.getElementById("t");
t.scrollTop = t.scrollHeight;
}
}
});
</script>
</body>
</html>
common.js为随机用户名,内容如下
function getName() {
var familyNames = new Array(
"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈",
"褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许",
"何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏",
"陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章",
"云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦",
"昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳",
"酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺",
"倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常",
"乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余",
"元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹"
);
var givenNames = new Array(
"子璇", "淼", "国栋", "夫子", "瑞堂", "甜", "敏", "尚", "国贤", "贺祥", "晨涛",
"昊轩", "易轩", "益辰", "益帆", "益冉", "瑾春", "瑾昆", "春齐", "杨", "文昊",
"东东", "雄霖", "浩晨", "熙涵", "溶溶", "冰枫", "欣欣", "宜豪", "欣慧", "建政",
"美欣", "淑慧", "文轩", "文杰", "欣源", "忠林", "榕润", "欣汝", "慧嘉", "新建",
"建林", "亦菲", "林", "冰洁", "佳欣", "涵涵", "禹辰", "淳美", "泽惠", "伟洋",
"涵越", "润丽", "翔", "淑华", "晶莹", "凌晶", "苒溪", "雨涵", "嘉怡", "佳毅",
"子辰", "佳琪", "紫轩", "瑞辰", "昕蕊", "萌", "明远", "欣宜", "泽远", "欣怡",
"佳怡", "佳惠", "晨茜", "晨璐", "运昊", "汝鑫", "淑君", "晶滢", "润莎", "榕汕",
"佳钰", "佳玉", "晓庆", "一鸣", "语晨", "添池", "添昊", "雨泽", "雅晗", "雅涵",
"清妍", "诗悦", "嘉乐", "晨涵", "天赫", "?傲", "佳昊", "天昊", "萌萌", "若萌"
);
var i = parseInt(10 * Math.random()) * 10 + parseInt(10 * Math.random());
var familyName = familyNames[i];
var j = parseInt(10 * Math.random()) * 10 + parseInt(10 * Math.random());
var givenName = givenNames[i];
var name = familyName + givenName;
return name;
}
最终效果,多开几个客户端测试
去对应的 看是否收到消息
测试成功 ,有疑问可以留言询问