keys
This commit is contained in:
parent
6bd396d622
commit
5adc269011
17
YD_AllHeartRates.Api/Mqtt/Device/BleDeviceParser.cs
Normal file
17
YD_AllHeartRates.Api/Mqtt/Device/BleDeviceParser.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using YD_AllHeartRates.Commons.Dto.Mqtt;
|
||||
|
||||
namespace YD_AllHeartRates.Api.Mqtt.Device
|
||||
{
|
||||
public abstract class BleDeviceParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备识别标识,比如 "GTY0", "RS207"
|
||||
/// </summary>
|
||||
public abstract bool CanHandle(string deviceName);
|
||||
|
||||
/// <summary>
|
||||
/// 解析并写入缓存、生成实体
|
||||
/// </summary>
|
||||
public abstract void Parse(DeviceDto ble, DateTime scoreTime, byte[] data);
|
||||
}
|
||||
}
|
||||
13
YD_AllHeartRates.Api/Mqtt/Device/DeviceDto.cs
Normal file
13
YD_AllHeartRates.Api/Mqtt/Device/DeviceDto.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace YD_AllHeartRates.Api.Mqtt.Device
|
||||
{
|
||||
public class DeviceDto
|
||||
{
|
||||
[JsonPropertyName("bs_name")]
|
||||
public string BsName { get; set; }
|
||||
|
||||
[JsonPropertyName("Broadcast_data")]
|
||||
public string BstData { get; set; }
|
||||
}
|
||||
}
|
||||
33
YD_AllHeartRates.Api/Mqtt/Device/Parser/ParserContext.cs
Normal file
33
YD_AllHeartRates.Api/Mqtt/Device/Parser/ParserContext.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Collections.Concurrent;
|
||||
using YD_AllHeartRates.Api.Entitys;
|
||||
using YD_AllHeartRates.Commons.Dto.Student;
|
||||
using YD_AllHeartRates.Commons.MemoryCaches;
|
||||
|
||||
namespace YD_AllHeartRates.Api.Mqtt.Device.Parser
|
||||
{
|
||||
public class ParserContext
|
||||
{
|
||||
public ICaching Caching { get; }
|
||||
public ILogger Log { get; }
|
||||
public ConcurrentDictionary<string, StudentDto> StudentHMap { get; }
|
||||
public ConcurrentDictionary<string, StudentDto> StudentJMap { get; }
|
||||
public ConcurrentBag<HeartRateData> HeartRateEntities { get; }
|
||||
public ConcurrentBag<JumpRopeData> JumpRopeEntities { get; }
|
||||
|
||||
public ParserContext(
|
||||
ICaching caching,
|
||||
ILogger log,
|
||||
ConcurrentDictionary<string, StudentDto> studentHMap,
|
||||
ConcurrentDictionary<string, StudentDto> studentJMap,
|
||||
ConcurrentBag<HeartRateData> heartRateEntities,
|
||||
ConcurrentBag<JumpRopeData> jumpRopeEntities)
|
||||
{
|
||||
Caching = caching;
|
||||
Log = log;
|
||||
StudentHMap = studentHMap;
|
||||
StudentJMap = studentJMap;
|
||||
HeartRateEntities = heartRateEntities;
|
||||
JumpRopeEntities = jumpRopeEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
using System.Collections.Concurrent;
|
||||
using YD_AllHeartRates.Api.Entitys;
|
||||
using YD_AllHeartRates.Commons.Dto.Mqtt;
|
||||
using YD_AllHeartRates.Commons.Dto.Student;
|
||||
using YD_AllHeartRates.Commons.MemoryCaches;
|
||||
|
||||
namespace YD_AllHeartRates.Api.Mqtt.Device.Parser
|
||||
{
|
||||
public class QL_HeartRateDeviceParser : BleDeviceParser
|
||||
{
|
||||
private readonly ConcurrentBag<HeartRateData> _heartRateEntities;
|
||||
private readonly ICaching _caching;
|
||||
private readonly ILogger _log;
|
||||
private readonly ConcurrentDictionary<string, StudentDto> _studentHMap;
|
||||
|
||||
public QL_HeartRateDeviceParser(
|
||||
ConcurrentBag<HeartRateData> heartRateEntities,
|
||||
ICaching caching,
|
||||
ILogger log,
|
||||
ConcurrentDictionary<string, StudentDto> studentHMap)
|
||||
{
|
||||
_heartRateEntities = heartRateEntities;
|
||||
_caching = caching;
|
||||
_log = log;
|
||||
_studentHMap = studentHMap;
|
||||
}
|
||||
|
||||
public override bool CanHandle(string deviceName) => deviceName.Contains("GTY0");
|
||||
|
||||
public override void Parse(DeviceDto ble, DateTime scoreTime, byte[] data)
|
||||
{
|
||||
if (!_studentHMap.TryGetValue(ble.BsName, out var student)) return;
|
||||
|
||||
int cd = Array.IndexOf(data, (byte)0xCD);
|
||||
if (cd < 0 || data.Length < cd + 9) return;
|
||||
|
||||
int battery = data[cd + 1];
|
||||
int heartRate = data[cd + 2];
|
||||
if (heartRate == 0) return;
|
||||
|
||||
var entity = new HeartRateData
|
||||
{
|
||||
ScoreTime = scoreTime,
|
||||
Code = ble.BsName,
|
||||
GradeId = student.GradeId,
|
||||
GradeName = student.GradeName,
|
||||
ClassId = student.ClassId,
|
||||
ClassName = student.ClassName ?? "",
|
||||
SchoolCode = student.SchoolCode ?? "",
|
||||
Sex = student.Sex,
|
||||
Value = heartRate,
|
||||
QuantityOfElectricity = battery,
|
||||
StudentNo = student.StudentNo,
|
||||
StudentName = student.StudentName,
|
||||
Strength = (int)Math.Round(((double)heartRate / (220 - student.Age)) * 100)
|
||||
};
|
||||
|
||||
_heartRateEntities.Add(entity);
|
||||
_caching.AddObject($"heartRate:{student.StudentNo}", entity, 60);
|
||||
RedisHelper.SAdd($"school:{student.SchoolCode}:students", student.StudentNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -384,7 +384,15 @@ namespace YD_AllHeartRates.Api.Mqtt
|
||||
|
||||
var today = DateTime.Today;
|
||||
var dateStr = today.ToString("yyyyMMdd");
|
||||
var keys = RedisHelper.Keys($"jumpRope:active:*:{dateStr}");
|
||||
var keys = new List<string>();
|
||||
var cursor = 0L;
|
||||
do
|
||||
{
|
||||
var scan = RedisHelper.Scan(cursor, $"jumpRope:active:*:{dateStr}", 200);
|
||||
cursor = scan.Cursor;
|
||||
if (scan.Items != null && scan.Items.Any())
|
||||
keys.AddRange(scan.Items);
|
||||
} while (cursor != 0);
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<UserContext>();
|
||||
|
||||
446
YD_AllHeartRates.Api/Mqtt/MqttBackgroundServiceback.cs
Normal file
446
YD_AllHeartRates.Api/Mqtt/MqttBackgroundServiceback.cs
Normal file
@ -0,0 +1,446 @@
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.FileSystemGlobbing.Internal;
|
||||
using MQTTnet;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Protocol;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
using YD_AllHeartRates.Api.Context;
|
||||
using YD_AllHeartRates.Api.Entitys;
|
||||
using YD_AllHeartRates.Api.SmartSportsEntitys;
|
||||
using YD_AllHeartRates.Api.Utilities;
|
||||
using YD_AllHeartRates.Api.WebSocket;
|
||||
using YD_AllHeartRates.Commons.Dto.Mqtt;
|
||||
using YD_AllHeartRates.Commons.Dto.Student;
|
||||
using YD_AllHeartRates.Commons.MemoryCaches;
|
||||
|
||||
namespace YD_AllHeartRates.Api.Mqtt
|
||||
{
|
||||
/// <summary>
|
||||
/// 接受写入数据
|
||||
/// </summary>
|
||||
public class MqttBackgroundServiceback : BackgroundService
|
||||
{
|
||||
private readonly IMqttClient _client;
|
||||
private readonly ILogger _log;
|
||||
private readonly UserContext _userContext;
|
||||
private readonly SmartSportsContext _smartSportsContext;
|
||||
private readonly ICaching _caching;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
private readonly List<HeartRateData> _pendingHeartRates = new();
|
||||
|
||||
private DateTime _lastHeartRateSaveTime = DateTime.Now;
|
||||
private DateTime _lastJumpRopeSaveTime = DateTime.Now;
|
||||
|
||||
private readonly Channel<MqttMessage> _queue = Channel.CreateUnbounded<MqttMessage>();
|
||||
|
||||
static readonly JsonSerializerOptions _jsonOpt = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
public MqttBackgroundServiceback(
|
||||
IMqttClient client,
|
||||
ILogger<MqttBackgroundService> log,
|
||||
UserContext userContext,
|
||||
SmartSportsContext smartSportsContext,
|
||||
ICaching caching,
|
||||
IServiceScopeFactory scopeFactory
|
||||
)
|
||||
{
|
||||
_client = client;
|
||||
_log = log;
|
||||
_userContext = userContext;
|
||||
_smartSportsContext = smartSportsContext;
|
||||
_caching = caching;
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_ = RunJumpSyncLoopAsync(stoppingToken);
|
||||
|
||||
await _client.ConnectAsync(new MqttClientOptionsBuilder()
|
||||
.WithTcpServer(AppSettings.Mqtt.Host, AppSettings.Mqtt.Port)
|
||||
.Build(), stoppingToken);
|
||||
|
||||
await _client.SubscribeAsync(new MqttTopicFilterBuilder()
|
||||
.WithTopic(AppSettings.Mqtt.Topic)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.Build(), stoppingToken);
|
||||
|
||||
_client.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
var msg = new MqttMessage
|
||||
{
|
||||
Topic = e.ApplicationMessage.Topic,
|
||||
Payload = e.ApplicationMessage.Payload == null ? string.Empty : Encoding.UTF8.GetString(e.ApplicationMessage.Payload),
|
||||
ReceivedAt = DateTime.Now
|
||||
};
|
||||
return _queue.Writer.WriteAsync(msg, stoppingToken).AsTask();
|
||||
};
|
||||
|
||||
await foreach (var batch in ReadBatchesAsync(stoppingToken))
|
||||
{
|
||||
List<StudentDto> studentList;
|
||||
//List<S_Device> devices;
|
||||
|
||||
var studentListCacheKey = $"{AppSettings.StudentListCacheKey}_{AppSettings.SchoolCode}";
|
||||
//var deviceListCacheKey = $"{AppSettings.DeviceListCacheKey}_{AppSettings.SchoolCode}";
|
||||
|
||||
studentList = _caching.Get<List<StudentDto>>(studentListCacheKey);
|
||||
//devices = _caching.Get<List<S_Device>>(deviceListCacheKey);
|
||||
|
||||
if (studentList == null || studentList.Count > 0)
|
||||
{
|
||||
studentList = (
|
||||
from s in _smartSportsContext.Student
|
||||
join c in _smartSportsContext.Class on s.ClassId equals c.Id
|
||||
join d in _smartSportsContext.Device on s.StudentNo equals d.StudentNo into sd
|
||||
from d in sd.DefaultIfEmpty()
|
||||
where s.SchoolCode == AppSettings.SchoolCode && s.StudentStatus == 1
|
||||
select new StudentDto
|
||||
{
|
||||
SchoolCode = s.SchoolCode,
|
||||
StudentNo = s.StudentNo,
|
||||
StudentName = s.StudentName,
|
||||
Sex = s.Sex,
|
||||
Age = s.Age,
|
||||
HeartRateId = d != null ? d.Code : "",
|
||||
JumpRopeId = d != null ? d.Code : "",
|
||||
ClassId = s.ClassId,
|
||||
ClassName = s.ClassName,
|
||||
GradeId = c.GradeId,
|
||||
GradeName = c.GradeName ?? "",
|
||||
DeviceType = d != null ? d.DeviceType : 0
|
||||
}
|
||||
).ToList();
|
||||
|
||||
|
||||
//devices = _smartSportsContext.Device.Where(x => x.SchoolCode == AppSettings.SchoolCode && !string.IsNullOrWhiteSpace(x.StudentNo)).ToList();
|
||||
|
||||
_caching.AddObject(studentListCacheKey, studentList, 600);
|
||||
//_caching.AddObject(deviceListCacheKey, devices, 600);
|
||||
}
|
||||
|
||||
var heartRateEntities = new ConcurrentBag<HeartRateData>();
|
||||
var jumpRopeEntities = new ConcurrentBag<JumpRopeData>();
|
||||
|
||||
//var deviceHMap = devices.Where(x => x.DeviceType == 1).ToDictionary(x => x.Code, x => x.StudentNo);
|
||||
//var deviceJMap = devices.Where(x => x.DeviceType == 2).ToDictionary(x => x.Code, x => x.StudentNo);
|
||||
|
||||
var studentHMap = studentList.Where(x => x.DeviceType == 1).GroupBy(x => x.HeartRateId).ToDictionary(g => g.Key, g => g.First());
|
||||
var studentJMap = studentList.Where(x => x.DeviceType == 2).GroupBy(x => x.JumpRopeId).ToDictionary(g => g.Key, g => g.First());
|
||||
|
||||
Parallel.ForEach(batch, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, msg =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg.Payload)) return;
|
||||
var payload = JsonSerializer.Deserialize<BlePayload>(msg.Payload, _jsonOpt);
|
||||
if (payload == null || payload.devices == null || payload.devices.Count == 0) return;
|
||||
|
||||
var scoreTime = DateTime.ParseExact(payload.send_time, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
foreach (var ble in payload.devices)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ble.Broadcast_data)) continue;
|
||||
|
||||
var data = ParseHexData(ble.Broadcast_data);
|
||||
if (data == null) continue;
|
||||
|
||||
if (ble.bs_name.Contains("GTY0"))
|
||||
{
|
||||
//if (!deviceHMap.TryGetValue(ble.bs_name, out var studentNo)) continue;
|
||||
if (!studentHMap.TryGetValue(ble.bs_name, out var student)) continue;
|
||||
|
||||
int cd = Array.IndexOf(data, (byte)0xCD);
|
||||
if (cd < 0 || data.Length < cd + 9) continue;
|
||||
|
||||
int battery = data[cd + 1];
|
||||
int heartRate = data[cd + 2];
|
||||
if (heartRate == 0) continue;
|
||||
|
||||
var entity = new HeartRateData
|
||||
{
|
||||
ScoreTime = scoreTime,
|
||||
Code = ble.bs_name,
|
||||
GradeId = student.GradeId,
|
||||
GradeName = student.GradeName,
|
||||
ClassId = student.ClassId,
|
||||
ClassName = student.ClassName ?? "",
|
||||
SchoolCode = student.SchoolCode ?? "",
|
||||
Sex = student.Sex,
|
||||
Value = heartRate,
|
||||
QuantityOfElectricity = battery,
|
||||
StudentNo = student.StudentNo,
|
||||
StudentName = student.StudentName,
|
||||
Strength = (int)Math.Round(((double)heartRate / (220 - student.Age)) * 100)
|
||||
};
|
||||
|
||||
heartRateEntities.Add(entity);
|
||||
_caching.AddObject($"heartRate:{student.StudentNo}", entity, 60);
|
||||
RedisHelper.SAdd($"school:{student.SchoolCode}:students", student.StudentNo);
|
||||
}
|
||||
|
||||
else if (ble.bs_name.Contains("RS207"))
|
||||
{
|
||||
//if (!deviceJMap.TryGetValue(ble.bs_name, out var studentNo)) continue;
|
||||
if (!studentJMap.TryGetValue(ble.bs_name, out var student)) continue;
|
||||
|
||||
int mfIndex = IndexOfSequence(data, new byte[] { 0xFF, 0x04, 0xFF, 0xCF });
|
||||
if (mfIndex < 0 || data.Length < mfIndex + 10) continue;
|
||||
|
||||
int jumpCount = data[mfIndex + 5];
|
||||
int errorCount = data[mfIndex + 7];
|
||||
int battery = data[mfIndex + 10];
|
||||
|
||||
var jumpData = new JumpRopeData
|
||||
{
|
||||
ScoreTime = scoreTime,
|
||||
Code = ble.bs_name,
|
||||
GradeId = student.GradeId,
|
||||
GradeName = student.GradeName,
|
||||
ClassId = student.ClassId,
|
||||
ClassName = student.ClassName ?? "",
|
||||
SchoolCode = student.SchoolCode ?? "",
|
||||
Sex = student.Sex,
|
||||
JumpValue = jumpCount,
|
||||
QuantityOfElectricity = battery,
|
||||
ErrorNumber = errorCount,
|
||||
StudentNo = student.StudentNo,
|
||||
StudentName = student.StudentName
|
||||
};
|
||||
|
||||
var jumpKey = $"jumpRope:active:{student.StudentNo}:{DateTime.Now:yyyyMMdd}";
|
||||
var rawKey = $"jumpRope:raw:{student.StudentNo}:{DateTime.Now:yyyyMMdd}";
|
||||
|
||||
var totalData = _caching.Get<JumpRopeData>(jumpKey) ?? new JumpRopeData
|
||||
{
|
||||
Id = jumpData.Id,
|
||||
StudentNo = jumpData.StudentNo,
|
||||
ClassId = jumpData.ClassId,
|
||||
ClassName = jumpData.ClassName,
|
||||
Code = jumpData.Code,
|
||||
GradeId = jumpData.GradeId,
|
||||
GradeName = jumpData.GradeName,
|
||||
SchoolCode = jumpData.SchoolCode,
|
||||
Sex = jumpData.Sex,
|
||||
StudentName = jumpData.StudentName,
|
||||
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, 600);
|
||||
_caching.AddObject(rawKey, jumpData, 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "处理 BLE 消息异常");
|
||||
}
|
||||
});
|
||||
|
||||
// 心率每分钟保存一次
|
||||
if ((DateTime.Now - _lastHeartRateSaveTime).TotalSeconds >= 60 && _pendingHeartRates.Any())
|
||||
{
|
||||
_userContext.HeartRateData.AddRange(_pendingHeartRates);
|
||||
await _userContext.SaveChangesAsync();
|
||||
_pendingHeartRates.Clear();
|
||||
_lastHeartRateSaveTime = DateTime.Now;
|
||||
}
|
||||
_pendingHeartRates.AddRange(heartRateEntities);
|
||||
|
||||
// 跳绳每日更新保存
|
||||
|
||||
// 每隔 60 秒执行一次
|
||||
//_ = Task.Run(async () =>
|
||||
//{
|
||||
// while (true)
|
||||
// {
|
||||
// await SyncTodayJumpDataToDbAsync();
|
||||
// await Task.Delay(10000); // 每 10 秒检查一次
|
||||
// }
|
||||
//});
|
||||
}
|
||||
}
|
||||
private async Task RunJumpSyncLoopAsync(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SyncTodayJumpDataToDbAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "跳绳数据同步异常");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// 如果是取消任务导致 delay 中断,直接退出
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_log.LogInformation("跳绳数据同步任务已退出");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private byte[]? ParseHexData(string hex)
|
||||
{
|
||||
try
|
||||
{
|
||||
int len = hex.Length / 2;
|
||||
var bytes = new byte[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
|
||||
return bytes;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<List<MqttMessage>> ReadBatchesAsync(
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
var buffer = new List<MqttMessage>(AppSettings.Mqtt.BatchSize);
|
||||
while (await _queue.Reader.WaitToReadAsync(ct))
|
||||
{
|
||||
while (_queue.Reader.TryRead(out var m))
|
||||
{
|
||||
buffer.Add(m);
|
||||
if (buffer.Count == AppSettings.Mqtt.BatchSize)
|
||||
{
|
||||
yield return buffer;
|
||||
buffer = new List<MqttMessage>(AppSettings.Mqtt.BatchSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 keys = RedisHelper.Keys($"jumpRope:active:*:{dateStr}");
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<UserContext>();
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var data = _caching.Get<JumpRopeData>(key);
|
||||
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)
|
||||
{
|
||||
if (data.JumpValue > exist.JumpValue)
|
||||
{
|
||||
exist.JumpValue = data.JumpValue;
|
||||
exist.ErrorNumber = data.ErrorNumber;
|
||||
exist.QuantityOfElectricity = data.QuantityOfElectricity;
|
||||
exist.ScoreTime = data.ScoreTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
dbContext.JumpRopeData.Add(data);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateException ex)
|
||||
{
|
||||
// 如果是唯一约束冲突,说明有并发插入
|
||||
// 可以忽略或重试更新
|
||||
var existRetry = await dbContext.JumpRopeData
|
||||
.FirstOrDefaultAsync(x => x.StudentNo == data.StudentNo && x.ScoreTime.Date == today);
|
||||
|
||||
if (existRetry != null && data.JumpValue > existRetry.JumpValue)
|
||||
{
|
||||
existRetry.JumpValue = data.JumpValue;
|
||||
existRetry.ErrorNumber = data.ErrorNumber;
|
||||
existRetry.QuantityOfElectricity = data.QuantityOfElectricity;
|
||||
existRetry.ScoreTime = data.ScoreTime;
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
_lastJumpRopeSaveTime = DateTime.Now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "SyncTodayJumpDataToDbAsync:数据同步错误。");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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+39d73477fa9f5a21da7997a3dd7c8944e0d40ed0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6bd396d622f20bc5ca99fb500d0466a90a7f409c")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("YD_AllHeartRates.Api")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Api")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@ -1 +1 @@
|
||||
7edb1c4b66ea7aaa8d8126de81bd88fca00f6b5a820f3c5d394050afeae97abc
|
||||
a3a980977d20bb57fa1f83ee6bf5d8f1bf2b6b14f55da2f2582dca10b8411b17
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net6.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v6.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb = true
|
||||
build_property.ProjectTypeGuids =
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
// <auto-generated/>
|
||||
global using global::Microsoft.AspNetCore.Builder;
|
||||
global using global::Microsoft.AspNetCore.Hosting;
|
||||
global using global::Microsoft.AspNetCore.Http;
|
||||
global using global::Microsoft.AspNetCore.Routing;
|
||||
global using global::Microsoft.Extensions.Configuration;
|
||||
global using global::Microsoft.Extensions.DependencyInjection;
|
||||
global using global::Microsoft.Extensions.Hosting;
|
||||
global using global::Microsoft.Extensions.Logging;
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Net.Http.Json;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.Http;
|
||||
global using Microsoft.AspNetCore.Routing;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Net.Http.Json;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
Binary file not shown.
@ -49,7 +49,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.200"
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net6.0": {
|
||||
@ -139,7 +139,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201\\RuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.2.25502.107\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -184,7 +184,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.200"
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net6.0": {
|
||||
@ -231,7 +231,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201\\RuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.2.25502.107\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\tangl\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\tangl\.nuget\packages\" />
|
||||
|
||||
@ -7217,7 +7217,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.200"
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net6.0": {
|
||||
@ -7307,7 +7307,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201\\RuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.2.25502.107\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "AiAfdq5dmDc=",
|
||||
"dgSpecHash": "yrRs/HTlGY8=",
|
||||
"success": true,
|
||||
"projectFilePath": "C:\\Users\\tangl\\Desktop\\Git\\YD_AllHeartRates.Api\\YD_AllHeartRates.Api\\YD_AllHeartRates.Api.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("YD_AllHeartRates.Commons")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+adb7a5132ba408ee1c99d16fb2ead9bed7a7ccc5")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6bd396d622f20bc5ca99fb500d0466a90a7f409c")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("YD_AllHeartRates.Commons")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Commons")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@ -1 +1 @@
|
||||
39e19d927f4b3fa147400b1bba96cb47a1b0af113b4d9bd138d8a25b4ed27817
|
||||
6ee32e37865a6a4bc603c1673def4e33a752eb3490ab8db8e6549d7513049ad8
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net6.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v6.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
Binary file not shown.
@ -45,7 +45,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.200"
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net6.0": {
|
||||
@ -92,7 +92,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201\\RuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.2.25502.107\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\tangl\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\tangl\.nuget\packages\" />
|
||||
|
||||
@ -1007,7 +1007,7 @@
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "9.0.200"
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net6.0": {
|
||||
@ -1054,7 +1054,7 @@
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201\\RuntimeIdentifierGraph.json"
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.2.25502.107\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "HLEndOEnoqo=",
|
||||
"dgSpecHash": "odNTO1WSCgk=",
|
||||
"success": true,
|
||||
"projectFilePath": "C:\\Users\\tangl\\Desktop\\Git\\YD_AllHeartRates.Api\\YD_AllHeartRates.Commons\\YD_AllHeartRates.Commons.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user