ss
This commit is contained in:
parent
43f076f872
commit
d73d342ab0
@ -95,7 +95,7 @@ namespace YD_AllHeartRates.Api.Entitys
|
|||||||
[Display(Name = "错误个数")]
|
[Display(Name = "错误个数")]
|
||||||
[Comment("错误个数")]
|
[Comment("错误个数")]
|
||||||
[Column(TypeName = "int")]
|
[Column(TypeName = "int")]
|
||||||
public int? ErrorNumber { get; set; }
|
public int ErrorNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///正确个数[敏捷:正确个数]
|
///正确个数[敏捷:正确个数]
|
||||||
@ -103,7 +103,7 @@ namespace YD_AllHeartRates.Api.Entitys
|
|||||||
[Display(Name = "正确个数")]
|
[Display(Name = "正确个数")]
|
||||||
[Comment("正确个数")]
|
[Comment("正确个数")]
|
||||||
[Column(TypeName = "int")]
|
[Column(TypeName = "int")]
|
||||||
public int? JumpValue { get; set; }
|
public int JumpValue { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///电量
|
///电量
|
||||||
|
@ -29,13 +29,13 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
private readonly UserContext _userContext;
|
private readonly UserContext _userContext;
|
||||||
private readonly SmartSportsContext _smartSportsContext;
|
private readonly SmartSportsContext _smartSportsContext;
|
||||||
private readonly ICaching _caching;
|
private readonly ICaching _caching;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
private readonly List<StudentDto> _studentList = new();
|
private readonly List<StudentDto> _studentList = new();
|
||||||
private readonly List<HeartRateData> _pendingHeartRates = new();
|
private readonly List<HeartRateData> _pendingHeartRates = new();
|
||||||
private readonly Dictionary<string, JumpRopeData> _jumpDailyMap = new();
|
|
||||||
|
|
||||||
private DateTime _lastHeartRateSaveTime = DateTime.UtcNow;
|
private DateTime _lastHeartRateSaveTime = DateTime.Now;
|
||||||
private DateTime _lastJumpRopeSaveTime = DateTime.UtcNow;
|
private DateTime _lastJumpRopeSaveTime = DateTime.Now;
|
||||||
|
|
||||||
private readonly Channel<MqttMessage> _queue = Channel.CreateUnbounded<MqttMessage>();
|
private readonly Channel<MqttMessage> _queue = Channel.CreateUnbounded<MqttMessage>();
|
||||||
|
|
||||||
@ -46,7 +46,8 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
ILogger<MqttBackgroundService> log,
|
ILogger<MqttBackgroundService> log,
|
||||||
UserContext userContext,
|
UserContext userContext,
|
||||||
SmartSportsContext smartSportsContext,
|
SmartSportsContext smartSportsContext,
|
||||||
ICaching caching
|
ICaching caching,
|
||||||
|
IServiceScopeFactory scopeFactory
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
@ -54,6 +55,7 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
_userContext = userContext;
|
_userContext = userContext;
|
||||||
_smartSportsContext = smartSportsContext;
|
_smartSportsContext = smartSportsContext;
|
||||||
_caching = caching;
|
_caching = caching;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
|
||||||
_studentList = (from d in _smartSportsContext.Device
|
_studentList = (from d in _smartSportsContext.Device
|
||||||
join s in _smartSportsContext.Student on d.StudentNo equals s.StudentNo
|
join s in _smartSportsContext.Student on d.StudentNo equals s.StudentNo
|
||||||
@ -93,7 +95,7 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
{
|
{
|
||||||
Topic = e.ApplicationMessage.Topic,
|
Topic = e.ApplicationMessage.Topic,
|
||||||
Payload = e.ApplicationMessage.Payload == null ? string.Empty : Encoding.UTF8.GetString(e.ApplicationMessage.Payload),
|
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();
|
return _queue.Writer.WriteAsync(msg, stoppingToken).AsTask();
|
||||||
};
|
};
|
||||||
@ -172,17 +174,17 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
if (string.IsNullOrWhiteSpace(ble.RawData)) continue;
|
if (string.IsNullOrWhiteSpace(ble.RawData)) continue;
|
||||||
|
|
||||||
var student = _studentList.FirstOrDefault(x => x.JumpRopeId == ble.BleName);
|
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);
|
var data = ParseHexData(ble.RawData);
|
||||||
if (data == null) continue;
|
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;
|
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 errorCount = data[mfIndex + 7];
|
||||||
int battery = data[mfIndex + 9];
|
int battery = data[mfIndex + 10];
|
||||||
|
|
||||||
var jumpData = new JumpRopeData
|
var jumpData = new JumpRopeData
|
||||||
{
|
{
|
||||||
@ -208,61 +210,89 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
// await _wsSender.SendToSchoolAsync(student.SchoolCode, "jumpRope", _jumpDailyMap.Values.ToList());
|
// await _wsSender.SendToSchoolAsync(student.SchoolCode, "jumpRope", _jumpDailyMap.Values.ToList());
|
||||||
//}
|
//}
|
||||||
|
|
||||||
string key = $"{jumpData.StudentNo}_{jumpData.ScoreTime:yyyyMMdd}";
|
var jumpKey = $"jumpRope:active:{student.StudentNo}:{DateTime.Now:yyyyMMdd}";
|
||||||
if (_jumpDailyMap.ContainsKey(key))
|
var rawKey = $"jumpRope:raw:{student.StudentNo}:{DateTime.Now:yyyyMMdd}";
|
||||||
{
|
|
||||||
_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);
|
|
||||||
|
|
||||||
RedisHelper.SAdd(studentSetKey, student.StudentNo);
|
// 当前缓存的总值(当天跳绳数据)
|
||||||
|
var totalData = _caching.Get<JumpRopeData>(jumpKey);
|
||||||
|
|
||||||
|
// 初始化总值(首次记录)
|
||||||
|
if (totalData == null)
|
||||||
|
{
|
||||||
|
totalData = new JumpRopeData
|
||||||
|
{
|
||||||
|
StudentNo = jumpData.StudentNo,
|
||||||
|
JumpValue = 0,
|
||||||
|
ErrorNumber = 0,
|
||||||
|
QuantityOfElectricity = jumpData.QuantityOfElectricity,
|
||||||
|
ScoreTime = jumpData.ScoreTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上一条原始累计值(用于计算差值)
|
||||||
|
var lastRaw = _caching.Get<JumpRopeData>(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);
|
_userContext.HeartRateData.AddRange(_pendingHeartRates);
|
||||||
await _userContext.SaveChangesAsync();
|
await _userContext.SaveChangesAsync();
|
||||||
_pendingHeartRates.Clear();
|
_pendingHeartRates.Clear();
|
||||||
_lastHeartRateSaveTime = DateTime.UtcNow;
|
_lastHeartRateSaveTime = DateTime.Now;
|
||||||
}
|
}
|
||||||
_pendingHeartRates.AddRange(heartRateEntities);
|
_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
|
await SyncTodayJumpDataToDbAsync();
|
||||||
.FirstOrDefaultAsync(x => x.StudentNo == data.StudentNo && x.ScoreTime.Date == data.ScoreTime.Date);
|
await Task.Delay(10000); // 每 10 秒检查一次
|
||||||
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 _userContext.SaveChangesAsync();
|
});
|
||||||
_lastJumpRopeSaveTime = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private byte[]? ParseHexData(string hex)
|
private byte[]? ParseHexData(string hex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -297,5 +327,75 @@ namespace YD_AllHeartRates.Api.Mqtt
|
|||||||
}
|
}
|
||||||
if (buffer.Count > 0) yield return buffer;
|
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<UserContext>();
|
||||||
|
|
||||||
|
foreach (var studentNo in studentNos)
|
||||||
|
{
|
||||||
|
var cacheKey = $"jumpRope:active:{studentNo}:{dateStr}";
|
||||||
|
var data = _caching.Get<JumpRopeData>(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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
|
||||||
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
|
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Reflection.PortableExecutable;
|
using System.Reflection.PortableExecutable;
|
||||||
@ -25,7 +24,6 @@ namespace YD_AllHeartRates.Api.Services.Impl
|
|||||||
private readonly LoginContext _loginContext;
|
private readonly LoginContext _loginContext;
|
||||||
private string schoolCode;
|
private string schoolCode;
|
||||||
private readonly ICaching _caching;
|
private readonly ICaching _caching;
|
||||||
private readonly Dictionary<string, JumpRopeData> _jumpDailyMap = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造
|
/// 构造
|
||||||
@ -119,7 +117,7 @@ namespace YD_AllHeartRates.Api.Services.Impl
|
|||||||
res.HeartRateOnlineCount = onlineHeartRateCount;
|
res.HeartRateOnlineCount = onlineHeartRateCount;
|
||||||
|
|
||||||
// 在线跳绳设备
|
// 在线跳绳设备
|
||||||
int onlineJumpRopeCount = RedisHelper.Keys("jumpRope:*").Length;
|
int onlineJumpRopeCount = RedisHelper.Keys("jumpRope:active:*").Length;
|
||||||
res.JumpingRopeOnLineCount = onlineJumpRopeCount;
|
res.JumpingRopeOnLineCount = onlineJumpRopeCount;
|
||||||
|
|
||||||
// 1. 构建缓存 key
|
// 1. 构建缓存 key
|
||||||
@ -151,15 +149,14 @@ namespace YD_AllHeartRates.Api.Services.Impl
|
|||||||
var heartRateKey = $"heartRate:{student.StudentNo}";
|
var heartRateKey = $"heartRate:{student.StudentNo}";
|
||||||
//var jumpRopeKey = $"jumpRope:{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<HeartRateData>(heartRateKey);
|
var heartRate = _caching.Get<HeartRateData>(heartRateKey);
|
||||||
|
|
||||||
//var jumpRope = _caching.Get<JumpRopeData>(jumpRopeKey);
|
var jumpRope = _caching.Get<JumpRopeData>(jumpRopeKey);
|
||||||
|
|
||||||
_jumpDailyMap.TryGetValue(jumpRopeKey, out var jumpRope);
|
|
||||||
|
|
||||||
|
|
||||||
// ❗心率缓存未命中 → 单独查数据库
|
// ❗心率缓存未命中 → 单独查数据库
|
||||||
//if (heartRate == null)
|
//if (heartRate == null)
|
||||||
|
@ -14,7 +14,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("YD_AllHeartRates.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("YD_AllHeartRates.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[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.AssemblyProductAttribute("YD_AllHeartRates.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
@ -1 +1 @@
|
|||||||
937ead42c43efa52de467b7e6b0b10b17938c2bb3b9c6b465840319e0efb56a9
|
b0f272ad7220d6b797997e6256efe0155a22b7fdee2e14ca711ed9643b29fa86
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user