using Azure.Cosmos; using MathNet.Numerics.LinearAlgebra.Double; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using TEAMModelBI.Tool; using TEAMModelOS.SDK.Context.BI; using TEAMModelOS.SDK.DI; using TEAMModelOS.SDK.Extension; using TEAMModelOS.SDK.Models; using TEAMModelOS.SDK.Models.Cosmos.BI.BISchool; using TEAMModelOS.SDK.Models.Cosmos.BI.BITable; using TEAMModelOS.SDK.Models.Service.BI; namespace TEAMModelBI.Controllers.BISchool { [Route("Lesson")] [ApiController] public class LessonController : ControllerBase { private readonly AzureCosmosFactory _azureCosmos; private readonly AzureStorageFactory _azureStorage; private readonly AzureRedisFactory _azureRedis; private readonly DingDing _dingDing; public LessonController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing) { _azureCosmos = azureCosmos; _azureStorage = azureStorage; _azureRedis = azureRedis; _dingDing = dingDing; } [HttpPost("get-less")] public async Task GetLess(JsonElement jsonElement) { if (!jsonElement.TryGetProperty("school", out JsonElement school)) return BadRequest(); var table = _azureStorage.GetCloudTableClient().GetTableReference("BIStats"); var tableSql = $" PartitionKey eq 'LessonHour' and school eq '{school}'"; //lambda 表达式排序 var lessStats = await table.QueryWhereString(tableSql.ToString()); DenseMatrix openDense = null; DenseMatrix lessDense = null; List> tempOpens = new(); List> tempLesss = new(); foreach (var item in lessStats) { if (item.open != null) { List opens = item.open.Split(',').ToList().ConvertAll(c => Convert.ToDouble(c)); tempOpens.Add(opens); } if(item.lesson != null) { List lesss = item.lesson.Split(',').ToList().ConvertAll(c => Convert.ToDouble(c)); tempLesss.Add(lesss); } } openDense = DenseMatrix.OfColumns(tempOpens); lessDense = DenseMatrix.OfColumns(tempLesss); return Ok(new { state = 200 , openDense ,lessDense}); } /// /// 测试 通过时间戳保存统计类型接口 /// /// /// [HttpPost("set-bilessonstats")] public async Task SetBILeesonStats(JsonElement jsonElement) { if (!jsonElement.TryGetProperty("unix", out JsonElement unix)) return BadRequest(); if (!jsonElement.TryGetProperty("num", out JsonElement num)) return BadRequest(); jsonElement.TryGetProperty("schoolId", out JsonElement schoolId); jsonElement.TryGetProperty("type", out JsonElement type); jsonElement.TryGetProperty("unixs", out JsonElement unixs); var cosmosClient = _azureCosmos.GetCosmosClient(); DateTime expireYear = DateTime.UtcNow.AddYears(1); //一个月到期 var UUID = Guid.NewGuid().ToString(); List unixsT = unixs.ToObject>(); RedisValue token = Environment.MachineName; foreach (var item in unixsT) { //await SetBILeesonStats(cosmosClient, item, num.GetInt32(), type: type.GetInt32(), schoolId: schoolId.GetString()); //await BILeeson.SetCosmosDBStats(cosmosClient, _azureRedis, _dingDing, item, num.GetInt32(), type: type.GetInt32(), schoolId: schoolId.GetString()); await BILeeson.SetTableStats(_azureStorage, _azureRedis, _dingDing, item, num.GetInt32(), type: type.GetInt32(), schoolId: schoolId.GetString()); } //await BILeeson.SetBILeesonStats(cosmosClient, _azureRedis, unix.GetInt64(), num.GetInt32(), type: type.GetInt32(), schoolId: schoolId.GetString()); //await SetBILeesonStats(cosmosClient, unix.GetInt64(), num.GetInt32(), type: -1, schoolId: schoolId.GetString()); return Ok(new { state = 200 }); } /// /// 通过时间戳保存统计类型 /// /// 连接字符串 /// 时间戳 /// 加减数量,可为负数 /// 课例类型,0开课 1 课例 /// 学校ID,可不传 /// public static async Task SetBILeesonStats(CosmosClient cosmosClient, long unix, int num, int type = 0, string schoolId = null) { //Monitor.TryEnter(unix); //锁对象 SemaphoreSlim slimlock = new(1, 1); //对可同时访问资源或资源池的线程数加以限制 await slimlock.WaitAsync(); DateTimeOffset dateTime = DateTimeOffset.FromUnixTimeMilliseconds(unix); int year, month, day, hour, days; year = dateTime.Year; month = dateTime.Month; day = dateTime.Day; hour = dateTime.Hour; string hourId = $"{year}{month.ToString().PadLeft(2, '0')}{day.ToString().PadLeft(2, '0')}"; var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; days = dateTime.DayOfYear; double[] daDays = new double[yearDays]; daDays[days] = num; List yDays = new(daDays); double[] daHours = new double[23]; daHours[hour] = num; List yHours = new(daHours); var openRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{year}", new PartitionKey("LessonYear")); if (openRes.Status == 200) { using var json = await JsonDocument.ParseAsync(openRes.ContentStream); LessonStats lessonYear = json.ToObject(); if (type == 0) { if (lessonYear.open.Count == 0) lessonYear.open = yDays; else lessonYear.open[days] = lessonYear.open[days] + num; } else if (type == 1) { if (lessonYear.lesson.Count == 0) lessonYear.lesson = yDays; else lessonYear.lesson[days] = lessonYear.lesson[days] + num; } await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(lessonYear, lessonYear.id, new PartitionKey($"{lessonYear.code}")); } else { LessonStats lessonYear = new(); lessonYear.id = $"{year}"; lessonYear.code = "LessonYear"; lessonYear.pk = "LessonYear"; if (type == 0) lessonYear.open = yDays; else if (type == 1) lessonYear.lesson = yDays; await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync(lessonYear, new PartitionKey($"{lessonYear.code}")); } var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{hourId}", new PartitionKey("LessonHour")); if (lessRes.Status == 200) { using var json = await JsonDocument.ParseAsync(lessRes.ContentStream); LessonStats lessonHour = json.ToObject(); if (type == 0) { if (lessonHour.open.Count == 0) lessonHour.open = yHours; else lessonHour.open[hour] = lessonHour.open[hour] + num; } else if (type == 1) { if (lessonHour.lesson.Count == 0) lessonHour.lesson = yHours; else lessonHour.lesson[hour] = lessonHour.lesson[hour] + num; } await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(lessonHour, lessonHour.id, new PartitionKey($"LessonHour")); } else { LessonStats lessonHour = new(); lessonHour.id = $"{hourId}"; lessonHour.code = "LessonHour"; lessonHour.pk = "LessonHour"; if (type == 0) lessonHour.open = yDays; else if (type == 1) lessonHour.lesson = yDays; await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync(lessonHour, new PartitionKey("LessonHour")); } //学校统计 if (!string.IsNullOrEmpty(schoolId)) { var openResSc = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{year}", new PartitionKey($"LessonYear-{schoolId}")); if (openResSc.Status == 200) { using var jsonSc = await JsonDocument.ParseAsync(openResSc.ContentStream); LessonStats lessonYearSc = jsonSc.ToObject(); if (type == 0) if (lessonYearSc.open.Count == 0) lessonYearSc.open = yDays; else lessonYearSc.open[days] = lessonYearSc.open[days] + num; else if (type == 1) { if (lessonYearSc.lesson.Count == 0) lessonYearSc.lesson = yDays; else lessonYearSc.lesson[days] = lessonYearSc.lesson[days] + num; } await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(lessonYearSc, lessonYearSc.id, new PartitionKey($"LessonYear-{schoolId}")); } else { LessonStats lessonYearSc = new(); lessonYearSc.id = $"{year}"; lessonYearSc.code = $"LessonYear-{schoolId}"; lessonYearSc.pk = "LessonYear"; if (type == 0) lessonYearSc.open = yDays; else if (type == 1) lessonYearSc.lesson = yDays; await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync(lessonYearSc, new PartitionKey($"LessonYear-{schoolId}")); } var lessResSc = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{hourId}", new PartitionKey($"LessonHour-{schoolId}")); if (lessResSc.Status == 200) { using var jsonSc = await JsonDocument.ParseAsync(lessResSc.ContentStream); LessonStats lessonHourSc = jsonSc.ToObject(); if (type == 0) if (lessonHourSc.open.Count == 0) lessonHourSc.open = yDays; else lessonHourSc.open[days] = lessonHourSc.open[days] + num; else if (type == 1) { if (lessonHourSc.lesson.Count == 0) lessonHourSc.lesson = yHours; else lessonHourSc.lesson[hour] = lessonHourSc.lesson[hour] + num; } await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(lessonHourSc, lessonHourSc.id, new PartitionKey($"LessonHour-{schoolId}")); } else { LessonStats lessonYearSc = new(); lessonYearSc.id = $"{hourId}"; lessonYearSc.code = $"LessonHour-{schoolId}"; lessonYearSc.pk = "LessonHour"; if (type == 0) lessonYearSc.open = yHours; else if (type == 1) lessonYearSc.lesson = yHours; await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync(lessonYearSc, new PartitionKey($"LessonHour-{schoolId}")); } } slimlock.Release(); //Monitor.Enter(unix); } /// /// 历史记录读取,并统计存储 /// /// /// [HttpPost("get-alllesson")] public async Task GetAllLessonRecords(JsonElement jsonElement) { jsonElement.TryGetProperty("site", out JsonElement site); var cosmosClient = _azureCosmos.GetCosmosClient(); if ($"{site}".Equals(BIConst.Global)) cosmosClient = _azureCosmos.GetCosmosClient(name: BIConst.Global); List allLesson = new(); await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator(queryText: $"select value(c) from c where c.pk='LessonRecord'", requestOptions: new QueryRequestOptions() { })) { allLesson.Add(item); } await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator(queryText: $"select value(c) from c where c.pk='LessonRecord'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("LessonRecord") })) { allLesson.Add(item); } (List lessYears, List lessHours) = await AllLessonStats(allLesson, _azureRedis); foreach (var itemY in lessYears) { var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(itemY.id, new PartitionKey($"{itemY.code}")); if (response.Status == 200) { using var json = await JsonDocument.ParseAsync(response.ContentStream); LessonStats lessonYear = json.ToObject(); lessonYear.open = itemY.open; lessonYear.lesson = itemY.lesson; await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(lessonYear, lessonYear.id, new PartitionKey($"{lessonYear.code}")); } else await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync(itemY, new PartitionKey($"{itemY.code}")); } foreach (var itemH in lessHours) { var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(itemH.id, new PartitionKey($"{itemH.code}")); if (response.Status == 200) { using var json = await JsonDocument.ParseAsync(response.ContentStream); LessonStats lessonHour = json.ToObject(); lessonHour.open = itemH.open; lessonHour.lesson = itemH.lesson; await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(lessonHour, lessonHour.id, new PartitionKey($"{lessonHour.code}")); } else await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync(itemH, new PartitionKey($"{itemH.code}")); } return Ok(new { state = 200, ycnt = lessYears.Count, hcnt = lessHours.Count, lessYears, lessHours }); } #region 课例历史记录统计 /// /// 统计所有课例 /// /// /// public static async Task<(List lessonYears, List lessonHours)> AllLessonStats(List lessRecs, AzureRedisFactory azureRedis) { //统计所有 List allLess = lessRecs.Select(item => new LessCnt { hour = TimeHelper.GetDateTime(item.startTime).ToString("yyyyMMddHH"), cat = item.id, upload = item.upload, schoolId = item.school }).ToList(); var openL = allLess.FindAll(f => f.upload == 0).GroupBy(g => g.hour).Select(s => new AllLess { key = s.Key, cnt = s.Count() }).ToList(); var lessL = allLess.FindAll(s => s.upload == 1).GroupBy(g => g.hour).Select(s => new AllLess { key = s.Key, cnt = s.Count() }).ToList(); List lessYears = new(); List lessHours = new(); //统计开课记录 openL.ForEach(x => { var (year, month, day, days, hour) = TimeHelper.GetDateTime(x.key); string id = $"{year}{month.ToString().PadLeft(2, '0')}{day.ToString().PadLeft(2, '0')}"; LessonStats finHour = lessHours.Find(f => f.id.Equals(id)); if (finHour != null) { if (finHour.open.Count == 0) { double[] da = new double[23]; da[hour] = x.cnt; List tempDays = new(da); finHour.open = tempDays; } else finHour.open[hour] = x.cnt; azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"BILesson:All:Open:{finHour.id}", $"{hour}", x.cnt);//一天24小时课例数 有上传 base.josn 小时为单位 } else { //小时统计 LessonStats lessonHour = new(); lessonHour.id = id; lessonHour.code = "LessonHour"; lessonHour.pk = "LessonHour"; double[] daHours = new double[23]; daHours[hour] = x.cnt; List hourDays = new(daHours); lessonHour.open = hourDays; //lessonHour.ttl = 24 * 60 * 60 * 8; //设置过期时间 8天后自动删除 lessHours.Add(lessonHour); } LessonStats findLess = lessYears.Find(f => f.id.Equals($"{year}")); if (findLess != null) { if (findLess.open.Count == 0) { var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] da = new double[yearDays]; da[days] = x.cnt; List tempDays = new(da); findLess.open = tempDays; } else findLess.open[days] = x.cnt; } else { LessonStats lessonYear = new(); lessonYear.id = $"{year}"; lessonYear.code = "LessonYear"; lessonYear.pk = "LessonYear"; var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] daDays = new double[yearDays]; daDays[days] = x.cnt; List yDays = new(daDays); lessonYear.open = yDays; lessYears.Add(lessonYear); } }); //统计课例 lessL.ForEach(x => { var (year, month, day, days, hour) = TimeHelper.GetDateTime(x.key); string id = $"{year}{month.ToString().PadLeft(2, '0')}{day.ToString().PadLeft(2, '0')}"; LessonStats finHour = lessHours.Find(f => f.id.Equals(id)); if (finHour != null) { if (finHour.lesson.Count == 0) { double[] da = new double[23]; da[hour] = x.cnt; List tempDays = new(da); finHour.lesson = tempDays; } else finHour.lesson[hour] = x.cnt; } else { //小时统计 LessonStats lessonHour = new(); lessonHour.id = id; lessonHour.code = "LessonHour"; lessonHour.pk = "LessonHour"; double[] daHours = new double[23]; daHours[hour] = x.cnt; List hourDays = new(daHours); lessonHour.lesson = hourDays; lessHours.Add(lessonHour); } LessonStats findLess = lessYears.Find(f => f.id.Equals($"{year}")); if (findLess != null) { if (findLess.lesson.Count == 0) { var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] da = new double[yearDays]; da[days] = x.cnt; List tempDays = new(da); findLess.lesson = tempDays; } else findLess.lesson[days] = x.cnt; } else { LessonStats lessonYear = new(); lessonYear.id = $"{year}"; lessonYear.code = "LessonYear"; lessonYear.pk = "LessonYear"; var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] da = new double[yearDays]; da[days] = x.cnt; List tempDays = new(da); lessonYear.lesson = tempDays; lessYears.Add(lessonYear); } }); //统计学校课例 var scLess = lessRecs.Select(item => new LessCnt { hour = TimeHelper.GetDateTime(item.startTime).ToString("yyyyMMddHH"), cat = item.id, upload = item.upload, schoolId = item.school }).GroupBy(g => g.schoolId).Select(s => new ScLess { key = s.Key, lessCnt = s.ToList() }).ToList(); foreach (var itemLess in scLess) { var openSCL = itemLess.lessCnt.FindAll(f => f.upload == 0).GroupBy(g => g.hour).Select(s => new ScLesson { key = s.Key, schoolId = itemLess.key, cnt = s.Count() }).ToList(); var lessSCL = itemLess.lessCnt.FindAll(f => f.upload == 1).GroupBy(g => g.hour).Select(s => new ScLesson { key = s.Key, schoolId = itemLess.key, cnt = s.Count() }).ToList(); openSCL.ForEach(f => { if (!string.IsNullOrEmpty(f.schoolId)) { var (year, month, day, days, hour) = TimeHelper.GetDateTime(f.key); string id = $"{year}{month.ToString().PadLeft(2, '0')}{day.ToString().PadLeft(2, '0')}"; string codeHour = $"LessonHour-{f.schoolId}"; LessonStats finHour = lessHours.Find(f => f.id.Equals(id) && f.code.Equals(codeHour)); if (finHour != null) { if (finHour.open.Count == 0) { double[] da = new double[23]; da[hour] = f.cnt; List tempDays = new(da); finHour.open = tempDays; } else finHour.open[hour] = f.cnt; } else { //小时统计 LessonStats lessonHour = new(); lessonHour.id = id; lessonHour.code = codeHour; lessonHour.pk = "LessonHour"; double[] daHours = new double[23]; daHours[hour] = f.cnt; List hourDays = new(daHours); lessonHour.open = hourDays; //lessonHour.ttl = 24 * 60 * 60 * 8; //设置过期时间 lessHours.Add(lessonHour); } string codeYear = $"LessonYear-{f.schoolId}"; LessonStats findLess = lessYears.Find(f => f.id.Equals($"{year}") && f.code.Equals(codeYear)); if (findLess != null) { if (findLess.open.Count == 0) { var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] da = new double[yearDays]; da[days] = f.cnt; List tempDays = new(da); findLess.open = tempDays; } else findLess.open[days] = f.cnt; } else { LessonStats lessonYear = new(); lessonYear.id = $"{year}"; lessonYear.code = codeYear; lessonYear.code = "LessonYear"; var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] daDays = new double[yearDays]; daDays[days] = f.cnt; List yDays = new(daDays); lessonYear.open = yDays; lessYears.Add(lessonYear); } } }); lessSCL.ForEach(x => { if (!string.IsNullOrEmpty(x.schoolId)) { if (!string.IsNullOrEmpty(x.schoolId)) { var (year, month, day, days, hour) = TimeHelper.GetDateTime(x.key); string id = $"{year}{month.ToString().PadLeft(2, '0')}{day.ToString().PadLeft(2, '0')}"; string codeHour = $"LessonHour-{x.schoolId}"; LessonStats finHour = lessHours.Find(f => f.id.Equals(id) && f.code.Equals(codeHour)); if (finHour != null) { if (finHour.lesson.Count == 0) { double[] da = new double[23]; da[hour] = x.cnt; List tempDays = new(da); finHour.lesson = tempDays; } else finHour.lesson[hour] = x.cnt; } else { //小时统计 LessonStats lessonHour = new(); lessonHour.id = id; lessonHour.code = codeHour; lessonHour.pk = "LessonHour"; double[] daHours = new double[23]; daHours[hour] = x.cnt; List hourDays = new(daHours); lessonHour.lesson = hourDays; lessHours.Add(lessonHour); } string codeYear = $"LessonYear-{x.schoolId}"; LessonStats findLess = lessYears.Find(f => f.id.Equals($"{year}") && f.code.Equals(codeYear)); if (findLess != null) { if (findLess.lesson.Count == 0) { var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] da = new double[yearDays]; da[days] = x.cnt; List tempDays = new(da); findLess.lesson = tempDays; } else findLess.lesson[days] = x.cnt; } else { LessonStats lessonYear = new(); lessonYear.id = $"{year}"; lessonYear.code = codeYear; lessonYear.pk = "LessonYear"; var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; double[] da = new double[yearDays]; da[days] = x.cnt; List tempDays = new(da); lessonYear.lesson = tempDays; lessYears.Add(lessonYear); } } } }); }; //openL.ForEach(x => { // var (year, days, hour) = TimeHelper.GetDateTime(x.key); // LessonYear findLess = lessYears.Find(f => f.id.Equals($"{year}")); // if (findLess != null) // { // if (findLess.openYear.Count == 0) // { // var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; // double[] da = new double[yearDays]; // da[days] = x.cnt; // List tempDays = new(da); // findLess.openYear = tempDays; // } // else findLess.openYear[days] = x.cnt; // } // else // { // LessonYear lessonYear = new(); // lessonYear.id = $"{year}"; // lessonYear.code = "LessonYear"; // var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; // double[] da = new double[yearDays]; // da[days] = x.cnt; // List tempDays = new(da); // lessonYear.openYear = tempDays; // lessYears.Add(lessonYear); // } //}); //lessL.ForEach(x => { // var (year, days, hour) = TimeHelper.GetDateTime(x.key); // LessonYear findLess = lessYears.Find(f => f.id.Equals($"{year}")); // if (findLess != null) // { // if (findLess.lessonYear.Count == 0) // { // var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; // double[] da = new double[yearDays]; // da[days] = x.cnt; // List tempDays = new(da); // findLess.lessonYear = tempDays; // } // else findLess.lessonYear[days] = x.cnt; // } // else // { // LessonYear lessonYear = new(); // lessonYear.id = $"{year}"; // lessonYear.code = "LessonYear"; // var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365; // double[] da = new double[yearDays]; // da[days] = x.cnt; // List tempDays = new(da); // lessonYear.lessonYear = tempDays; // lessYears.Add(lessonYear); // } //}); return (lessYears, lessHours); } /// /// 统计学校课例 /// public record ScLesson { public string key { get; set; } public string schoolId { get; set; } public int cnt { get; set; } } /// /// 统计学校课例详细信息 /// public record ScLess { public string key { get; set; } public List lessCnt { get; set; } } /// /// 统计所有课例 /// public record AllLess { public string key { get; set; } public int cnt { get; set; } } /// /// 统计所有课例详细 /// public record LessCnt { public string hour { get; set; } public string schoolId { get; set; } public string cat { get; set; } public int upload { get; set; } } #endregion } }