新增signalR连接

This commit is contained in:
不做码农
2022-02-27 21:11:46 +08:00
parent 2a84f7d322
commit 748b9065db
15 changed files with 259 additions and 61 deletions

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Infrastructure.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using ZR.Admin.WebApi.Filters;
using ZR.Model;
namespace ZR.Admin.WebApi.Hubs
{
[Verify]
public class MessageHub : Hub
{
//创建用户集合,用于存储所有链接的用户数据
private static readonly List<OnlineUsers> clientUsers = new();
#region
/// <summary>
/// 客户端连接的时候调用
/// </summary>
/// <returns></returns>
public override Task OnConnectedAsync()
{
//name 获取不到有待研究
var name = Context.User.Identity.Name;
var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId);
//判断用户是否存在,否则添加集合
if (!user)
{
clientUsers.Add(new OnlineUsers(Context.ConnectionId, Context.User.Identity.Name));
Console.WriteLine($"{DateTime.Now}{Context.User.Identity.Name},{Context.ConnectionId}连接服务端success当前已连接{clientUsers.Count}个");
}
Clients.All.SendAsync("onlineNum", clientUsers.Count);
return base.OnConnectedAsync();
}
/// <summary>
/// 连接终止时调用。
/// </summary>
/// <returns></returns>
public override Task OnDisconnectedAsync(Exception exception)
{
var user = clientUsers.Where(p => p.ConnnectionId == Context.ConnectionId).FirstOrDefault();
//判断用户是否存在,否则添加集合
if (user != null)
{
Console.WriteLine($"用户{user?.Name}离开了,当前已连接{clientUsers.Count}个");
clientUsers.Remove(user);
Clients.All.SendAsync("onlineNum", clientUsers.Count);
}
return base.OnDisconnectedAsync(exception);
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace ZR.Model
{
public class OnlineUsers
{
/// <summary>
/// 客户端连接Id
/// </summary>
public string ConnnectionId { get; set; }
/// <summary>
/// 用户id
/// </summary>
public int Userid { get; set; }
public string Name { get; set; }
public DateTime LoginTime { get; set; }
public OnlineUsers(string clientid, string name)
{
ConnnectionId = clientid;
Name = name;
LoginTime = DateTime.Now;
}
}
}

View File

@@ -15,8 +15,8 @@ using System.Threading.Tasks;
using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters; using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Framework; using ZR.Admin.WebApi.Framework;
using ZR.Admin.WebApi.Hubs;
using ZR.Admin.WebApi.Middleware; using ZR.Admin.WebApi.Middleware;
using ZR.Common.Cache;
namespace ZR.Admin.WebApi namespace ZR.Admin.WebApi
{ {
@@ -43,6 +43,14 @@ namespace ZR.Admin.WebApi
.AllowAnyMethod();//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E2B7BD> .AllowAnyMethod();//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E2B7BD>
}); });
}); });
//ע<><D7A2>SignalRʵʱͨѶ<CDA8><D1B6>Ĭ<EFBFBD><C4AC><EFBFBD><EFBFBD>json<6F><6E><EFBFBD><EFBFBD>
services.AddSignalR(options =>
{
//<2F>ͻ<EFBFBD><CDBB>˷<EFBFBD><CBB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>󵽷<EFBFBD><F3B5BDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EEB3A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><C4AC>30<33><EFBFBD>ij<EFBFBD>4<EFBFBD><34><EFBFBD>ӣ<EFBFBD><D3A3><EFBFBD>ҳ<EFBFBD><D2B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>connection.keepAliveIntervalInMilliseconds = 12e4;<3B><>2<EFBFBD><32><EFBFBD><EFBFBD>
//options.ClientTimeoutInterval = TimeSpan.FromMinutes(4);
//<2F><><EFBFBD><EFBFBD><EFBFBD>˷<EFBFBD><CBB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>󵽿ͻ<F3B5BDBF><CDBB>˼<EFBFBD><CBBC><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><C4AC>15<31><EFBFBD>ij<EFBFBD>2<EFBFBD><32><EFBFBD>ӣ<EFBFBD><D3A3><EFBFBD>ҳ<EFBFBD><D2B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>connection.serverTimeoutInMilliseconds = 24e4;<3B><>4<EFBFBD><34><EFBFBD><EFBFBD>
//options.KeepAliveInterval = TimeSpan.FromMinutes(2);
});
//<2F><><EFBFBD><EFBFBD>Error unprotecting the session cookie<69><65><EFBFBD><EFBFBD> //<2F><><EFBFBD><EFBFBD>Error unprotecting the session cookie<69><65><EFBFBD><EFBFBD>
services.AddDataProtection() services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection")); .PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection"));
@@ -96,7 +104,7 @@ namespace ZR.Admin.WebApi
}); });
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD>̬<EFBFBD>ļ<EFBFBD>/wwwrootĿ¼<C4BF>ļ<EFBFBD><C4BC><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>UseRoutingǰ<67><C7B0> //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD>̬<EFBFBD>ļ<EFBFBD>/wwwrootĿ¼<C4BF>ļ<EFBFBD><C4BC><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>UseRoutingǰ<67><C7B0>
app.UseStaticFiles(); app.UseStaticFiles();
//<2F><><EFBFBD><EFBFBD>·<EFBFBD>ɷ<EFBFBD><C9B7><EFBFBD>
app.UseRouting(); app.UseRouting();
app.UseCors("Policy");//Ҫ<><D2AA><EFBFBD><EFBFBD>app.UseEndpointsǰ<73><C7B0> app.UseCors("Policy");//Ҫ<><D2AA><EFBFBD><EFBFBD>app.UseEndpointsǰ<73><C7B0>
@@ -107,12 +115,13 @@ namespace ZR.Admin.WebApi
app.UseAuthentication(); app.UseAuthentication();
//2.<2E>ٿ<EFBFBD><D9BF><EFBFBD><EFBFBD><EFBFBD>Ȩ //2.<2E>ٿ<EFBFBD><D9BF><EFBFBD><EFBFBD><EFBFBD>Ȩ
app.UseAuthorization(); app.UseAuthorization();
//<2F><><EFBFBD><EFBFBD>session
app.UseSession(); app.UseSession();
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
app.UseResponseCaching(); app.UseResponseCaching();
//<2F>ָ<EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// <20>ָ<EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
app.UseAddTaskSchedulers(); app.UseAddTaskSchedulers();
//ʹ<><CAB9>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD>м<EFBFBD><D0BC><EFBFBD>
app.UseMiddleware<GlobalExceptionMiddleware>(); app.UseMiddleware<GlobalExceptionMiddleware>();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
@@ -120,6 +129,9 @@ namespace ZR.Admin.WebApi
endpoints.MapControllerRoute( endpoints.MapControllerRoute(
name: "default", name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"); pattern: "{controller=Home}/{action=Index}/{id?}");
//<2F><><EFBFBD><EFBFBD>socket<65><74><EFBFBD><EFBFBD>
endpoints.MapHub<MessageHub>("/msgHub");
}); });
} }

View File

@@ -7,6 +7,9 @@ VUE_APP_TITLE = 'ZrAdmin.NET后台管理'
# 开发环境 # 开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = '/dev-api'
#socket API
VUE_APP_SOCKET_API = '/msgHub'
# 路由前缀 # 路由前缀
VUE_APP_ROUTER_PREFIX = '/' VUE_APP_ROUTER_PREFIX = '/'

View File

@@ -7,8 +7,11 @@ VUE_APP_TITLE = 'ZrAdmin.NET后台管理'
# 生产环境 # 生产环境
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = '/prod-api'
#socket API
VUE_APP_SOCKET_API = '/msgHub'
# 路由前缀 # 路由前缀
VUE_APP_ROUTER_PREFIX = '/' VUE_APP_ROUTER_PREFIX = '/'
# 默认上传地址 # 默认上传地址
VUE_APP_UPLOAD_URL = '/Common/UploadFile' VUE_APP_UPLOAD_URL = '/Common/UploadFile'

View File

@@ -7,6 +7,9 @@ VUE_APP_TITLE = 'ZrAdmin.NET后台管理'
# 测试环境 # 测试环境
VUE_APP_BASE_API = '/stage-api' VUE_APP_BASE_API = '/stage-api'
#socket API
VUE_APP_SOCKET_API = '/msgHub'
# 路由前缀 # 路由前缀
VUE_APP_ROUTER_PREFIX = '/' VUE_APP_ROUTER_PREFIX = '/'

View File

@@ -19,6 +19,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@microsoft/signalr": "^6.0.2",
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",
"axios": "^0.21.4", "axios": "^0.21.4",
"clipboard": "2.0.8", "clipboard": "2.0.8",

View File

@@ -12,6 +12,7 @@ import store from './store'
import router from './router' import router from './router'
import permission from './directive/permission' import permission from './directive/permission'
import plugins from './plugins' // plugins import plugins from './plugins' // plugins
import signalR from '@/utils/signalR'
import './assets/icons' // icon import './assets/icons' // icon
import './permission' // permission control import './permission' // permission control
@@ -43,6 +44,9 @@ Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download Vue.prototype.download = download
Vue.prototype.handleTree = handleTree Vue.prototype.handleTree = handleTree
signalR.init(process.env.VUE_APP_SOCKET_API);
Vue.prototype.signalr = signalR
Vue.prototype.msgSuccess = function (msg) { Vue.prototype.msgSuccess = function (msg) {
this.$message({ showClose: true, message: msg, type: "success" }); this.$message({ showClose: true, message: msg, type: "success" });
} }

View File

@@ -1,20 +1,21 @@
const getters = { const getters = {
sidebar: state => state.app.sidebar, sidebar: state => state.app.sidebar,
size: state => state.app.size, size: state => state.app.size,
device: state => state.app.device, device: state => state.app.device,
visitedViews: state => state.tagsView.visitedViews, visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews, cachedViews: state => state.tagsView.cachedViews,
token: state => state.user.token, token: state => state.user.token,
avatar: state => state.user.avatar, avatar: state => state.user.avatar,
name: state => state.user.name, name: state => state.user.name,
userId: state => state.user.userInfo.userId, userId: state => state.user.userInfo.userId,
introduction: state => state.user.introduction, introduction: state => state.user.introduction,
roles: state => state.user.roles, roles: state => state.user.roles,
permissions: state => state.user.permissions, permissions: state => state.user.permissions,
permission_routes: state => state.permission.routes, permission_routes: state => state.permission.routes,
userinfo: state => state.user.userInfo, userinfo: state => state.user.userInfo,
topbarRouters: state => state.permission.topbarRouters, topbarRouters: state => state.permission.topbarRouters,
defaultRoutes: state => state.permission.defaultRoutes, defaultRoutes: state => state.permission.defaultRoutes,
sidebarRouters: state => state.permission.sidebarRouters, sidebarRouters: state => state.permission.sidebarRouters,
onlineUserNum: state => state.socket.onlineNum
} }
export default getters export default getters

View File

@@ -5,6 +5,7 @@ import user from './modules/user'
import tagsView from './modules/tagsView' import tagsView from './modules/tagsView'
import permission from './modules/permission' import permission from './modules/permission'
import settings from './modules/settings' import settings from './modules/settings'
import socket from './modules/socket'
import getters from './getters' import getters from './getters'
Vue.use(Vuex) Vue.use(Vuex)
@@ -21,7 +22,8 @@ const store = new Vuex.Store({
user, user,
tagsView, tagsView,
permission, permission,
settings settings,
socket
}, },
state: state,//这里放全局参数 state: state,//这里放全局参数
getters getters

View File

@@ -0,0 +1,22 @@
const state = {
onlineNum: 0
}
const mutations = {
SET_ONLINEUSER_NUM: (state, num) => {
state.onlineNum = num
},
}
const actions = {
//更新在线人数
changeOnlineNum({ commit }, data) {
commit('SET_ONLINEUSER_NUM', data)
},
}
export default {
namespaced: true,
state,
mutations,
actions
}

View File

@@ -0,0 +1,49 @@
// 官方文档https://docs.microsoft.com/zh-cn/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0&viewFallbackFrom=aspnetcore-2.2&tabs=visual-studio
import * as signalR from '@microsoft/signalr'
import store from '../store'
export default {
// signalR对象
SR: {},
// 失败连接重试次数
failNum: 4,
baseUrl: '',
init(url) {
const connection = new signalR.HubConnectionBuilder()
.withUrl(url)
.build();
// console.log('conn', connection);
this.SR = connection;
// 断线重连
connection.onclose(async () => {
await this.SR.start();
})
connection.on("onlineNum", (data) => {
store.dispatch("socket/changeOnlineNum", data);
});
// 启动
this.start();
},
async start() {
var that = this;
try {
//使用async和await 或 promise的then 和catch 处理来自服务端的异常
await this.SR.start();
//console.assert(this.SR.state === signalR.HubConnectionState.Connected);
console.log('signalR 连接成功了', this.SR.state);
} catch (error) {
that.failNum--;
console.log(`失败重试剩余次数${that.failNum}`, error)
if (that.failNum > 0) {
setTimeout(async () => {
await this.SR.start()
}, 5000);
}
}
}
}

View File

@@ -7,9 +7,9 @@
</div> </div>
<div class="card-panel-description"> <div class="card-panel-description">
<div class="card-panel-text"> <div class="card-panel-text">
访客 在线用户
</div> </div>
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" /> <count-to :start-val="0" :end-val="onlineUserNum" :duration="2600" class="card-panel-num" />
</div> </div>
</div> </div>
</el-col> </el-col>
@@ -56,18 +56,21 @@
</template> </template>
<script> <script>
import CountTo from 'vue-count-to' import CountTo from "vue-count-to";
import { mapGetters } from "vuex";
export default { export default {
components: { components: {
CountTo CountTo,
},
computed: {
...mapGetters(["onlineUserNum"]),
}, },
methods: { methods: {
handleSetLineChartData(type) { handleSetLineChartData(type) {
this.$emit('handleSetLineChartData', type) this.$emit("handleSetLineChartData", type);
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -86,8 +89,8 @@ export default {
overflow: hidden; overflow: hidden;
color: #666; color: #666;
background: #fff; background: #fff;
box-shadow: 4px 4px 40px rgba(0, 0, 0, .05); box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, .05); border-color: rgba(0, 0, 0, 0.05);
&:hover { &:hover {
.card-panel-icon-wrapper { .card-panel-icon-wrapper {
@@ -107,7 +110,7 @@ export default {
} }
.icon-shopping { .icon-shopping {
background: #34bfa3 background: #34bfa3;
} }
} }
@@ -124,7 +127,7 @@ export default {
} }
.icon-shopping { .icon-shopping {
color: #34bfa3 color: #34bfa3;
} }
.card-panel-icon-wrapper { .card-panel-icon-wrapper {
@@ -160,7 +163,7 @@ export default {
} }
} }
@media (max-width:550px) { @media (max-width: 550px) {
.card-panel-description { .card-panel-description {
display: none; display: none;
} }

View File

@@ -25,56 +25,54 @@
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script>
import PanelGroup from './dashboard/PanelGroup' import PanelGroup from "./dashboard/PanelGroup";
import LineChart from './dashboard/LineChart' import LineChart from "./dashboard/LineChart";
import RaddarChart from './dashboard/RaddarChart' import RaddarChart from "./dashboard/RaddarChart";
import PieChart from './dashboard/PieChart' import PieChart from "./dashboard/PieChart";
import BarChart from './dashboard/BarChart' import BarChart from "./dashboard/BarChart";
const lineChartData = { const lineChartData = {
newVisitis: { newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165], expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145] actualData: [120, 82, 91, 154, 162, 140, 145],
}, },
messages: { messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140], expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130] actualData: [180, 160, 151, 106, 145, 150, 130],
}, },
purchases: { purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100], expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130] actualData: [120, 90, 100, 138, 142, 130, 130],
}, },
shoppings: { shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160], expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130] actualData: [120, 82, 91, 154, 162, 140, 130],
} },
} };
export default { export default {
name: 'Index', name: "Index",
components: { components: {
PanelGroup, PanelGroup,
LineChart, LineChart,
RaddarChart, RaddarChart,
PieChart, PieChart,
BarChart BarChart,
}, },
data() { data() {
return { return {
lineChartData: lineChartData.newVisitis lineChartData: lineChartData.newVisitis,
} };
}, },
methods: { methods: {
handleSetLineChartData(type) { handleSetLineChartData(type) {
this.lineChartData = lineChartData[type] this.lineChartData = lineChartData[type];
} },
} },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -90,7 +88,7 @@ export default {
} }
} }
@media (max-width:1024px) { @media (max-width: 1024px) {
.chart-wrapper { .chart-wrapper {
padding: 8px; padding: 8px;
} }

View File

@@ -44,7 +44,15 @@ module.exports = {
pathRewrite: { pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '' //需要rewrite的 ['^' + process.env.VUE_APP_BASE_API]: '' //需要rewrite的
} }
} },
"msgHub": {
target: 'http://localhost:8888/msgHub',
ws: true,
changeOrigin: true,
pathRewrite: {
['^/msgHub']: '' //需要rewrite的
}
}
}, },
disableHostCheck: true disableHostCheck: true
}, },