JAELYS 3 년 전
부모
커밋
b4e5eb0337
2개의 변경된 파일190개의 추가작업 그리고 5개의 파일을 삭제
  1. 186 3
      TEAMModelOS/Controllers/Client/HiTeachccControlller.cs
  2. 4 2
      TEAMModelOS/Models/Request/CreateLessondRequest.cs

+ 186 - 3
TEAMModelOS/Controllers/Client/HiTeachccControlller.cs

@@ -1,10 +1,12 @@
 using Azure;
 using Azure.Cosmos;
+using Azure.Messaging.ServiceBus;
 using Azure.Storage.Blobs.Models;
 using Azure.Storage.Sas;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Options;
 using StackExchange.Redis;
 using System;
@@ -15,12 +17,14 @@ using System.Linq;
 using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelOS.Models;
+using TEAMModelOS.Models.Request;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
+using TEAMModelOS.SDK.Models;
 
 namespace TEAMModelOS.Controllers.Client
 {
-    [Authorize(Roles = "HiTeachCC")]
+    //[Authorize(Roles = "HiTeachCC")]
     [Route("hiteachcc")]
     [ApiController]
     public class HiTeachccControlller : ControllerBase
@@ -28,26 +32,205 @@ namespace TEAMModelOS.Controllers.Client
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureRedisFactory _azureRedis;
         private readonly AzureCosmosFactory _azureCosmos;
+        private readonly AzureServiceBusFactory _serviceBus;
         private readonly DingDing _dingDing;
         private readonly Option _option;
+        private readonly IConfiguration _configuration;
         private readonly SnowflakeId _snowflakeId;
 
         public HiTeachccControlller(
             AzureStorageFactory azureStorage,
             AzureRedisFactory azureRedis,
             AzureCosmosFactory azureCosmos,
+            AzureServiceBusFactory serviceBus,
             DingDing dingDing,
+            IConfiguration configuration,
             SnowflakeId snowflakeId,
             IOptionsSnapshot<Option> option)
         {
             _azureStorage = azureStorage;
             _azureRedis = azureRedis;
             _azureCosmos = azureCosmos;
+            _serviceBus = serviceBus;
             _dingDing = dingDing;
+            _configuration = configuration;
             _snowflakeId = snowflakeId;
             _option = option?.Value;
         }
 
+        /// <summary>
+        /// 開始課堂,建立課堂記錄,返回課程ID,相關數據與存取授權        
+        /// cid:课程id;
+        /// sid:名单id;
+        /// rid:课程资源id(pdf);
+        /// sp:课程类型(school:校本课程,private:个人课程)
+        /// rp:資源类型(school:校本資源,private:个人資源)
+        /// school:學校簡碼
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [HttpPost("create-lesson")]        
+        [ProducesResponseType(StatusCodes.Status200OK)]
+        [ProducesResponseType(StatusCodes.Status400BadRequest)]
+        [ProducesDefaultResponseType]
+
+        public async Task<IActionResult> CreateLessond(CreateLessondRequest request)
+        {
+            string id_token = HttpContext.GetXAuth("IdToken");
+            int dmax = 0; //學校CC授權總數
+            int client = 0; //CC每間教室Client數
+            int size = 0; //學校個人總空間
+            int tsize = 0; //學校分配給老師的總空間
+            double usize = 0; //學校個人已使用空間
+            JsonElement.ArrayEnumerator members = default; //學生名單
+            string res = string.Empty;//資源位置加金鑰
+            string timezone = string.Empty;
+
+            if (string.IsNullOrWhiteSpace(id_token)) return BadRequest();
+            var jwt = new JwtSecurityToken(id_token);
+            if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
+            var tid = jwt.Payload.Sub;
+
+            var redis = _azureRedis.GetRedisClient(8);
+            var db = _azureCosmos.GetCosmosClient();
+
+            var sp = request.sp.Equals("school", StringComparison.OrdinalIgnoreCase);
+            try
+            {
+                //判斷是否有足夠的CC授權,每個老師一天扣除一個授權           
+                await foreach (Response item in db.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryStreamIterator(
+                    queryText: $"SELECT TOP 1 c.deviceMax, c.clientQty, c.prodCode FROM c WHERE c.prodCode = 'LZLL6ZEI'",
+                    requestOptions: new() { PartitionKey = new($"Product-{request.school}") }))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        var school = json.RootElement.GetProperty("Documents").EnumerateArray().First();
+                        dmax = school.GetProperty("deviceMax").GetInt32();
+                        client = school.GetProperty("clientQty").GetInt32();
+                    }
+                    else
+                    {
+                        return Ok(new { status = 1, msg = "學校沒有CC授權" });
+                    }
+                }
+                //取得學校資訊           
+                await foreach (Response item in db.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryStreamIterator(
+                    queryText: $"SELECT TOP 1 c.id, c.size, c.tsize, c.timeZone FROM c WHERE c.id = {request.school}",
+                    requestOptions: new() { PartitionKey = new("base") }))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        var school = json.RootElement.GetProperty("Documents").EnumerateArray().First();
+                        timezone = school.GetProperty("timeZone").GetProperty("value").GetString();
+                        size = school.GetProperty("size").GetInt32();
+                        tsize = school.GetProperty("tsize").GetInt32();
+                    }
+                }
+
+                //緩存檢查已記錄的授權數是否已滿,未滿則紀錄ID跟時間
+                var cccount = await redis.HashLengthAsync($"CC:License:{request.school}");
+                if (cccount >= dmax) return Ok(new { status = 2, msg = "學校CC授權已滿" });
+                else if (await redis.HashSetAsync($"CC:License:{request.school}", tid, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()))
+                {
+                    if (cccount == 0 && !string.IsNullOrWhiteSpace(timezone)) //代表不存在緩存,設置TTL
+                    {
+                        //UTC轉換為學校時區計算到晚上24:00為止的TTL
+                        var tz = TimeSpan.Parse(timezone.TrimStart('+'));
+                        TimeSpan ts1 = new(DateTimeOffset.UtcNow.ToOffset(tz).Ticks);
+                        TimeSpan ts2 = new(DateTimeOffset.UtcNow.AddDays(1).Date.Ticks);
+                        TimeSpan ts = ts1.Subtract(ts2).Duration();
+                        await redis.KeyExpireAsync($"CC:License:{request.school}", ts);
+                    }
+                }
+                else
+                {
+                    await _dingDing.SendBotMsg($"IES5,{_option.Location}, hiteachcc/create-lesson:()\n HashSetAsync CC:License:{request.school} {tid} 失敗", GroupNames.醍摩豆服務運維群組);
+                }
+                //如果是個人,則取出老師空間size覆蓋
+                if (request.sp.Equals("private"))
+                {
+                    Teacher teacher = await db.GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>(tid, new PartitionKey("Base"));
+                    size = teacher.size;
+                    foreach (var school in teacher.schools)
+                    {
+                        SchoolTeacher st = await db.GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<SchoolTeacher>(tid, new PartitionKey($"Teacher-{school.schoolId}"));
+                        size += st.size;
+                    }
+                }
+                //計算學校或個人的使用空間
+                RedisValue redisValue = redis.HashGet($"Blob:Record", $"{(sp ? request.school : tid)}");
+                if (redisValue.HasValue && long.TryParse(redisValue.ToString(), out var bsize))
+                {
+                    usize = Math.Round(bsize / 1073741824.0, 2, MidpointRounding.AwayFromZero) - (sp ? tsize : 0); //1073741824  1G
+                }
+                else //如果檢測不到緩存,觸發刷新計算空間
+                {
+                    var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", name = $"{(sp ? request.school : tid)}" }.ToJsonString()); ;
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(_configuration.GetValue<string>("Azure:ServiceBus:ActiveTask"), messageBlob);
+                }
+                //取得學校或個人名單
+                await foreach (Response item in db.GetContainer(Constant.TEAMModelOS, sp ? Constant.School : Constant.Teacher).GetItemQueryStreamIterator(
+                    queryText: $"SELECT TOP 1 c.members FROM c WHERE c.id = {request.school}",
+                    requestOptions: new() { PartitionKey = new(sp ? $"GroupList-{request.school}" : "GroupList") }))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        var root = json.RootElement.GetProperty("Documents").EnumerateArray().First();
+                        members = root.GetProperty("members").EnumerateArray();
+                    }
+                }
+                //取得學校或個人資源
+                if (!string.IsNullOrWhiteSpace(request.rid))
+                {
+                    var rp = request.rp.Equals("school", StringComparison.OrdinalIgnoreCase);
+                    await foreach (Response item in db.GetContainer(Constant.TEAMModelOS, rp ? Constant.School : Constant.Teacher).GetItemQueryStreamIterator(
+                        queryText: $"SELECT TOP 1 c.url FROM c WHERE c.id = {request.school}",
+                        requestOptions: new() { PartitionKey = new(rp ? $"BlobLog-{request.school}" : "GroupList") }))
+                    {
+                        using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                        if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                        {
+                            var root = json.RootElement.GetProperty("Documents").EnumerateArray().First();
+                            var url = root.GetProperty("url").GetString();
+                            res = _azureStorage.GetBlobSAS($"Bloblog-{(rp ? request.school : tid)}", url, BlobSasPermissions.Read);
+                        }
+                    }
+                }
+
+
+
+                //開課記錄保存
+                LessonRecord lr = new()
+                {
+                    code = request.sp.Equals("school") ? $"LessonRecord-{request.school}" : $"LessonRecord",
+                    id = _snowflakeId.NextId().ToString(),  //取得授課ID
+                    courseId = request.cid,
+                    groupIds = new() { request.sid },
+                    tmdid = tid,
+                    scope = request.sp,
+                    pk = "LessonRecord",
+                    startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
+                };
+                await db.GetContainer(Constant.TEAMModelOS, request.sp.Equals("school") ? "School" : "Teacher").CreateItemAsync(lr, new PartitionKey(lr.code));
+
+                //觸發開課統計                
+                var messageChange = new ServiceBusMessage(lr.ToJsonString());
+                messageChange.ApplicationProperties.Add("name", "LessonRecordEvent");
+                await _serviceBus.GetServiceBusClient().SendMessageAsync(_configuration.GetValue<string>("Azure:ServiceBus:ActiveTask"), messageChange);
+
+                return Ok(new { status = 200, lr.id, client, members, res, size, usize });
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"IES5,{_option.Location}, hiteachcc/create-lesson:()\n{ex.Message}\n{ex.StackTrace}{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
+                return BadRequest();
+            }
+        }
+
         //取得老師資訊
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -67,7 +250,7 @@ namespace TEAMModelOS.Controllers.Client
                 await container.CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建Teacher私有容器,如存在則不做任何事,保障容器一定存在
                 var (blob_uri, blob_sas_read) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Read);
                 var (blob_uri_write, blob_sas_write) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Write);
-                
+
                 return Ok(new { blob_uri, blob_sas_read, blob_sas_write });
             }
             catch (Exception ex)
@@ -105,7 +288,7 @@ namespace TEAMModelOS.Controllers.Client
                     var now = DateTimeOffset.UtcNow;
                     DateTimeOffset today = new DateTimeOffset(now.Year, now.Month, now.Day, 0, 0, 0, 0, TimeSpan.Zero);
                     int diff = DateTimeOffset.Compare(blobTime, today);
-                    if(diff < 0)
+                    if (diff < 0)
                     {
                         await blob.DeleteIfExistsAsync();
                     }

+ 4 - 2
TEAMModelOS/Models/Request/CreateLessondRequest.cs

@@ -13,12 +13,14 @@ namespace TEAMModelOS.Models.Request
         /// 课程类型(school:校本课程,private:个人课程)
         [Required(ErrorMessage = "{0} 必填")]
         public string sp { get; set; }
+        /// 學校簡碼
+        [Required(ErrorMessage = "{0} 必填")]
+        public string school { get; set; }
         /// 课程资源id(pdf);        
         public string rid { get; set; }
         /// 课程资源类型(school:校本课程,private:个人课程)        
         public string rp { get; set; }
-        /// 學校簡碼
-        public string school { get; set; }
+       
 
     }
 }