diff --git a/YD_AllHeartRates.Api/Entitys/JumpRopeData.cs b/YD_AllHeartRates.Api/Entitys/JumpRopeData.cs index 744441e..460dc9e 100644 --- a/YD_AllHeartRates.Api/Entitys/JumpRopeData.cs +++ b/YD_AllHeartRates.Api/Entitys/JumpRopeData.cs @@ -95,7 +95,7 @@ namespace YD_AllHeartRates.Api.Entitys [Display(Name = "错误个数")] [Comment("错误个数")] [Column(TypeName = "int")] - public int? ErrorNumber { get; set; } + public int ErrorNumber { get; set; } /// ///正确个数[敏捷:正确个数] @@ -103,7 +103,7 @@ namespace YD_AllHeartRates.Api.Entitys [Display(Name = "正确个数")] [Comment("正确个数")] [Column(TypeName = "int")] - public int? JumpValue { get; set; } + public int JumpValue { get; set; } /// ///电量 diff --git a/YD_AllHeartRates.Api/Mqtt/MqttBackgroundService.cs b/YD_AllHeartRates.Api/Mqtt/MqttBackgroundService.cs index eeef06d..ddad782 100644 --- a/YD_AllHeartRates.Api/Mqtt/MqttBackgroundService.cs +++ b/YD_AllHeartRates.Api/Mqtt/MqttBackgroundService.cs @@ -29,13 +29,13 @@ namespace YD_AllHeartRates.Api.Mqtt private readonly UserContext _userContext; private readonly SmartSportsContext _smartSportsContext; private readonly ICaching _caching; + private readonly IServiceScopeFactory _scopeFactory; private readonly List _studentList = new(); private readonly List _pendingHeartRates = new(); - private readonly Dictionary _jumpDailyMap = new(); - private DateTime _lastHeartRateSaveTime = DateTime.UtcNow; - private DateTime _lastJumpRopeSaveTime = DateTime.UtcNow; + private DateTime _lastHeartRateSaveTime = DateTime.Now; + private DateTime _lastJumpRopeSaveTime = DateTime.Now; private readonly Channel _queue = Channel.CreateUnbounded(); @@ -46,7 +46,8 @@ namespace YD_AllHeartRates.Api.Mqtt ILogger log, UserContext userContext, SmartSportsContext smartSportsContext, - ICaching caching + ICaching caching, + IServiceScopeFactory scopeFactory ) { _client = client; @@ -54,6 +55,7 @@ namespace YD_AllHeartRates.Api.Mqtt _userContext = userContext; _smartSportsContext = smartSportsContext; _caching = caching; + _scopeFactory = scopeFactory; _studentList = (from d in _smartSportsContext.Device join s in _smartSportsContext.Student on d.StudentNo equals s.StudentNo @@ -93,7 +95,7 @@ namespace YD_AllHeartRates.Api.Mqtt { Topic = e.ApplicationMessage.Topic, Payload = e.ApplicationMessage.Payload == null ? string.Empty : Encoding.UTF8.GetString(e.ApplicationMessage.Payload), - ReceivedAt = DateTime.UtcNow + ReceivedAt = DateTime.Now }; return _queue.Writer.WriteAsync(msg, stoppingToken).AsTask(); }; @@ -172,17 +174,17 @@ namespace YD_AllHeartRates.Api.Mqtt if (string.IsNullOrWhiteSpace(ble.RawData)) continue; var student = _studentList.FirstOrDefault(x => x.JumpRopeId == ble.BleName); - if (student == null || student.GradeId == 0 || student.ClassId == 0) continue; + if (student == null || student?.GradeId == 0 || student?.ClassId == 0) continue; var data = ParseHexData(ble.RawData); if (data == null) continue; - int mfIndex = Array.IndexOf(data, new byte[] { 0xFF, 0x04, 0xFF, 0xCF }); + int mfIndex = IndexOfSequence(data, new byte[] { 0xFF, 0x04, 0xFF, 0xCF }); if (mfIndex < 0 || data.Length < mfIndex + 10) continue; - int jumpCount = data[mfIndex + 5] + (data[mfIndex + 6] << 8); + int jumpCount = data[mfIndex + 5]; int errorCount = data[mfIndex + 7]; - int battery = data[mfIndex + 9]; + int battery = data[mfIndex + 10]; var jumpData = new JumpRopeData { @@ -208,61 +210,89 @@ namespace YD_AllHeartRates.Api.Mqtt // await _wsSender.SendToSchoolAsync(student.SchoolCode, "jumpRope", _jumpDailyMap.Values.ToList()); //} - string key = $"{jumpData.StudentNo}_{jumpData.ScoreTime:yyyyMMdd}"; - if (_jumpDailyMap.ContainsKey(key)) - { - _jumpDailyMap[key].JumpValue += jumpData.JumpValue; - _jumpDailyMap[key].ErrorNumber += jumpData.ErrorNumber; - _jumpDailyMap[key].QuantityOfElectricity = jumpData.QuantityOfElectricity; - _jumpDailyMap[key].ScoreTime = jumpData.ScoreTime; - } - else - { - _jumpDailyMap[key] = jumpData; - } - var studentSetKey = $"school:{student.SchoolCode}:students"; - var jumpKey = $"jumpRope:{student.StudentNo}"; - _caching.AddObject(jumpKey, jumpData, 60); + var jumpKey = $"jumpRope:active:{student.StudentNo}:{DateTime.Now:yyyyMMdd}"; + var rawKey = $"jumpRope:raw:{student.StudentNo}:{DateTime.Now:yyyyMMdd}"; - RedisHelper.SAdd(studentSetKey, student.StudentNo); + // 当前缓存的总值(当天跳绳数据) + var totalData = _caching.Get(jumpKey); + + // 初始化总值(首次记录) + if (totalData == null) + { + totalData = new JumpRopeData + { + StudentNo = jumpData.StudentNo, + JumpValue = 0, + ErrorNumber = 0, + QuantityOfElectricity = jumpData.QuantityOfElectricity, + ScoreTime = jumpData.ScoreTime + }; + } + + // 上一条原始累计值(用于计算差值) + var lastRaw = _caching.Get(rawKey); + + // 差值计算 + int deltaJump = jumpData.JumpValue; + int deltaError = jumpData.ErrorNumber; + + if (lastRaw != null) + { + deltaJump = jumpData.JumpValue >= lastRaw.JumpValue + ? jumpData.JumpValue - lastRaw.JumpValue + : jumpData.JumpValue; + + deltaError = jumpData.ErrorNumber >= lastRaw.ErrorNumber + ? jumpData.ErrorNumber - lastRaw.ErrorNumber + : jumpData.ErrorNumber; + } + + // 汇总 + totalData.JumpValue += deltaJump; + totalData.ErrorNumber += deltaError; + totalData.QuantityOfElectricity = jumpData.QuantityOfElectricity; + totalData.ScoreTime = jumpData.ScoreTime; + + // 写入缓存(总值 + 原始值) + _caching.AddObject(jumpKey, totalData, 60 * 24); + _caching.AddObject(rawKey, jumpData, 60 * 24); + + // 记录活跃学生编号(便于定时入库) + //RedisHelper.SAdd($"jumpRope:active:{DateTime.Now:yyyyMMdd}", student.StudentNo); + + //var studentSetKey = $"school:{student.SchoolCode}:students"; + //var jumpKey = $"jumpRope:{student.StudentNo}"; + //_caching.AddObject(jumpKey, jumpData, 60); + + //RedisHelper.SAdd(studentSetKey, student.StudentNo); } } // 心率每分钟保存一次 - if ((DateTime.UtcNow - _lastHeartRateSaveTime).TotalSeconds >= 60 && _pendingHeartRates.Any()) + if ((DateTime.Now - _lastHeartRateSaveTime).TotalSeconds >= 60 && _pendingHeartRates.Any()) { _userContext.HeartRateData.AddRange(_pendingHeartRates); await _userContext.SaveChangesAsync(); _pendingHeartRates.Clear(); - _lastHeartRateSaveTime = DateTime.UtcNow; + _lastHeartRateSaveTime = DateTime.Now; } _pendingHeartRates.AddRange(heartRateEntities); // 跳绳每日更新保存 - if ((DateTime.UtcNow - _lastJumpRopeSaveTime).TotalSeconds >= 60 && _jumpDailyMap.Any()) + + // 每隔 60 秒执行一次 + _ = Task.Run(async () => { - foreach (var data in _jumpDailyMap.Values) + while (true) { - var exist = await _userContext.JumpRopeData - .FirstOrDefaultAsync(x => x.StudentNo == data.StudentNo && x.ScoreTime.Date == data.ScoreTime.Date); - if (exist != null) - { - exist.JumpValue = data.JumpValue; - exist.ErrorNumber = data.ErrorNumber; - exist.QuantityOfElectricity = data.QuantityOfElectricity; - exist.ScoreTime = data.ScoreTime; - } - else - { - _userContext.JumpRopeData.Add(data); - } + await SyncTodayJumpDataToDbAsync(); + await Task.Delay(10000); // 每 10 秒检查一次 } - await _userContext.SaveChangesAsync(); - _lastJumpRopeSaveTime = DateTime.UtcNow; - } + }); } } + private byte[]? ParseHexData(string hex) { try @@ -297,5 +327,75 @@ namespace YD_AllHeartRates.Api.Mqtt } if (buffer.Count > 0) yield return buffer; } + public static int IndexOfSequence(byte[] buffer, byte[] pattern) + { + if (pattern.Length == 0 || buffer.Length < pattern.Length) + return -1; + + for (int i = 0; i <= buffer.Length - pattern.Length; i++) + { + bool matched = true; + for (int j = 0; j < pattern.Length; j++) + { + if (buffer[i + j] != pattern[j]) + { + matched = false; + break; + } + } + if (matched) + return i; + } + return -1; + } + public async Task SyncTodayJumpDataToDbAsync() + { + try + { + if ((DateTime.Now - _lastJumpRopeSaveTime).TotalSeconds < 60) + return; + + var today = DateTime.Today; + var dateStr = today.ToString("yyyyMMdd"); + var studentNos = RedisHelper.SMembers($"jumpRope:active:{dateStr}"); + + if (studentNos == null || studentNos.Length == 0) + return; + + using var scope = _scopeFactory.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + foreach (var studentNo in studentNos) + { + var cacheKey = $"jumpRope:active:{studentNo}:{dateStr}"; + var data = _caching.Get(cacheKey); + + if (data == null || string.IsNullOrWhiteSpace(data.StudentNo)) continue; + + var exist = await dbContext.JumpRopeData + .FirstOrDefaultAsync(x => x.StudentNo == data.StudentNo && x.ScoreTime.Date == today); + + if (exist != null) + { + exist.JumpValue = data.JumpValue; + exist.ErrorNumber = data.ErrorNumber; + exist.QuantityOfElectricity = data.QuantityOfElectricity; + exist.ScoreTime = data.ScoreTime; + } + else + { + dbContext.JumpRopeData.Add(data); + } + } + + await dbContext.SaveChangesAsync(); + _lastJumpRopeSaveTime = DateTime.Now; + } + catch (Exception ex) + { + + } + } + } } diff --git a/YD_AllHeartRates.Api/Services/Impl/LargeScreenService.cs b/YD_AllHeartRates.Api/Services/Impl/LargeScreenService.cs index c4225b1..c34f3b7 100644 --- a/YD_AllHeartRates.Api/Services/Impl/LargeScreenService.cs +++ b/YD_AllHeartRates.Api/Services/Impl/LargeScreenService.cs @@ -1,7 +1,6 @@  using AutoMapper; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime; using System.Drawing; using System.Reflection.PortableExecutable; @@ -25,7 +24,6 @@ namespace YD_AllHeartRates.Api.Services.Impl private readonly LoginContext _loginContext; private string schoolCode; private readonly ICaching _caching; - private readonly Dictionary _jumpDailyMap = new(); /// /// 构造 @@ -119,7 +117,7 @@ namespace YD_AllHeartRates.Api.Services.Impl res.HeartRateOnlineCount = onlineHeartRateCount; // 在线跳绳设备 - int onlineJumpRopeCount = RedisHelper.Keys("jumpRope:*").Length; + int onlineJumpRopeCount = RedisHelper.Keys("jumpRope:active:*").Length; res.JumpingRopeOnLineCount = onlineJumpRopeCount; // 1. 构建缓存 key @@ -151,15 +149,14 @@ namespace YD_AllHeartRates.Api.Services.Impl var heartRateKey = $"heartRate:{student.StudentNo}"; //var jumpRopeKey = $"jumpRope:{student.StudentNo}"; - string jumpRopeKey = $"{student.StudentNo}_{DateTime.Now:yyyyMMdd}"; + //string jumpRopeKey = $"{student.StudentNo}_{DateTime.Now:yyyyMMdd}"; + + var jumpRopeKey = $"jumpRope:active:{student.StudentNo}:{DateTime.Now:yyyyMMdd}"; // 先从缓存拿 var heartRate = _caching.Get(heartRateKey); - //var jumpRope = _caching.Get(jumpRopeKey); - - _jumpDailyMap.TryGetValue(jumpRopeKey, out var jumpRope); - + var jumpRope = _caching.Get(jumpRopeKey); // ❗心率缓存未命中 → 单独查数据库 //if (heartRate == null) diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfo.cs b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfo.cs index 65cab11..9a4ecea 100644 --- a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfo.cs +++ b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("YD_AllHeartRates.Api")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3c399871d7a4fff8b5d368263e3942991a974fdd")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+43f076f872b66673a4a48984e93b5af18678ab90")] [assembly: System.Reflection.AssemblyProductAttribute("YD_AllHeartRates.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Api")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfoInputs.cache b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfoInputs.cache index 4aecdd9..51759bd 100644 --- a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfoInputs.cache +++ b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.AssemblyInfoInputs.cache @@ -1 +1 @@ -937ead42c43efa52de467b7e6b0b10b17938c2bb3b9c6b465840319e0efb56a9 +b0f272ad7220d6b797997e6256efe0155a22b7fdee2e14ca711ed9643b29fa86 diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.dll b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.dll index cf0b253..4500d1a 100644 Binary files a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.dll and b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.dll differ diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.pdb b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.pdb index 37dfbee..4a19f84 100644 Binary files a/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.pdb and b/YD_AllHeartRates.Api/obj/Debug/net6.0/YD_AllHeartRates.Api.pdb differ diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/apphost.exe b/YD_AllHeartRates.Api/obj/Debug/net6.0/apphost.exe index 9101a1c..395e652 100644 Binary files a/YD_AllHeartRates.Api/obj/Debug/net6.0/apphost.exe and b/YD_AllHeartRates.Api/obj/Debug/net6.0/apphost.exe differ diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/ref/YD_AllHeartRates.Api.dll b/YD_AllHeartRates.Api/obj/Debug/net6.0/ref/YD_AllHeartRates.Api.dll index 396ffe1..9c54afe 100644 Binary files a/YD_AllHeartRates.Api/obj/Debug/net6.0/ref/YD_AllHeartRates.Api.dll and b/YD_AllHeartRates.Api/obj/Debug/net6.0/ref/YD_AllHeartRates.Api.dll differ diff --git a/YD_AllHeartRates.Api/obj/Debug/net6.0/refint/YD_AllHeartRates.Api.dll b/YD_AllHeartRates.Api/obj/Debug/net6.0/refint/YD_AllHeartRates.Api.dll index 396ffe1..9c54afe 100644 Binary files a/YD_AllHeartRates.Api/obj/Debug/net6.0/refint/YD_AllHeartRates.Api.dll and b/YD_AllHeartRates.Api/obj/Debug/net6.0/refint/YD_AllHeartRates.Api.dll differ