添加项目文件。

This commit is contained in:
2023-03-13 15:00:34 +08:00
parent 42bf06ca3e
commit 1d73df3235
1205 changed files with 185078 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
using JNPF.WebSockets;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Builder;
/// <summary>
/// WebSocket 中间件拓展.
/// </summary>
public static class WebSocketApplicationBuilderExtensions
{
/// <summary>
/// 映射 WebSocket 管理.
/// </summary>
/// <param name="app"></param>
/// <param name="path"></param>
/// <param name="handler"></param>
/// <returns></returns>
public static IApplicationBuilder MapWebSocketManager(
this IApplicationBuilder app,
PathString path,
WebSocketHandler handler)
{
return app.Map(path, (_app) => _app.UseMiddleware<WebSocketMiddleware>(handler));
}
}

View File

@@ -0,0 +1,25 @@
using JNPF.WebSockets;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// WebSocket服务集合拓展.
/// </summary>
public static class WebSocketServiceCollectionExtensions
{
public static IServiceCollection AddWebSocketManager(this IServiceCollection services)
{
services.AddTransient<WebSocketConnectionManager>();
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes().Where(t => t.BaseType == typeof(WebSocketHandler)))
.ToArray();
foreach (var type in types)
{
services.AddSingleton(type);
}
return services;
}
}

View File

@@ -0,0 +1,223 @@
using System.Net.WebSockets;
using System.Reflection;
using System.Text;
using System.Text.Json;
using JNPF.Extras.WebSockets.Models;
namespace JNPF.WebSockets;
/// <summary>
/// WebSocket 处理程序.
/// </summary>
public abstract class WebSocketHandler
{
/// <summary>
/// WebSocket 连接管理.
/// </summary>
protected WebSocketConnectionManager WebSocketConnectionManager { get; set; }
/// <summary>
/// 初始化一个<see cref="WebSocketHandler"/>类型的新实例.
/// </summary>
public WebSocketHandler(WebSocketConnectionManager webSocketConnectionManager)
{
WebSocketConnectionManager = webSocketConnectionManager;
}
/// <summary>
/// 连接.
/// </summary>
/// <param name="socket">socket.</param>
/// <returns></returns>
public virtual async Task OnConnected(WebSocketClient socket)
{
WebSocketConnectionManager.AddSocket(socket);
}
/// <summary>
/// 连接.
/// </summary>
/// <param name="socketId">连接ID.</param>
/// <param name="socket">socket.</param>
/// <returns></returns>
public virtual async Task OnConnected(string socketId, WebSocketClient socket)
{
WebSocketConnectionManager.AddSocket(socketId, socket);
}
/// <summary>
/// 断开连接.
/// </summary>
/// <param name="socket"></param>
/// <returns></returns>
public virtual async Task OnDisconnected(WebSocket socket)
{
string socketId = WebSocketConnectionManager.GetId(socket);
if (!string.IsNullOrWhiteSpace(socketId))
await WebSocketConnectionManager.RemoveSocket(socketId).ConfigureAwait(false);
}
/// <summary>
/// 发送消息给指定 id 的 socket.
/// </summary>
/// <param name="client">socket 客户端.</param>
/// <param name="message">消息.</param>
/// <returns></returns>
public async Task SendMessageAsync(WebSocketClient client, string message)
{
if (client.WebSocket.State != WebSocketState.Open)
return;
byte[] encodedMessage = Encoding.UTF8.GetBytes(message);
try
{
await client.WebSocket.SendAsync(
buffer: new ArraySegment<byte>(
array: encodedMessage,
offset: 0,
count: encodedMessage.Length),
messageType: WebSocketMessageType.Text,
endOfMessage: true,
cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
catch (WebSocketException e)
{
if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
{
await OnDisconnected(client.WebSocket);
}
}
}
/// <summary>
/// 给指定 socket 发送信息.
/// </summary>
/// <param name="socketId">连接ID.</param>
/// <param name="message">消息内容.</param>
/// <returns></returns>
public async Task SendMessageAsync(string socketId, string message)
{
var socket = WebSocketConnectionManager.GetSocketById(socketId);
if (socket != null)
await SendMessageAsync(socket, message).ConfigureAwait(false);
}
/// <summary>
/// 发送消息 全频道.
/// </summary>
/// <param name="message">消息内容.</param>
/// <returns></returns>
public async Task SendMessageToAllAsync(string message)
{
foreach (var pair in WebSocketConnectionManager.GetAll())
{
try
{
if (pair.Value.WebSocket.State == WebSocketState.Open)
await SendMessageAsync(pair.Value, message).ConfigureAwait(false);
}
catch (WebSocketException e)
{
if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
{
await OnDisconnected(pair.Value.WebSocket);
}
}
}
}
/// <summary>
/// 给指定用户组发送信息.
/// </summary>
/// <param name="userID">用户ID.</param>
/// <param name="message">消息内容.</param>
/// <returns></returns>
public async Task SendMessageToUserAsync(string userID, string message)
{
var sockets = WebSocketConnectionManager.GetAllFromUser(userID);
if (sockets != null)
{
foreach (var socket in sockets)
{
await SendMessageAsync(socket, message);
}
}
}
/// <summary>
/// 给指定租户组发送信息.
/// </summary>
/// <param name="tenantID">租户ID.</param>
/// <param name="message">消息内容.</param>
/// <returns></returns>
public async Task SendMessageToTenantAsync(string tenantID, string message)
{
var sockets = WebSocketConnectionManager.GetAllFromTenant(tenantID);
if (sockets != null)
{
foreach (var socket in sockets)
{
await SendMessageAsync(socket, message);
}
}
}
/// <summary>
/// 给指定租户组发送信息.
/// </summary>
/// <param name="tenantID">租户ID.</param>
/// <param name="message">消息内容.</param>
/// <param name="except">除了某个用户.</param>
/// <returns></returns>
public async Task SendMessageToTenantAsync(string tenantID, string message, string except)
{
var sockets = WebSocketConnectionManager.GetAllFromTenant(tenantID);
if (sockets != null)
{
foreach (var id in sockets)
{
if (id != except)
await SendMessageAsync(id, message);
}
}
}
/// <summary>
/// 获取租户组内全部用户ID.
/// </summary>
/// <param name="tenantID"></param>
/// <returns></returns>
public List<string> GetAllUserIdFromTenant(string tenantID)
{
List<string> connectionList = new List<string>();
foreach (var item in WebSocketConnectionManager.GetAllFromTenant(tenantID))
{
var client = WebSocketConnectionManager.GetSocketById(item);
if (client != null && !connectionList.Any(it => it.Equals(client.UserId)))
connectionList.Add(client.UserId);
}
return connectionList;
}
/// <summary>
/// 接收信息.
/// </summary>
/// <param name="client"></param>
/// <param name="result"></param>
/// <param name="receivedMessage"></param>
/// <returns></returns>
public virtual async Task ReceiveAsync(WebSocketClient client, WebSocketReceiveResult result, string receivedMessage)
{
try
{
await SendMessageAsync(client, receivedMessage).ConfigureAwait(false);
}
catch (TargetParameterCountException)
{
await SendMessageAsync(client, JsonSerializer.Serialize(new { method = "error", msg = $"does not take parameters!" })).ConfigureAwait(false);
}
catch (ArgumentException)
{
await SendMessageAsync(client, JsonSerializer.Serialize(new { method = "error", msg = $"takes different arguments!" })).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,215 @@
using System.Collections.Concurrent;
using System.Net.WebSockets;
using JNPF.Extras.WebSockets.Models;
namespace JNPF.WebSockets;
/// <summary>
/// WebSocket 连接管理.
/// </summary>
public class WebSocketConnectionManager
{
/// <summary>
/// 全部 socket 池.
/// </summary>
private ConcurrentDictionary<string, WebSocketClient> _sockets = new ConcurrentDictionary<string, WebSocketClient>();
/// <summary>
/// 用户组 socket 池.
/// </summary>
private ConcurrentDictionary<string, List<string>> _users = new ConcurrentDictionary<string, List<string>>();
/// <summary>
/// 租户组 socket 池.
/// </summary>
private ConcurrentDictionary<string, List<string>> _tenant = new ConcurrentDictionary<string, List<string>>();
/// <summary>
/// 获取指定 id 的 socket.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public WebSocketClient GetSocketById(string id)
{
if (_sockets.TryGetValue(id, out WebSocketClient socket))
return socket;
else
return null;
}
/// <summary>
/// 获取全部socket.
/// </summary>
/// <returns></returns>
public ConcurrentDictionary<string, WebSocketClient> GetAll()
{
return _sockets;
}
/// <summary>
/// 获取租户组内全部socket.
/// </summary>
/// <param name="tenantID"></param>
/// <returns></returns>
public List<string> GetAllFromTenant(string tenantID)
{
if (_tenant.ContainsKey(tenantID))
{
return _tenant[tenantID];
}
return default;
}
/// <summary>
/// 获取用户组内全部socket.
/// </summary>
/// <param name="userID"></param>
/// <returns></returns>
public List<string> GetAllFromUser(string userID)
{
if (_users.ContainsKey(userID))
{
return _users[userID];
}
return default;
}
/// <summary>
/// 根据 socket 获取其 id.
/// </summary>
/// <param name="socket"></param>
/// <returns></returns>
public string GetId(WebSocket socket)
{
return _sockets.FirstOrDefault(p => p.Value.WebSocket == socket).Key;
}
/// <summary>
/// 添加无连接ID socket.
/// </summary>
/// <param name="socket"></param>
public void AddSocket(WebSocketClient socket)
{
_sockets.TryAdd(CreateConnectionId(), socket);
}
/// <summary>
/// 添加带连接 id socket.
/// </summary>
/// <param name="socketID"></param>
/// <param name="socket"></param>
public void AddSocket(string socketID, WebSocketClient socket)
{
_sockets.TryAdd(socketID, socket);
}
/// <summary>
/// 将连接添加至租户组.
/// </summary>
/// <param name="socketID"></param>
/// <param name="tenantID"></param>
public void AddToTenant(string socketID, string tenantID)
{
if (_tenant.ContainsKey(tenantID))
{
if (!_tenant[tenantID].Contains(socketID)) _tenant[tenantID].Add(socketID);
return;
}
_tenant.TryAdd(tenantID, new List<string> { socketID });
}
/// <summary>
/// 移除租户内某个连接.
/// </summary>
/// <param name="socketID"></param>
/// <param name="tenantID"></param>
public void RemoveFromTenant(string socketID, string tenantID)
{
if (_tenant.ContainsKey(tenantID))
{
_tenant[tenantID].Remove(socketID);
}
}
/// <summary>
/// 将连接添加至用户组
/// 用户可以多端登录.
/// </summary>
/// <param name="socketID"></param>
/// <param name="userID">格式为租户ID+用户ID.</param>
public void AddToUser(string socketID, string userID)
{
if (_users.ContainsKey(userID))
{
if (!_users[userID].Contains(socketID)) _users[userID].Add(socketID);
return;
}
_users.TryAdd(userID, new List<string> { socketID });
}
/// <summary>
/// 移除用户组内某个连接.
/// </summary>
/// <param name="socketID"></param>
/// <param name="userID">格式为租户ID+用户ID.</param>
public void RemoveFromUser(string socketID, string userID)
{
if (_users.ContainsKey(userID))
{
_users[userID].Remove(socketID);
}
}
/// <summary>
/// 移除某个 socket.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task RemoveSocket(string id)
{
if (id == null) return;
if (_sockets.TryRemove(id, out WebSocketClient client))
{
if (client.WebSocket.State != WebSocketState.Open) return;
await client.WebSocket.CloseAsync(closeStatus: WebSocketCloseStatus.NormalClosure,
statusDescription: "Closed by the WebSocketManager",
cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}
/// <summary>
/// 生成连接ID.
/// </summary>
/// <returns></returns>
private string CreateConnectionId()
{
return Guid.NewGuid().ToString();
}
/// <summary>
/// 获取全部客户端数量.
/// </summary>
/// <returns></returns>
public int GetSocketClientCount()
{
return _sockets.Count();
}
/// <summary>
/// 返回用户组客户端数量.
/// </summary>
/// <param name="userID"></param>
/// <returns></returns>
public int GetSocketClientToUserCount(string userID)
{
if (_users.ContainsKey(userID))
return _users[userID].Count();
return 0;
}
}

View File

@@ -0,0 +1,140 @@
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Web;
using JNPF.Common.Net;
using JNPF.Common.Security;
using JNPF.DataEncryption;
using JNPF.Extras.WebSockets.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.JsonWebTokens;
namespace JNPF.WebSockets;
/// <summary>
/// WebSocket 中间件.
/// </summary>
public class WebSocketMiddleware
{
/// <summary>
/// 请求委托.
/// </summary>
private readonly RequestDelegate _next;
/// <summary>
/// webSocket 处理程序.
/// </summary>
private WebSocketHandler _webSocketHandler { get; set; }
/// <summary>
/// 初始化一个<see cref="WebSocketMiddleware"/>类型的新实例.
/// </summary>
/// <param name="next"></param>
/// <param name="webSocketHandler"></param>
public WebSocketMiddleware(
RequestDelegate next,
WebSocketHandler webSocketHandler)
{
_next = next;
_webSocketHandler = webSocketHandler;
}
/// <summary>
/// 异步调用.
/// </summary>
public async Task InvokeAsync(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await _next.Invoke(context);
return;
}
WebSocket? socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
var token = new JsonWebToken(context.Request.Path.ToString().TrimStart('/').Replace("Bearer%20", string.Empty).Replace("bearer%20", string.Empty));
var httpContext = (DefaultHttpContext)context;
httpContext.Request.Headers["Authorization"] = HttpUtility.UrlDecode(context.Request.Path.ToString().TrimStart('/'), Encoding.UTF8);
UserAgent userAgent = new UserAgent(httpContext);
if (!JWTEncryption.ValidateJwtBearerToken(httpContext, out token))
{
await _webSocketHandler.OnDisconnected(socket);
}
else
{
var connectionId = Guid.NewGuid().ToString("N");
var wsClient = new WebSocketClient
{
ConnectionId = connectionId,
WebSocket = socket,
LoginIpAddress = NetHelper.Ip,
LoginPlatForm = string.Format("{0}-{1}", userAgent.OS.ToString(), userAgent.RawValue)
};
await _webSocketHandler.OnConnected(connectionId, wsClient).ConfigureAwait(false);
await Receive(wsClient, async (result, serializedMessage) =>
{
switch (result.MessageType)
{
case WebSocketMessageType.Text:
await _webSocketHandler.ReceiveAsync(wsClient, result, serializedMessage).ConfigureAwait(false);
break;
case WebSocketMessageType.Close:
await _webSocketHandler.OnDisconnected(socket);
break;
case WebSocketMessageType.Binary:
break;
default:
throw new ArgumentOutOfRangeException();
}
});
}
}
/// <summary>
/// 接收数据.
/// </summary>
/// <param name="client"></param>
/// <param name="handleMessage"></param>
/// <returns></returns>
private async Task Receive(WebSocketClient client, Action<WebSocketReceiveResult, string> handleMessage)
{
while (client.WebSocket.State == WebSocketState.Open)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024 * 4]);
string message = string.Empty;
WebSocketReceiveResult result = null;
try
{
using (var ms = new MemoryStream())
{
do
{
result = await client.WebSocket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms, Encoding.UTF8))
{
message = await reader.ReadToEndAsync().ConfigureAwait(false);
}
}
handleMessage(result, message);
}
catch (WebSocketException e)
{
if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
{
client.WebSocket.Abort();
}
}
}
await _webSocketHandler.OnDisconnected(client.WebSocket);
}
}

View File

@@ -0,0 +1,76 @@
using System.Net.WebSockets;
using JNPF.Common.Enums;
using SqlSugar;
namespace JNPF.Extras.WebSockets.Models;
/// <summary>
/// WebSocket客户端信息.
/// </summary>
public class WebSocketClient
{
/// <summary>
/// 连接Id.
/// </summary>
public string ConnectionId { get; set; }
/// <summary>
/// 用户Id.
/// </summary>
public string UserId { get; set; }
/// <summary>
/// 用户账号.
/// </summary>
public string Account { get; set; }
/// <summary>
/// 头像.
/// </summary>
public string HeadIcon { get; set; }
/// <summary>
/// 用户名称.
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 登录IP.
/// </summary>
public string LoginIpAddress { get; set; }
/// <summary>
/// 登录设备.
/// </summary>
public string LoginPlatForm { get; set; }
/// <summary>
/// 登录时间.
/// </summary>
public string LoginTime { get; set; }
/// <summary>
/// 连接字符串.
/// </summary>
public ConnectionConfigOptions ConnectionConfig { get; set; }
/// <summary>
/// 移动端.
/// </summary>
public bool IsMobileDevice { get; set; }
/// <summary>
/// 单一登录方式1后登录踢出先登录 2同时登录.
/// </summary>
public LoginMethod SingleLogin { get; set; }
/// <summary>
/// token.
/// </summary>
public string Token { get; set; }
/// <summary>
/// WebSocket对象.
/// </summary>
public WebSocket WebSocket { get; set; }
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)\common.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Description>JNPF WebSocket 拓展插件。</Description>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tnb.Common\Tnb.Common.csproj" />
</ItemGroup>
</Project>