HiTeachController.cs 115 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990
  1. using Azure.Cosmos;
  2. using Azure.Storage.Blobs.Models;
  3. using Azure.Storage.Sas;
  4. using Microsoft.AspNetCore.Authorization;
  5. using Microsoft.AspNetCore.Http;
  6. using Microsoft.AspNetCore.Mvc;
  7. using Microsoft.Extensions.Options;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.IdentityModel.Tokens.Jwt;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Text;
  14. using System.Text.Json;
  15. using System.Threading.Tasks;
  16. using TEAMModelOS.Models;
  17. using TEAMModelOS.Filter;
  18. using TEAMModelOS.SDK.DI;
  19. using TEAMModelOS.SDK.Extension;
  20. using Microsoft.Azure.Cosmos.Table;
  21. using TEAMModelOS.SDK.Models;
  22. using System.Dynamic;
  23. using HTEXLib.COMM.Helpers;
  24. using StackExchange.Redis;
  25. using TEAMModelOS.SDK.Models.Cosmos.Common;
  26. using TEAMModelOS.SDK;
  27. using Microsoft.Extensions.Configuration;
  28. using Azure.Messaging.ServiceBus;
  29. using TEAMModelOS.SDK.Services;
  30. using TEAMModelOS.SDK.Models.Service;
  31. using TEAMModelOS.Models.Request;
  32. using Azure;
  33. namespace TEAMModelOS.Controllers.Client
  34. {
  35. [ProducesResponseType(StatusCodes.Status200OK)]
  36. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  37. //[Authorize(Roles = "HiTeach")]
  38. [Route("hiteach")]
  39. [ApiController]
  40. public class HiTeachController : ControllerBase
  41. {
  42. private readonly AzureStorageFactory _azureStorage;
  43. private readonly AzureRedisFactory _azureRedis;
  44. private readonly AzureCosmosFactory _azureCosmos;
  45. private readonly DingDing _dingDing;
  46. private readonly Option _option;
  47. private readonly SnowflakeId _snowflakeId;
  48. private readonly AzureServiceBusFactory _serviceBus;
  49. private readonly IConfiguration _configuration;
  50. private readonly CoreAPIHttpService _coreAPIHttpService;
  51. private readonly IPSearcher _searcher;
  52. private readonly HttpTrigger _httpTrigger;
  53. public HiTeachController(
  54. AzureStorageFactory azureStorage,
  55. AzureRedisFactory azureRedis,
  56. AzureCosmosFactory azureCosmos,
  57. DingDing dingDing,
  58. SnowflakeId snowflakeId,
  59. IOptionsSnapshot<Option> option,
  60. AzureServiceBusFactory serviceBus,
  61. IConfiguration configuration,
  62. CoreAPIHttpService coreAPIHttpService, IPSearcher searcher, HttpTrigger httpTrigger)
  63. {
  64. _azureStorage = azureStorage;
  65. _azureRedis = azureRedis;
  66. _azureCosmos = azureCosmos;
  67. _dingDing = dingDing;
  68. _snowflakeId = snowflakeId;
  69. _option = option?.Value;
  70. _serviceBus = serviceBus;
  71. _configuration = configuration;
  72. _coreAPIHttpService = coreAPIHttpService;
  73. _searcher = searcher;
  74. _httpTrigger = httpTrigger;
  75. }
  76. /// <summary>
  77. /// 更新课堂记录
  78. /// </summary>
  79. /// <param name="request"></param>
  80. /// <returns></returns>
  81. //[Authorize(Roles = "HiTeach")]
  82. [ProducesDefaultResponseType]
  83. [HttpPost("update-lesson-record")]
  84. public async Task<IActionResult> UpdateLessonRecord(JsonElement request)
  85. {
  86. var client = _azureCosmos.GetCosmosClient();
  87. if (!request.TryGetProperty("lesson_id", out JsonElement _lessonId)) return BadRequest();
  88. if (!request.TryGetProperty("tmdid", out JsonElement _tmdid)) return BadRequest();
  89. request.TryGetProperty("name", out JsonElement name);
  90. request.TryGetProperty("school", out JsonElement _school);
  91. if (!request.TryGetProperty("scope", out JsonElement _scope)) return BadRequest();
  92. request.TryGetProperty("grant_types", out JsonElement _grant_types);
  93. string tbname;
  94. string code;
  95. if (_scope.GetString().Equals("school") && !string.IsNullOrWhiteSpace(_school.GetString()))
  96. {
  97. code = $"LessonRecord-{_school}";
  98. tbname = "School";
  99. }
  100. else if ($"{_scope}".Equals("private"))
  101. {
  102. code = $"LessonRecord";
  103. tbname = "Teacher";
  104. }
  105. else
  106. {
  107. return BadRequest();
  108. }
  109. try
  110. {
  111. LessonRecord lessonRecord = await client.GetContainer(Constant.TEAMModelOS, tbname).ReadItemAsync<LessonRecord>($"{_lessonId}", new PartitionKey(code));
  112. List<LessonUpdate> updates = new List<LessonUpdate>() { new LessonUpdate { grant_type = "up-base" } };
  113. if (_grant_types.ValueKind.Equals(JsonValueKind.Array))
  114. {
  115. updates = _grant_types.ToObject<List<LessonUpdate>>();
  116. if (!updates.Select(x => x.grant_type).Contains("up-base"))
  117. {
  118. updates.Add(new LessonUpdate { grant_type = "up-base" });
  119. }
  120. }
  121. if (!string.IsNullOrWhiteSpace($"{name}"))
  122. {
  123. lessonRecord.name = $"{name}";
  124. await client.GetContainer(Constant.TEAMModelOS, tbname).ReplaceItemAsync<LessonRecord>(lessonRecord, $"{_lessonId}", new PartitionKey(code));
  125. }
  126. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  127. string msg = null;
  128. msg = new { lesson_id = $"{_lessonId}", tmdid = $"{_tmdid}", grant_types = updates, school = $"{_school}", scope = $"{_scope}" }.ToJsonString();
  129. var messageChange = new ServiceBusMessage(msg);
  130. messageChange.ApplicationProperties.Add("name", "LessonRecordEvent");
  131. //await _dingDing.SendBotMsg($"{_option.Location},课堂id:{_lessonId} 更新事件,{msg}", GroupNames.醍摩豆服務運維群組);
  132. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChange);
  133. return Ok(new { status = 200 });
  134. }
  135. catch (CosmosException ex) when (ex.Status == 404)
  136. {
  137. await _dingDing.SendBotMsg($"更新课堂记录出错\n{ex.StackTrace}{ex.Message}", GroupNames.成都开发測試群組);
  138. return BadRequest("课堂记录不存在");
  139. }
  140. catch (Exception ex)
  141. {
  142. await _dingDing.SendBotMsg($"更新课堂记录出错\n{ex.StackTrace}{ex.Message}", GroupNames.成都开发測試群組);
  143. return BadRequest();
  144. }
  145. }
  146. /// <summary>
  147. /// 创建课堂开课记录(新版)
  148. /// </summary>
  149. /// <param name="request"></param>
  150. /// <returns></returns>
  151. [Authorize(Roles = "HiTeach")]
  152. [ProducesDefaultResponseType]
  153. [HttpPost("create-lesson")]
  154. public async Task<IActionResult> CreateLesson(CreateLessondRequest request)
  155. {
  156. try
  157. {
  158. string id_token = HttpContext.GetXAuth("IdToken");
  159. //判斷ID Token
  160. if (string.IsNullOrWhiteSpace(id_token)) return BadRequest();
  161. var jwt = new JwtSecurityToken(id_token);
  162. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
  163. var tid = jwt.Payload.Sub;
  164. //初始化
  165. var r8 = _azureRedis.GetRedisClient(8);
  166. var db = _azureCosmos.GetCosmosClient();
  167. var sbm = new List<ServiceBusMessage>();
  168. var sp = request.sp.Equals("school", StringComparison.OrdinalIgnoreCase);
  169. int size = 0; //學校或個人總空間
  170. int tsize = 0; //學校分配給老師的總空間
  171. double usize = 0; //學校或個人已使用空間
  172. string timezone = string.Empty;
  173. if (sp)
  174. {
  175. //取得學校資訊
  176. if (string.IsNullOrWhiteSpace(request.school)) return BadRequest();
  177. await foreach (Response item in db.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryStreamIterator(
  178. queryText: $"SELECT TOP 1 c.id, c.size, c.tsize, c.timeZone FROM c WHERE c.id = '{request.school}'",
  179. requestOptions: new() { PartitionKey = new("Base") }))
  180. {
  181. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  182. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  183. {
  184. var school = json.RootElement.GetProperty("Documents").EnumerateArray().First();
  185. timezone = school.GetProperty("timeZone").GetProperty("value").GetString();
  186. size = school.GetProperty("size").GetInt32();
  187. tsize = school.TryGetProperty("tsize", out var tsvalue) ? tsvalue.GetInt32() : 0;
  188. }
  189. else return BadRequest();
  190. }
  191. }
  192. else
  193. {
  194. Teacher teacher = await db.GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>(tid, new PartitionKey("Base"));
  195. size = teacher.size;
  196. foreach (var school in teacher.schools)
  197. {
  198. SchoolTeacher st = await db.GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<SchoolTeacher>(tid, new PartitionKey($"Teacher-{school.schoolId}"));
  199. size += st.size;
  200. }
  201. }
  202. //計算學校或個人的使用空間
  203. RedisValue redisValue = r8.HashGet($"Blob:Record", $"{(sp ? request.school : tid)}");
  204. if (redisValue.HasValue && long.TryParse(redisValue.ToString(), out var bsize))
  205. {
  206. usize = Math.Round(bsize / 1073741824.0 - (sp ? tsize : 0), 2, MidpointRounding.AwayFromZero); //1073741824 1G
  207. }
  208. else //如果檢測不到緩存,觸發刷新計算空間
  209. {
  210. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = "records", name = $"{(sp ? request.school : tid)}" }, _serviceBus, _configuration, _azureRedis);
  211. }
  212. //取得學校或個人名單
  213. (List<RMember> students, _) = await GroupListService.GetMemberByListids(_coreAPIHttpService, db, _dingDing, new List<string>() { request.sid }, request.school);
  214. //觸發IMEI更新消息
  215. var stus = students.Select(x => x.id).ToList();
  216. var imeimsg = new ServiceBusMessage(new { request.channel, userid = request.did, request.school, stus }.ToJsonString());
  217. imeimsg.ApplicationProperties.Add("name", "Imei");
  218. sbm.Add(imeimsg);
  219. //開課記錄保存
  220. LessonRecord lr = new()
  221. {
  222. school = request.sp.Equals("school") ? request?.school : null,
  223. code = request.sp.Equals("school") ? $"LessonRecord-{request.school}" : $"LessonRecord",
  224. id = _snowflakeId.NextId().ToString(), //取得授課ID
  225. courseId = request.cid,
  226. groupIds = new() { request.sid },
  227. tmdid = tid,
  228. scope = request.sp,
  229. pk = "LessonRecord",
  230. name = request.cname,
  231. startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
  232. };
  233. await db.GetContainer(Constant.TEAMModelOS, request.sp.Equals("school") ? "School" : "Teacher").CreateItemAsync(lr, new PartitionKey(lr.code));
  234. //觸發開課統計
  235. var messageChange = new ServiceBusMessage(lr.ToJsonString());
  236. messageChange.ApplicationProperties.Add("name", "LessonRecordEvent");
  237. sbm.Add(messageChange);
  238. //批量發送消息
  239. await _serviceBus.GetServiceBusClient().SendBatchMessageAsync(_configuration.GetValue<string>("Azure:ServiceBus:ActiveTask"), sbm);
  240. if (sp && usize>size)
  241. {
  242. ////处理学校开课,空间不足时。检查是否有 当前教师tid,强制保存save<>1,没有标记未删除status<>404,没有被收藏favorite<=0 ,时间最旧的一条记录startTime
  243. LessonRecord lessonRecord = null;
  244. string sql = $"SELECT top 1 value(c) FROM c where ( c.expire<=0 or IS_DEFINED(c.expire) = false ) and c.tmdid='{tid}' and c.save<>1 and c.status<>404 and c.favorite<=0 order by c.startTime ";
  245. await foreach (var item in db.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIterator<LessonRecord>(
  246. queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"LessonRecord-{request.school}") }))
  247. {
  248. lessonRecord = item;
  249. break;
  250. }
  251. if (lessonRecord != null)
  252. {
  253. lessonRecord.status = 404;
  254. await db.GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync(lessonRecord, lessonRecord.id, new PartitionKey(lessonRecord.code));
  255. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  256. var messageChangeEvent = new ServiceBusMessage(request.ToJsonString());
  257. messageChangeEvent.ApplicationProperties.Add("name", "LessonRecordEvent");
  258. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChangeEvent);
  259. //保证客户端可以正常开课。
  260. usize -= 1;
  261. }
  262. else
  263. { //没有找到匹配当前 教师tid,save<>1,status<>404,没有被收藏,时间最旧的一条记录。无法手动再继续 usize -= 1;,则不能继续开课。
  264. }
  265. }
  266. return Ok(new { status = 200, lr.id, students, size, usize });
  267. }
  268. catch (Exception ex)
  269. {
  270. await _dingDing.SendBotMsg($"IES5,{_option.Location}, hiteach/create-lesson:()\n{ex.Message}\n{ex.StackTrace}{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  271. return BadRequest();
  272. }
  273. }
  274. /// <summary>
  275. /// 创建课堂开课记录(舊版,已被CreateLesson取代)
  276. /// </summary>
  277. /// <param name="request"></param>
  278. /// <returns></returns>
  279. [Authorize(Roles = "HiTeach")]
  280. [ProducesDefaultResponseType]
  281. [HttpPost("create-lesson-record")]
  282. public async Task<IActionResult> CreateLessonRecord(JsonElement request)
  283. {
  284. if (!request.TryGetProperty("lesson", out JsonElement _lesson)) return BadRequest();
  285. var client = _azureCosmos.GetCosmosClient();
  286. LessonRecord lessonRecord = _lesson.ToObject<LessonRecord>();
  287. int blobTotal = 0;
  288. long teach = 0;
  289. string blobName = "";
  290. if (!string.IsNullOrEmpty(lessonRecord.scope) && !string.IsNullOrEmpty(lessonRecord.tmdid) && !string.IsNullOrEmpty(lessonRecord.id))
  291. {
  292. string tbname = null;
  293. if (lessonRecord.scope.Equals("school") && !string.IsNullOrEmpty(lessonRecord.school))
  294. {
  295. School school = await client.GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<School>(lessonRecord.school, new PartitionKey("Base"));
  296. lessonRecord.code = $"LessonRecord-{lessonRecord.school}";
  297. tbname = "School";
  298. blobTotal = school.size;
  299. //计算分配给学校教师的空间
  300. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT sum(c.size) as size FROM c ",
  301. requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{lessonRecord.school}") }))
  302. {
  303. var json = await JsonDocument.ParseAsync(item.ContentStream);
  304. foreach (var elmt in json.RootElement.GetProperty("Documents").EnumerateArray())
  305. {
  306. if (elmt.TryGetProperty("size", out JsonElement _size) && _size.ValueKind.Equals(JsonValueKind.Number))
  307. {
  308. teach = _size.GetInt32();
  309. break;
  310. }
  311. }
  312. }
  313. blobName = lessonRecord.school;
  314. }
  315. if (lessonRecord.scope.Equals("private"))
  316. {
  317. lessonRecord.code = $"LessonRecord";
  318. tbname = "Teacher";
  319. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>(lessonRecord.tmdid, new PartitionKey("Base"));
  320. blobTotal = teacher.size;
  321. foreach (var school in teacher.schools)
  322. {
  323. SchoolTeacher st = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<SchoolTeacher>(lessonRecord.tmdid, new PartitionKey($"Teacher-{school.schoolId}"));
  324. blobTotal += st.size;
  325. }
  326. blobName = lessonRecord.tmdid;
  327. }
  328. RedisValue redisValue = _azureRedis.GetRedisClient(8).HashGet($"Blob:Record", $"{blobName}");
  329. long blobsize = 0;
  330. if (redisValue != default && !redisValue.IsNullOrEmpty)
  331. {
  332. JsonElement record = redisValue.ToString().ToObject<JsonElement>();
  333. if (record.TryGetInt64(out blobsize))
  334. {
  335. }
  336. }
  337. else
  338. {
  339. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = "records", name = $"{blobName}" }, _serviceBus, _configuration, _azureRedis);
  340. }
  341. double blobUsed = blobsize / 1073741824.0 + teach; //1073741824 1G
  342. if (tbname == null)
  343. {
  344. return BadRequest("参数异常:scope应为school或private,scope=school ,school字段不能为空");
  345. }
  346. if (lessonRecord.startTime == 0)
  347. {
  348. lessonRecord.startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  349. }
  350. lessonRecord.pk = "LessonRecord";
  351. try
  352. {
  353. await client.GetContainer(Constant.TEAMModelOS, tbname).CreateItemAsync(lessonRecord, new PartitionKey(lessonRecord.code));
  354. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  355. var messageChange = new ServiceBusMessage(lessonRecord.ToJsonString());
  356. messageChange.ApplicationProperties.Add("name", "LessonRecordEvent");
  357. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChange);
  358. return Ok(new { status = 200, blobTotal, blobUsed });
  359. }
  360. catch (Exception ex)
  361. {
  362. await _dingDing.SendBotMsg($"IES5,{_option.Location}, hiteach/create-lesson-record:()\n{ex.Message}\n{ex.StackTrace}{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  363. return BadRequest();
  364. }
  365. }
  366. else
  367. {
  368. return BadRequest();
  369. }
  370. }
  371. [Authorize(Roles = "HiTeach")]
  372. [ProducesDefaultResponseType]
  373. [HttpPost("get-teacher-info")]
  374. public async Task<IActionResult> GetTeacherInfo()
  375. {
  376. //Debug
  377. //string json = System.Text.Json.JsonSerializer.Serialize(id_token);
  378. try
  379. {
  380. string id_token = HttpContext.GetXAuth("IdToken");
  381. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  382. if (string.IsNullOrEmpty(id_token)) return BadRequest();
  383. var jwt = new JwtSecurityToken(id_token);
  384. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
  385. var id = jwt.Payload.Sub;
  386. jwt.Payload.TryGetValue("name", out object name);
  387. jwt.Payload.TryGetValue("picture", out object picture);
  388. List<object> schools = new List<object>();
  389. string defaultschool = null;
  390. //TODK 取得Teacher 個人相關數據(課程清單、虛擬教室清單、歷史紀錄清單等),學校數據另外API處理,多校切換時不同
  391. var client = _azureCosmos.GetCosmosClient();
  392. var response = await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReadItemStreamAsync(id, new PartitionKey("Base"));
  393. if (response.Status == 200)
  394. {
  395. var jsonsc = await JsonDocument.ParseAsync(response.ContentStream);
  396. if (jsonsc.RootElement.TryGetProperty("schools", out JsonElement value))
  397. {
  398. GetTeacherInfoApiSchool schoolExtobj;
  399. foreach (var obj in value.EnumerateArray())
  400. {
  401. string statusNow = obj.GetProperty("status").ToString();
  402. if (statusNow.Equals("join")) //成為老師的才放入
  403. {
  404. var schoolJson = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync($"{obj.GetProperty("schoolId")}", new PartitionKey("Base"));
  405. var school = await JsonDocument.ParseAsync(schoolJson.ContentStream);
  406. schoolExtobj = new GetTeacherInfoApiSchool();
  407. schoolExtobj.schoolId = Convert.ToString(obj.GetProperty("schoolId"));
  408. schoolExtobj.name = Convert.ToString(school.RootElement.GetProperty("name"));
  409. schoolExtobj.status = Convert.ToString(obj.GetProperty("status"));
  410. schoolExtobj.picture = Convert.ToString(school.RootElement.GetProperty("picture"));
  411. schools.Add(schoolExtobj);
  412. var sctch = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(id, new PartitionKey($"Teacher-{obj.GetProperty("schoolId")}"));
  413. if (sctch.Status == 200 && sctch != null && sctch.ContentStream != null)
  414. {
  415. var jsonDoc = await JsonDocument.ParseAsync(sctch.ContentStream);
  416. SchoolTeacher schoolTeacher = jsonDoc.RootElement.ToObject<SchoolTeacher>();
  417. }
  418. }
  419. }
  420. }
  421. //預設學校ID
  422. if (jsonsc.RootElement.TryGetProperty("defaultSchool", out JsonElement valueD) && !string.IsNullOrEmpty(valueD.ToString()))
  423. {
  424. defaultschool = valueD.ToString();
  425. }
  426. }
  427. else
  428. {
  429. //如果沒有,則初始化Teacher基本資料到Cosmos
  430. Teacher teacher = new Teacher
  431. {
  432. createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
  433. id = id,
  434. pk = "Base",
  435. code = "Base",
  436. name = name?.ToString(),
  437. picture = picture?.ToString(),
  438. //创建账号并第一次登录IES5则默认赠送1G
  439. size = 1,
  440. defaultSchool = null,
  441. schools = new List<Teacher.TeacherSchool>(),
  442. };
  443. teacher = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Teacher").CreateItemAsync<Teacher>(teacher, new PartitionKey("Base"));
  444. }
  445. //老師個人課程清單
  446. List<GetTeacherInfoApiCourse> courses = new List<GetTeacherInfoApiCourse>();
  447. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.schedule, c.scope FROM c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{id}") }))
  448. {
  449. var jsontcs = await JsonDocument.ParseAsync(item.ContentStream);
  450. if (jsontcs.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  451. {
  452. GetTeacherInfoApiCourse courseExtobj;
  453. List<GetTeacherInfoApiCourseClass> classes;
  454. foreach (var obj in jsontcs.RootElement.GetProperty("Documents").EnumerateArray())
  455. {
  456. courseExtobj = new GetTeacherInfoApiCourse();
  457. courseExtobj.id = Convert.ToString(obj.GetProperty("id"));
  458. courseExtobj.name = Convert.ToString(obj.GetProperty("name"));
  459. courseExtobj.scope = Convert.ToString(obj.GetProperty("scope"));
  460. classes = new List<GetTeacherInfoApiCourseClass>();
  461. if (obj.TryGetProperty("schedule", out JsonElement schedule))
  462. {
  463. GetTeacherInfoApiCourseClass classExtobj;
  464. foreach (var scheduleobj in schedule.EnumerateArray())
  465. {
  466. classExtobj = new GetTeacherInfoApiCourseClass();
  467. classExtobj.id = null;
  468. classExtobj.code = null;
  469. classExtobj.teacher = null;
  470. if (scheduleobj.TryGetProperty("teacherId", out JsonElement teacherId) && !string.IsNullOrWhiteSpace(Convert.ToString(teacherId)))
  471. {
  472. await foreach (var teaitem in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name FROM c WHERE c.id = '{teacherId}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  473. {
  474. var jsontea = await JsonDocument.ParseAsync(teaitem.ContentStream);
  475. foreach (var teaobj in jsontea.RootElement.GetProperty("Documents").EnumerateArray())
  476. {
  477. classExtobj.teacher = new GetTeacherInfoApiSimlpeBase();
  478. classExtobj.teacher = teaobj.ToObject<GetTeacherInfoApiSimlpeBase>();
  479. }
  480. }
  481. }
  482. classExtobj.scope = "private";
  483. int stuCount = 0;
  484. string stuListId = Convert.ToString(scheduleobj.GetProperty("stulist"));
  485. classExtobj.stuListId = stuListId;
  486. await foreach (var stuitem in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.tcount, c.scount FROM c WHERE c.id = '{stuListId}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("GroupList") }))
  487. {
  488. //取得學生總數
  489. var jsonstu = await JsonDocument.ParseAsync(stuitem.ContentStream);
  490. foreach (var stuobj in jsonstu.RootElement.GetProperty("Documents").EnumerateArray())
  491. {
  492. classExtobj.name = Convert.ToString(stuobj.GetProperty("name"));
  493. stuCount += stuobj.GetProperty("scount").GetInt32();
  494. stuCount += stuobj.GetProperty("tcount").GetInt32();
  495. }
  496. }
  497. classExtobj.stuCnt = stuCount;
  498. classExtobj.grpCnt = 0;
  499. classExtobj.gradeId = null;
  500. classExtobj.year = 0;
  501. classes.Add(classExtobj);
  502. }
  503. }
  504. courseExtobj.classes = classes;
  505. courses.Add(courseExtobj);
  506. }
  507. }
  508. }
  509. //取得老師個人評測
  510. List<object> exams = new List<object>();
  511. //取得評測ID List (HiTeach只取source='1'(課中评量) && progress = 'going'(進行中))
  512. List<string> examIdList = new List<string>();
  513. await foreach (var exam in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT c.id FROM c where (c.status<>404 or IS_DEFINED(c.status) = false ) and c.source = '1' AND c.progress = 'going'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{id}") }))
  514. {
  515. using var json = await JsonDocument.ParseAsync(exam.ContentStream);
  516. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  517. {
  518. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  519. {
  520. examIdList.Add(obj.GetProperty("id").GetString());
  521. }
  522. }
  523. }
  524. //取得有作答的評測班級
  525. Dictionary<string, List<string>> examClassFinDic = new Dictionary<string, List<string>>();
  526. await foreach (var exam in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT c.examId, c.info.id as classId FROM c where (c.status<>404 or IS_DEFINED(c.status) = false ) and ARRAY_CONTAINS({JsonSerializer.Serialize(examIdList)}, c.examId) AND c.progress=true", requestOptions: new QueryRequestOptions() { }))
  527. {
  528. var jsonecr = await JsonDocument.ParseAsync(exam.ContentStream);
  529. if (jsonecr.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  530. {
  531. foreach (var obj in jsonecr.RootElement.GetProperty("Documents").EnumerateArray())
  532. {
  533. string examId = obj.GetProperty("examId").ToString();
  534. string classId = obj.GetProperty("classId").ToString();
  535. if (examClassFinDic.ContainsKey(examId) && !examClassFinDic[examId].Contains(classId))
  536. {
  537. examClassFinDic[examId].Add(classId);
  538. }
  539. else
  540. {
  541. List<string> classIdList = new List<string>();
  542. classIdList.Add(classId);
  543. examClassFinDic.Add(examId, classIdList);
  544. }
  545. }
  546. }
  547. }
  548. //取得評測資料
  549. await foreach (var exam in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT c.code, c.id, c.name, c.createTime, c.startTime, c.endTime ,c.year, c.source, c.type, c.progress, c.stuCount, c.scope, c.owner, c.period, c.grades, c.subjects, c.classes, c.stuLists, ARRAY(SELECT p.id, p.code, p.name, p.blob, p.scope FROM p IN c.papers) AS papers FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(examIdList)}, c.id)", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{id}") }))
  550. {
  551. var jsonex = await JsonDocument.ParseAsync(exam.ContentStream);
  552. if (jsonex.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  553. {
  554. dynamic examExtobj;
  555. foreach (var obj in jsonex.RootElement.GetProperty("Documents").EnumerateArray())
  556. {
  557. examExtobj = new ExpandoObject();
  558. string examId = obj.GetProperty("id").GetString();
  559. examExtobj.code = obj.GetProperty("code");
  560. examExtobj.id = examId;
  561. examExtobj.name = obj.GetProperty("name");
  562. examExtobj.createTime = obj.GetProperty("createTime");
  563. examExtobj.startTime = obj.GetProperty("startTime");
  564. examExtobj.endTime = obj.GetProperty("endTime");
  565. examExtobj.year = obj.GetProperty("year");
  566. examExtobj.source = obj.GetProperty("source");
  567. examExtobj.type = obj.GetProperty("type");
  568. examExtobj.progress = obj.GetProperty("progress");
  569. examExtobj.stuCount = obj.GetProperty("stuCount");
  570. examExtobj.scope = obj.GetProperty("scope");
  571. examExtobj.owner = obj.GetProperty("owner");
  572. examExtobj.period = obj.GetProperty("period");
  573. examExtobj.grades = obj.GetProperty("grades");
  574. examExtobj.subjects = obj.GetProperty("subjects");
  575. examExtobj.classes = (obj.TryGetProperty("classes", out JsonElement classes)) ? classes.ToObject<List<string>>() : new List<string>();
  576. examExtobj.stuLists = (obj.TryGetProperty("stuLists", out JsonElement stuLists)) ? stuLists.ToObject<List<string>>() : new List<string>();
  577. examExtobj.finishClasses = (examClassFinDic.ContainsKey(examId)) ? examClassFinDic[examId] : new List<string>();
  578. examExtobj.papers = obj.GetProperty("papers");
  579. //examExtobj = obj.ToObject<object>();
  580. exams.Add(examExtobj);
  581. }
  582. }
  583. }
  584. //用户在线记录
  585. try
  586. {
  587. _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  588. }
  589. catch { }
  590. //取得Teacher Blob 容器位置及SAS
  591. var container = _azureStorage.GetBlobContainerClient(id);
  592. await container.CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建Teacher私有容器,如存在則不做任何事,保障容器一定存在
  593. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete);
  594. var (blob_uri_read, blob_sas_read) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Read);
  595. var (blob_uri_write, blob_sas_write) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Write);
  596. return Ok(new { blob_uri, blob_sas, blob_sas_read, blob_sas_write, schools, defaultschool, courses, exams });
  597. }
  598. catch (Exception ex)
  599. {
  600. await _dingDing.SendBotMsg($"IES5,{_option.Location},hiteach/GetTeacherInfo()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  601. return BadRequest();
  602. }
  603. }
  604. [Authorize(Roles = "HiTeach")]
  605. [ProducesDefaultResponseType]
  606. [HttpPost("get-school-info")]
  607. public async Task<IActionResult> GetSchoolInfo(JsonElement request)
  608. {
  609. try
  610. {
  611. string id_token = HttpContext.GetXAuth("IdToken");
  612. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  613. if (string.IsNullOrEmpty(id_token)) return BadRequest();
  614. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  615. var jwt = new JwtSecurityToken(id_token);
  616. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.Ordinal)) return BadRequest();
  617. var id = jwt.Payload.Sub;
  618. var client = _azureCosmos.GetCosmosClient();
  619. //取得學校學段、年級、科目、考試類型
  620. List<object> periods = new List<object>();
  621. List<object> grades = new List<object>();
  622. List<object> subjects = new List<object>();
  623. List<object> examTypes = new List<object>();
  624. School school_base = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  625. if (!string.IsNullOrWhiteSpace(school_base.id))
  626. {
  627. foreach(Period periodinfo in school_base.period)
  628. {
  629. periods.Add(new { id= periodinfo.id, name= periodinfo.name });
  630. int gradeIndex = 0;
  631. foreach (string gradeName in periodinfo.grades)
  632. {
  633. grades.Add(new { id = gradeIndex.ToString(), name = gradeName, periodId = periodinfo.id });
  634. gradeIndex++;
  635. }
  636. foreach (Subject subjectinfo in periodinfo.subjects)
  637. {
  638. subjects.Add(new { id = subjectinfo.id, name = subjectinfo.name, periodId = periodinfo.id });
  639. }
  640. foreach (var examType in periodinfo.analysis.type)
  641. {
  642. examTypes.Add(examType);
  643. }
  644. }
  645. }
  646. else //無此學校資料
  647. {
  648. return BadRequest();
  649. }
  650. //該老師排定的學校課程
  651. List<object> courses = new List<object>();
  652. List<string> classIds = new List<string>(); //班級ID列表 篩選校園評測用
  653. List<string> groupIds = new List<string>(); //團體ID列表 篩選校園評測用
  654. //取得所有班级
  655. (List<Class> school_classes, _) = await SchoolService.DoGraduateClasses(_httpTrigger, _azureCosmos, null, school_base, _option, _dingDing);
  656. //取得學校安排老師課程
  657. var query = $"SELECT DISTINCT c.id, c.name, c.scope, c.subject, c.period.id AS periodId, schedule.classId AS scheduleClassId, schedule.teacher AS scheduleTeacher, schedule.stulist AS scheduleStulist, schedule.notice AS scheduleNotice FROM c JOIN schedule IN c.schedule WHERE schedule.teacherId = '{id}'";
  658. //var query = $"SELECT c.id, c.name, c.teacher, cc.course, c.scope FROM c JOIN cc IN c.courses JOIN cct IN cc.teachers WHERE cct.id = '{id}'";
  659. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{school_code}") }))
  660. {
  661. var jsoncs = await JsonDocument.ParseAsync(item.ContentStream);
  662. if (jsoncs.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  663. {
  664. foreach (var obj in jsoncs.RootElement.GetProperty("Documents").EnumerateArray())
  665. {
  666. dynamic classExtobj = new ExpandoObject();
  667. classExtobj.id = null;
  668. classExtobj.code = null;
  669. classExtobj.name = null;
  670. classExtobj.scope = null;
  671. classExtobj.gradeId = null;
  672. classExtobj.gradeName = null;
  673. classExtobj.year = 0;
  674. classExtobj.teacher = null;
  675. classExtobj.stuListId = null;
  676. classExtobj.stuCnt = 0;
  677. classExtobj.grpCnt = 0;
  678. //編制班
  679. string classIdNow = string.Empty;
  680. if (obj.TryGetProperty("scheduleClassId", out JsonElement scheduleClassId))
  681. {
  682. classIdNow = Convert.ToString(scheduleClassId);
  683. }
  684. if (!string.IsNullOrWhiteSpace(classIdNow))
  685. {
  686. Class classInfo = school_classes.Where((Class x) => x.id == classIdNow).FirstOrDefault();
  687. if(classInfo != null && !string.IsNullOrWhiteSpace(classInfo.id))
  688. {
  689. classIds.Add(classInfo.id);
  690. classExtobj.id = classInfo.id;
  691. classExtobj.code = classInfo.code;
  692. classExtobj.name = classInfo.name;
  693. classExtobj.year = classInfo.year;
  694. classExtobj.teacher = classInfo.teacher;
  695. //取得學生數
  696. var queryclstc = $"SELECT Count(1) AS stuCnt FROM c WHERE c.classId = '{classIdNow}'";
  697. await foreach (var itemclstc in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: queryclstc, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{school_code}") }))
  698. {
  699. var jsonclstc = await JsonDocument.ParseAsync(itemclstc.ContentStream);
  700. foreach (var objstc in jsonclstc.RootElement.GetProperty("Documents").EnumerateArray())
  701. {
  702. classExtobj.stuCnt = objstc.GetProperty("stuCnt").GetInt32();
  703. }
  704. }
  705. //取得分組數
  706. var queryclgp = $"SELECT c.groupId FROM c WHERE c.classId = '{classIdNow}' AND IS_NULL(c.groupId)=false GROUP BY c.groupId";
  707. await foreach (var itemgp in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: queryclgp, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{school_code}") }))
  708. {
  709. var jsongp = await JsonDocument.ParseAsync(itemgp.ContentStream);
  710. if (jsongp.RootElement.TryGetProperty("_count", out JsonElement gpcount) && gpcount.GetInt32() > 0)
  711. {
  712. classExtobj.grpCnt = gpcount.GetInt32();
  713. }
  714. }
  715. //取得年級
  716. if (obj.TryGetProperty("periodId", out JsonElement curPeriodIdJson))
  717. {
  718. string curPeriodId = curPeriodIdJson.GetString();
  719. Period curPeriod = school_base.period.Where((Period x) => x.id == curPeriodId).FirstOrDefault();
  720. if(curPeriodId != null)
  721. {
  722. var gradeInfo = getGradeInfoByYear(classInfo.year, curPeriod);
  723. if(!string.IsNullOrWhiteSpace(gradeInfo.id))
  724. {
  725. classExtobj.gradeId = gradeInfo.id;
  726. classExtobj.gradeName = gradeInfo.name;
  727. }
  728. }
  729. }
  730. }
  731. }
  732. //選課班 (過期者不選)
  733. long nowtime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  734. var stuListId = obj.GetProperty("scheduleStulist");
  735. if (!stuListId.ValueKind.Equals(JsonValueKind.Null) && !string.IsNullOrWhiteSpace(stuListId.GetString()))
  736. {
  737. classExtobj.stuListId = Convert.ToString(stuListId);
  738. await foreach (var stuitem in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.code, c.id, c.name, c.year, c.tcount, c.scount FROM c WHERE c.id = '{stuListId}' AND (NOT IS_DEFINED(c.expire) OR {nowtime} <= c.expire )", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GroupList-{school_code}") }))
  739. {
  740. var jsonstu = await JsonDocument.ParseAsync(stuitem.ContentStream);
  741. foreach (var stuobj in jsonstu.RootElement.GetProperty("Documents").EnumerateArray())
  742. {
  743. groupIds.Add(Convert.ToString(stuobj.GetProperty("id")));
  744. classExtobj.id = Convert.ToString(stuobj.GetProperty("id"));
  745. classExtobj.code = Convert.ToString(stuobj.GetProperty("code"));
  746. classExtobj.name = Convert.ToString(stuobj.GetProperty("name"));
  747. classExtobj.gradeId = null;
  748. classExtobj.year = (stuobj.TryGetProperty("year", out JsonElement yearJson)) ? yearJson.GetInt32() : 0;
  749. classExtobj.teacher = null;
  750. classExtobj.stuCnt += stuobj.GetProperty("tcount").GetInt32();
  751. classExtobj.stuCnt += stuobj.GetProperty("scount").GetInt32();
  752. }
  753. }
  754. }
  755. //課程
  756. string courseIdNow = obj.GetProperty("id").ToString();
  757. var courseExist = courses.Where((dynamic x) => x.id == courseIdNow).FirstOrDefault();
  758. if (courseExist == null)
  759. {
  760. dynamic courseExtobj = new ExpandoObject();
  761. courseExtobj.id = courseIdNow;
  762. courseExtobj.name = obj.GetProperty("name").ToString();
  763. courseExtobj.scope = obj.GetProperty("scope").ToString();
  764. courseExtobj.classes = new List<object>();
  765. courseExtobj.subject = obj.GetProperty("subject");
  766. //classExtobj.teacher = scheduleTeacherInfo;
  767. if (!string.IsNullOrWhiteSpace(classExtobj.id))
  768. {
  769. courseExtobj.classes.Add(classExtobj);
  770. }
  771. courses.Add(courseExtobj);
  772. }
  773. else
  774. {
  775. //classExtobj.teacher = scheduleTeacherInfo;
  776. if (!string.IsNullOrWhiteSpace(classExtobj.id))
  777. {
  778. courseExist.classes.Add(classExtobj);
  779. }
  780. }
  781. }
  782. }
  783. }
  784. //取得校園評測 (HiTeach只取source='1'(課中评量) && progress = 'going'(進行中)) && 排定的學校課程班級
  785. List<object> exams = new List<object>();
  786. string classSqlString = "";
  787. if (classIds.Count > 0 || groupIds.Count > 0)
  788. {
  789. if (classIds.Count > 0)
  790. {
  791. foreach (string classId in classIds)
  792. {
  793. if (!string.IsNullOrWhiteSpace(classSqlString))
  794. {
  795. classSqlString += " OR ";
  796. }
  797. classSqlString += $"ARRAY_CONTAINS(c.classes, '{classId}')";
  798. }
  799. }
  800. if (groupIds.Count > 0)
  801. {
  802. foreach (string groupId in groupIds)
  803. {
  804. if (!string.IsNullOrWhiteSpace(classSqlString))
  805. {
  806. classSqlString += " OR ";
  807. }
  808. classSqlString += $"ARRAY_CONTAINS(c.classes, '{groupId}')";
  809. }
  810. }
  811. }
  812. else //無分配任何課程教室,校園評測不取
  813. {
  814. classSqlString += "c.id = '0'";
  815. }
  816. classSqlString = $"AND ({classSqlString})";
  817. //取得評測ID List
  818. List<string> examIdList = new List<string>();
  819. await foreach (var exam in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT c.id FROM c WHERE c.source = '1' AND c.progress = 'going' {classSqlString}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{school_code}") }))
  820. {
  821. using var json = await JsonDocument.ParseAsync(exam.ContentStream);
  822. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  823. {
  824. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  825. {
  826. examIdList.Add(obj.GetProperty("id").GetString());
  827. }
  828. }
  829. }
  830. //取得有作答的評測班級
  831. Dictionary<string, List<string>> examClassFinDic = new Dictionary<string, List<string>>();
  832. await foreach (var exam in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT c.examId, c.info.id as classId FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(examIdList)}, c.examId) AND c.progress=true", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{school_code}") }))
  833. {
  834. var jsonecr = await JsonDocument.ParseAsync(exam.ContentStream);
  835. if (jsonecr.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  836. {
  837. foreach (var obj in jsonecr.RootElement.GetProperty("Documents").EnumerateArray())
  838. {
  839. string examId = obj.GetProperty("examId").ToString();
  840. string classId = obj.GetProperty("classId").ToString();
  841. if (examClassFinDic.ContainsKey(examId) && !examClassFinDic[examId].Contains(classId))
  842. {
  843. examClassFinDic[examId].Add(classId);
  844. }
  845. else
  846. {
  847. List<string> classIdList = new List<string>();
  848. classIdList.Add(classId);
  849. examClassFinDic.Add(examId, classIdList);
  850. }
  851. }
  852. }
  853. }
  854. //取得評測資料
  855. await foreach (var exam in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: $"SELECT c.code, c.id, c.name, c.createTime, c.startTime, c.endTime ,c.year, c.source, c.type, c.progress, c.stuCount, c.scope, c.owner, c.period, c.grades, c.subjects, c.classes, c.stuLists, ARRAY(SELECT p.id, p.code, p.name, p.blob, p.scope FROM p IN c.papers) AS papers FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(examIdList)}, c.id)", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{school_code}") }))
  856. {
  857. var jsonex = await JsonDocument.ParseAsync(exam.ContentStream);
  858. if (jsonex.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  859. {
  860. foreach (var obj in jsonex.RootElement.GetProperty("Documents").EnumerateArray())
  861. {
  862. dynamic examExtobj = new ExpandoObject();
  863. string examId = obj.GetProperty("id").GetString();
  864. examExtobj.code = obj.GetProperty("code");
  865. examExtobj.id = examId;
  866. examExtobj.name = obj.GetProperty("name");
  867. examExtobj.createTime = obj.GetProperty("createTime");
  868. examExtobj.startTime = obj.GetProperty("startTime");
  869. examExtobj.endTime = obj.GetProperty("endTime");
  870. examExtobj.year = obj.GetProperty("year");
  871. examExtobj.source = obj.GetProperty("source");
  872. examExtobj.type = obj.GetProperty("type");
  873. examExtobj.progress = obj.GetProperty("progress");
  874. examExtobj.stuCount = obj.GetProperty("stuCount");
  875. examExtobj.scope = obj.GetProperty("scope");
  876. examExtobj.owner = obj.GetProperty("owner");
  877. examExtobj.period = obj.GetProperty("period");
  878. examExtobj.grades = obj.GetProperty("grades");
  879. examExtobj.subjects = obj.GetProperty("subjects");
  880. examExtobj.classes = (obj.TryGetProperty("classes", out JsonElement classes)) ? classes.ToObject<List<string>>() : new List<string>();
  881. examExtobj.stuLists = (obj.TryGetProperty("stuLists", out JsonElement stuLists)) ? stuLists.ToObject<List<string>>() : new List<string>();
  882. examExtobj.finishClasses = (examClassFinDic.ContainsKey(examId)) ? examClassFinDic[examId] : new List<string>();
  883. examExtobj.papers = obj.GetProperty("papers");
  884. //examExtobj = obj.ToObject<object>();
  885. exams.Add(examExtobj);
  886. }
  887. }
  888. }
  889. //用户在线记录
  890. try
  891. {
  892. _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  893. }
  894. catch { }
  895. //取得School Blob 容器位置及SAS
  896. string school_code_blob = school_code.GetString().ToLower();
  897. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Write); //讀列
  898. var (blob_uri_read, blob_sas_read) = _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Read); //讀
  899. var (blob_uri_write, blob_sas_write) = _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Write); //寫
  900. return Ok(new { blob_uri, blob_sas, blob_sas_read, blob_sas_write, periods, grades, subjects, courses, examTypes, exams });
  901. }
  902. catch (Exception ex)
  903. {
  904. await _dingDing.SendBotMsg($"IES5,{_option.Location},hiteach/GetSchoolInfo()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  905. return BadRequest();
  906. }
  907. }
  908. //取得試卷
  909. [Authorize(Roles = "HiTeach")]
  910. [ProducesDefaultResponseType]
  911. [HttpPost("get-paper")]
  912. public async Task<IActionResult> GetPaperList(JsonElement request)
  913. {
  914. try
  915. {
  916. //Header驗證
  917. string id_token = HttpContext.GetXAuth("IdToken");
  918. if (string.IsNullOrEmpty(id_token)) return BadRequest();
  919. var jwt = new JwtSecurityToken(id_token);
  920. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.Ordinal)) return BadRequest();
  921. var id = jwt.Payload.Sub;
  922. var client = _azureCosmos.GetCosmosClient();
  923. //參數
  924. if (!request.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest();
  925. string partitionid = string.Empty;
  926. string container = string.Empty;
  927. if (grant_type.ToString().Equals("school"))
  928. {
  929. if (!request.TryGetProperty("school_code", out JsonElement school_code_json))
  930. {
  931. return BadRequest();
  932. }
  933. else
  934. {
  935. partitionid = school_code_json.ToString();
  936. container = "School";
  937. }
  938. }
  939. else
  940. {
  941. partitionid = id.ToString();
  942. container = "Teacher";
  943. }
  944. //SQL文
  945. List<object> papers = new List<object>();
  946. string queryWhere = " WHERE 1=1 ";
  947. string queryOption = string.Empty;
  948. //學段
  949. if (request.TryGetProperty("periodId", out JsonElement periodId) && container.Equals("School"))
  950. {
  951. queryWhere += $" AND c.periodId = '{periodId}'";
  952. }
  953. //年級
  954. if (request.TryGetProperty("gradeId", out JsonElement gradeIds) && gradeIds.GetArrayLength() > 0 && container.Equals("School"))
  955. {
  956. string queryOptionForGrade = string.Empty;
  957. for (int i = 0; i < gradeIds.GetArrayLength(); i++)
  958. {
  959. if (!string.IsNullOrWhiteSpace(queryOptionForGrade))
  960. {
  961. queryOptionForGrade += " OR ";
  962. }
  963. queryOptionForGrade += $" ARRAY_CONTAINS(c.gradeIds, '{gradeIds[i]}', true)";
  964. }
  965. queryWhere += $" AND ( {queryOptionForGrade} )";
  966. }
  967. //科目ID
  968. if (request.TryGetProperty("subjectId", out JsonElement subjectId) && container.Equals("School"))
  969. {
  970. queryWhere += $" AND c.subjectId = '{subjectId}'";
  971. }
  972. //科目名稱
  973. if (request.TryGetProperty("subjectName", out JsonElement subjectName))
  974. {
  975. queryWhere += $" AND c.subjectName = '{subjectName}'";
  976. }
  977. //試卷ID
  978. if (request.TryGetProperty("id", out JsonElement paperId))
  979. {
  980. queryWhere += $" AND c.id = '{paperId}'";
  981. }
  982. int perpage = 0; //每頁幾條
  983. if (request.TryGetProperty("perpage", out JsonElement perpage_json)) perpage = perpage_json.GetInt32();
  984. int page = 0; //目前第幾頁
  985. if (request.TryGetProperty("page", out JsonElement page_json))
  986. {
  987. if (page_json.GetInt32() > 0)
  988. {
  989. page = page_json.GetInt32() - 1;
  990. }
  991. }
  992. string order = "createTime"; //排序項目
  993. if (request.TryGetProperty("order", out JsonElement order_json))
  994. {
  995. if (order_json.ToString().Equals("useCount"))
  996. {
  997. order = order_json.ToString();
  998. }
  999. }
  1000. queryOption += $" Order By c." + order + " DESC ";
  1001. if (perpage > 0)
  1002. {
  1003. queryOption += $" OFFSET {perpage * page} LIMIT {perpage}";
  1004. }
  1005. //資料取得
  1006. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIterator(queryText: $"SELECT c.code, c.id, c.periodId, c.gradeIds, c.subjectId, c.subjectName, c.name, REPLACE(c.blob, 'index.json', '') AS blob, c.score, c.useCount, ARRAY_LENGTH(c.scoring) AS itemCount, c.createTime, c.scope From c {queryWhere + queryOption}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Paper-{partitionid}") }))
  1007. {
  1008. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1009. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1010. {
  1011. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1012. {
  1013. papers.Add(obj.ToObject<object>());
  1014. }
  1015. }
  1016. }
  1017. //總件數
  1018. int totalCount = 0;
  1019. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIterator(queryText: $"SELECT VALUE COUNT(1) From c {queryWhere}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Paper-{partitionid}") }))
  1020. {
  1021. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1022. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1023. {
  1024. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1025. {
  1026. totalCount = obj.GetInt32();
  1027. }
  1028. }
  1029. }
  1030. return Ok(new { papers, totalCount });
  1031. }
  1032. catch (Exception ex)
  1033. {
  1034. return BadRequest();
  1035. }
  1036. }
  1037. //取得試題
  1038. [Authorize(Roles = "HiTeach")]
  1039. [ProducesDefaultResponseType]
  1040. [HttpPost("get-item")]
  1041. public async Task<IActionResult> GetItemList(JsonElement request)
  1042. {
  1043. try
  1044. {
  1045. //Header驗證
  1046. string id_token = HttpContext.GetXAuth("IdToken");
  1047. if (string.IsNullOrEmpty(id_token)) return BadRequest();
  1048. var jwt = new JwtSecurityToken(id_token);
  1049. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.Ordinal)) return BadRequest();
  1050. var id = jwt.Payload.Sub;
  1051. var client = _azureCosmos.GetCosmosClient();
  1052. //參數
  1053. if (!request.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest();
  1054. string partitionid = string.Empty;
  1055. string container = string.Empty;
  1056. if (grant_type.ToString().Equals("school"))
  1057. {
  1058. if (!request.TryGetProperty("school_code", out JsonElement school_code_json))
  1059. {
  1060. return BadRequest();
  1061. }
  1062. else
  1063. {
  1064. partitionid = school_code_json.ToString();
  1065. container = "School";
  1066. }
  1067. }
  1068. else
  1069. {
  1070. partitionid = id.ToString();
  1071. container = "Teacher";
  1072. }
  1073. //SQL文
  1074. List<object> items = new List<object>();
  1075. string queryWhere = " WHERE 1=1 ";
  1076. string queryOption = string.Empty;
  1077. //學段
  1078. if (request.TryGetProperty("periodId", out JsonElement periodId) && container.Equals("School"))
  1079. {
  1080. queryWhere += $" AND c.periodId = '{periodId}'";
  1081. }
  1082. //年級
  1083. if (request.TryGetProperty("gradeId", out JsonElement gradeIds) && gradeIds.GetArrayLength() > 0 && container.Equals("School"))
  1084. {
  1085. string queryOptionForGrade = string.Empty;
  1086. for (int i = 0; i < gradeIds.GetArrayLength(); i++)
  1087. {
  1088. if (!string.IsNullOrWhiteSpace(queryOptionForGrade))
  1089. {
  1090. queryOptionForGrade += " OR ";
  1091. }
  1092. queryOptionForGrade += $" ARRAY_CONTAINS(c.gradeIds, '{gradeIds[i]}', true)";
  1093. }
  1094. queryWhere += $" AND ( {queryOptionForGrade} )";
  1095. }
  1096. //科目ID
  1097. if (request.TryGetProperty("subjectId", out JsonElement subjectId) && container.Equals("school"))
  1098. {
  1099. queryWhere += $" AND c.subjectId = '{subjectId}'";
  1100. }
  1101. //科目名稱
  1102. if (request.TryGetProperty("subjectName", out JsonElement subjectName))
  1103. {
  1104. queryWhere += $" AND c.subjectName = '{subjectName}'";
  1105. }
  1106. //題型
  1107. int dummy = 0;
  1108. if (request.TryGetProperty("type", out JsonElement type))
  1109. {
  1110. queryWhere += $" AND c.type = '{type}'";
  1111. }
  1112. //難度
  1113. dummy = 0;
  1114. if (request.TryGetProperty("level", out JsonElement level) && int.TryParse(level.ToString(), out dummy))
  1115. {
  1116. queryWhere += $" AND c.level = {level}";
  1117. }
  1118. //層次
  1119. if (request.TryGetProperty("field", out JsonElement field) && int.TryParse(field.ToString(), out dummy))
  1120. {
  1121. queryWhere += $" AND c.field = {field}";
  1122. }
  1123. int perpage = 0; //每頁幾條
  1124. if (request.TryGetProperty("perpage", out JsonElement perpage_json)) perpage = perpage_json.GetInt32();
  1125. int page = 0; //目前第幾頁
  1126. if (request.TryGetProperty("page", out JsonElement page_json))
  1127. {
  1128. if (page_json.GetInt32() > 0)
  1129. {
  1130. page = page_json.GetInt32() - 1;
  1131. }
  1132. }
  1133. string order = "createTime"; //排序項目
  1134. if (request.TryGetProperty("order", out JsonElement order_json))
  1135. {
  1136. if (order_json.ToString().Equals("useCount"))
  1137. {
  1138. order = order_json.ToString();
  1139. }
  1140. }
  1141. queryOption += $" Order By c." + order + " DESC ";
  1142. if (perpage > 0)
  1143. {
  1144. queryOption += $" OFFSET {perpage * page} LIMIT {perpage}";
  1145. }
  1146. //資料取得
  1147. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.periodId, c.gradeIds, c.subjectId, c.subjectName, c.blob, c.field, c.level, c.type, c.useCount, c.createTime From c {queryWhere + queryOption}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-{partitionid}") }))
  1148. {
  1149. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1150. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1151. {
  1152. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1153. {
  1154. items.Add(obj.ToObject<object>());
  1155. }
  1156. }
  1157. }
  1158. //總件數
  1159. int totalCount = 0;
  1160. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIterator(queryText: $"SELECT VALUE COUNT(1) From c {queryWhere}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-{partitionid}") }))
  1161. {
  1162. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1163. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1164. {
  1165. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1166. {
  1167. totalCount = obj.GetInt32();
  1168. }
  1169. }
  1170. }
  1171. return Ok(new { items, totalCount });
  1172. }
  1173. catch (Exception ex)
  1174. {
  1175. return BadRequest();
  1176. }
  1177. }
  1178. //取得知識點
  1179. [Authorize(Roles = "HiTeach")]
  1180. [ProducesDefaultResponseType]
  1181. [HttpPost("get-knowledge")]
  1182. public async Task<IActionResult> GetKnowledgePointList(JsonElement request)
  1183. {
  1184. var client = _azureCosmos.GetCosmosClient();
  1185. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  1186. //知識點
  1187. List<object> points = new List<object>();
  1188. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.subjectId FROM c WHERE c.type = 'point'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Knowledge-{school_code}") }))
  1189. {
  1190. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1191. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1192. {
  1193. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1194. {
  1195. points.Add(obj.ToObject<object>());
  1196. }
  1197. }
  1198. }
  1199. return Ok(points);
  1200. }
  1201. //取得課綱
  1202. [Authorize(Roles = "HiTeach")]
  1203. [ProducesDefaultResponseType]
  1204. [HttpPost("get-syllabus")]
  1205. public async Task<IActionResult> GetSyllabusList(JsonElement request)
  1206. {
  1207. //Header驗證
  1208. string id_token = HttpContext.GetXAuth("IdToken");
  1209. if (string.IsNullOrEmpty(id_token)) return BadRequest();
  1210. var jwt = new JwtSecurityToken(id_token);
  1211. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
  1212. var id = jwt.Payload.Sub;
  1213. //參數驗證
  1214. if (!request.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest();
  1215. string grantType = (grant_type.GetString().Equals("school")) ? "school" : "private";
  1216. JsonElement school_code = new();
  1217. if (grantType == "school" && !request.TryGetProperty("school_code", out school_code)) return BadRequest();
  1218. string dataId = (grantType.Equals("school")) ? school_code.GetString() : id;
  1219. string container = (grantType.Equals("school")) ? "School" : "Teacher";
  1220. string periodId = (request.TryGetProperty("periodId", out JsonElement periodIdJobj)) ? periodIdJobj.GetString() : String.Empty;
  1221. string subjectId = (request.TryGetProperty("subjectId", out JsonElement subjectIdJobj)) ? subjectIdJobj.GetString() : String.Empty;
  1222. string volumeId = (request.TryGetProperty("volumeId", out JsonElement volumeIdJobj)) ? volumeIdJobj.GetString() : String.Empty;
  1223. string syllabusId = (request.TryGetProperty("syllabusId", out JsonElement syllabusIdJobj)) ? syllabusIdJobj.GetString() : String.Empty;
  1224. int display = (request.TryGetProperty("display", out JsonElement displayJobj)) ? displayJobj.GetInt32() : 0;
  1225. var client = _azureCosmos.GetCosmosClient();
  1226. //取得卷前置作業:有給syllabusId,取得該課綱的卷ID
  1227. string volumeIdFromSyllabusId = String.Empty;
  1228. if (!string.IsNullOrWhiteSpace(syllabusId))
  1229. {
  1230. await foreach (string volid in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryIterator<string>(queryText: $"SELECT value(c.volumeId) FROM c WHERE c.id = '{syllabusId}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{dataId}") }))
  1231. {
  1232. volumeIdFromSyllabusId = volid;
  1233. }
  1234. }
  1235. //取得卷
  1236. List<object> volumes = new();
  1237. List<Volume> volumeList = new();
  1238. List<string> volumeIdList = new();
  1239. string queryTextVol = string.Empty;
  1240. List<string> WhereVol = new();
  1241. if (!string.IsNullOrWhiteSpace(periodId)) WhereVol.Add($" c.periodId = '{periodId}' ");
  1242. if (!string.IsNullOrWhiteSpace(subjectId)) WhereVol.Add($" c.subjectId = '{subjectId}' ");
  1243. if (!string.IsNullOrWhiteSpace(volumeId)) WhereVol.Add($" c.id = '{volumeId}' ");
  1244. if (!string.IsNullOrWhiteSpace(volumeIdFromSyllabusId)) WhereVol.Add($" c.id = '{volumeIdFromSyllabusId}' ");
  1245. foreach (string Where in WhereVol)
  1246. {
  1247. queryTextVol += (String.IsNullOrWhiteSpace(queryTextVol)) ? $" WHERE {Where} " : $" AND {Where} ";
  1248. }
  1249. queryTextVol = "SELECT * FROM c " + queryTextVol;
  1250. await foreach (var itemv in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryStreamIterator(queryText: queryTextVol, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Volume-{dataId}") }))
  1251. {
  1252. var jsons = await JsonDocument.ParseAsync(itemv.ContentStream);
  1253. if (jsons.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1254. {
  1255. foreach (var obj in jsons.RootElement.GetProperty("Documents").EnumerateArray())
  1256. {
  1257. Volume volExtobj = obj.ToObject<Volume>();
  1258. volumeList.Add(volExtobj);
  1259. volumeIdList.Add(volExtobj.id);
  1260. }
  1261. }
  1262. }
  1263. //取得課綱 ※display=1時不取任何課綱
  1264. List<SyllabusTreeNode> treeNodes = new();
  1265. if (!display.Equals(1))
  1266. {
  1267. string queryTextSyl = $"SELECT value(c) FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(volumeIdList)}, c.volumeId, true)";
  1268. if (!string.IsNullOrWhiteSpace(syllabusId)) queryTextSyl += $" AND c.id = '{syllabusId}'";
  1269. await foreach (Syllabus items in client.GetContainer(Constant.TEAMModelOS, container).GetItemQueryIterator<Syllabus>(queryText: queryTextSyl, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{dataId}") }))
  1270. {
  1271. List<SyllabusTree> trees = SyllabusService.ListToTree(items.children);
  1272. SyllabusTreeNode tree = new() { id = items.id, scope = items.scope, trees = trees, volumeId = items.volumeId, auth = items.auth, codeval = $"{id}" };
  1273. treeNodes.Add(tree);
  1274. }
  1275. }
  1276. //輸出結果
  1277. foreach (Volume vr in volumeList)
  1278. {
  1279. volumes.Add(new
  1280. {
  1281. vr.periodId,
  1282. vr.subjectId,
  1283. vr.id,
  1284. vr.gradeId,
  1285. vr.semesterId,
  1286. vr.name,
  1287. vr.creatorId,
  1288. vr.creatorName,
  1289. vr.school,
  1290. vr.scope,
  1291. vr.syllabusIds,
  1292. vr.auth,
  1293. vr.order,
  1294. syllabus = treeNodes.Where(t => t.volumeId == vr.id).ToList()
  1295. });
  1296. }
  1297. return Ok(volumes);
  1298. }
  1299. //取得被分享的課綱
  1300. [ProducesDefaultResponseType]
  1301. [Authorize(Roles = "HiTeach")]
  1302. [HttpPost("get-share-syllabus")]
  1303. public async Task<IActionResult> GetShareSyllabusList(JsonElement request)
  1304. {
  1305. //Header驗證
  1306. string id_token = HttpContext.GetXAuth("IdToken");
  1307. if (string.IsNullOrEmpty(id_token)) return BadRequest();
  1308. var jwt = new JwtSecurityToken(id_token);
  1309. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
  1310. var id = jwt.Payload.Sub;
  1311. //參數驗證
  1312. string volumeId = (request.TryGetProperty("volumeId", out JsonElement volumeIdJobj)) ? volumeIdJobj.GetString() : String.Empty;
  1313. string syllabusId = (request.TryGetProperty("syllabusId", out JsonElement syllabusIdJobj)) ? syllabusIdJobj.GetString() : String.Empty;
  1314. int display = (request.TryGetProperty("display", out JsonElement displayJobj)) ? displayJobj.GetInt32() : 0;
  1315. var client = _azureCosmos.GetCosmosClient();
  1316. //取得分享內容
  1317. Dictionary<string, string> tmidDic = new Dictionary<string, string>();
  1318. List<Volume> volumeList = new List<Volume>();
  1319. StringBuilder queryText = new StringBuilder("SELECT value(c) FROM c WHERE c.type='share' ");
  1320. if (!string.IsNullOrWhiteSpace(volumeId))
  1321. {
  1322. queryText.Append($"AND c.volumeId='{volumeId}'");
  1323. }
  1324. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<Share>(queryText: queryText.ToString(),
  1325. requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Share-share-{id}") }))
  1326. {
  1327. if(!string.IsNullOrWhiteSpace(syllabusId))
  1328. {
  1329. if(item.id.Equals(syllabusId))
  1330. {
  1331. Volume volumeExist = volumeList.Where((Volume v) => v.id == item.volumeId).FirstOrDefault();
  1332. if(volumeExist == null)
  1333. {
  1334. Volume volume = new Volume();
  1335. volume.id = item.volumeId;
  1336. volume.name = item.volumeName;
  1337. volume.creatorId = item.issuer;
  1338. volume.creatorName = item.issuerName;
  1339. volume.syllabusIds.Add(item.id);
  1340. volumeList.Add(volume);
  1341. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(item.issuer, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
  1342. if (!tmidDic.ContainsKey(item.issuer)) tmidDic.Add(item.issuer, blob_sas);
  1343. }
  1344. else
  1345. {
  1346. volumeExist.syllabusIds.Add(item.id);
  1347. }
  1348. }
  1349. }
  1350. else
  1351. {
  1352. Volume volumeExist = volumeList.Where((Volume v) => v.id == item.volumeId).FirstOrDefault();
  1353. if (volumeExist == null)
  1354. {
  1355. Volume volume = new Volume();
  1356. volume.id = item.volumeId;
  1357. volume.name = item.volumeName;
  1358. volume.creatorId = item.issuer;
  1359. volume.creatorName = item.issuerName;
  1360. volume.syllabusIds.Add(item.id);
  1361. volumeList.Add(volume);
  1362. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(item.issuer, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
  1363. if(!tmidDic.ContainsKey(item.issuer)) tmidDic.Add(item.issuer, blob_sas);
  1364. }
  1365. else
  1366. {
  1367. volumeExist.syllabusIds.Add(item.id);
  1368. }
  1369. }
  1370. }
  1371. //取得課綱並輸出回傳值
  1372. List<object> result = new List<object>();
  1373. foreach(Volume volTmp in volumeList)
  1374. {
  1375. List<SyllabusTreeNode> treeNodes = new();
  1376. if (!display.Equals(1))
  1377. {
  1378. string queryTextSyl = $"SELECT value(c) FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(volTmp.syllabusIds)}, c.id, true)";
  1379. await foreach (Syllabus items in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<Syllabus>(queryText: queryTextSyl, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{volTmp.creatorId}") }))
  1380. {
  1381. List<SyllabusTree> trees = SyllabusService.ListToTree(items.children);
  1382. SyllabusTreeNode tree = new() { id = items.id, scope = items.scope, trees = trees, volumeId = items.volumeId, auth = items.auth, codeval = $"{volTmp.creatorId}" };
  1383. treeNodes.Add(tree);
  1384. }
  1385. }
  1386. result.Add(new
  1387. {
  1388. id = volTmp.id,
  1389. name = volTmp.name,
  1390. creatorId = volTmp.creatorId,
  1391. creatorName = volTmp.creatorName,
  1392. creatorSas = (tmidDic.ContainsKey(volTmp.creatorId)) ? tmidDic[volTmp.creatorId] : null,
  1393. syllabusIds = volTmp.syllabusIds,
  1394. syllabus = treeNodes.ToList(),
  1395. });
  1396. }
  1397. return Ok(result);
  1398. }
  1399. //取得某班級的學生成員
  1400. [Authorize(Roles = "HiTeach")]
  1401. [ProducesDefaultResponseType]
  1402. [HttpPost("get-students-list")]
  1403. public async Task<IActionResult> GetStudentsList(JsonElement request)
  1404. {
  1405. string id_token = HttpContext.GetXAuth("IdToken");
  1406. if (string.IsNullOrWhiteSpace(id_token)) return BadRequest();
  1407. var jwt = new JwtSecurityToken(id_token);
  1408. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.Ordinal)) return BadRequest();
  1409. var id = jwt.Payload.Sub;
  1410. if (!request.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest();
  1411. request.TryGetProperty("class_code", out JsonElement class_code);
  1412. request.TryGetProperty("stulist_id", out JsonElement stulist_id);
  1413. string classId = Convert.ToString(class_code);
  1414. string stulist = Convert.ToString(stulist_id);
  1415. if (string.IsNullOrWhiteSpace(classId) && string.IsNullOrWhiteSpace(stulist)) return BadRequest();
  1416. request.TryGetProperty("school_code", out JsonElement school_code);
  1417. if (grant_type.GetString().Equals("school") && string.IsNullOrWhiteSpace(Convert.ToString(school_code))) return BadRequest();
  1418. var client = _azureCosmos.GetCosmosClient();
  1419. Dictionary<string, string> irsDic = new Dictionary<string, string>(); //key:學生ID或TMID value:irs號碼
  1420. string container = (grant_type.GetString().Equals("school")) ? "School" : "Teacher";
  1421. List<string> listids = new List<string>();
  1422. if (!string.IsNullOrWhiteSpace(stulist))
  1423. {
  1424. listids.Add(stulist);
  1425. }
  1426. else if (!string.IsNullOrWhiteSpace(classId))
  1427. {
  1428. listids.Add(classId);
  1429. }
  1430. (List<RMember> students, List<RGroupList> groupList) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, listids, $"{school_code}");
  1431. return Ok(new { students });
  1432. }
  1433. /// <summary>
  1434. /// 開始課堂(舊版,已被CreateLesson取代)
  1435. /// </summary>
  1436. /// <param name="request"></param>
  1437. /// <returns></returns>
  1438. [Authorize(Roles = "HiTeach")]
  1439. [ProducesDefaultResponseType]
  1440. [HttpPost("start-lesson")]
  1441. public async Task<IActionResult> StartLesson(JsonElement request)
  1442. {
  1443. //醍摩豆ID驗證
  1444. string teacherId = string.Empty;
  1445. string id_token = HttpContext.GetXAuth("IdToken");
  1446. if (!string.IsNullOrWhiteSpace(id_token))
  1447. {
  1448. var jwt = new JwtSecurityToken(id_token);
  1449. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.OrdinalIgnoreCase)) return BadRequest();
  1450. teacherId = jwt.Payload.Sub;
  1451. }
  1452. if (string.IsNullOrWhiteSpace(teacherId)) return BadRequest(); //無醍摩豆ID,BadRequest
  1453. //取得授課ID
  1454. string lesson_code = _snowflakeId.NextId().ToString();
  1455. //bool get_lesson_id = (string.IsNullOrWhiteSpace(Convert.ToString(lesson_id)) || Convert.ToString(lesson_id) != lesson_code) ? false : true;
  1456. //string tableName = "TeacherLesson";
  1457. return Ok(new { lesson_code });
  1458. }
  1459. //上傳評測結果
  1460. //錯誤代碼:error = 1001 message = "Paper blob copy failure."
  1461. // error = 1002 message = "Student answers blob upload failure."
  1462. [Authorize(Roles = "HiTeach")]
  1463. [ProducesDefaultResponseType]
  1464. [HttpPost("upd-exam-result")]
  1465. public async Task<IActionResult> UploadExamResult(JsonElement request)
  1466. {
  1467. try
  1468. {
  1469. string message = "";
  1470. int error = 0;
  1471. string id_token = HttpContext.GetXAuth("IdToken");
  1472. if (string.IsNullOrWhiteSpace(id_token)) return BadRequest();
  1473. var jwt = new JwtSecurityToken(id_token);
  1474. if (!jwt.Payload.Iss.Equals("account.teammodel", StringComparison.Ordinal)) return BadRequest();
  1475. var id = jwt.Payload.Sub;
  1476. if (!request.TryGetProperty("exam", out JsonElement exam)) return BadRequest();
  1477. if (!request.TryGetProperty("examClassResult", out JsonElement examClassResult)) return BadRequest();
  1478. bool blobUploaded = (request.TryGetProperty("blobUploaded", out JsonElement blobUploadedJson)) ? blobUploadedJson.GetBoolean() : false;
  1479. bool recordSwitch = (request.TryGetProperty("recordSwitch", out JsonElement recordSwitchJson)) ? recordSwitchJson.GetBoolean() : false;
  1480. //ExamInfo ExamInfoFromReq = exam.ToObject<ExamInfo>();
  1481. string strExam = JsonSerializer.Serialize(exam);
  1482. if (strExam.Contains("\"publish\":\"0\""))
  1483. {
  1484. strExam = strExam.Replace("\"publish\":\"0\"", "\"publish\":0");
  1485. }
  1486. ExamInfo ExamInfoFromReq = JsonSerializer.Deserialize<ExamInfo>(strExam);
  1487. string examId = ExamInfoFromReq.id;
  1488. string excode = ExamInfoFromReq.code;
  1489. //ExamInfo dbExamInfo = exam.ToObject<ExamInfo>();
  1490. ExamInfo dbExamInfo = JsonSerializer.Deserialize<ExamInfo>(strExam);
  1491. var queryex = $"SELECT * FROM c WHERE c.id = '{examId}'";
  1492. await foreach (var itemex in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: queryex, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{excode}") }))
  1493. {
  1494. var jsonex = await JsonDocument.ParseAsync(itemex.ContentStream);
  1495. if (jsonex.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1496. {
  1497. foreach (var obj in jsonex.RootElement.GetProperty("Documents").EnumerateArray())
  1498. {
  1499. string strExamDb = JsonSerializer.Serialize(obj);
  1500. if (strExamDb.Contains("\"publish\":\"0\""))
  1501. {
  1502. strExamDb = strExamDb.Replace("\"publish\":\"0\"", "\"publish\":0");
  1503. }
  1504. dbExamInfo = JsonSerializer.Deserialize<ExamInfo>(strExamDb);
  1505. //dbExamInfo = obj.ToObject<ExamInfo>();
  1506. }
  1507. }
  1508. }
  1509. if (string.IsNullOrWhiteSpace(dbExamInfo.id))
  1510. {
  1511. dbExamInfo = ExamInfoFromReq;
  1512. }
  1513. //Boolean isTestFlg = (_option.Location.Contains("Test") || _option.Location.Contains("Dep")) ? true : false;
  1514. //ExamInfo內容取得、調整 [2021-7-13 廢除,給予HiTeach學校Blob寫入權限,API不再對Blob做搬運]
  1515. //※規則 owner:"school" => 校園評測 "teacher" => 個人評測
  1516. //※規則 scope:"school" => 校本班級 "private" => 個人班級
  1517. //※規則 BLOB容器: scope:"school" => {學校ID}下 scope:"private" => {個人ID}下
  1518. string blobContainer = (!string.IsNullOrWhiteSpace(dbExamInfo.school) && dbExamInfo.scope.Equals("school")) ? dbExamInfo.school : id; //blob容器
  1519. //dbExamInfo.source = "1"; //評測來源固定為 1:課中評量(不應由API擅自變更)
  1520. //試卷List
  1521. List<Dictionary<string, string>> recordPaperInfo = new List<Dictionary<string, string>>();
  1522. int paperIndex = 0;
  1523. foreach (PaperSimple paperInfo in ExamInfoFromReq.papers)
  1524. {
  1525. string paperBlobPath = (!paperInfo.blob.EndsWith("/")) ? paperInfo.blob + "/" : paperInfo.blob;
  1526. paperBlobPath = (paperInfo.blob.StartsWith("/")) ? paperBlobPath.Remove(0, 1) : paperBlobPath;
  1527. string subjectId = dbExamInfo.subjects[paperIndex].id;
  1528. recordPaperInfo.Add(new Dictionary<string, string>() { { "id", paperInfo.id }, { "blob", paperBlobPath }, { "subjectId", subjectId }, { "itemcount", paperInfo.point.Count.ToString() } });
  1529. paperIndex++;
  1530. }
  1531. //取得課堂紀錄下的試卷資料(blob)、複製到評測紀錄下
  1532. bool paperDataCopyErrFlg = false; //試卷資料拷貝錯誤Flag true:拷貝錯誤 [2021-7-13 (測試站)不再對Blob做搬運 永為false]
  1533. //Blob搬運 (待HiTeach完善後刪除)
  1534. if (!blobUploaded)
  1535. {
  1536. foreach (Dictionary<string, string> recordPaperInfoDic in recordPaperInfo)
  1537. {
  1538. string targetScope = dbExamInfo.scope; //評測對象 school:校本班級 private:私人課程
  1539. var blobPrivateContainer = _azureStorage.GetBlobContainerClient(id);
  1540. string sourceBlobPath = recordPaperInfoDic["blob"];
  1541. string destBlobPath = $"exam/{dbExamInfo.id}/paper/{recordPaperInfoDic["subjectId"]}/"; //拷貝對象路徑 path:exam/{評測ID}/paper/{subjectID}/ ※2022-1-6 式樣變更[原]/paper/{試卷ID}/ [新]/paper/{subjectID}/
  1542. if (targetScope.Equals("school")) //校本
  1543. {
  1544. string schoolId = dbExamInfo.school;
  1545. var blobSchoolContainer = _azureStorage.GetBlobContainerClient(schoolId);
  1546. var sourceBlobContainer = (recordSwitch) ? blobSchoolContainer : blobPrivateContainer;
  1547. Azure.Pageable<BlobItem> sourceBlobs = sourceBlobContainer.GetBlobs(prefix: sourceBlobPath);
  1548. if (sourceBlobs.Count() > 0)
  1549. {
  1550. foreach (var blob in sourceBlobs)
  1551. {
  1552. var sourceFileBlob = sourceBlobContainer.GetBlobClient(blob.Name);
  1553. if (sourceFileBlob.Exists())
  1554. {
  1555. var sourceFileUri = sourceBlobContainer.GetBlobClient(blob.Name).Uri;
  1556. string fileName = blob.Name.Replace(sourceBlobPath, "");
  1557. string destBlobFilePath = $"{destBlobPath}{fileName}";
  1558. await blobSchoolContainer.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
  1559. }
  1560. else
  1561. {
  1562. paperDataCopyErrFlg = true;
  1563. }
  1564. }
  1565. }
  1566. }
  1567. else //私人
  1568. {
  1569. var sourceBlobContainer = blobPrivateContainer;
  1570. Azure.Pageable<BlobItem> sourceBlobs = sourceBlobContainer.GetBlobs(prefix: sourceBlobPath);
  1571. if (sourceBlobs.Count() > 0)
  1572. {
  1573. foreach (var blob in sourceBlobs)
  1574. {
  1575. var sourceFileBlob = sourceBlobContainer.GetBlobClient(blob.Name);
  1576. if (sourceFileBlob.Exists())
  1577. {
  1578. var sourceFileUri = sourceBlobContainer.GetBlobClient(blob.Name).Uri;
  1579. string fileName = blob.Name.Replace(sourceBlobPath, "");
  1580. string destBlobFilePath = $"{destBlobPath}{fileName}";
  1581. await blobPrivateContainer.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
  1582. }
  1583. else
  1584. {
  1585. paperDataCopyErrFlg = true;
  1586. }
  1587. }
  1588. }
  1589. }
  1590. //替換 Exam.papers.blob
  1591. PaperSimple paperInfoNow = dbExamInfo.papers.Where((PaperSimple p) => p.id == recordPaperInfoDic["id"]).FirstOrDefault();
  1592. string destBlobPathForDocument = (destBlobPath.EndsWith("/")) ? destBlobPath.Remove(destBlobPath.Length - 1, 1) : destBlobPath;
  1593. destBlobPathForDocument = (!destBlobPathForDocument.StartsWith("/")) ? "/" + destBlobPathForDocument : destBlobPathForDocument;
  1594. paperInfoNow.blob = destBlobPathForDocument;
  1595. }
  1596. }
  1597. //ExamClassResult內容調整
  1598. bool studentAnswerCopyErrFlg = false; //學生作答資料拷貝錯誤Flag true:拷貝錯誤 [2021-7-13 不再對Blob做搬運 永為false]
  1599. //Blob搬運 (待HiTeach完善後刪除)
  1600. if (!blobUploaded)
  1601. {
  1602. List<ExamClassResultStudentAnswerArrayOld> dbExamClassResultList = examClassResult.ToObject<List<ExamClassResultStudentAnswerArrayOld>>();
  1603. foreach (ExamClassResultStudentAnswerArrayOld examClassResultRow in dbExamClassResultList)
  1604. {
  1605. //以subjectId及classId(info.id)取得DB中的ExamClassResult
  1606. ExamClassResult examClassResultUpd = new ExamClassResult();
  1607. string exclcode = examClassResultRow.code;
  1608. string classId = examClassResultRow.info.id;
  1609. string subjectId = examClassResultRow.subjectId;
  1610. var querycr = $"SELECT * FROM c WHERE c.examId = '{examId}' AND c.info.id = '{classId}' AND c.subjectId = '{subjectId}'";
  1611. await foreach (var itemcr in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: querycr, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{exclcode}") }))
  1612. {
  1613. var jsontcr = await JsonDocument.ParseAsync(itemcr.ContentStream);
  1614. if (jsontcr.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1615. {
  1616. foreach (var obj in jsontcr.RootElement.GetProperty("Documents").EnumerateArray())
  1617. {
  1618. examClassResultUpd = obj.ToObject<ExamClassResult>();
  1619. examClassResultUpd.progress = examClassResultRow.progress;
  1620. examClassResultUpd.studentIds = examClassResultRow.studentIds;
  1621. examClassResultUpd.studentAnswers = new List<List<string>>();
  1622. examClassResultUpd.studentScores = examClassResultRow.studentScores;
  1623. examClassResultUpd.status = examClassResultRow.status;
  1624. examClassResultUpd.sum = examClassResultRow.sum;
  1625. }
  1626. }
  1627. }
  1628. //無法取得既有ExamClassResult,新建
  1629. if (string.IsNullOrWhiteSpace(examClassResultUpd.id))
  1630. {
  1631. examClassResultUpd.pk = examClassResultRow.pk;
  1632. examClassResultUpd.code = examClassResultRow.code;
  1633. examClassResultUpd.id = examClassResultRow.id;
  1634. examClassResultUpd.school = examClassResultRow.school;
  1635. examClassResultUpd.examId = examClassResultRow.examId;
  1636. examClassResultUpd.subjectId = examClassResultRow.subjectId;
  1637. examClassResultUpd.gradeId = examClassResultRow.gradeId;
  1638. examClassResultUpd.year = examClassResultRow.year;
  1639. examClassResultUpd.info = examClassResultRow.info;
  1640. examClassResultUpd.progress = examClassResultRow.progress;
  1641. examClassResultUpd.studentIds = examClassResultRow.studentIds;
  1642. examClassResultUpd.studentAnswers = new List<List<string>>(); //學生作答Blob位置
  1643. examClassResultUpd.studentScores = examClassResultRow.studentScores;
  1644. examClassResultUpd.status = examClassResultRow.status;
  1645. examClassResultUpd.scope = examClassResultRow.scope;
  1646. examClassResultUpd.sum = examClassResultRow.sum;
  1647. }
  1648. //examClassResult.studentAnswers (1)將學生答案上傳blob後轉換內容為blob路徑 //[2021-7-13 不再對Blob做搬運] (2)將學生答案放入examClassResult.ans
  1649. if (examClassResultRow.studentIds != null && examClassResultRow.studentIds.Count > 0 && examClassResultRow.studentAnswersArray != null && examClassResultRow.studentAnswersArray.Count > 0)
  1650. {
  1651. for (int i = 0; i < examClassResultRow.studentAnswersArray.Count; i++)
  1652. {
  1653. string studentId = examClassResultRow.studentIds[i];
  1654. string fileName = examId + "/" + subjectId + "/" + studentId;
  1655. string blob = fileName + "/" + "ans.json";
  1656. var uploadFileResult = await _azureStorage.GetBlobContainerClient(blobContainer).UploadFileByContainer(examClassResultRow.studentAnswersArray[i].ToJsonString(), "exam", blob, false);
  1657. if (string.IsNullOrWhiteSpace(uploadFileResult))
  1658. {
  1659. studentAnswerCopyErrFlg = true;
  1660. }
  1661. //studentAnswers
  1662. List<string> studenrAnswerRow = new List<string>();
  1663. studenrAnswerRow.Add(blob);
  1664. examClassResultUpd.studentAnswers.Add(studenrAnswerRow);
  1665. }
  1666. //examClassResult.ans 將學生答案放入
  1667. examClassResultUpd.ans = examClassResultRow.studentAnswersArray;
  1668. }
  1669. //批註欄位處理
  1670. Dictionary<string, string> paperStatusNow = recordPaperInfo.Where((Dictionary<string, string> p) => p.TryGetValue("subjectId", out string paperStatusSubjectId) && paperStatusSubjectId.Equals(subjectId)).FirstOrDefault();
  1671. int itemCount = Convert.ToInt32(paperStatusNow["itemcount"]);
  1672. if (examClassResultUpd.mark.Count != examClassResultUpd.studentIds.Count || (examClassResultUpd.mark.ElementAtOrDefault(0) != null && examClassResultUpd.mark[0].Count != itemCount)) //第一層:學生數不符 OR 第二層:第一位學生批註數!=試卷題數 => 批註格式不符
  1673. {
  1674. examClassResultUpd.mark = this.createEmptyMark(examClassResultUpd.studentIds.Count, itemCount);
  1675. }
  1676. //UPDATE ExamClassResult
  1677. var examClassResultResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync(examClassResultUpd, new PartitionKey(examClassResultUpd.code));
  1678. }
  1679. }
  1680. else
  1681. {
  1682. List<ExamClassResultStudentAnswerArray> dbExamClassResultList = examClassResult.ToObject<List<ExamClassResultStudentAnswerArray>>();
  1683. foreach (ExamClassResultStudentAnswerArray examClassResultRow in dbExamClassResultList)
  1684. {
  1685. //以subjectId及classId(info.id)取得DB中的ExamClassResult
  1686. ExamClassResult examClassResultUpd = new ExamClassResult();
  1687. string exclcode = examClassResultRow.code;
  1688. string classId = examClassResultRow.info.id;
  1689. string subjectId = examClassResultRow.subjectId;
  1690. var querycr = $"SELECT * FROM c WHERE c.examId = '{examId}' AND c.info.id = '{classId}' AND c.subjectId = '{subjectId}'";
  1691. await foreach (var itemcr in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: querycr, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{exclcode}") }))
  1692. {
  1693. var jsontcr = await JsonDocument.ParseAsync(itemcr.ContentStream);
  1694. if (jsontcr.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1695. {
  1696. foreach (var obj in jsontcr.RootElement.GetProperty("Documents").EnumerateArray())
  1697. {
  1698. examClassResultUpd = obj.ToObject<ExamClassResult>();
  1699. examClassResultUpd.progress = examClassResultRow.progress;
  1700. examClassResultUpd.studentIds = examClassResultRow.studentIds;
  1701. examClassResultUpd.studentAnswers = new List<List<string>>();
  1702. examClassResultUpd.studentScores = examClassResultRow.studentScores;
  1703. examClassResultUpd.status = examClassResultRow.status;
  1704. examClassResultUpd.sum = examClassResultRow.sum;
  1705. }
  1706. }
  1707. }
  1708. //無法取得既有ExamClassResult,新建
  1709. if (string.IsNullOrWhiteSpace(examClassResultUpd.id))
  1710. {
  1711. examClassResultUpd.pk = examClassResultRow.pk;
  1712. examClassResultUpd.code = examClassResultRow.code;
  1713. examClassResultUpd.id = examClassResultRow.id;
  1714. examClassResultUpd.school = examClassResultRow.school;
  1715. examClassResultUpd.examId = examClassResultRow.examId;
  1716. examClassResultUpd.subjectId = examClassResultRow.subjectId;
  1717. examClassResultUpd.gradeId = examClassResultRow.gradeId;
  1718. examClassResultUpd.year = examClassResultRow.year;
  1719. examClassResultUpd.info = examClassResultRow.info;
  1720. examClassResultUpd.progress = examClassResultRow.progress;
  1721. examClassResultUpd.studentIds = examClassResultRow.studentIds;
  1722. examClassResultUpd.studentAnswers = new List<List<string>>();
  1723. examClassResultUpd.studentScores = examClassResultRow.studentScores;
  1724. examClassResultUpd.status = examClassResultRow.status;
  1725. examClassResultUpd.scope = examClassResultRow.scope;
  1726. examClassResultUpd.sum = examClassResultRow.sum;
  1727. }
  1728. //學生作答Blob位置[2021-7-13 不再對Blob做搬運 學生答案直接寫入DB]
  1729. examClassResultUpd.studentAnswers = examClassResultRow.studentAnswersArray;
  1730. //學生作答答案 (從blob取得學生答案、放入ans欄位)
  1731. string targetScope = dbExamInfo.scope; //評測對象 school:校本班級 private:私人課程
  1732. var targetBlobContainer = _azureStorage.GetBlobContainerClient(id);
  1733. string blobCntr = id;
  1734. if (targetScope.Equals("school")) //校本
  1735. {
  1736. string schoolId = dbExamInfo.school;
  1737. blobCntr = schoolId;
  1738. }
  1739. targetBlobContainer = _azureStorage.GetBlobContainerClient(blobCntr);
  1740. if (examClassResultRow.studentAnswersArray.Count > 0)
  1741. {
  1742. foreach (List<string> studentAnswerBlobPath in examClassResultRow.studentAnswersArray)
  1743. {
  1744. string stuAnswerBlobPath = studentAnswerBlobPath.FirstOrDefault();
  1745. if (stuAnswerBlobPath != null)
  1746. {
  1747. StringBuilder builder = new StringBuilder();
  1748. builder.Append("exam").Append("/").Append(stuAnswerBlobPath);
  1749. var Download = await targetBlobContainer.GetBlobClient(builder.ToString()).DownloadAsync();
  1750. var json = await JsonDocument.ParseAsync(Download.Value.Content);
  1751. var Record = json.RootElement.ToObject<List<List<string>>>();
  1752. examClassResultUpd.ans.Add(Record);
  1753. }
  1754. }
  1755. }
  1756. //批註欄位處理
  1757. Dictionary<string, string> paperStatusNow = recordPaperInfo.Where((Dictionary<string, string> p) => p.TryGetValue("subjectId", out string subjectId) && subjectId.Equals(examClassResultUpd.subjectId)).FirstOrDefault();
  1758. int itemCount = Convert.ToInt32(paperStatusNow["itemcount"]);
  1759. if (examClassResultUpd.mark.Count != examClassResultUpd.studentIds.Count || (examClassResultUpd.mark.ElementAtOrDefault(0) != null && examClassResultUpd.mark[0].Count != itemCount)) //第一層:學生數不符 OR 第二層:第一位學生批註數!=試卷題數 => 批註格式不符
  1760. {
  1761. examClassResultUpd.mark = this.createEmptyMark(examClassResultUpd.studentIds.Count, itemCount);
  1762. }
  1763. //UPDATE ExamClassResult
  1764. var examClassResultResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync(examClassResultUpd, new PartitionKey(examClassResultUpd.code));
  1765. }
  1766. }
  1767. //UPDATE Exam
  1768. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync(dbExamInfo, new PartitionKey(dbExamInfo.code));
  1769. //錯誤處理
  1770. if (paperDataCopyErrFlg) //試卷Blob拷貝失敗
  1771. {
  1772. error = 1001;
  1773. message = "Paper blob copy failure.";
  1774. }
  1775. else if (studentAnswerCopyErrFlg) //學生作答資料上傳blob失敗
  1776. {
  1777. error = 1002;
  1778. message = "Student answers blob upload failure.";
  1779. }
  1780. return Ok(new { error, message });
  1781. }
  1782. catch (CosmosException cex)
  1783. {
  1784. return BadRequest(cex.Message);
  1785. }
  1786. catch (StorageException bex)
  1787. {
  1788. return BadRequest(bex.Message);
  1789. }
  1790. catch (Exception ex)
  1791. {
  1792. return BadRequest(ex.Message);
  1793. }
  1794. }
  1795. /**
  1796. * 根据学年获取年级信息
  1797. * @param year 学年
  1798. * @param Period 学段資料
  1799. */
  1800. private ExamSimple getGradeInfoByYear(int year, Period curPeriod)
  1801. {
  1802. ExamSimple result = new ExamSimple();
  1803. if (year > 0)
  1804. {
  1805. DateTime date = DateTime.UtcNow;
  1806. int curYear = date.Year;
  1807. int month = date.Month;
  1808. Semester semesterStart = curPeriod.semesters.Where((Semester x) => x.start.Equals(1)).FirstOrDefault();
  1809. if (semesterStart != null)
  1810. {
  1811. if (month < semesterStart.month)
  1812. {
  1813. curYear--;
  1814. }
  1815. int gradeIndex = curYear - year;
  1816. if (gradeIndex >= curPeriod.grades.Count)
  1817. {
  1818. result.id = gradeIndex.ToString();
  1819. result.name = "graduated";
  1820. }
  1821. else
  1822. {
  1823. result.id = gradeIndex.ToString();
  1824. result.name = curPeriod.grades[gradeIndex];
  1825. }
  1826. }
  1827. }
  1828. return result;
  1829. }
  1830. private List<List<List<Details>>> createEmptyMark(int studentNum, int itemNum)
  1831. {
  1832. List<List<List<Details>>> mark = new List<List<List<Details>>>();
  1833. for (int i = 0; i < studentNum; i++)
  1834. {
  1835. List<List<Details>> markRow = new List<List<Details>>();
  1836. for (int j = 0; j < itemNum; j++)
  1837. {
  1838. markRow.Add(new List<Details>());
  1839. }
  1840. mark.Add(markRow);
  1841. }
  1842. return mark;
  1843. }
  1844. public class ClassStudents
  1845. {
  1846. public string id { get; set; }
  1847. public string name { get; set; }
  1848. public string no { get; set; }
  1849. public string schoolId { get; set; }
  1850. public string groupId { get; set; }
  1851. public string groupName { get; set; }
  1852. public string irs { get; set; }
  1853. public int type { get; set; } //类型 1 tmdid,2 student
  1854. public string nickname { get; set; }
  1855. }
  1856. public class ClassGroups
  1857. {
  1858. public ClassGroups()
  1859. {
  1860. studentIds = new List<string>();
  1861. }
  1862. public string id { get; set; }
  1863. public string name { get; set; }
  1864. public List<string> studentIds { get; set; }
  1865. }
  1866. //ExamClassResult 學生作答紀錄(舊式 正式站暫時用這個)
  1867. public class ExamClassResultStudentAnswerArrayOld : ExamClassResult
  1868. {
  1869. public List<List<List<string>>> studentAnswersArray { get; set; }
  1870. }
  1871. //ExamClassResult 學生作答紀錄
  1872. public class ExamClassResultStudentAnswerArray : ExamClassResult
  1873. {
  1874. public List<List<string>> studentAnswersArray { get; set; }
  1875. }
  1876. //get-teacher-info API輸出 schools
  1877. private class GetTeacherInfoApiSchool
  1878. {
  1879. public string schoolId { get; set; }
  1880. public string name { get; set; }
  1881. public string status { get; set; }
  1882. public string picture { get; set; }
  1883. }
  1884. //get-teacher-info輸出 courses
  1885. private class GetTeacherInfoApiCourse : GetTeacherInfoApiSimlpeBase
  1886. {
  1887. public string scope { get; set; }
  1888. public List<GetTeacherInfoApiCourseClass> classes { get; set; }
  1889. }
  1890. //get-teacher-info輸出 courses.classes
  1891. private class GetTeacherInfoApiCourseClass : GetTeacherInfoApiSimlpeBase
  1892. {
  1893. public string code { get; set; }
  1894. public GetTeacherInfoApiSimlpeBase teacher { get; set; }
  1895. public string scope { get; set; }
  1896. public string stuListId { get; set; }
  1897. public int stuCnt { get; set; }
  1898. public int grpCnt { get; set; }
  1899. public string gradeId { get; set; }
  1900. public int year { get; set; }
  1901. }
  1902. //get-teacher-info輸出 基本class(各class繼承用)
  1903. internal class GetTeacherInfoApiSimlpeBase
  1904. {
  1905. public string id { get; set; }
  1906. public string name { get; set; }
  1907. }
  1908. }
  1909. }