|
@@ -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();
|
|
|
}
|