This commit is contained in:
tanglong 2025-07-07 10:41:29 +08:00
parent ee9116aea2
commit d6d2020f50
35 changed files with 414 additions and 184 deletions

View File

@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
@ -11,8 +12,10 @@ 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
{
@ -23,34 +26,38 @@ namespace YD_AllHeartRates.Api.Mqtt
{
private readonly IMqttClient _client;
private readonly ILogger _log;
private UserContext _userContext;
private SmartSportsContext _smartSportsContext;
private readonly UserContext _userContext;
private readonly SmartSportsContext _smartSportsContext;
private readonly ICaching _caching;
private List<StudentDto> _studentList = new List<StudentDto>();
private readonly List<StudentDto> _studentList = new();
private readonly List<HeartRateData> _pendingHeartRates = new();
private readonly Dictionary<string, JumpRopeData> _jumpDailyMap = new();
static readonly JsonSerializerOptions _jsonOpt = new()
{
PropertyNameCaseInsensitive = true
};
private DateTime _lastHeartRateSaveTime = DateTime.UtcNow;
private DateTime _lastJumpRopeSaveTime = DateTime.UtcNow;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="client"></param>
/// <param name="log"></param>
public MqttBackgroundService(IMqttClient client, ILogger<MqttBackgroundService> log, UserContext userContext, SmartSportsContext smartSportsContext)
private readonly Channel<MqttMessage> _queue = Channel.CreateUnbounded<MqttMessage>();
static readonly JsonSerializerOptions _jsonOpt = new() { PropertyNameCaseInsensitive = true };
public MqttBackgroundService(
IMqttClient client,
ILogger<MqttBackgroundService> log,
UserContext userContext,
SmartSportsContext smartSportsContext,
ICaching caching
)
{
_client = client;
_log = log;
_userContext = userContext;
_smartSportsContext = smartSportsContext;
_caching = caching;
_studentList = (from d in _smartSportsContext.Device
join s in _smartSportsContext.Student
on d.StudentNo equals s.StudentNo
join c in _smartSportsContext.Class
on s.ClassId equals c.Id
//where d.SchoolCode == "202501060001" && s.StudentStatus == 1
join s in _smartSportsContext.Student on d.StudentNo equals s.StudentNo
join c in _smartSportsContext.Class on s.ClassId equals c.Id
where s.StudentStatus == 1
select new StudentDto
{
@ -69,16 +76,8 @@ namespace YD_AllHeartRates.Api.Mqtt
}).ToList();
}
private readonly Channel<MqttMessage> _queue = Channel.CreateUnbounded<MqttMessage>();
/// <summary>
/// 消费队列
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 连接 MQTT
await _client.ConnectAsync(new MqttClientOptionsBuilder()
.WithTcpServer(AppSettings.Mqtt.Host, AppSettings.Mqtt.Port)
.Build(), stoppingToken);
@ -88,33 +87,25 @@ namespace YD_AllHeartRates.Api.Mqtt
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build(), stoppingToken);
_log.LogInformation("MQTT subscribed: {Topic}", AppSettings.Mqtt.Topic);
// 消息回调 → 入队
_client.ApplicationMessageReceivedAsync += e =>
{
var msg = new MqttMessage
{
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
};
return _queue.Writer.WriteAsync(msg, stoppingToken).AsTask();
};
// ✅ 启动消费循环(写入数据库)
//await foreach (var msg in _queue.Reader.ReadAllAsync(stoppingToken))
await foreach (var batch in ReadBatchesAsync(stoppingToken))
{
var heartRateEntities = new List<HeartRateData>();
var jumpRopeEntities = new List<JumpRopeData>();
foreach (var msg in batch) // batch 内部还是你业务消息
foreach (var msg in batch)
{
if (string.IsNullOrWhiteSpace(msg.Payload))
continue;
if (string.IsNullOrWhiteSpace(msg.Payload)) continue;
List<BlePayload>? list;
try
@ -127,59 +118,37 @@ namespace YD_AllHeartRates.Api.Mqtt
continue;
}
if (list is null || list.Count == 0)
continue;
if (list is null || list.Count == 0) continue;
var heartRateList = list.Where(x => x.BleName.Contains("GTY0")).ToList();
var jumpRopeList = list.Where(x => x.BleName.Contains("RS207")).ToList();
foreach (var ble in heartRateList)
{
if (string.IsNullOrWhiteSpace(ble.RawData))
continue;
if (string.IsNullOrWhiteSpace(ble.RawData)) continue;
var student = _studentList.FirstOrDefault(x => x.HeartRateId == ble.BleName);
if (student == null)
continue;
if (student == null || student.GradeId == 0 || student.ClassId == 0) continue;
if (student.GradeId == 0 || student.ClassId == 0)
continue;
var data = ParseHexData(ble.RawData);
if (data == null) continue;
int len = ble.RawData.Length / 2;
var data = new byte[len];
try
{
for (int i = 0; i < len; i++)
data[i] = Convert.ToByte(ble.RawData.Substring(i * 2, 2), 16);
}
catch (FormatException ex)
{
_log.LogWarning(ex, "rawData 非十六进制: {Raw}", ble.RawData);
continue;
}
// 2) 定位 0xCD 起始字段
int cd = Array.IndexOf(data, (byte)0xCD);
if (cd < 0 || data.Length < cd + 9)
{
_log.LogWarning("找不到 0xCD 或字段不足: {Raw}", ble.RawData);
continue;
}
// 3) 逐字段解析
int battery = data[cd + 1]; // 电量 %
int heartRate = data[cd + 2]; // 心率 bpm
if (cd < 0 || data.Length < cd + 9) continue;
int battery = data[cd + 1];
int heartRate = data[cd + 2];
if (heartRate == 0) continue;
// 4) 写实体
heartRateEntities.Add(new HeartRateData
var entity = new HeartRateData
{
ScoreTime = ble.Timestamp,
Code = ble.BleName,
GradeId = student.GradeId,
GradeName = student.GradeName,
ClassId = student?.ClassId ?? 0,
ClassId = student.ClassId,
ClassName = student.ClassName ?? "",
SchoolCode = student.SchoolCode ?? "",
Sex = student.Sex,
@ -188,92 +157,133 @@ namespace YD_AllHeartRates.Api.Mqtt
StudentNo = student.StudentNo,
StudentName = student.StudentName,
Strength = (int)Math.Round(((double)heartRate / (220 - student.Age)) * 100)
});
}
};
heartRateEntities.Add(entity);
var heartRateKey = $"heartRate:{student.StudentNo}";
_caching.AddObject(heartRateKey, entity, 600); // 10分钟缓存
// 更新学校学生编号集合Set
var studentSetKey = $"school:{student.SchoolCode}:students";
RedisHelper.SAdd(studentSetKey, student.StudentNo); // 自动去重
}
foreach (var ble in jumpRopeList)
{
if (string.IsNullOrWhiteSpace(ble.RawData))
continue;
if (string.IsNullOrWhiteSpace(ble.RawData)) continue;
var student = _studentList.FirstOrDefault(x => x.JumpRopeId == ble.BleName);
if (student == null)
continue;
if (student == null || student.GradeId == 0 || student.ClassId == 0) continue;
if (student.GradeId == 0 || student.ClassId == 0)
continue;
var data = ParseHexData(ble.RawData);
if (data == null) continue;
int len = ble.RawData.Length / 2;
var data = new byte[len];
try
int mfIndex = Array.IndexOf(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 errorCount = data[mfIndex + 7];
int battery = data[mfIndex + 9];
var jumpData = new JumpRopeData
{
for (int i = 0; i < len; i++)
data[i] = Convert.ToByte(ble.RawData.Substring(i * 2, 2), 16);
}
catch (FormatException ex)
{
_log.LogWarning(ex, "rawData 非十六进制: {Raw}", ble.RawData);
continue;
}
ScoreTime = ble.Timestamp,
Code = ble.BleName,
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
};
int mfIndex = -1;
for (int i = 0; i < data.Length - 4; i++)
{
if (data[i] == 0xFF && data[i + 1] == 0x04 && data[i + 2] == 0xFF && data[i + 3] == 0xCF)
{
mfIndex = i + 4; // CF 后第一个字节是数据起始位置
break;
}
}
//if (!string.IsNullOrWhiteSpace(student.SchoolCode))
//{
// // WebSocket 推送
// await _wsSender.SendToSchoolAsync(student.SchoolCode, "heartRate", heartRateEntities);
// await _wsSender.SendToSchoolAsync(student.SchoolCode, "jumpRope", _jumpDailyMap.Values.ToList());
//}
// 确保数据长度足够读取所有字段
if (mfIndex >= 0 && data.Length > mfIndex + 5)
string key = $"{jumpData.StudentNo}_{jumpData.ScoreTime:yyyyMMdd}";
if (_jumpDailyMap.ContainsKey(key))
{
// 解析跳绳个数,低位 + 高位 << 8
int jumpCount = data[mfIndex + 1] + (data[mfIndex + 2] << 8);
int errorCount = data[mfIndex + 3];
int battery = data[mfIndex + 5];
// 添加跳绳数据实体
jumpRopeEntities.Add(new JumpRopeData
{
ScoreTime = ble.Timestamp,
Code = ble.BleName,
GradeId = student.GradeId,
GradeName = student.GradeName,
ClassId = student?.ClassId ?? 0,
ClassName = student.ClassName ?? "",
SchoolCode = student.SchoolCode ?? "",
Sex = student.Sex,
JumpValue = jumpCount,
QuantityOfElectricity = battery,
ErrorNumber = errorCount,
StudentNo = student.StudentNo,
StudentName = student.StudentName
});
_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, 600);
RedisHelper.SAdd(studentSetKey, student.StudentNo);
}
}
// 心率
if (heartRateEntities.Count > 0)
_userContext.HeartRateData.AddRange(heartRateEntities);
// 心率每分钟保存一次
if ((DateTime.UtcNow - _lastHeartRateSaveTime).TotalSeconds >= 60 && _pendingHeartRates.Any())
{
_userContext.HeartRateData.AddRange(_pendingHeartRates);
await _userContext.SaveChangesAsync();
_pendingHeartRates.Clear();
_lastHeartRateSaveTime = DateTime.UtcNow;
}
_pendingHeartRates.AddRange(heartRateEntities);
// 跳绳
if (jumpRopeEntities.Count > 0)
_userContext.JumpRopeData.AddRange(jumpRopeEntities);
await _userContext.SaveChangesAsync();
// 跳绳每日更新保存
if ((DateTime.UtcNow - _lastJumpRopeSaveTime).TotalSeconds >= 60 && _jumpDailyMap.Any())
{
foreach (var data in _jumpDailyMap.Values)
{
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 _userContext.SaveChangesAsync();
_lastJumpRopeSaveTime = DateTime.UtcNow;
}
}
}
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)
[EnumeratorCancellation] CancellationToken ct)
{
var buffer = new List<MqttMessage>(AppSettings.Mqtt.BatchSize);
while (await _queue.Reader.WaitToReadAsync(ct))
{
while (_queue.Reader.TryRead(out var m))
@ -286,7 +296,6 @@ namespace YD_AllHeartRates.Api.Mqtt
}
}
}
if (buffer.Count > 0) yield return buffer;
}
}

View File

@ -2,11 +2,14 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using System.Drawing;
using System.Reflection.PortableExecutable;
using YD_AllHeartRates.Api.Context;
using YD_AllHeartRates.Api.Entitys;
using YD_AllHeartRates.Api.Services.Interface;
using YD_AllHeartRates.Api.SmartSportsEntitys;
using YD_AllHeartRates.Api.Utilities;
using YD_AllHeartRates.Commons.Dto.LargeScreen;
using YD_AllHeartRates.Commons.MemoryCaches;
namespace YD_AllHeartRates.Api.Services.Impl
{
@ -19,15 +22,18 @@ namespace YD_AllHeartRates.Api.Services.Impl
public UserContext _userContext;
private readonly LoginContext _loginContext;
private string schoolCode;
private readonly ICaching _caching;
/// <summary>
/// 构造
/// </summary>
public LargeScreenService(SmartSportsContext sportsContext, UserContext userContext, LoginContext loginContext)
public LargeScreenService(SmartSportsContext sportsContext, UserContext userContext, LoginContext loginContext, ICaching caching)
{
_sportsContext = sportsContext;
_userContext = userContext;
_loginContext = loginContext;
_caching = caching;
schoolCode = _loginContext.SchoolCode;
}
@ -138,46 +144,78 @@ namespace YD_AllHeartRates.Api.Services.Impl
{
var res = new HeartRateDataDto();
var tenMinutesAgo = DateTime.Now.AddMinutes(-1);
var now = DateTime.Now;
var tenMinutesAgo = now.AddMinutes(-10);
var studentList = await _sportsContext.Student.Where(x => x.ClassId == classId && x.SchoolCode == schoolCode).ToListAsync();
// 班级学生列表
var studentList = await _sportsContext.Student
.Where(x => x.ClassId == classId && x.SchoolCode == schoolCode && x.StudentStatus == 1)
.ToListAsync();
var heartRateData = await _userContext.HeartRateData
.Where(x => x.SchoolCode == schoolCode && x.ScoreTime >= tenMinutesAgo)
.GroupBy(x => x.StudentNo)
.Select(g => g.OrderByDescending(x => x.ScoreTime).FirstOrDefault())
.ToListAsync();
var jumpingRopeData = await _userContext.JumpRopeData
.Where(x => x.SchoolCode == schoolCode && x.ScoreTime >= tenMinutesAgo)
.GroupBy(x => x.StudentNo)
.Select(g => g.OrderByDescending(x => x.ScoreTime).FirstOrDefault())
.ToListAsync();
res.WarmUp = studentList.Count == 0 ? 0 : (int)Math.Round(heartRateData.Count(x => x.Strength < 50) * 100.0 / studentList.Count);
res.Low = studentList.Count == 0 ? 0 : (int)Math.Round(heartRateData.Count(x => x.Strength >= 50 && x.Strength < 60) * 100.0 / studentList.Count);
res.Medium = studentList.Count == 0 ? 0 : (int)Math.Round(heartRateData.Count(x => x.Strength >= 60 && x.Strength < 70) * 100.0 / studentList.Count);
res.High = studentList.Count == 0 ? 0 : (int)Math.Round(heartRateData.Count(x => x.Strength >= 70 && x.Strength < 85) * 100.0 / studentList.Count);
res.Warning = studentList.Count == 0 ? 0 : (int)Math.Round(heartRateData.Count(x => x.Strength >= 85) * 100.0 / studentList.Count);
int warmUp = 0, low = 0, medium = 0, high = 0, warning = 0;
foreach (var student in studentList)
{
var heartRate = heartRateData.FirstOrDefault(x => x.StudentNo == student.StudentNo);
var jumpingRope = jumpingRopeData.FirstOrDefault(x => x.StudentNo == student.StudentNo);
var heartRateKey = $"heartRate:{student.StudentNo}";
var jumpRopeKey = $"jumpRope:{student.StudentNo}";
res.StudentList.Add(new StudentDto()
// 先从缓存拿
var heartRate = _caching.Get<HeartRateData>(heartRateKey);
var jumpRope = _caching.Get<JumpRopeData>(jumpRopeKey);
// ❗心率缓存未命中 → 单独查数据库
if (heartRate == null)
{
heartRate = await _userContext.HeartRateData
.Where(x => x.StudentNo == student.StudentNo && x.ScoreTime >= tenMinutesAgo)
.OrderByDescending(x => x.ScoreTime)
.FirstOrDefaultAsync();
if (heartRate != null)
_caching.AddObject(heartRateKey, heartRate, 600);
}
// ❗跳绳缓存未命中 → 单独查数据库
if (jumpRope == null)
{
jumpRope = await _userContext.JumpRopeData
.Where(x => x.StudentNo == student.StudentNo && x.ScoreTime.Date == now.Date)
.OrderByDescending(x => x.ScoreTime)
.FirstOrDefaultAsync();
if (jumpRope != null)
_caching.AddObject(jumpRopeKey, jumpRope, 600);
}
// 心率强度判断
int strength = heartRate?.Strength ?? 0;
if (strength < 50) warmUp++;
else if (strength < 60) low++;
else if (strength < 70) medium++;
else if (strength < 85) high++;
else warning++;
res.StudentList.Add(new StudentDto
{
StudentName = student.StudentName,
StudentNo = student?.StudentNo ?? "",
Photo = student?.Photo ?? "",
StudentNo = student.StudentNo,
Photo = student.Photo ?? "",
Sex = student.Sex,
HeartRate = heartRate?.Value ?? 0,
JumpingRope = jumpingRope?.JumpValue ?? 0,
Strength = heartRate?.Strength ?? 0
JumpingRope = jumpRope?.JumpValue ?? 0,
Strength = strength
});
}
int total = studentList.Count == 0 ? 1 : studentList.Count;
res.WarmUp = (int)Math.Round(warmUp * 100.0 / total);
res.Low = (int)Math.Round(low * 100.0 / total);
res.Medium = (int)Math.Round(medium * 100.0 / total);
res.High = (int)Math.Round(high * 100.0 / total);
res.Warning = (int)Math.Round(warning * 100.0 / total);
return res;
}
}
}

View File

@ -66,7 +66,7 @@ namespace YD_AllHeartRates.Api.Services.Impl
};
}
//GTY0|RS207
/// <summary>
/// 获取个人信息
/// </summary>

View File

@ -27,6 +27,7 @@ using MQTTnet;
using YD_AllHeartRates.Api.Mqtt;
using YD_AllHeartRates.Commons.MemoryCaches;
using YD_AllHeartRates.Api.Middlewares;
using YD_AllHeartRates.Api.WebSocket;
namespace YD_AllHeartRates.Api
{
@ -235,13 +236,14 @@ namespace YD_AllHeartRates.Api
"https://*/404";
});
services.AddSignalR();
services.AddSingleton<IWebSocketSender, SignalRWebSocketSender>();
services.AddHttpClient();
services.AddAutoMapper(typeof(MappingProfile));
services.AddLogging();
//设置文件上传大小限制
//设置文件上传大小限制
services.Configure<FormOptions>(x =>
{

View File

@ -0,0 +1,25 @@
namespace YD_AllHeartRates.Api.WebSocket
{
/// <summary>
/// WebSocket
/// </summary>
public interface IWebSocketSender
{
/// <summary>
/// 消息发送
/// </summary>
/// <param name="channel"></param>
/// <param name="data"></param>
/// <returns></returns>
Task SendAsync(string channel, object data);
/// <summary>
/// 根据学校发送消息
/// </summary>
/// <param name="schoolCode"></param>
/// <param name="channel"></param>
/// <param name="data"></param>
/// <returns></returns>
Task SendToSchoolAsync(string schoolCode, string channel, object data);
}
}

View File

@ -0,0 +1,50 @@
using Microsoft.AspNetCore.SignalR;
namespace YD_AllHeartRates.Api.WebSocket
{
/// <summary>
/// RealtimeHub
/// </summary>
public class RealtimeHub : Hub
{
/// <summary>
/// 客户端连接时调用(可选)
/// </summary>
public override async Task OnConnectedAsync()
{
var connectionId = Context.ConnectionId;
Console.WriteLine($"客户端连接:{connectionId}");
await base.OnConnectedAsync();
}
/// <summary>
/// 客户端断开连接时调用(可选)
/// </summary>
public override async Task OnDisconnectedAsync(Exception? exception)
{
var connectionId = Context.ConnectionId;
Console.WriteLine($"客户端断开连接:{connectionId}");
await base.OnDisconnectedAsync(exception);
}
/// <summary>
/// 客户端可调用的方法:加入指定分组(如按学校、班级)
/// </summary>
public async Task JoinGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
Console.WriteLine($"加入分组:{groupName}");
}
/// <summary>
/// 客户端可调用的方法:离开指定分组
/// </summary>
public async Task LeaveGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
Console.WriteLine($"离开分组:{groupName}");
}
}
}

View File

@ -0,0 +1,45 @@
using Microsoft.AspNetCore.SignalR;
namespace YD_AllHeartRates.Api.WebSocket
{
/// <summary>
/// 发送SignalRWebSocket
/// </summary>
public class SignalRWebSocketSender : IWebSocketSender
{
private readonly IHubContext<RealtimeHub> _hubContext;
/// <summary>
/// hubContext
/// </summary>
/// <param name="hubContext"></param>
public SignalRWebSocketSender(IHubContext<RealtimeHub> hubContext)
{
_hubContext = hubContext;
}
/// <summary>
/// 消息发送
/// </summary>
/// <param name="channel"></param>
/// <param name="data"></param>
/// <returns></returns>
public async Task SendAsync(string channel, object data)
{
await _hubContext.Clients.All.SendAsync(channel, data);
}
/// <summary>
/// 根据学校发送消息
/// </summary>
/// <param name="schoolCode"></param>
/// <param name="channel"></param>
/// <param name="data"></param>
/// <returns></returns>
public async Task SendToSchoolAsync(string schoolCode, string channel, object data)
{
string groupName = $"school_{schoolCode}";
await _hubContext.Clients.Group(groupName).SendAsync(channel, data);
}
}
}

View File

@ -1 +1 @@
kpAOGOwQr+PqcqSkET2ftxen78nx6+ufMJyhzNQMesU=WWmIfOUbyYPYdKFHlVpzo+vbEnfIsJNTTcG8+oWdtSc=
OZ2FMHhzcKLaTsTH79CqMYu8/tuIh7oBcr70vB907z4=WWmIfOUbyYPYdKFHlVpzo+vbEnfIsJNTTcG8+oWdtSc=

View File

@ -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+c65f0d665eff3d83c1cdb0eaf581369baecbe26a")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ee9116aea22701bf6d184657d96c4e24b2bc0095")]
[assembly: System.Reflection.AssemblyProductAttribute("YD_AllHeartRates.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@ -1 +1 @@
0edc1169727cbac8f4192a409031e62e7df5ab080dd285125c8d176be132b106
1c24cdf25780b49d3cd275d101f9545d4bc591310051a502fe2c6f782554626d

View File

@ -1 +1 @@
e1c1286248133e4f873378c81bc1cba408058a1c11789d81f9a4f37908ac6243
be24640c820a322f6ed07b42336b9100bbed9b708418756cbe716ef9eb7afbd3

View File

@ -426,20 +426,6 @@
接受写入数据
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.Mqtt.MqttBackgroundService.#ctor(MQTTnet.Client.IMqttClient,Microsoft.Extensions.Logging.ILogger{YD_AllHeartRates.Api.Mqtt.MqttBackgroundService},YD_AllHeartRates.Api.Context.UserContext,YD_AllHeartRates.Api.Context.SmartSportsContext)">
<summary>
构造函数
</summary>
<param name="client"></param>
<param name="log"></param>
</member>
<member name="M:YD_AllHeartRates.Api.Mqtt.MqttBackgroundService.ExecuteAsync(System.Threading.CancellationToken)">
<summary>
消费队列
</summary>
<param name="stoppingToken"></param>
<returns></returns>
</member>
<member name="T:YD_AllHeartRates.Api.Services.Impl.DeviceService">
<summary>
服务实现
@ -533,7 +519,7 @@
服务实现
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.Services.Impl.LargeScreenService.#ctor(YD_AllHeartRates.Api.Context.SmartSportsContext,YD_AllHeartRates.Api.Context.UserContext,YD_AllHeartRates.Api.Utilities.LoginContext)">
<member name="M:YD_AllHeartRates.Api.Services.Impl.LargeScreenService.#ctor(YD_AllHeartRates.Api.Context.SmartSportsContext,YD_AllHeartRates.Api.Context.UserContext,YD_AllHeartRates.Api.Utilities.LoginContext,YD_AllHeartRates.Commons.MemoryCaches.ICaching)">
<summary>
构造
</summary>
@ -1385,5 +1371,80 @@
每个UserContext的属性至多读取一次redis或Memory缓存从而提高查询效率
</summary>
</member>
<member name="T:YD_AllHeartRates.Api.WebSocket.IWebSocketSender">
<summary>
WebSocket
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.IWebSocketSender.SendAsync(System.String,System.Object)">
<summary>
消息发送
</summary>
<param name="channel"></param>
<param name="data"></param>
<returns></returns>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.IWebSocketSender.SendToSchoolAsync(System.String,System.String,System.Object)">
<summary>
根据学校发送消息
</summary>
<param name="schoolCode"></param>
<param name="channel"></param>
<param name="data"></param>
<returns></returns>
</member>
<member name="T:YD_AllHeartRates.Api.WebSocket.RealtimeHub">
<summary>
RealtimeHub
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.RealtimeHub.OnConnectedAsync">
<summary>
客户端连接时调用(可选)
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.RealtimeHub.OnDisconnectedAsync(System.Exception)">
<summary>
客户端断开连接时调用(可选)
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.RealtimeHub.JoinGroup(System.String)">
<summary>
客户端可调用的方法:加入指定分组(如按学校、班级)
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.RealtimeHub.LeaveGroup(System.String)">
<summary>
客户端可调用的方法:离开指定分组
</summary>
</member>
<member name="T:YD_AllHeartRates.Api.WebSocket.SignalRWebSocketSender">
<summary>
发送SignalRWebSocket
</summary>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.SignalRWebSocketSender.#ctor(Microsoft.AspNetCore.SignalR.IHubContext{YD_AllHeartRates.Api.WebSocket.RealtimeHub})">
<summary>
hubContext
</summary>
<param name="hubContext"></param>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.SignalRWebSocketSender.SendAsync(System.String,System.Object)">
<summary>
消息发送
</summary>
<param name="channel"></param>
<param name="data"></param>
<returns></returns>
</member>
<member name="M:YD_AllHeartRates.Api.WebSocket.SignalRWebSocketSender.SendToSchoolAsync(System.String,System.String,System.Object)">
<summary>
根据学校发送消息
</summary>
<param name="schoolCode"></param>
<param name="channel"></param>
<param name="data"></param>
<returns></returns>
</member>
</members>
</doc>

View File

@ -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+580d586b2d20254267b1182a8ceff393c252da81")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ee9116aea22701bf6d184657d96c4e24b2bc0095")]
[assembly: System.Reflection.AssemblyProductAttribute("YD_AllHeartRates.Commons")]
[assembly: System.Reflection.AssemblyTitleAttribute("YD_AllHeartRates.Commons")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@ -1 +1 @@
35d58a7d90dcca1c52be85a8d3e3e2c8fc425c6c7ec2830346ca890579682685
ad77ca7765704675fc50e971094eeabaa926244d017d6f5a36917f704f444668