-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(IConnectionService): add IConnectionService (#3121)
* refactor: 增加指纹方法 * refactor: 更新浏览器指纹组件 * refactor: 更新 WebClientService 脚本 * feat: 增加连接服务 * feat: 增加 CollectionHub 组件 * refactor: 集成连接器组件 * test: 增加单元测试 * refactor: 更新脚本 * refactor: 增加扫描周期逻辑 * feat: 增加 ConfigureCollectionHubOptions 方法 * doc: 更新 Console 文档 * refactor: 重构缓存类型 * feat: 重构 CollectionHubOptions 配置类 * test: 更新单元测试 * test: 更新单元测试 * test: 更新单元测试 * test: 更新单元测试 * test: 更新单元测试
- Loading branch information
Showing
15 changed files
with
321 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/BootstrapBlazor/Components/ConnectionHub/CollectionItem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
// Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
||
namespace BootstrapBlazor.Components; | ||
|
||
/// <summary> | ||
/// 连接对象实体类 | ||
/// </summary> | ||
public class CollectionItem | ||
{ | ||
/// <summary> | ||
/// 获得/设置 连接 Id | ||
/// </summary> | ||
[NotNull] | ||
public string? Id { get; internal set; } | ||
|
||
/// <summary> | ||
/// 获得/设置 连接 Ip 地址 | ||
/// </summary> | ||
public ClientInfo? ClientInfo { get; set; } | ||
|
||
/// <summary> | ||
/// 获得/设置 开始连接时间 | ||
/// </summary> | ||
public DateTimeOffset ConnectionTime { get; internal set; } | ||
|
||
/// <summary> | ||
/// 获得/设置 上次心跳时间 | ||
/// </summary> | ||
public DateTimeOffset LastBeatTime { get; internal set; } | ||
} |
37 changes: 37 additions & 0 deletions
37
src/BootstrapBlazor/Components/ConnectionHub/ConnectionHub.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
// Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
||
namespace BootstrapBlazor.Components; | ||
|
||
/// <summary> | ||
/// 客户端链接组件 | ||
/// </summary> | ||
[BootstrapModuleAutoLoader(ModuleName = "hub", JSObjectReference = true, AutoInvokeInit = true, AutoInvokeDispose = false)] | ||
public class ConnectionHub : BootstrapModuleComponentBase | ||
{ | ||
[Inject] | ||
[NotNull] | ||
private IConnectionService? ConnectionService { get; set; } | ||
|
||
/// <summary> | ||
/// <inheritdoc/> | ||
/// </summary> | ||
/// <returns></returns> | ||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", new { Invoke = Interop, Method = nameof(Callback) }); | ||
|
||
/// <summary> | ||
/// JSInvoke 回调方法 | ||
/// </summary> | ||
/// <param name="client"></param> | ||
/// <returns></returns> | ||
[JSInvokable] | ||
public Task Callback(ClientInfo client) | ||
{ | ||
if (!string.IsNullOrEmpty(client.Id)) | ||
{ | ||
ConnectionService.AddOrUpdate(client); | ||
} | ||
return Task.CompletedTask; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
// Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
||
namespace BootstrapBlazor.Components; | ||
|
||
/// <summary> | ||
/// CollectionHub 配置类 | ||
/// </summary> | ||
public class CollectionHubOptions | ||
{ | ||
/// <summary> | ||
/// 获得/设置 过期扫描周期 默认 30秒 | ||
/// </summary> | ||
public TimeSpan ExpirationScanFrequency { get; set; } = TimeSpan.FromSeconds(30); | ||
|
||
/// <summary> | ||
/// 获得/设置 ConnectionHub 组件心跳间隔 | ||
/// </summary> | ||
public int BeatInterval { get; set; } = 5000; | ||
} |
103 changes: 103 additions & 0 deletions
103
src/BootstrapBlazor/Services/DefaultConnectionService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
// Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
||
using System.Collections.Concurrent; | ||
|
||
namespace BootstrapBlazor.Components; | ||
|
||
/// <summary> | ||
/// 当前链接服务 | ||
/// </summary> | ||
class DefaultConnectionService : IConnectionService, IDisposable | ||
{ | ||
private readonly ConcurrentDictionary<string, CollectionItem> _connectionCache = new(); | ||
|
||
private readonly CollectionHubOptions _options = default!; | ||
|
||
private readonly CancellationTokenSource _cancellationTokenSource = new(); | ||
|
||
public DefaultConnectionService(IOptions<BootstrapBlazorOptions> options) | ||
{ | ||
_options = options.Value.CollectionHubOptions ?? new CollectionHubOptions(); | ||
|
||
Task.Run(() => | ||
{ | ||
while (!_cancellationTokenSource.IsCancellationRequested) | ||
{ | ||
try | ||
{ | ||
Task.Delay(_options.ExpirationScanFrequency, _cancellationTokenSource.Token); | ||
|
||
var keys = _connectionCache.Values.Where(i => i.LastBeatTime.AddMilliseconds(_options.BeatInterval) < DateTimeOffset.Now).Select(i => i.Id).ToList(); | ||
keys.ForEach(i => _connectionCache.TryRemove(i, out _)); | ||
} | ||
catch { } | ||
} | ||
}); | ||
} | ||
|
||
/// <summary> | ||
/// <inheritdoc/> | ||
/// </summary> | ||
public long Count => _connectionCache.Values.LongCount(i => i.LastBeatTime.AddMilliseconds(_options.BeatInterval) > DateTimeOffset.Now); | ||
|
||
/// <summary> | ||
/// <inheritdoc/> | ||
/// </summary> | ||
/// <param name="client"></param> | ||
public void AddOrUpdate(ClientInfo client) | ||
{ | ||
if (!string.IsNullOrEmpty(client.Id)) | ||
{ | ||
_connectionCache.AddOrUpdate(client.Id, key => CreateItem(key, client), (k, v) => UpdateItem(v, client)); | ||
} | ||
} | ||
|
||
private static CollectionItem CreateItem(string key, ClientInfo client) | ||
{ | ||
return new CollectionItem() | ||
{ | ||
Id = key, | ||
ConnectionTime = DateTimeOffset.Now, | ||
LastBeatTime = DateTimeOffset.Now, | ||
ClientInfo = client | ||
}; | ||
} | ||
|
||
private static CollectionItem UpdateItem(CollectionItem item, ClientInfo val) | ||
{ | ||
item.LastBeatTime = DateTimeOffset.Now; | ||
item.ClientInfo = val; | ||
return item; | ||
} | ||
|
||
/// <summary> | ||
/// <inheritdoc/> | ||
/// </summary> | ||
/// <param name="key"></param> | ||
/// <param name="value"></param> | ||
/// <returns></returns> | ||
public bool TryGetValue(string key, [MaybeNullWhen(false)] out CollectionItem? value) => _connectionCache.TryGetValue(key, out value); | ||
|
||
private void Dispose(bool disposing) | ||
{ | ||
if (disposing) | ||
{ | ||
if (!_cancellationTokenSource.IsCancellationRequested) | ||
{ | ||
_cancellationTokenSource.Cancel(); | ||
_cancellationTokenSource.Dispose(); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// <inheritdoc/> | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
// Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
||
namespace BootstrapBlazor.Components; | ||
|
||
/// <summary> | ||
/// 当前链接服务 | ||
/// </summary> | ||
public interface IConnectionService | ||
{ | ||
/// <summary> | ||
/// 增加或更新当前 Key | ||
/// </summary> | ||
/// <param name="client">ClientInfo 实例</param> | ||
void AddOrUpdate(ClientInfo client); | ||
|
||
/// <summary> | ||
/// 获得指定 key 的连接信息 | ||
/// </summary> | ||
/// <param name="key">键值</param> | ||
/// <param name="value">连接信息</param> | ||
/// <returns></returns> | ||
bool TryGetValue(string key, out CollectionItem? value); | ||
|
||
/// <summary> | ||
/// 获得在线连接数 | ||
/// </summary> | ||
long Count { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import "./browser.js?v=$version" | ||
import { execute } from "./ajax.js?v=$version" | ||
import { getFingerCode } from "./utility.js?v=$version" | ||
|
||
export async function init(options) { | ||
const { invoke, method, interval = 3000 } = options; | ||
const info = browser() | ||
let data = { | ||
browser: info.browser + ' ' + info.version, | ||
device: info.device, | ||
language: info.language, | ||
engine: info.engine, | ||
userAgent: navigator.userAgent, | ||
os: info.system + ' ' + info.systemVersion | ||
} | ||
const result = await execute({ | ||
method: 'GET', | ||
url: './ip.axd' | ||
}); | ||
const code = getFingerCode(); | ||
data.id = code; | ||
data.ip = result.ip; | ||
|
||
setTimeout(() => { | ||
invoke.invokeMethodAsync(method, data); | ||
}, interval); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
// Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace UnitTest.Components; | ||
|
||
public class ConnectionHubTest : BootstrapBlazorTestBase | ||
{ | ||
[Fact] | ||
public async Task AddConnection_Ok() | ||
{ | ||
var mockData = new ClientInfo() | ||
{ | ||
Id = "test_id", | ||
Ip = "192.168.0.1", | ||
OS = "ios", | ||
Browser = "chrome", | ||
Device = WebClientDeviceType.Mobile, | ||
Language = "zh", | ||
Engine = "engine", | ||
UserAgent = "test_agent" | ||
}; | ||
|
||
var service = Context.Services.GetRequiredService<IConnectionService>(); | ||
var cut = Context.RenderComponent<ConnectionHub>(); | ||
await cut.InvokeAsync(() => | ||
{ | ||
cut.Instance.Callback(mockData); | ||
}); | ||
Assert.Equal(1, service.Count); | ||
|
||
// 触发 Beat 时间 | ||
await Task.Delay(10); | ||
await cut.InvokeAsync(() => | ||
{ | ||
cut.Instance.Callback(mockData); | ||
}); | ||
Assert.True(service.TryGetValue(mockData.Id, out var item)); | ||
Assert.NotNull(item?.ClientInfo); | ||
Assert.True(item?.ConnectionTime < DateTimeOffset.Now); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.