commit 51c1218a93e5856ed79cfdfa35e6061ad83142fe Author: NuanRMxi <2308425927@qq.com> Date: Fri Jul 11 22:03:31 2025 +0800 first commit and delete token diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5930028 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +token.txt \ No newline at end of file diff --git a/Controllers/HeartRateController.cs b/Controllers/HeartRateController.cs new file mode 100644 index 0000000..b2db42f --- /dev/null +++ b/Controllers/HeartRateController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; +using HikarinHeartRateMonitorService.Models; + +namespace HikarinHeartRateMonitorService.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class HeartRateController : ControllerBase + { + private readonly ILogger _logger; + + public HeartRateController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IActionResult Get() + { + return Ok(new { Message = "心率监测WebSocket服务正在运行。请使用WebSocket连接到'/ws'路径。" }); + } + } +} diff --git a/HikarinHeartRateMonitorService.csproj b/HikarinHeartRateMonitorService.csproj new file mode 100644 index 0000000..629f246 --- /dev/null +++ b/HikarinHeartRateMonitorService.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/HikarinHeartRateMonitorService.http b/HikarinHeartRateMonitorService.http new file mode 100644 index 0000000..2ce8496 --- /dev/null +++ b/HikarinHeartRateMonitorService.http @@ -0,0 +1,6 @@ +@HikarinHeartRateMonitorService_HostAddress = http://localhost:5037 + +GET {{HikarinHeartRateMonitorService_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/HikarinHeartRateMonitorService.sln b/HikarinHeartRateMonitorService.sln new file mode 100644 index 0000000..5ca0677 --- /dev/null +++ b/HikarinHeartRateMonitorService.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HikarinHeartRateMonitorService", "HikarinHeartRateMonitorService.csproj", "{81D483F5-853A-4614-9E01-F8121FDE8DAE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {81D483F5-853A-4614-9E01-F8121FDE8DAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81D483F5-853A-4614-9E01-F8121FDE8DAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81D483F5-853A-4614-9E01-F8121FDE8DAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81D483F5-853A-4614-9E01-F8121FDE8DAE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Middleware/WebSocketMiddleware.cs b/Middleware/WebSocketMiddleware.cs new file mode 100644 index 0000000..0da7827 --- /dev/null +++ b/Middleware/WebSocketMiddleware.cs @@ -0,0 +1,77 @@ +using System.Net.WebSockets; +using System.Text; +using HikarinHeartRateMonitorService.Services; +using WebSocketManager = HikarinHeartRateMonitorService.Services.WebSocketManager; + +namespace HikarinHeartRateMonitorService.Middleware +{ + public class WebSocketMiddleware + { + private readonly RequestDelegate _next; + private readonly WebSocketManager _webSocketManager; + private readonly ILogger _logger; + + public WebSocketMiddleware(RequestDelegate next, WebSocketManager webSocketManager, + ILogger logger) + { + _next = next; + _webSocketManager = webSocketManager; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + if (!context.WebSockets.IsWebSocketRequest) + { + await _next(context); + return; + } + + var socket = await context.WebSockets.AcceptWebSocketAsync(); + var socketId = Guid.NewGuid().ToString(); + + _webSocketManager.AddSocket(socketId, socket); + _logger.LogInformation($"WebSocket连接已建立: {socketId}"); + + await ReceiveMessages(socketId, socket); + } + + private async Task ReceiveMessages(string socketId, WebSocket socket) + { + var buffer = new byte[4096]; + + try + { + while (socket.State == WebSocketState.Open) + { + var result = await socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + var message = Encoding.UTF8.GetString(buffer, 0, result.Count); + await _webSocketManager.HandleMessageAsync(socketId, message); + } + else if (result.MessageType == WebSocketMessageType.Close) + { + await _webSocketManager.RemoveSocket(socketId); + _logger.LogInformation($"WebSocket连接已关闭: {socketId}"); + break; + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"WebSocket处理时发生错误: {socketId}"); + await _webSocketManager.RemoveSocket(socketId); + } + } + } + + public static class WebSocketMiddlewareExtensions + { + public static IApplicationBuilder UseHeartRateWebSockets(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/Models/HeartRateData.cs b/Models/HeartRateData.cs new file mode 100644 index 0000000..f121a9a --- /dev/null +++ b/Models/HeartRateData.cs @@ -0,0 +1,17 @@ +namespace HikarinHeartRateMonitorService.Models +{ + public class HeartRateData + { + public int HeartRate { get; set; } + public DateTime Timestamp { get; set; } + public string DeviceName { get; set; } + public readonly string Token = File.ReadAllText("token.txt"); + } + + public class HeartRateResponse + { + public int HeartRate { get; set; } + public DateTime Timestamp { get; set; } + public string DeviceName { get; set; } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..857b86b --- /dev/null +++ b/Program.cs @@ -0,0 +1,47 @@ +using HikarinHeartRateMonitorService.Middleware; +using HikarinHeartRateMonitorService.Services; +using WebSocketManager = HikarinHeartRateMonitorService.Services.WebSocketManager; +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +// 添加控制器支持 +builder.Services.AddControllers(); + +// 注册WebSocket管理器 +builder.Services.AddSingleton(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +// 添加WebSocket支持 +app.UseWebSockets(new WebSocketOptions +{ + KeepAliveInterval = TimeSpan.FromMinutes(2), + AllowedOrigins = { "*" } // 生产环境中请设置具体的允许来源 +}); + +// 使用自定义WebSocket中间件 +app.UseHeartRateWebSockets(); + +// 添加静态文件支持 +app.UseStaticFiles(); + +/*调试页面 +app.MapGet("/", async context => { + context.Response.Redirect("/index.html"); +}); +*/ + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..79f26ef --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:8081", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7060;http://localhost:5037", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e58b2a1 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# 心率监测 WebSocket 服务 + +这是一个基于ASP.NET Core的WebSocket服务,用于接收心率监测设备的数据并转发给其他连接的客户端。 + +## 功能 + +- 接收心率数据(包含心率值、时间戳和设备名称) +- 验证客户端Token确保安全性 +- 向其他所有已连接的客户端广播心率数据(不包含Token) + +## 技术栈 + +- ASP.NET Core 9.0 +- WebSockets +- System.Text.Json + +## 数据格式 + +### 接收的数据格式 + +```json +{ + "heartRate": 75, + "timestamp": "2023-04-10T15:30:45.123Z", + "deviceName": "HeartMonitor-X1", + "token": "token.txt" +} +``` +token是鉴权的唯一方式,请自行在程序主目录添加token.txt,并填入token + +### 广播的数据格式 + +```json +{ + "heartRate": 75, + "timestamp": "2023-04-10T15:30:45.123Z", + "deviceName": "HeartMonitor-X1" +} +``` + +## 使用方式 + +1. 启动服务 +2. 通过WebSocket连接到 `ws://localhost:5000/ws` 或 `wss://localhost:5001/ws` +3. 发送包含有效Token的心率数据JSON +4. 接收来自其他客户端的心率数据广播 diff --git a/Services/WebSocketManager.cs b/Services/WebSocketManager.cs new file mode 100644 index 0000000..0e52cff --- /dev/null +++ b/Services/WebSocketManager.cs @@ -0,0 +1,84 @@ +using System.Collections.Concurrent; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using HikarinHeartRateMonitorService.Models; + +namespace HikarinHeartRateMonitorService.Services +{ + public class WebSocketManager + { + private readonly ConcurrentDictionary _sockets = new(); + private readonly string _validToken = File.ReadAllText("token.txt"); + + public void AddSocket(string id, WebSocket socket) + { + _sockets.TryAdd(id, socket); + } + + public async Task RemoveSocket(string id) + { + if (_sockets.TryRemove(id, out var socket)) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, + "Connection closed by the server", CancellationToken.None); + } + } + + public async Task HandleMessageAsync(string senderId, string message) + { + try + { + var heartRateData = JsonSerializer.Deserialize(message); + + // 验证Token + if (heartRateData?.Token != _validToken) + { + await CloseInvalidConnection(senderId, "Invalid token"); + return; + } + + // 创建不包含Token的响应对象 + var response = new HeartRateResponse + { + HeartRate = heartRateData.HeartRate, + Timestamp = heartRateData.Timestamp, + DeviceName = heartRateData.DeviceName + }; + + var responseJson = JsonSerializer.Serialize(response); + var responseBytes = Encoding.UTF8.GetBytes(responseJson); + + // 向除了发送者之外的所有客户端广播消息 + var tasks = _sockets + .Where(kvp => kvp.Key != senderId) + .Select(kvp => SendMessageAsync(kvp.Value, responseBytes)); + + await Task.WhenAll(tasks); + } + catch (JsonException) + { + await CloseInvalidConnection(senderId, "Invalid message format"); + } + } + + private async Task CloseInvalidConnection(string id, string reason) + { + if (_sockets.TryGetValue(id, out var socket)) + { + await socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, + reason, CancellationToken.None); + await RemoveSocket(id); + } + } + + private static async Task SendMessageAsync(WebSocket socket, byte[] message) + { + if (socket.State == WebSocketState.Open) + { + await socket.SendAsync(new ArraySegment(message), + WebSocketMessageType.Text, true, CancellationToken.None); + } + } + } +} diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/obs_plugin.html b/obs_plugin.html new file mode 100644 index 0000000..a517e6b --- /dev/null +++ b/obs_plugin.html @@ -0,0 +1,558 @@ + + + + + + 心率监控 - OBS插件 + + + +
+
+
❤️
+
--
+
BPM
+
+ +
+
+ 等待连接... + +
+ +
+ +
+ +
+
+
--
+
平均
+
+
+
--
+
最高
+
+
+
--
+
最低
+
+
+
00:00
+
时长
+
+
+
+ + + + \ No newline at end of file diff --git a/wwwroot/index.html b/wwwroot/index.html new file mode 100644 index 0000000..66264c0 --- /dev/null +++ b/wwwroot/index.html @@ -0,0 +1,226 @@ + + + + + + 心率监测WebSocket测试 + + + +
+

心率监测WebSocket测试

+ +
+
连接控制
+ + + 未连接 +
+ +
+
发送心率数据
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
日志
+
+
+
+ + + +