2026-03-16 10:33:46 +08:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
|
using Newtonsoft.Json;
|
2025-12-08 09:11:05 +08:00
|
|
|
|
using System;
|
2025-12-31 14:07:16 +08:00
|
|
|
|
using YD_AllHeartRates.Api.Context;
|
|
|
|
|
|
using YD_AllHeartRates.Api.Entitys;
|
2025-11-24 09:11:20 +08:00
|
|
|
|
using YD_AllHeartRates.Api.Services.Interface;
|
2025-12-31 14:07:16 +08:00
|
|
|
|
using YD_AllHeartRates.Api.Utilities;
|
2025-11-24 09:11:20 +08:00
|
|
|
|
using YD_AllHeartRates.Commons.Dto.DataPush;
|
2025-12-08 09:11:05 +08:00
|
|
|
|
using YD_AllHeartRates.Commons.MemoryCaches;
|
|
|
|
|
|
using YD_AllHeartRates.Commons.Utils;
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
|
|
|
|
|
namespace YD_AllHeartRates.Api.Middlewares
|
|
|
|
|
|
{
|
|
|
|
|
|
public class SportsRoutineReportJob : IHostedService, IDisposable
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IDataPushService _dataPushService;
|
|
|
|
|
|
private Timer _timer;
|
|
|
|
|
|
|
2025-12-08 09:11:05 +08:00
|
|
|
|
private readonly ICaching _caching;
|
2025-12-31 14:07:16 +08:00
|
|
|
|
public UserContext _userContext;
|
2025-12-08 09:11:05 +08:00
|
|
|
|
|
2025-11-24 09:11:20 +08:00
|
|
|
|
private static string[] ChangXing = new string[]
|
|
|
|
|
|
{
|
|
|
|
|
|
"曹贺淳",
|
|
|
|
|
|
"陈思禹",
|
|
|
|
|
|
"陈朱海",
|
|
|
|
|
|
"代宇若",
|
|
|
|
|
|
"丁淑雅",
|
|
|
|
|
|
"董静思",
|
|
|
|
|
|
"樊玉顺",
|
|
|
|
|
|
"方欣玥",
|
|
|
|
|
|
"宫恩佑",
|
|
|
|
|
|
"郭依诺",
|
2026-03-16 10:33:46 +08:00
|
|
|
|
//"黄子熙",
|
|
|
|
|
|
//"计永安",
|
|
|
|
|
|
//"蒋圣煊",
|
|
|
|
|
|
//"李锦娴",
|
|
|
|
|
|
//"李语汐",
|
|
|
|
|
|
//"李梓皓",
|
|
|
|
|
|
//"刘家耀",
|
|
|
|
|
|
//"龙正柏瑀",
|
|
|
|
|
|
//"罗昕",
|
|
|
|
|
|
//"聂新淼",
|
|
|
|
|
|
//"彭禹凡",
|
|
|
|
|
|
//"宋皓晨",
|
|
|
|
|
|
//"孙海翔",
|
|
|
|
|
|
//"唐瀚宇",
|
|
|
|
|
|
//"陶柔紫若",
|
|
|
|
|
|
//"万欣怡",
|
|
|
|
|
|
//"王心语",
|
|
|
|
|
|
//"王梓皓",
|
|
|
|
|
|
//"王梓煜",
|
|
|
|
|
|
//"徐子琪",
|
|
|
|
|
|
//"许穆晨",
|
|
|
|
|
|
//"杨温馨",
|
|
|
|
|
|
//"杨子墨",
|
|
|
|
|
|
//"袁洛霏",
|
|
|
|
|
|
//"张宸溪",
|
|
|
|
|
|
//"张羽曦",
|
|
|
|
|
|
//"赵昊辰",
|
|
|
|
|
|
//"周舒洋"
|
2025-11-24 09:11:20 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static string[] ZhengDa = new string[]
|
|
|
|
|
|
{
|
|
|
|
|
|
"沈懿轩",
|
|
|
|
|
|
"周宇飛",
|
|
|
|
|
|
"范昊瑜",
|
|
|
|
|
|
"张翼成",
|
|
|
|
|
|
"卫宇航",
|
|
|
|
|
|
"董书涵",
|
|
|
|
|
|
"吴彦君",
|
|
|
|
|
|
"朱灏辰",
|
|
|
|
|
|
"王叡之",
|
|
|
|
|
|
"施梓俊",
|
2026-03-16 10:33:46 +08:00
|
|
|
|
//"龚怡豪",
|
|
|
|
|
|
//"张子赫",
|
|
|
|
|
|
//"仇泽宇",
|
|
|
|
|
|
//"陈歆蕾",
|
|
|
|
|
|
//"朱旖文",
|
|
|
|
|
|
//"郭笑笑",
|
|
|
|
|
|
//"陈瑾萱",
|
|
|
|
|
|
//"蔡乐妮",
|
|
|
|
|
|
//"侯熙琰",
|
|
|
|
|
|
//"龚思涵",
|
|
|
|
|
|
//"朱瑾萱",
|
|
|
|
|
|
//"宋依涵",
|
|
|
|
|
|
//"顾渝尧",
|
|
|
|
|
|
//"张赛文",
|
|
|
|
|
|
//"沈美夕",
|
|
|
|
|
|
//"吴佳凤",
|
|
|
|
|
|
//"蒋添依",
|
|
|
|
|
|
//"茅苹苹",
|
|
|
|
|
|
//"李腾宇",
|
|
|
|
|
|
//"林少熙",
|
|
|
|
|
|
//"史梓晨",
|
|
|
|
|
|
//"丁文洁",
|
|
|
|
|
|
//"董利妍",
|
|
|
|
|
|
//"朱紫芸"
|
2025-11-24 09:11:20 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-31 14:07:16 +08:00
|
|
|
|
private readonly List<StudentCacheInfo> _students = new();
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
2025-12-31 14:07:16 +08:00
|
|
|
|
public SportsRoutineReportJob(IDataPushService dataPushService, UserContext userContext, ICaching caching)
|
2025-11-24 09:11:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
_dataPushService = dataPushService;
|
2025-12-31 14:07:16 +08:00
|
|
|
|
_userContext = userContext;
|
2025-12-08 09:11:05 +08:00
|
|
|
|
_caching = caching;
|
|
|
|
|
|
|
2025-12-31 14:07:16 +08:00
|
|
|
|
// Redis 中缓存键
|
|
|
|
|
|
const string redisKey = "StudentEduInfos";
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 尝试从 Redis 获取缓存
|
|
|
|
|
|
var cachedList = _caching.Get<List<StudentCacheInfo>>(redisKey);
|
|
|
|
|
|
if (cachedList != null && cachedList.Count > 0)
|
2025-12-08 09:11:05 +08:00
|
|
|
|
{
|
2025-12-31 14:07:16 +08:00
|
|
|
|
_students = cachedList;
|
|
|
|
|
|
Console.WriteLine("从 Redis 加载学生信息完成。");
|
|
|
|
|
|
return;
|
2025-12-08 09:11:05 +08:00
|
|
|
|
}
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
2025-12-31 14:07:16 +08:00
|
|
|
|
// 2. 缓存不存在 → 调用接口获取
|
|
|
|
|
|
var result = new List<StudentCacheInfo>();
|
|
|
|
|
|
|
2025-12-08 09:11:05 +08:00
|
|
|
|
// 正大中学
|
2025-11-24 09:11:20 +08:00
|
|
|
|
foreach (var name in ZhengDa)
|
|
|
|
|
|
{
|
2025-12-08 09:11:05 +08:00
|
|
|
|
var student = _dataPushService
|
|
|
|
|
|
.GetStudentEduId("上海市崇明区正大中学", "七年级", "5班", name, "")
|
2025-12-31 14:07:16 +08:00
|
|
|
|
.Result?
|
2025-12-08 09:11:05 +08:00
|
|
|
|
.FirstOrDefault();
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
|
|
|
|
|
if (student != null)
|
|
|
|
|
|
{
|
2025-12-31 14:07:16 +08:00
|
|
|
|
result.Add(new StudentCacheInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
School = "上海市崇明区正大中学",
|
|
|
|
|
|
Grade = "七年级",
|
|
|
|
|
|
ClassName = "5班",
|
|
|
|
|
|
Name = name,
|
|
|
|
|
|
EduId = student.EduId
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-08 09:11:05 +08:00
|
|
|
|
Console.WriteLine($"正大中学:[{DateTime.Now}] {name}: {student.EduId}");
|
2025-11-24 09:11:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 09:11:05 +08:00
|
|
|
|
// 长兴小学
|
2025-11-24 09:11:20 +08:00
|
|
|
|
foreach (var name in ChangXing)
|
|
|
|
|
|
{
|
2025-12-08 09:11:05 +08:00
|
|
|
|
var student = _dataPushService
|
|
|
|
|
|
.GetStudentEduId("上海市崇明区长兴小学", "二年级", "二年级2班", name, "")
|
2025-12-31 14:07:16 +08:00
|
|
|
|
.Result?
|
2025-12-08 09:11:05 +08:00
|
|
|
|
.FirstOrDefault();
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
|
|
|
|
|
if (student != null)
|
|
|
|
|
|
{
|
2025-12-31 14:07:16 +08:00
|
|
|
|
result.Add(new StudentCacheInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
School = "上海市崇明区长兴小学",
|
|
|
|
|
|
Grade = "二年级",
|
|
|
|
|
|
ClassName = "二年级2班",
|
|
|
|
|
|
Name = name,
|
|
|
|
|
|
EduId = student.EduId
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-08 09:11:05 +08:00
|
|
|
|
Console.WriteLine($"长兴小学:[{DateTime.Now}] {name}: {student.EduId}");
|
2025-11-24 09:11:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-08 09:11:05 +08:00
|
|
|
|
|
2025-12-31 14:07:16 +08:00
|
|
|
|
// 写入 Redis(直接存对象,不再存 json 字符串)
|
|
|
|
|
|
_caching.AddObject(redisKey, result);
|
|
|
|
|
|
_students = result;
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("学生信息已缓存到 Redis。");
|
2025-11-24 09:11:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
2026-03-16 10:33:46 +08:00
|
|
|
|
{
|
2025-11-24 09:11:20 +08:00
|
|
|
|
// 每分钟检查一次时间,看是否在8-16点并整点执行
|
|
|
|
|
|
_timer = new Timer(async _ => await DoWork(), null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task DoWork()
|
|
|
|
|
|
{
|
|
|
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
// 每天 8:30 ~ 18:30 每小时30分执行
|
|
|
|
|
|
if (now.Hour < 8 || now.Hour > 18 || now.Minute != 23)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var startTime = now.AddHours(-1);
|
|
|
|
|
|
var endTime = now;
|
2025-12-31 14:07:16 +08:00
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
Console.WriteLine($"[{now}] 开始统计心率数据 {startTime} ~ {endTime}");
|
|
|
|
|
|
|
|
|
|
|
|
try
|
2025-11-24 09:11:20 +08:00
|
|
|
|
{
|
2026-03-16 10:33:46 +08:00
|
|
|
|
// 1 查询1小时心率数据,并按分钟计算平均值
|
|
|
|
|
|
var list = await _userContext.HeartRateData
|
|
|
|
|
|
.Where(x => x.SchoolCode == AppSettings.SchoolCode && x.ScoreTime >= startTime && x.ScoreTime <= endTime)
|
|
|
|
|
|
.GroupBy(x => new
|
|
|
|
|
|
{
|
|
|
|
|
|
x.StudentNo,
|
|
|
|
|
|
Minute = new DateTime(
|
|
|
|
|
|
x.ScoreTime.Year,
|
|
|
|
|
|
x.ScoreTime.Month,
|
|
|
|
|
|
x.ScoreTime.Day,
|
|
|
|
|
|
x.ScoreTime.Hour,
|
|
|
|
|
|
x.ScoreTime.Minute,
|
|
|
|
|
|
0)
|
|
|
|
|
|
})
|
|
|
|
|
|
.Select(g => new
|
|
|
|
|
|
{
|
|
|
|
|
|
g.Key.StudentNo,
|
|
|
|
|
|
Minute = g.Key.Minute,
|
|
|
|
|
|
AvgValue = g.Average(x => x.Value),
|
|
|
|
|
|
AvgInstant = g.Average(x => x.InstantValue)
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 2 按学生分组(提高查找效率)
|
|
|
|
|
|
var groups = list
|
|
|
|
|
|
.GroupBy(x => x.StudentNo)
|
|
|
|
|
|
.ToDictionary(
|
|
|
|
|
|
x => x.Key,
|
|
|
|
|
|
x => x.OrderBy(t => t.Minute).ToList()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-11-24 09:11:20 +08:00
|
|
|
|
var data = new List<StudentData>();
|
|
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
// 3 生成推送数据
|
2025-12-31 14:07:16 +08:00
|
|
|
|
for (int i = 0; i < _students.Count; i++)
|
2025-11-24 09:11:20 +08:00
|
|
|
|
{
|
2026-03-16 10:33:46 +08:00
|
|
|
|
var stu = _students[i];
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
if (!groups.TryGetValue(stu.EduId, out var stuData))
|
|
|
|
|
|
continue;
|
2025-11-24 09:11:20 +08:00
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
var hdsjList = stuData.Select(x => new HeartRateRecordDto
|
2025-11-24 09:11:20 +08:00
|
|
|
|
{
|
2026-03-16 10:33:46 +08:00
|
|
|
|
SJSJ = x.Minute.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
|
XL = (int)Math.Round(x.AvgValue),
|
|
|
|
|
|
JXXL = (int)Math.Round(x.AvgInstant)
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 保证每个学生60条数据
|
|
|
|
|
|
if (hdsjList.Count == 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2025-11-24 09:11:20 +08:00
|
|
|
|
var student = new StudentData
|
|
|
|
|
|
{
|
|
|
|
|
|
SBID = (i + 1).ToString("D2"),
|
2025-12-31 14:07:16 +08:00
|
|
|
|
XSEDUID = stu.EduId,
|
2025-11-24 09:11:20 +08:00
|
|
|
|
HDSJ = hdsjList
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
data.Add(student);
|
2025-12-31 14:07:16 +08:00
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(
|
2026-03-16 10:33:46 +08:00
|
|
|
|
$"生成数据 → {stu.School}-{stu.Grade}-{stu.ClassName}-{stu.Name} | EduId:{stu.EduId} | {hdsjList.Count}条");
|
2025-11-24 09:11:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
// 4 推送数据
|
|
|
|
|
|
if (data.Count > 0)
|
2025-11-24 09:11:20 +08:00
|
|
|
|
{
|
2025-12-08 09:11:05 +08:00
|
|
|
|
var res = await _dataPushService.SportsRoutineReport(data);
|
2025-12-31 14:07:16 +08:00
|
|
|
|
|
2026-03-16 10:33:46 +08:00
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
$"[{DateTime.Now}] HeartRate job code:{res?.code} msg:{res?.msg} 学生数:{data.Count}");
|
2025-12-31 14:07:16 +08:00
|
|
|
|
}
|
2026-03-16 10:33:46 +08:00
|
|
|
|
else
|
2025-12-31 14:07:16 +08:00
|
|
|
|
{
|
2026-03-16 10:33:46 +08:00
|
|
|
|
Console.WriteLine($"[{DateTime.Now}] 无心率数据需要推送");
|
2025-12-31 14:07:16 +08:00
|
|
|
|
}
|
2025-11-24 09:11:20 +08:00
|
|
|
|
}
|
2026-03-16 10:33:46 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"[{DateTime.Now}] HeartRate job error:{ex.Message}");
|
|
|
|
|
|
}
|
2025-11-24 09:11:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
_timer?.Change(Timeout.Infinite, 0);
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
_timer?.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|