JointService.cs 15 KB

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