JointService.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. using Azure.Messaging.ServiceBus;
  2. using Azure.Storage.Blobs.Models;
  3. using Azure;
  4. using Microsoft.Azure.Cosmos;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Security.Cryptography;
  9. using System.Text;
  10. using System.Text.Json;
  11. using System.Threading.Tasks;
  12. using TEAMModelOS.SDK.DI;
  13. using TEAMModelOS.SDK.Extension;
  14. using TEAMModelOS.SDK.Models.Service.BI;
  15. using TEAMModelOS.SDK.Services;
  16. using static TEAMModelOS.SDK.Models.JointEventGroupBase;
  17. using Azure.Core;
  18. using Microsoft.Extensions.Configuration;
  19. namespace TEAMModelOS.SDK.Models.Service
  20. {
  21. public static class JointService
  22. {
  23. //取得JointExam生成Exam
  24. public static async Task GenerateExamFromJointAsync(CosmosClient client, AzureStorageFactory _azureStorage, AzureServiceBusFactory _serviceBus, CoreAPIHttpService _coreAPIHttpService, AzureRedisFactory _azureRedis, IConfiguration _configuration, DingDing _dingDing, string jointExamId)
  25. {
  26. try
  27. {
  28. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  29. //取得JointExam
  30. List<JointEventClassBase> classes = new List<JointEventClassBase>();
  31. List<JointEventGroupBase> stuLists = new List<JointEventGroupBase>();
  32. JointExam jointExam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<JointExam>(jointExamId, new PartitionKey("JointExam"));
  33. //取得JointCourse ※examType == "custom" 之後再處理
  34. List<JointEventGroupDb> jointCourses = new List<JointEventGroupDb>();
  35. if (!jointExam.examType.Equals("custom"))
  36. {
  37. string jointCourseSql = $"SELECT * FROM c WHERE c.jointEventId = '{jointExam.jointEventId}' AND c.jointGroupId = '{jointExam.jointGroupId}'";
  38. await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIteratorSql<JointEventGroupDb>(queryText: jointCourseSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"JointCourse") }))
  39. {
  40. jointCourses.Add(item);
  41. }
  42. }
  43. //評量資料生成 ExamInfo actExamInfo ※一個課程一個Exam
  44. List<ExamInfo> examList = new List<ExamInfo>();
  45. foreach (JointEventGroupDb jointCourse in jointCourses)
  46. {
  47. string actExamCreatorId = jointCourse.creatorId;
  48. //個人課程
  49. if(jointCourse.scope.Equals("private"))
  50. {
  51. foreach (JointEventGroupCourse jointExamGroupCourse in jointCourse.courseLists)
  52. {
  53. string actExamCourseId = jointExamGroupCourse.courseId;
  54. string actExamCourseName = jointExamGroupCourse.courseName;
  55. //評量資料生成
  56. ExamInfo actExamInfo = new ExamInfo();
  57. ///取得已生成的Exam ※
  58. string examSql = $"SELECT DISTINCT c.id, c.source, c.name, c.jointExamId, c.subjects, c.stuLists, c.targets, c.papers, c.year, c.startTime, c.endTime FROM c JOIN s IN c.subjects WHERE c.jointExamId = '{jointExam.id}' AND s.id = '{actExamCourseId}'";
  59. await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIteratorSql<ExamInfo>(queryText: examSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{actExamCreatorId}") }))
  60. {
  61. actExamInfo = item;
  62. }
  63. ///尚無評量資料
  64. if (string.IsNullOrWhiteSpace(actExamInfo.id))
  65. {
  66. actExamInfo.code = $"Exam-{actExamCreatorId}";
  67. actExamInfo.owner = "teacher";
  68. actExamInfo.scope = jointCourse.scope;
  69. actExamInfo.creatorId = actExamCreatorId;
  70. actExamInfo.id = Guid.NewGuid().ToString();
  71. }
  72. actExamInfo.source = jointExam.source;
  73. actExamInfo.name = jointExam.name;
  74. actExamInfo.jointExamId = jointExam.id;
  75. actExamInfo.subjects = new List<ExamSubject>() { new ExamSubject() { id = actExamCourseId, name = actExamCourseName, classCount = jointExamGroupCourse.groupLists.Count } };
  76. ///評量stuLists
  77. foreach (JointEventGroupCourseGroup actGroup in jointExamGroupCourse.groupLists)
  78. {
  79. if(!actExamInfo.stuLists.Contains(actGroup.id))
  80. {
  81. actExamInfo.stuLists.Add(actGroup.id);
  82. }
  83. List<string> targetRow = new List<string>() { actExamCourseId, actGroup.id };
  84. var targetRowJson = JsonSerializer.SerializeToElement(targetRow);
  85. if(!actExamInfo.targets.Contains(targetRowJson))
  86. {
  87. actExamInfo.targets.Add(targetRowJson);
  88. }
  89. }
  90. ///試卷
  91. actExamInfo.papers = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PaperSimple>>(Newtonsoft.Json.JsonConvert.SerializeObject(jointExam.papers));
  92. ///時間
  93. actExamInfo.year = DateTimeOffset.UtcNow.Year;
  94. actExamInfo.startTime = jointExam.startTime;
  95. actExamInfo.endTime = jointExam.endTime;
  96. examList.Add(actExamInfo);
  97. }
  98. }
  99. //[待做] 學校班級
  100. }
  101. //生成評量
  102. if (examList.Count > 0)
  103. {
  104. foreach (ExamInfo exam in examList)
  105. {
  106. await GenerateExam(client, _azureStorage, _serviceBus, _coreAPIHttpService, _azureRedis, _configuration, _dingDing, jointExam, exam);
  107. }
  108. }
  109. }
  110. catch (Exception)
  111. {
  112. }
  113. }
  114. //生成評量(單)
  115. private static async Task<string> GenerateExam(CosmosClient client, AzureStorageFactory _azureStorage, AzureServiceBusFactory _serviceBus, CoreAPIHttpService _coreAPIHttpService, AzureRedisFactory _azureRedis, IConfiguration _configuration, DingDing _dingDing, JointExam jointExam, ExamInfo exam)
  116. {
  117. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  118. string Result = string.Empty;
  119. exam.createTime = now;
  120. if (exam.startTime <= 0) exam.startTime = now;
  121. List<(string pId, List<string> gid)> ps = new();
  122. var group = exam.groupLists;
  123. if (group.Count > 0)
  124. {
  125. foreach (var keys in group)
  126. {
  127. foreach (KeyValuePair<string, List<string>> pp in keys)
  128. {
  129. ps.Add((pp.Key, pp.Value));
  130. }
  131. }
  132. }
  133. List<string> classes = ExamService.getClasses(exam.classes, exam.stuLists);
  134. (List<RMember> tchList, List<RGroupList> classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classes, exam.school, ps);
  135. exam.stuCount = tchList.Count;
  136. string mode = string.Empty;
  137. ResponseMessage response = null;
  138. if (string.IsNullOrEmpty(exam.id))
  139. {
  140. mode = "add";
  141. }
  142. else
  143. {
  144. response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(exam.id, new PartitionKey($"{exam.code}"));
  145. if (response.StatusCode == System.Net.HttpStatusCode.OK) mode = "upd";
  146. else mode = "add";
  147. }
  148. //DB操作
  149. if (mode.Equals("add")) //新建
  150. {
  151. if (string.IsNullOrWhiteSpace(exam.id)) exam.id = Guid.NewGuid().ToString();
  152. exam.progress = (exam.startTime > now) ? "pending" : "going";
  153. var messageBlob = new ServiceBusMessage();
  154. if (exam.scope.Equals("school") && !string.IsNullOrWhiteSpace(exam.school))
  155. {
  156. exam.size = await _azureStorage.GetBlobContainerClient(exam.school).GetBlobsSize($"exam/{exam.id}");
  157. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "insert", root = $"exam", name = exam.school }, _serviceBus, _configuration, _azureRedis);
  158. }
  159. else
  160. {
  161. exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}");
  162. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "insert", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
  163. }
  164. int n = 0;
  165. List<string> sheetIds = new List<string>();
  166. foreach (PaperSimple simple in exam.papers)
  167. {
  168. simple.blob = $"/exam/{exam.id}/paper/{exam.subjects[n].id}";
  169. n++;
  170. simple.sheet = null;
  171. }
  172. exam = await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(exam, new PartitionKey($"{exam.code}"));
  173. await BIStats.SetTypeAddStats(client, _dingDing, exam.school, "Exam", 1);//BI统计增/减量
  174. }
  175. else if (response != null) //更新
  176. {
  177. using var json = await JsonDocument.ParseAsync(response.Content);
  178. ExamInfo info = json.ToObject<ExamInfo>();
  179. if (info.progress.Equals("going"))
  180. {
  181. Result = "活动正在进行中,无法修改";
  182. }
  183. var messageBlob = new ServiceBusMessage();
  184. if (exam.scope.Equals("school") && !string.IsNullOrWhiteSpace(exam.school))
  185. {
  186. exam.size = await _azureStorage.GetBlobContainerClient(exam.school).GetBlobsSize($"exam/{exam.id}");
  187. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = $"exam", name = exam.school }, _serviceBus, _configuration, _azureRedis);
  188. }
  189. else
  190. {
  191. exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}");
  192. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
  193. }
  194. exam.progress = info.progress;
  195. int n = 0;
  196. List<string> sheetIds = new List<string>();
  197. foreach (PaperSimple simple in exam.papers)
  198. {
  199. if (!string.IsNullOrEmpty(simple.subjectId))
  200. {
  201. simple.blob = $"/exam/{exam.id}/paper/{simple.subjectId}/{simple.id}";
  202. }
  203. else
  204. {
  205. simple.blob = $"/exam/{exam.id}/paper/{exam.subjects[n].id}";
  206. n++;
  207. }
  208. simple.sheet = null;
  209. }
  210. exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(exam, exam.id, new PartitionKey($"{exam.code}"));
  211. }
  212. //Blob操作 ※取得試卷源(blob)、複製到評測紀錄下
  213. ///試卷源字典
  214. List<Dictionary<string, string>> sourcePaperInfo = new List<Dictionary<string, string>>();
  215. foreach (PaperSimple paperInfo in jointExam.papers)
  216. {
  217. string paperBlobPath = (!paperInfo.blob.EndsWith("/")) ? paperInfo.blob + "/" : paperInfo.blob;
  218. paperBlobPath = (paperInfo.blob.StartsWith("/")) ? paperBlobPath.Remove(0, 1) : paperBlobPath;
  219. sourcePaperInfo.Add(new Dictionary<string, string>() { { "id", paperInfo.id }, { "blob", paperBlobPath }, { "itemcount", paperInfo.point.Count.ToString() } });
  220. }
  221. bool paperDataCopyErrFlg = false; //試卷資料拷貝錯誤Flag true:拷貝錯誤
  222. //Blob拷貝程序
  223. int paperIndex = 0;
  224. foreach (Dictionary<string, string> sourcePaperInfoDic in sourcePaperInfo)
  225. {
  226. //拷貝源:Container => jointExam.creatorId Path:papers.blob
  227. //拷貝對象:Container => exam.creatorId, Path:exam/{exam.id}/paper/{exam.subjects[paperIndex].id}/
  228. string targetScope = exam.scope; //評測對象 school:校本班級 private:私人課程
  229. var sourceBlobContainer = _azureStorage.GetBlobContainerClient(jointExam.creatorId); //統測活動來源一定是個人
  230. var blobPrivateContainer = (targetScope.Equals("school")) ? _azureStorage.GetBlobContainerClient(exam.school) : _azureStorage.GetBlobContainerClient(exam.creatorId);
  231. string sourceBlobPath = sourcePaperInfoDic["blob"];
  232. string subjectId = exam.subjects[paperIndex].id;
  233. string destBlobPath = $"exam/{exam.id}/paper/{subjectId}/"; //拷貝對象路徑 path:exam/{評測ID}/paper/{subjectID}/
  234. Pageable<BlobItem> sourceBlobs = sourceBlobContainer.GetBlobs(prefix: sourceBlobPath);
  235. if (sourceBlobs.Count() > 0)
  236. {
  237. foreach (var blob in sourceBlobs)
  238. {
  239. var sourceFileBlob = sourceBlobContainer.GetBlobClient(blob.Name);
  240. if (sourceFileBlob.Exists())
  241. {
  242. var sourceFileUri = sourceBlobContainer.GetBlobClient(blob.Name).Uri;
  243. string fileName = blob.Name.Replace(sourceBlobPath, "");
  244. string destBlobFilePath = $"{destBlobPath}{fileName}";
  245. await blobPrivateContainer.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
  246. }
  247. else
  248. {
  249. paperDataCopyErrFlg = true;
  250. }
  251. }
  252. }
  253. paperIndex++;
  254. }
  255. return Result;
  256. }
  257. }
  258. }