Browse Source

课例统计数据存储在table表中接口,以及课例数据结构,活动数据结构。

Li 3 years ago
parent
commit
c92cdf38e6

+ 49 - 3
TEAMModelBI/Controllers/BISchool/LessonController.cs

@@ -15,6 +15,8 @@ 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
 {
@@ -25,14 +27,55 @@ namespace TEAMModelBI.Controllers.BISchool
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureRedisFactory _azureRedis;
+        private readonly DingDing _dingDing;
 
-        public LessonController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis)
+        public LessonController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing)
         {
             _azureCosmos = azureCosmos;
             _azureStorage = azureStorage;
             _azureRedis = azureRedis;
+            _dingDing = dingDing;
         }
 
+        [HttpPost("get-less")]
+        public async Task<IActionResult> 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<LessStats>(tableSql.ToString());
+
+            DenseMatrix openDense = null;
+            DenseMatrix lessDense = null;
+
+            List<List<double>> tempOpens = new();
+            List<List<double>> tempLesss = new();
+            foreach (var item in lessStats)
+            {
+                if (item.open != null) 
+                {
+                    List<double> opens = item.open.Split(',').ToList().ConvertAll(c => Convert.ToDouble(c));
+                    tempOpens.Add(opens);
+                }
+
+                if(item.lesson != null)
+                {
+                    List<double> 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});
+        }
+
+
         /// <summary>
         /// 测试 通过时间戳保存统计类型接口
         /// </summary>
@@ -48,6 +91,8 @@ namespace TEAMModelBI.Controllers.BISchool
             jsonElement.TryGetProperty("unixs", out JsonElement unixs);
             var cosmosClient = _azureCosmos.GetCosmosClient();
 
+            DateTime expireYear = DateTime.UtcNow.AddYears(1);   //一个月到期
+
             var UUID = Guid.NewGuid().ToString();
             List<long> unixsT = unixs.ToObject<List<long>>();
 
@@ -55,7 +100,9 @@ namespace TEAMModelBI.Controllers.BISchool
 
             foreach (var item in unixsT)
             {
-                await SetBILeesonStats(cosmosClient, item, num.GetInt32(), type: type.GetInt32(), schoolId: schoolId.GetString());
+                //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());
@@ -299,7 +346,6 @@ namespace TEAMModelBI.Controllers.BISchool
             return Ok(new { state = 200, ycnt = lessYears.Count, hcnt = lessHours.Count, lessYears, lessHours });
         }
 
-
         #region   课例历史记录统计
 
         /// <summary>

+ 88 - 0
TEAMModelOS.SDK/Models/Cosmos/BI/BITable/LessStats.cs

@@ -0,0 +1,88 @@
+using Microsoft.Azure.Cosmos.Table;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Context.Attributes.Azure;
+
+namespace TEAMModelOS.SDK.Models.Cosmos.BI.BITable
+{
+    /// <summary>
+    /// BI课例数据实体
+    /// </summary>
+    [TableName(Name = "BIStats")]
+    public class LessStats: TableEntity
+    {
+        public LessStats()
+        {
+            //PartitionKey LessonYear 记录一年的数据  天    RowKey 2022 一年所有; 2022-学校编码   学校一年所有
+            //PartitionKey LessonHour 记录一天的数据  小时   RowKey 20220615 一天的记录; 20220615-学校编码   学校一天的记录
+        }
+
+        /// <summary>
+        /// Hiteach开课记录
+        /// </summary>
+        public string open { get; set; }
+        /// <summary>
+        /// 课例记录
+        /// </summary>
+        public string lesson { get; set; }
+
+        /// <summary>
+        /// 时间    当PartitionKey是LessonHour    yyyyMMdd 
+        /// </summary>
+        public string time { get; set; }
+
+        /// <summary>
+        /// 学校编号
+        /// </summary>
+        public string school { get; set; }
+    }
+
+    /// <summary>
+    /// BI活动  0 Exam评测  1 Survey问卷  2 Vote投票 3 Homework作业活动  
+    /// </summary>
+    [TableName(Name = "BIStats")]
+    public class ActivityStats : TableEntity
+    {
+        public ActivityStats()
+        {
+            //PartitionKey LessonYear 记录一年的数据  天    RowKey 2022 一年所有; 2022-学校编码   学校一年所有
+            //PartitionKey LessonHour 记录一天的数据  小时   RowKey 20220615 一天的记录; 20220615-学校编码   学校一天的记录
+        }
+
+        /// <summary>
+        /// 评测
+        /// </summary>
+        public string exam { get; set; }
+
+        /// <summary>
+        /// 问卷
+        /// </summary>
+        public string survey { get; set; }
+
+        /// <summary>
+        /// 投票活动
+        /// </summary>
+        public string vote { get; set; }
+
+        /// <summary>
+        /// 作业活动
+        /// </summary>
+        public string homework { get; set; }
+
+        /// <summary>
+        /// 时间 
+        /// PartitionKey是LessonHour    yyyyMMdd     
+        /// PartitionKey是LessonYear    yyyy   
+        /// </summary>
+        public string time { get; set; }
+
+        /// <summary>
+        /// 学校编号
+        /// </summary>
+        public string school { get; set; }
+    }
+
+}

+ 264 - 0
TEAMModelOS.SDK/Models/Service/BI/BIActivity.cs

@@ -0,0 +1,264 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Models.Cosmos.BI.BITable;
+
+namespace TEAMModelOS.SDK.Models.Service.BI
+{
+    public class BIActivity
+    {
+        /// <summary>
+        /// 通过时间戳保存活动统计记录统计
+        /// 统计数据存储在Table表中
+        /// </summary>
+        /// <param name="_azureStorage">table连接池</param>
+        /// <param name="_azureRedis">redis连接池</param>
+        /// <param name="_dingDing">错误消息发送</param>
+        /// <param name="unix">时间戳</param>
+        /// <param name="num">数量:1 -1</param>
+        /// <param name="type">活动类型:0 Exam评测  1 Survey问卷  2 Vote投票 3 Homework作业活动  </param>
+        /// <param name="schoolId">学校编码</param>
+        /// <returns></returns>
+        public static async Task SetTableStats(AzureStorageFactory _azureStorage, AzureRedisFactory _azureRedis, DingDing _dingDing, long unix, int num = 1, int type = 0, string schoolId = null)
+        {
+            try
+            {
+                //SemaphoreSlim slimlock = new(1, 1);    //对可同时访问资源或资源池的线程数加以限制   结束
+                //await slimlock.WaitAsync();
+                Monitor.TryEnter(unix);  //锁对象
+
+                DateTimeOffset dateTime = DateTimeOffset.FromUnixTimeMilliseconds(unix);
+                int year, month, day, hour, days;
+                year = dateTime.Year;
+                month = dateTime.Month;
+                day = dateTime.Day;
+                hour = dateTime.Hour;
+                days = dateTime.DayOfYear;
+                var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
+                var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365;
+                var table = _azureStorage.GetCloudTableClient().GetTableReference("BIStats");
+                var redisClient = _azureRedis.GetRedisClient(8);
+                DateTime expireDay = DateTime.UtcNow.AddDays(8);   //8天后到期
+                DateTime expireYear = DateTime.UtcNow.AddDays(396);   //一年后到期
+
+                string actType = "Exam";
+                switch (type)
+                {
+                    case 1:
+                        actType = "Survey";
+                        break;
+                    case 2:
+                        actType = "Vote";
+                        break;
+                    case 3:
+                        actType = "Homework";
+                        break;
+                    default:
+                        actType = "Exam";
+                        break;
+                }
+
+                await redisClient.SortedSetIncrementAsync($"BIStats:ACT:All:{actType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
+                await redisClient.SortedSetIncrementAsync($"BIStats:ACT:All:{actType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
+
+                var expDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:ACT:All:{actType}:{dateDay}");
+                if (expDay == null)
+                    await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:ACT:All:{actType}:{dateDay}", expireDay);  //设置八天后到期
+                var expYear = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:ACT:All:{actType}:{year}");
+                if (expYear == null)
+                    await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:ACT:All:{actType}:{year}", expireYear);  //设置一年后到期
+
+                //保存当天的统计  小时
+                var dayCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:ACT:All:{actType}:{dateDay}");
+                if (dayCnt != null && dayCnt.Length > 0)
+                {
+                    double[] daHour = new double[23];
+                    foreach (var item in dayCnt)
+                    {
+                        double val = ((double)item.Score);
+                        int key = ((int)item.Element);
+                        daHour[key] = val;
+                    }
+
+                    string hoursStats = string.Join(",", daHour);
+                    ActivityStats actStats = table.Get<ActivityStats>("LessonHour", $"{dateDay}");
+                    if (actStats != null)
+                    {
+                        if (type == 1)
+                            actStats.survey = hoursStats;
+                        else if (type == 2)
+                            actStats.vote = hoursStats;
+                        else if (type == 3)
+                            actStats.homework = hoursStats;
+                        else
+                            actStats.exam = hoursStats;
+                    }
+                    else
+                    {
+                        actStats = new() { PartitionKey = "LessonHour", RowKey = $"{dateDay}" };
+                        if (type == 1)
+                            actStats.survey = hoursStats;
+                        else if (type == 2)
+                            actStats.vote = hoursStats;
+                        else if (type == 3)
+                            actStats.homework = hoursStats;
+                        else
+                            actStats.exam = hoursStats;
+                    }
+                    try
+                    {
+                        await table.SaveOrUpdate<ActivityStats>(actStats);
+                    }
+                    catch { }
+                }
+
+                //保一年的统计   天
+                var yearCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:ACT:All:{actType}:{year}");
+                if (yearCnt != null && yearCnt.Length > 0)
+                {
+                    double[] daYear = new double[yearDays];
+                    foreach (var item in yearCnt)
+                    {
+                        double val = ((double)item.Score);
+                        int key = ((int)item.Element);
+                        daYear[key] = val;
+                    }
+                    string yearStats = string.Join(",", daYear);
+                    ActivityStats actStats = table.Get<ActivityStats>("LessonYear", $"{year}");
+                    if (actStats != null)
+                    {
+                        if (type == 1)
+                            actStats.survey = yearStats;
+                        else if (type == 2)
+                            actStats.vote = yearStats;
+                        else if (type == 3)
+                            actStats.homework = yearStats;
+                        else
+                            actStats.exam = yearStats;
+                    }
+                    else
+                    {
+                        actStats = new() { PartitionKey = "LessonYear", RowKey = $"{year}" };
+                        if (type == 1)
+                            actStats.survey = yearStats;
+                        else if (type == 2)
+                            actStats.vote = yearStats;
+                        else if (type == 3)
+                            actStats.homework = yearStats;
+                        else
+                            actStats.exam = yearStats;
+                    }
+                    try
+                    {
+                        await table.SaveOrUpdate<ActivityStats>(actStats);
+                    }
+                    catch { }
+                }
+
+                if (!string.IsNullOrEmpty(schoolId))
+                {
+                    await redisClient.SortedSetIncrementAsync($"BIStats:ACT:{schoolId}:{actType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
+                    await redisClient.SortedSetIncrementAsync($"BIStats:ACT:{schoolId}:{actType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
+
+                    var scExpDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:ACT:{schoolId}:{actType}:{dateDay}");
+                    if (scExpDay == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:ACT:{schoolId}:{actType}:{dateDay}", expireDay);  //设置八天后到期
+                    var scExpYear = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:ACT:{schoolId}:{actType}:{year}");
+                    if (scExpYear == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:ACT:{schoolId}:{actType}:{year}", expireYear);  //设置一年后到期
+
+                    //保存学校当天的统计  小时
+                    var dayScCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:ACT:{schoolId}:{actType}:{dateDay}");
+                    if (dayScCnt != null && dayScCnt.Length > 0)
+                    {
+                        double[] daHourSc = new double[23];
+                        foreach (var item in dayScCnt)
+                        {
+                            double val = ((double)item.Score);
+                            int key = ((int)item.Element);
+                            daHourSc[key] = val;
+                        }
+
+                        string hoursStatsSc = string.Join(",", daHourSc);
+                        LessStats lessStatsSc = table.Get<LessStats>("LessonHour", $"{dateDay}-{schoolId}");
+                        if (lessStatsSc != null)
+                        {
+                            if (type == 1)
+                                lessStatsSc.lesson = hoursStatsSc;
+                            else
+                                lessStatsSc.open = hoursStatsSc;
+                        }
+                        else
+                        {
+                            lessStatsSc = new() { PartitionKey = "LessonHour", RowKey = $"{dateDay}-{schoolId}" };
+                            if (type == 1)
+                                lessStatsSc.lesson = hoursStatsSc;
+                            else
+                                lessStatsSc.open = hoursStatsSc;
+                            lessStatsSc.time = dateDay;
+                            lessStatsSc.school = schoolId;
+                        }
+
+                        try
+                        {
+                            await table.SaveOrUpdate<LessStats>(lessStatsSc);
+                        }
+                        catch { }
+                    }
+
+                    //保学校一年的统计   天
+                    var scYearCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:ACT:{schoolId}:{actType}:{year}");
+                    if (scYearCnt != null && scYearCnt.Length > 0)
+                    {
+                        double[] daYearSc = new double[yearDays];
+                        foreach (var item in scYearCnt)
+                        {
+                            double val = ((double)item.Score);
+                            int key = ((int)item.Element);
+                            daYearSc[key] = val;
+                        }
+
+                        string tempStatsSc = string.Join(",", daYearSc);
+                        LessStats lessStatsSc = table.Get<LessStats>("LessonYear", $"{year}-{schoolId}");
+                        if (lessStatsSc != null)
+                        {
+                            if (type == 1)
+                                lessStatsSc.lesson = tempStatsSc;
+                            else
+                                lessStatsSc.open = tempStatsSc;
+                        }
+                        else
+                        {
+                            lessStatsSc = new() { PartitionKey = "LessonYear", RowKey = $"{year}-{schoolId}" };
+                            if (type == 1)
+                                lessStatsSc.lesson = tempStatsSc;
+                            else
+                                lessStatsSc.open = tempStatsSc;
+
+                            lessStatsSc.time = $"{year}";
+                            lessStatsSc.school = schoolId;
+                        }
+                        try
+                        {
+                            await table.SaveOrUpdate<LessStats>(lessStatsSc);
+                        }
+                        catch { }
+                    }
+                }
+
+                Monitor.Enter(unix);  //锁对象  结束
+                //slimlock.Release(); // 对可同时访问资源或资源池的线程数加以限制 结束  
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-SetBITableStats  活动统计异常参数:{unix}|{num}|{type}|{schoolId} ;错误信息:{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+            }
+        }
+
+
+    }
+}

+ 365 - 116
TEAMModelOS.SDK/Models/Service/BI/BILeeson.cs

@@ -4,10 +4,12 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Text.Json;
+using System.Threading;
 using System.Threading.Tasks;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models.Cosmos.BI.BISchool;
+using TEAMModelOS.SDK.Models.Cosmos.BI.BITable;
 
 namespace TEAMModelOS.SDK.Models.Service.BI
 {
@@ -15,125 +17,53 @@ namespace TEAMModelOS.SDK.Models.Service.BI
     {
         /// <summary>
         /// 通过时间戳保存开课记录和课例记录统计
+        /// 统计数据存储在CosmosDB表中
         /// </summary>
         /// <param name="cosmosClient">cosmosDB连接</param>
-        /// <param name="azureRedis">redis</param>
+        /// <param name="_azureRedis">redis</param>
+        /// <param name="_dingDing">错误消息发送</param>
         /// <param name="unix">13位时间戳</param>
         /// <param name="num">数量</param>
         /// <param name="type">类型:0 开课记录  1 课例记录</param>
         /// <param name="schoolId">学校编码</param>
         /// <returns></returns>
-        public static async Task SetBILeesonStats(CosmosClient cosmosClient, AzureRedisFactory azureRedis, long unix, int num = 1, int type = 0, string schoolId = null)
+        public static async Task SetCosmosDBStats(CosmosClient cosmosClient, AzureRedisFactory _azureRedis, DingDing _dingDing, long unix, int num = 1, int type = 0, string schoolId = null)
         {
-            DateTimeOffset dateTime = DateTimeOffset.FromUnixTimeMilliseconds(unix);
-            int year, month, day, hour, days;
-            year = dateTime.Year;
-            month = dateTime.Month;
-            day = dateTime.Day;
-            hour = dateTime.Hour;
-            days = dateTime.DayOfYear;
-            var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
-            var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365;
-
-            string LessType = "Open";
-            if (type == 1)
-                LessType = "Lesson";
-
-            await azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"BILesson:All:{LessType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
-            await azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"BILesson:All:{LessType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
-
-            //保存当天的统计  小时
-            var dayCnt = azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"BILesson:All:{LessType}:{dateDay}");
-            if (dayCnt != null && dayCnt.Length > 0)
+            try
             {
-                double[] daHour = new double[23];
-                foreach (var item in dayCnt)
-                {
-                    double val = ((double)item.Score);
-                    int key = ((int)item.Element);
-                    daHour[key] = val;
-                }
-                List<double> lessHours = new(daHour);
+                DateTimeOffset dateTime = DateTimeOffset.FromUnixTimeMilliseconds(unix);
+                int year, month, day, hour, days;
+                year = dateTime.Year;
+                month = dateTime.Month;
+                day = dateTime.Day;
+                hour = dateTime.Hour;
+                days = dateTime.DayOfYear;
+                var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
+                var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365;
+                DateTime expireDay = DateTime.UtcNow.AddDays(8);   //8天后到期
+                DateTime expireYear = DateTime.UtcNow.AddDays(396);   //一年后到期
 
-                var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{dateDay}", new PartitionKey("LessonHour"));
-                if (lessRes.Status == 200)
-                {
-                    using var json = await JsonDocument.ParseAsync(lessRes.ContentStream);
-                    LessonStats lessonStats = json.ToObject<LessonStats>();
-                    lessonStats.code = "LessonHour";
-                    lessonStats.pk = "LessonHour";
-                    if (type == 1)
-                        lessonStats.lesson = lessHours;
-                    else
-                        lessonStats.open = lessHours;
-                    await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonStats, lessonStats.id, new PartitionKey("LessonHour"));
-                }
-                else
-                {
-                    LessonStats lessonStats = new();
-                    lessonStats.id = $"{dateDay}";
-                    lessonStats.code = "LessonHour";
-                    lessonStats.pk = "LessonHour";
-                    if (type == 1)
-                        lessonStats.lesson = lessHours;
-                    else
-                        lessonStats.open = lessHours;
+                var redisClient = _azureRedis.GetRedisClient(8);
+                string LessType = "Open";
+                if (type == 1)
+                    LessType = "Lesson";
 
-                    await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonStats, new PartitionKey("LessonHour"));
-                }
-            }
-
-            //保一年的统计   天
-            var yearCnt = azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"BILesson:All:{LessType}:{year}");
-            if (yearCnt != null && yearCnt.Length > 0)
-            {
-                double[] daYear = new double[yearDays];
-                foreach (var item in yearCnt)
-                {
-                    double val = ((double)item.Score);
-                    int key = ((int)item.Element);
-                    daYear[key] = val;
-                }
-                List<double> lessYear = new(daYear);
-
-                var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{year}", new PartitionKey("LessonYear"));
-                if (lessRes.Status == 200)
-                {
-                    using var json = await JsonDocument.ParseAsync(lessRes.ContentStream);
-                    LessonStats lessonYear = json.ToObject<LessonStats>();
-                    lessonYear.code = "LessonYear";
-                    lessonYear.pk = "LessonYear";
-                    if (type == 1)
-                        lessonYear.lesson = lessYear;
-                    else
-                        lessonYear.open = lessYear;
-                    await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonYear, lessonYear.id, new PartitionKey("LessonYear"));
-                }
-                else
-                {
-                    LessonStats lessonYear = new();
-                    lessonYear.id = $"{year}";
-                    lessonYear.code = "LessonYear";
-                    lessonYear.pk = "LessonYear";
-                    if (type == 1)
-                        lessonYear.lesson = lessYear;
-                    else
-                        lessonYear.open = lessYear;
-                    await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonYear, new PartitionKey("LessonYear"));
-                }
-            }
+                await redisClient.SortedSetIncrementAsync($"BIStats:Less:All:{LessType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
+                await redisClient.SortedSetIncrementAsync($"BIStats:Less:All:{LessType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
 
-            if (!string.IsNullOrEmpty(schoolId))
-            {
-                await azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"BILesson:{schoolId}:{LessType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
-                await azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"BILesson:{schoolId}:{LessType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
+                var expDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:All:{LessType}:{dateDay}");
+                if (expDay == null)
+                    await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:All:{LessType}:{dateDay}", expireDay);  //设置八天后到期
+                var expYear = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:All:{LessType}:{year}");
+                if (expYear == null)
+                    await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:All:{LessType}:{year}", expireYear);  //设置一年后到期
 
                 //保存当天的统计  小时
-                var dayScCnt = azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"BILesson:{schoolId}:{LessType}:{dateDay}");
-                if (dayScCnt != null && dayScCnt.Length > 0)
+                var dayCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:All:{LessType}:{dateDay}");
+                if (dayCnt != null && dayCnt.Length > 0)
                 {
                     double[] daHour = new double[23];
-                    foreach (var item in dayScCnt)
+                    foreach (var item in dayCnt)
                     {
                         double val = ((double)item.Score);
                         int key = ((int)item.Element);
@@ -141,39 +71,40 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                     }
                     List<double> lessHours = new(daHour);
 
-                    var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{dateDay}", new PartitionKey($"LessonHour-{schoolId}"));
+                    var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{dateDay}", new PartitionKey("LessonHour"));
                     if (lessRes.Status == 200)
                     {
                         using var json = await JsonDocument.ParseAsync(lessRes.ContentStream);
                         LessonStats lessonStats = json.ToObject<LessonStats>();
-                        lessonStats.code = $"LessonHour-{schoolId}";
+                        lessonStats.code = "LessonHour";
                         lessonStats.pk = "LessonHour";
                         if (type == 1)
                             lessonStats.lesson = lessHours;
                         else
                             lessonStats.open = lessHours;
-                        await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonStats, lessonStats.id, new PartitionKey($"LessonHour-{schoolId}"));
+                        await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonStats, lessonStats.id, new PartitionKey("LessonHour"));
                     }
                     else
                     {
                         LessonStats lessonStats = new();
                         lessonStats.id = $"{dateDay}";
-                        lessonStats.code = $"LessonHour-{schoolId}";
+                        lessonStats.code = "LessonHour";
                         lessonStats.pk = "LessonHour";
                         if (type == 1)
                             lessonStats.lesson = lessHours;
                         else
                             lessonStats.open = lessHours;
-                        await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonStats, new PartitionKey($"LessonHour-{schoolId}"));
+
+                        await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonStats, new PartitionKey("LessonHour"));
                     }
                 }
 
                 //保一年的统计   天
-                var scYearCnt = azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"BILesson:{schoolId}:{LessType}:{year}");
-                if (scYearCnt != null && scYearCnt.Length > 0)
+                var yearCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:All:{LessType}:{year}");
+                if (yearCnt != null && yearCnt.Length > 0)
                 {
                     double[] daYear = new double[yearDays];
-                    foreach (var item in scYearCnt)
+                    foreach (var item in yearCnt)
                     {
                         double val = ((double)item.Score);
                         int key = ((int)item.Element);
@@ -181,33 +112,351 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                     }
                     List<double> lessYear = new(daYear);
 
-                    var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{year}", new PartitionKey($"LessonYear-{schoolId}"));
+                    var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{year}", new PartitionKey("LessonYear"));
                     if (lessRes.Status == 200)
                     {
                         using var json = await JsonDocument.ParseAsync(lessRes.ContentStream);
                         LessonStats lessonYear = json.ToObject<LessonStats>();
-                        lessonYear.code = $"LessonYear-{schoolId}";
+                        lessonYear.code = "LessonYear";
                         lessonYear.pk = "LessonYear";
                         if (type == 1)
                             lessonYear.lesson = lessYear;
                         else
                             lessonYear.open = lessYear;
-                        await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonYear, lessonYear.id, new PartitionKey($"LessonYear-{schoolId}"));
+                        await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonYear, lessonYear.id, new PartitionKey("LessonYear"));
                     }
                     else
                     {
                         LessonStats lessonYear = new();
                         lessonYear.id = $"{year}";
-                        lessonYear.code = $"LessonYear-{schoolId}";
+                        lessonYear.code = "LessonYear";
                         lessonYear.pk = "LessonYear";
                         if (type == 1)
                             lessonYear.lesson = lessYear;
                         else
                             lessonYear.open = lessYear;
-                        await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonYear, new PartitionKey($"LessonYear-{schoolId}"));
+                        await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonYear, new PartitionKey("LessonYear"));
+                    }
+                }
+
+                if (!string.IsNullOrEmpty(schoolId))
+                {
+                    await redisClient.SortedSetIncrementAsync($"BIStats:Less:{schoolId}:{LessType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
+                    await redisClient.SortedSetIncrementAsync($"BIStats:Less:{schoolId}:{LessType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
+
+                    var scExpDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:{schoolId}:{LessType}:{dateDay}");
+                    if (scExpDay == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:{schoolId}:{LessType}:{dateDay}", expireDay);  //设置八天后到期
+                    var scExpYear = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:{schoolId}:{LessType}:{year}");
+                    if (scExpYear == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:{schoolId}:{LessType}:{year}", expireYear);  //设置一年后到期
+
+                    //保存当天的统计  小时
+                    var dayScCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:{schoolId}:{LessType}:{dateDay}");
+                    if (dayScCnt != null && dayScCnt.Length > 0)
+                    {
+                        double[] daHour = new double[23];
+                        foreach (var item in dayScCnt)
+                        {
+                            double val = ((double)item.Score);
+                            int key = ((int)item.Element);
+                            daHour[key] = val;
+                        }
+                        List<double> lessHours = new(daHour);
+
+                        var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{dateDay}", new PartitionKey($"LessonHour-{schoolId}"));
+                        if (lessRes.Status == 200)
+                        {
+                            using var json = await JsonDocument.ParseAsync(lessRes.ContentStream);
+                            LessonStats lessonStats = json.ToObject<LessonStats>();
+                            lessonStats.code = $"LessonHour-{schoolId}";
+                            lessonStats.pk = "LessonHour";
+                            if (type == 1)
+                                lessonStats.lesson = lessHours;
+                            else
+                                lessonStats.open = lessHours;
+                            await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonStats, lessonStats.id, new PartitionKey($"LessonHour-{schoolId}"));
+                        }
+                        else
+                        {
+                            LessonStats lessonStats = new();
+                            lessonStats.id = $"{dateDay}";
+                            lessonStats.code = $"LessonHour-{schoolId}";
+                            lessonStats.pk = "LessonHour";
+                            if (type == 1)
+                                lessonStats.lesson = lessHours;
+                            else
+                                lessonStats.open = lessHours;
+                            await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonStats, new PartitionKey($"LessonHour-{schoolId}"));
+                        }
+                    }
+
+                    //保一年的统计   天
+                    var scYearCnt = redisClient.SortedSetRangeByScoreWithScores($"BILesson:{schoolId}:{LessType}:{year}");
+                    if (scYearCnt != null && scYearCnt.Length > 0)
+                    {
+                        double[] daYear = new double[yearDays];
+                        foreach (var item in scYearCnt)
+                        {
+                            double val = ((double)item.Score);
+                            int key = ((int)item.Element);
+                            daYear[key] = val;
+                        }
+                        List<double> lessYear = new(daYear);
+
+                        var lessRes = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{year}", new PartitionKey($"LessonYear-{schoolId}"));
+                        if (lessRes.Status == 200)
+                        {
+                            using var json = await JsonDocument.ParseAsync(lessRes.ContentStream);
+                            LessonStats lessonYear = json.ToObject<LessonStats>();
+                            lessonYear.code = $"LessonYear-{schoolId}";
+                            lessonYear.pk = "LessonYear";
+                            if (type == 1)
+                                lessonYear.lesson = lessYear;
+                            else
+                                lessonYear.open = lessYear;
+                            await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<LessonStats>(lessonYear, lessonYear.id, new PartitionKey($"LessonYear-{schoolId}"));
+                        }
+                        else
+                        {
+                            LessonStats lessonYear = new();
+                            lessonYear.id = $"{year}";
+                            lessonYear.code = $"LessonYear-{schoolId}";
+                            lessonYear.pk = "LessonYear";
+                            if (type == 1)
+                                lessonYear.lesson = lessYear;
+                            else
+                                lessonYear.open = lessYear;
+                            await cosmosClient.GetContainer("TEAMModelOS", "School").CreateItemAsync<LessonStats>(lessonYear, new PartitionKey($"LessonYear-{schoolId}"));
+                        }
                     }
                 }
             }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-SetBICosmosDBStats 课例统计异常 {ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+            }
+        }
+
+        /// <summary>
+        /// 通过时间戳保存开课记录和课例记录统计
+        /// 统计数据存储在table表中
+        /// </summary>
+        /// <param name="_azureStorage">table连接池</param>
+        /// <param name="_azureRedis">redis连接池</param>
+        /// <param name="_dingDing">错误消息发送</param>
+        /// <param name="unix">13位时间戳</param>
+        /// <param name="num">数量</param>
+        /// <param name="type">类型:0 开课记录  1 课例记录</param>
+        /// <param name="schoolId">学校编码</param>
+        /// <returns></returns>
+        public static async Task SetTableStats(AzureStorageFactory _azureStorage, AzureRedisFactory _azureRedis, DingDing _dingDing, long unix, int num = 1, int type = 0, string schoolId = null)
+        {
+            try
+            {
+                //SemaphoreSlim slimlock = new(1, 1);    //对可同时访问资源或资源池的线程数加以限制   结束
+                //await slimlock.WaitAsync();
+                Monitor.TryEnter(unix);  //锁对象
+
+                DateTimeOffset dateTime = DateTimeOffset.FromUnixTimeMilliseconds(unix);
+                int year, month, day, hour, days;
+                year = dateTime.Year;
+                month = dateTime.Month;
+                day = dateTime.Day;
+                hour = dateTime.Hour;
+                days = dateTime.DayOfYear;
+                var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
+                var yearDays = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) ? 366 : 365;
+                var table = _azureStorage.GetCloudTableClient().GetTableReference("BIStats");
+                var redisClient = _azureRedis.GetRedisClient(8);
+                DateTime expireDay = DateTime.UtcNow.AddDays(8);   //8天后到期
+                DateTime expireYear = DateTime.UtcNow.AddDays(396);   //一年后到期
+
+                string LessType = "Open";
+                if (type == 1)
+                    LessType = "Lesson";
+
+                await redisClient.SortedSetIncrementAsync($"BIStats:Less:All:{LessType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
+                await redisClient.SortedSetIncrementAsync($"BIStats:Less:All:{LessType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
+
+                var expDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:All:{LessType}:{dateDay}");
+                if (expDay == null)
+                    await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:All:{LessType}:{dateDay}", expireDay);  //设置八天后到期
+                var expYear = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:All:{LessType}:{year}");
+                if (expYear == null)
+                    await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:All:{LessType}:{year}", expireYear);  //设置一年后到期
+
+                //保存当天的统计  小时
+                var dayCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:All:{LessType}:{dateDay}");
+                if (dayCnt != null && dayCnt.Length > 0)
+                {
+                    double[] daHour = new double[23];
+                    foreach (var item in dayCnt)
+                    {
+                        double val = ((double)item.Score);
+                        int key = ((int)item.Element);
+                        daHour[key] = val;
+                    }
+
+                    string hoursStats = string.Join(",", daHour);
+                    LessStats lessStats = table.Get<LessStats>("LessonHour", $"{dateDay}");
+                    if (lessStats != null)
+                    {
+                        if (type == 1)
+                            lessStats.lesson = hoursStats;
+                        else
+                            lessStats.open = hoursStats;
+                    }
+                    else
+                    {
+                        lessStats = new() { PartitionKey = "LessonHour", RowKey = $"{dateDay}" };
+                        if (type == 1)
+                            lessStats.lesson = hoursStats;
+                        else
+                            lessStats.open = hoursStats;
+                    }
+                    try
+                    {
+                        await table.SaveOrUpdate<LessStats>(lessStats);
+                    }
+                    catch {}
+                }
+
+                //保一年的统计   天
+                var yearCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:All:{LessType}:{year}");
+                if (yearCnt != null && yearCnt.Length > 0)
+                {
+                    double[] daYear = new double[yearDays];
+                    foreach (var item in yearCnt)
+                    {
+                        double val = ((double)item.Score);
+                        int key = ((int)item.Element);
+                        daYear[key] = val;
+                    }
+                    string tempStats = string.Join(",", daYear);
+                    LessStats lessStats = table.Get<LessStats>("LessonYear", $"{year}");
+                    if (lessStats != null)
+                    {
+                        if (type == 1)
+                            lessStats.lesson = tempStats;
+                        else
+                            lessStats.open = tempStats;
+                    }
+                    else
+                    {
+                        lessStats = new() { PartitionKey = "LessonYear", RowKey = $"{year}" };
+                        if (type == 1)
+                            lessStats.lesson = tempStats;
+                        else
+                            lessStats.open = tempStats;
+                    }
+                    try
+                    {
+                        await table.SaveOrUpdate<LessStats>(lessStats);
+                    }
+                    catch { }
+                }
+
+                //保存学校课例数据
+                if (!string.IsNullOrEmpty(schoolId))
+                {
+                    await redisClient.SortedSetIncrementAsync($"BIStats:Less:{schoolId}:{LessType}:{dateDay}", $"{hour}", num);//一天24小时课例数 有上传 base.josn  小时为单位
+                    await redisClient.SortedSetIncrementAsync($"BIStats:Less:{schoolId}:{LessType}:{year}", $"{days}", num);//一年的课例数量 有上传 base.josn 小时为单位
+
+                    var scExpDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:{schoolId}:{LessType}:{dateDay}");
+                    if (scExpDay == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:{schoolId}:{LessType}:{dateDay}", expireDay);  //设置八天后到期
+                    var scExpYear = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"BIStats:Less:{schoolId}:{LessType}:{year}");
+                    if (scExpYear == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"BIStats:Less:{schoolId}:{LessType}:{year}", expireYear);  //设置一年后到期
+
+                    //保存学校当天的统计  小时
+                    var dayScCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:{schoolId}:{LessType}:{dateDay}");
+                    if (dayScCnt != null && dayScCnt.Length > 0)
+                    {
+                        double[] daHourSc = new double[23];
+                        foreach (var item in dayScCnt)
+                        {
+                            double val = ((double)item.Score);
+                            int key = ((int)item.Element);
+                            daHourSc[key] = val;
+                        }
+
+                        string hoursStatsSc = string.Join(",", daHourSc);
+                        LessStats lessStatsSc = table.Get<LessStats>("LessonHour", $"{dateDay}-{schoolId}");
+                        if (lessStatsSc != null)
+                        {
+                            if (type == 1)
+                                lessStatsSc.lesson = hoursStatsSc;
+                            else
+                                lessStatsSc.open = hoursStatsSc;
+                        }
+                        else
+                        {
+                            lessStatsSc = new() { PartitionKey = "LessonHour", RowKey = $"{dateDay}-{schoolId}" };
+                            if (type == 1)
+                                lessStatsSc.lesson = hoursStatsSc;
+                            else
+                                lessStatsSc.open = hoursStatsSc;
+                            lessStatsSc.time = dateDay;
+                            lessStatsSc.school = schoolId;
+                        }
+
+                        try
+                        {
+                            await table.SaveOrUpdate<LessStats>(lessStatsSc);
+                        }
+                        catch { }
+                    }
+
+                    //保学校一年的统计   天
+                    var scYearCnt = redisClient.SortedSetRangeByScoreWithScores($"BIStats:Less:{schoolId}:{LessType}:{year}");
+                    if (scYearCnt != null && scYearCnt.Length > 0)
+                    {
+                        double[] daYearSc = new double[yearDays];
+                        foreach (var item in scYearCnt)
+                        {
+                            double val = ((double)item.Score);
+                            int key = ((int)item.Element);
+                            daYearSc[key] = val;
+                        }
+
+                        string tempStatsSc = string.Join(",", daYearSc);
+                        LessStats lessStatsSc = table.Get<LessStats>("LessonYear", $"{year}-{schoolId}");
+                        if (lessStatsSc != null)
+                        {
+                            if (type == 1)
+                                lessStatsSc.lesson = tempStatsSc;
+                            else
+                                lessStatsSc.open = tempStatsSc;
+                        }
+                        else
+                        {
+                            lessStatsSc = new() { PartitionKey = "LessonYear", RowKey = $"{year}-{schoolId}" };
+                            if (type == 1)
+                                lessStatsSc.lesson = tempStatsSc;
+                            else
+                                lessStatsSc.open = tempStatsSc;
+
+                            lessStatsSc.time = $"{year}";
+                            lessStatsSc.school = schoolId;
+                        }
+                        try
+                        {
+                            await table.SaveOrUpdate<LessStats>(lessStatsSc);
+                        }
+                        catch { }
+                    }
+                }
+
+                Monitor.Enter(unix);  //锁对象  结束
+                //slimlock.Release(); // 对可同时访问资源或资源池的线程数加以限制 结束    
+
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-SetBITableStats  课例统计异常{unix}|{num}|{type}|{schoolId} ;错误信息:{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+            }
         }
     }
 }