HiTeachController.cs 120 KB

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