LessonRecordController.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. using DocumentFormat.OpenXml.Drawing.Charts;
  2. using HTEX.Lib.ETL.Lesson;
  3. using MathNet.Numerics.Distributions;
  4. using Microsoft.AspNetCore.Mvc;
  5. using Microsoft.Azure.Cosmos;
  6. using StackExchange.Redis;
  7. using System.Text.Json;
  8. using System.Xml;
  9. using TEAMModelOS.SDK;
  10. using TEAMModelOS.SDK.DI;
  11. using TEAMModelOS.SDK.Extension;
  12. using TEAMModelOS.SDK.Helper.Common.FileHelper;
  13. using TEAMModelOS.SDK.Models;
  14. using TEAMModelOS.SDK.Models.Cosmos.Common;
  15. using TEAMModelOS.SDK.Models.Cosmos.OpenEntity;
  16. namespace HTEX.DataETL.Controllers
  17. {
  18. [ApiController]
  19. [Route("lesson-record")]
  20. public class LessonRecordController : ControllerBase
  21. {
  22. private readonly ILogger<LessonRecordController> _logger;
  23. private readonly AzureCosmosFactory _azureCosmos;
  24. private readonly AzureStorageFactory _azureStorage;
  25. private readonly IConfiguration _configuration;
  26. private readonly IWebHostEnvironment _webHostEnvironment;
  27. private readonly DingDing _dingDing;
  28. private readonly AzureRedisFactory _azureRedis;
  29. public LessonRecordController(ILogger<LessonRecordController> logger, AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage , IConfiguration configuration, IWebHostEnvironment environment,DingDing dingDing,AzureRedisFactory azureRedis )
  30. {
  31. _logger = logger;
  32. _azureCosmos = azureCosmos;
  33. _azureStorage = azureStorage;
  34. _configuration = configuration;
  35. _webHostEnvironment = environment;
  36. _dingDing = dingDing;
  37. _azureRedis = azureRedis;
  38. }
  39. [HttpPost("redis-key")]
  40. public async Task<IActionResult> RedisKey(JsonElement json)
  41. {
  42. //List<string > keys= new List<string>();
  43. var server= _azureRedis.GetRedisClient(8);
  44. // 使用Keys方法获取所有匹配前缀的key
  45. var pattern = "LessonWeek*";
  46. // 获取所有匹配的keys
  47. List<string> keys = server.ScriptEvaluate("return redis.call('keys', ARGV[1])", keys: new RedisKey[] { pattern })
  48. .ToString()
  49. .Split(',') // 注意:这种方式在生产环境中可能存在问题,如果key数量过多,可能导致返回不完整
  50. .ToList();
  51. return Ok(keys);
  52. }
  53. [HttpPost("process-local")]
  54. public async Task<IActionResult> ProcessLocal(JsonElement json)
  55. {
  56. List<StudentLessonData> studentLessonDatas = new List<StudentLessonData>();
  57. string? id = json.GetProperty("id").GetString();
  58. if (!string.IsNullOrWhiteSpace(id))
  59. {
  60. string? lessonPath = _configuration.GetValue<string>("LessonPath");
  61. string? path = $"{lessonPath}\\locals\\{id}";
  62. var files = FileHelper.ListAllFiles(path);
  63. // var sampleJson =System.IO. File.ReadAllTextAsync(path);
  64. LessonBase? lessonBase = null;
  65. List<LocalStudent> localStudents = new List<LocalStudent>();
  66. List<TaskData> taskDatas = new List<TaskData>();
  67. List<SmartRatingData> smartRatingDatas = new List<SmartRatingData>();
  68. List<IRSData> irsDatas = new List<IRSData>();
  69. List<CoworkData> coworkDatas = new List<CoworkData>();
  70. List<ExamData> examDatas = new List<ExamData>();
  71. TimeLineData? timeLineData = null;
  72. foreach (var item in files)
  73. {
  74. if (item.Contains("IES\\base.json"))
  75. {
  76. string jsons = await System.IO.File.ReadAllTextAsync(item);
  77. jsons = jsons.Replace("\"Uncall\"", "0").Replace("Uncall", "0");
  78. lessonBase = jsons.ToObject<LessonBase>();
  79. var data = LessonETLService.GetBaseData(lessonBase);
  80. localStudents = data.studentLessonDatas;
  81. }
  82. if (item.Contains("IES\\TimeLine.json"))
  83. {
  84. string jsons = await System.IO.File.ReadAllTextAsync(item);
  85. timeLineData = jsons.ToObject<TimeLineData>();
  86. }
  87. if (item.Contains("IES\\Task.json"))
  88. {
  89. string jsons = await System.IO.File.ReadAllTextAsync(item);
  90. taskDatas = jsons.ToObject<List<TaskData>>();
  91. }
  92. if (item.Contains("IES\\SmartRating.json"))
  93. {
  94. string jsons = await System.IO.File.ReadAllTextAsync(item);
  95. smartRatingDatas = jsons.ToObject<List<SmartRatingData>>();
  96. }
  97. if (item.Contains("IES\\IRS.json"))
  98. {
  99. string jsons = await System.IO.File.ReadAllTextAsync(item);
  100. irsDatas = jsons.ToObject<List<IRSData>>();
  101. }
  102. if (item.Contains("IES\\Cowork.json"))
  103. {
  104. string jsons = await System.IO.File.ReadAllTextAsync(item);
  105. coworkDatas = jsons.ToObject<List<CoworkData>>();
  106. }
  107. try
  108. {
  109. if (item.Contains($"\\{id}\\Exam\\") && item.EndsWith("Exam.json"))
  110. {
  111. string examsFile = item;
  112. if (examsFile.EndsWith("Exam.json"))
  113. {
  114. ExamData? examData = null;
  115. string jsons = await System.IO.File.ReadAllTextAsync(item);
  116. jsons = jsons.Replace("\"publish\": \"0\"", "\"publish\": 0").Replace("\"publish\": \"1\"", "\"publish\": 1");
  117. examData = jsons.ToObject<ExamData>();
  118. if (examData != null && examData.exam.papers.IsNotEmpty())
  119. {
  120. string paperId = examData.exam.papers.First().id;
  121. string paperPath = $"{path}\\ExamPaper\\{paperId}\\index.json";
  122. string jsonp = await System.IO.File.ReadAllTextAsync(paperPath);
  123. LessonPaper lessonPaper = jsonp.ToObject<LessonPaper>();
  124. examData.paper = lessonPaper;
  125. examDatas.Add(examData);
  126. }
  127. }
  128. }
  129. }
  130. catch (Exception ex)
  131. {
  132. _logger.LogError(ex, ex.Message);
  133. }
  134. }
  135. if (lessonBase!=null && timeLineData!=null)
  136. {
  137. studentLessonDatas = localStudents.ToJsonString().ToObject<List<StudentLessonData>>();
  138. studentLessonDatas = LessonETLService.GetBaseInfo(lessonBase!, studentLessonDatas, id);
  139. studentLessonDatas = LessonETLService.GetIRSData(lessonBase, timeLineData, irsDatas, studentLessonDatas, examDatas, id);
  140. studentLessonDatas = LessonETLService.GetCoworkData(lessonBase, timeLineData, coworkDatas, studentLessonDatas, id);
  141. studentLessonDatas = LessonETLService.GetExamData(lessonBase, timeLineData, examDatas, studentLessonDatas, Constant.objectiveTypes, id);
  142. studentLessonDatas = LessonETLService.GetSmartRatingData(lessonBase, timeLineData, smartRatingDatas, studentLessonDatas, id);
  143. studentLessonDatas = LessonETLService.GetTaskData(lessonBase, timeLineData, taskDatas, studentLessonDatas, id);
  144. var pickupData = LessonETLService.GetPickupData(lessonBase, timeLineData, studentLessonDatas, id);
  145. studentLessonDatas= pickupData.studentLessonDatas;
  146. await System.IO.File.WriteAllTextAsync(Path.Combine(path, $"student-analysis.json"), studentLessonDatas.ToJsonString());
  147. string jsons = await System.IO.File.ReadAllTextAsync($"{lessonPath}\\analysis\\analysis.json");
  148. LessonDataAnalysisCluster lessonDataAnalysis = jsons.ToObject<LessonDataAnalysisCluster>();
  149. var lessonItems = LessonETLService.ProcessStudentDataV2(studentLessonDatas, lessonDataAnalysis);
  150. XmlDocument xmlDocument = new XmlDocument();
  151. var runtimePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
  152. xmlDocument.Load($"{runtimePath}\\summary.xml");
  153. await LessonETLService.ExportToExcelLocal(lessonItems, $"{path}\\analysis.xlsx", xmlDocument);
  154. }
  155. }
  156. return Ok();
  157. }
  158. /// <summary>
  159. ///
  160. /// </summary>
  161. /// <param name="json"></param>
  162. /// <returns></returns>
  163. //[HttpPost("process-fix-history-students")]
  164. //public async Task<IActionResult> ProcessFixHistoryStudents(JsonElement json)
  165. //{
  166. // string? lessonBasePath = _configuration.GetValue<string>("LessonPath");
  167. // string studentsPath = $"{lessonBasePath}\\students";
  168. // List<string> studentsPs = FileHelper.ListAllFiles(studentsPath);
  169. // string? pathLessons = $"{lessonBasePath}\\lessons";
  170. // List<string> filesLessons = FileHelper.ListAllFiles(pathLessons, "-local.json");
  171. // int index = 0;
  172. // foreach (var stu in studentsPs)
  173. // {
  174. // string stujson = await System.IO.File.ReadAllTextAsync(stu);
  175. // var studentSemester = stujson.ToObject<StudentSemesterRecord>();
  176. // await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(studentSemester, new PartitionKey(studentSemester.code));
  177. // index++;
  178. // }
  179. // return Ok();
  180. //}
  181. /// <summary>
  182. ///
  183. /// </summary>
  184. /// <param name="json"></param>
  185. /// <returns></returns>
  186. [HttpPost("process-history-students")]
  187. public async Task<IActionResult> ProcessHistoryStudents(JsonElement json)
  188. {
  189. string? lessonBasePath = _configuration.GetValue<string>("LessonPath");
  190. string? pathLessons = $"{lessonBasePath}\\lessons";
  191. string? pathAnalysis = $"{lessonBasePath}\\analysis";
  192. string jsons = await System.IO.File.ReadAllTextAsync($"{pathAnalysis}\\analysis.json");
  193. LessonDataAnalysisCluster lessonDataAnalysis = jsons.ToObject<LessonDataAnalysisCluster>();
  194. List<string> filesLessons = FileHelper.ListAllFiles(pathLessons, "-local.json");
  195. var runtimePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
  196. XmlDocument xmlDocument = new XmlDocument();
  197. xmlDocument.Load($"{runtimePath}\\summary.xml");
  198. List<StudentSemesterRecord> students = new List<StudentSemesterRecord>();
  199. List<School> schools = new List<School>();
  200. // await Parallel.ForEachAsync(filesLessons, async (fileLesson, _) =>
  201. List<string> localIds = new List<string>();
  202. foreach (var file in filesLessons)
  203. {
  204. if (file.EndsWith("-local.json"))
  205. {
  206. string lessonId = file.Split("\\").Last().Replace("-local.json", "");
  207. localIds.Add(lessonId);
  208. }
  209. }
  210. long stime = 1690819200000;//2023-08-01 00:00:00
  211. var resultSchool = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  212. .GetList<LessonRecord>($"SELECT value c FROM c where c.startTime>{stime} and c.expire<=0 and c.status<>404 and c.duration>300 and c.pk='LessonRecord' ", $"LessonRecord-ydzt");
  213. List<string> ignore = new List<string>() { "PgJump", "PgRcv", "PgAdd" };
  214. int index = 0;
  215. if (resultSchool.list.IsNotEmpty())
  216. {
  217. List<StudentSemesterRecord> studentSemesterRecords= new List<StudentSemesterRecord>();
  218. List<OverallEducation> overallEducations= new List<OverallEducation>();
  219. List<Student> studentsBase = new List<Student>();
  220. await foreach (var item in LessonETLService.GetLessonLocal(resultSchool.list, localIds, _azureStorage, pathLessons))
  221. {
  222. string yearMonthPath = DateTimeOffset.FromUnixTimeMilliseconds(item.lessonRecord.startTime).ToString("yyyyMM");
  223. if (item.lessonBase!=null && item.lessonBase.student!=null)
  224. {
  225. TechCount techCount = new TechCount
  226. {
  227. lessonId=item.lessonRecord?.id,
  228. examCount = item.examDatas.Count,
  229. taskCount = item.taskDatas.Count,
  230. irsCount = item.irsDatas.Count,
  231. coworkCount = item.coworkDatas.Count,
  232. smartRatingCount =item.smartRatingDatas.Count,
  233. timeCount=item.sokratesDatas.Where(x => !ignore.Contains(x.Event) && !x.Event.Contains("End", StringComparison.OrdinalIgnoreCase)).GroupBy(x => x.Event).Select(x => new CodeLong() { code=x.Key, value= x.ToList().Count }).ToList()
  234. };
  235. await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord.id}-count.json", techCount.ToJsonString());
  236. await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord!.id}-local.json", item.ToJsonString());
  237. }
  238. else
  239. {
  240. System.IO.File.Delete($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord!.id}-local.json");
  241. System.IO.File.Delete($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord!.id}-count.json");
  242. }
  243. await LessonETLService.DoStudentLessonData(Constant.objectiveTypes, _azureStorage, item, _dingDing, _azureCosmos.GetCosmosClient(), "China", _azureRedis, studentSemesterRecords,overallEducations, lessonDataAnalysis, studentsBase, schools);
  244. index++;
  245. }
  246. foreach (var studentSemester in studentSemesterRecords)
  247. {
  248. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(studentSemester, new PartitionKey(studentSemester.code));
  249. }
  250. foreach (var overallEducation in overallEducations)
  251. {
  252. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(overallEducation, partitionKey: new PartitionKey(overallEducation.code));
  253. string key = $"OverallEducation:{overallEducation.schoolCode}:{overallEducation.periodId}:{overallEducation.year}:{overallEducation.semesterId}:{overallEducation?.classId}";
  254. await _azureRedis.GetRedisClient(8).HashSetAsync(key, overallEducation.studentId, overallEducation.ToJsonString());
  255. await _azureRedis.GetRedisClient(8).KeyExpireAsync(key, new TimeSpan(180 *24, 0, 0));
  256. }
  257. }
  258. return Ok();
  259. }
  260. /// <summary>
  261. /// 课例数据ETL处理过程
  262. /// </summary>
  263. /// <param name="json"></param>
  264. /// <returns></returns>
  265. [HttpPost("process-history")]
  266. public async Task<IActionResult> ProcessHistory(JsonElement json)
  267. {
  268. List<string> localIds = new List<string>();
  269. string? lessonBasePath = _configuration.GetValue<string>("LessonPath");
  270. string? pathLessons = $"{lessonBasePath}\\lessons";
  271. string? pathAnalysis = $"{lessonBasePath}\\analysis";
  272. var filesLessons = FileHelper.ListAllFiles(pathLessons);
  273. foreach (var file in filesLessons)
  274. {
  275. if (file.EndsWith("-local.json"))
  276. {
  277. string lessonId = file.Split("\\").Last().Replace("-local.json", "");
  278. localIds.Add(lessonId);
  279. }
  280. }
  281. bool loadLocal = true;
  282. List<LessonDataAnalysisMonth> lessonDataAnalysisMonths = new List<LessonDataAnalysisMonth>();
  283. var filesAnalysis = FileHelper.ListAllFiles(pathAnalysis);
  284. long stime = 1690819200000;//2023-08-01 00:00:00
  285. foreach (var file in filesAnalysis)
  286. {
  287. //读取每月的数据
  288. if (file.EndsWith("-m-analysis.json"))
  289. {
  290. string jsons = await System.IO.File.ReadAllTextAsync(file);
  291. LessonDataAnalysisMonth lessonDataAnalysis = jsons.ToObject<LessonDataAnalysisMonth>();
  292. lessonDataAnalysisMonths.Add(lessonDataAnalysis);
  293. }
  294. }
  295. if (lessonDataAnalysisMonths.IsNotEmpty())
  296. {
  297. var maxUpdateTime = lessonDataAnalysisMonths.Max(x => x.updateTime);
  298. if (maxUpdateTime>0)
  299. {
  300. //更新周期是一周
  301. if (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()- maxUpdateTime>604800000)
  302. {
  303. stime=maxUpdateTime;
  304. loadLocal =true;
  305. }
  306. else
  307. {
  308. stime=maxUpdateTime;
  309. loadLocal=false;
  310. }
  311. }
  312. }
  313. HashSet<string> yearMonth = new HashSet<string>();
  314. long newest = 0;
  315. bool force = false;
  316. if ((json.TryGetProperty("force", out JsonElement _force)&& _force.ValueKind.Equals(JsonValueKind.True)))
  317. {
  318. force= _force.GetBoolean();
  319. }
  320. // if (loadLocal ||force)
  321. {
  322. List<LessonRecord> lessonRecords = new List<LessonRecord>();
  323. var resultSchool = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  324. .GetList<LessonRecord>($"SELECT value c FROM c where c.startTime>={stime} and c.expire<=0 and c.status<>404 and c.duration>300 and c.pk='LessonRecord' ", null);
  325. if (resultSchool.list.IsNotEmpty())
  326. {
  327. newest= resultSchool.list.Max(x => x.startTime);
  328. lessonRecords.AddRange(resultSchool.list);
  329. }
  330. else
  331. {
  332. newest=stime;
  333. }
  334. var resultTeacher = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
  335. .GetList<LessonRecord>($"SELECT value c FROM c where c.startTime>={stime} and c.expire<=0 and c.status<>404 and c.duration>300 and c.pk='LessonRecord' ", null);
  336. if (resultTeacher.list.IsNotEmpty())
  337. {
  338. long max = resultTeacher.list.Max(x => x.startTime);
  339. if (max<newest)
  340. {
  341. newest=max;
  342. }
  343. lessonRecords.AddRange(resultTeacher.list);
  344. }
  345. List<string> ignore = new List<string>() { "PgJump", "PgRcv", "PgAdd" };
  346. if (lessonRecords.IsNotEmpty())
  347. {
  348. await foreach (var item in LessonETLService.GetLessonLocal(lessonRecords, localIds, _azureStorage, pathLessons))
  349. {
  350. string yearMonthPath = DateTimeOffset.FromUnixTimeMilliseconds(item.lessonRecord.startTime).ToString("yyyyMM");
  351. if (item.lessonBase!=null && item.lessonBase.student!=null)
  352. {
  353. TechCount techCount = new TechCount
  354. {
  355. lessonId=item.lessonRecord?.id,
  356. examCount = item.examDatas.Count,
  357. taskCount = item.taskDatas.Count,
  358. irsCount = item.irsDatas.Count,
  359. coworkCount = item.coworkDatas.Count,
  360. smartRatingCount =item.smartRatingDatas.Count,
  361. timeCount=item.sokratesDatas.Where(x => !ignore.Contains(x.Event) && !x.Event.Contains("End", StringComparison.OrdinalIgnoreCase)).GroupBy(x => x.Event).Select(x => new CodeLong() { code=x.Key, value= x.ToList().Count }).ToList()
  362. };
  363. await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord.id}-count.json", techCount.ToJsonString());
  364. await System.IO.File.WriteAllTextAsync($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord!.id}-local.json", item.ToJsonString());
  365. }
  366. else
  367. {
  368. System.IO.File.Delete($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord!.id}-local.json");
  369. System.IO.File.Delete($"{pathLessons}\\MM{yearMonthPath}\\{item.lessonRecord!.id}-count.json");
  370. }
  371. }
  372. }
  373. List<TechCount> techCounts = new List<TechCount>();
  374. filesLessons = FileHelper.ListAllFiles(pathLessons, "-local.json");
  375. await foreach (var item in LessonETLService.GetTeachCount(lessonRecords, filesLessons, pathLessons, ignore, Constant.objectiveTypes, _azureStorage, force))
  376. {
  377. techCounts.Add(item);
  378. }
  379. await LessonETLService.GenAnalysisData(pathAnalysis, newest, techCounts,_azureStorage);
  380. }
  381. return Ok(new { yearMonth });
  382. }
  383. }
  384. }