ManageController.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. using IES.ExamLib.Models;
  2. using IES.ExamServer.DI;
  3. using IES.ExamServer.DI.SignalRHost;
  4. using IES.ExamServer.Filters;
  5. using IES.ExamServer.Helper;
  6. using IES.ExamServer.Helpers;
  7. using IES.ExamServer.Models;
  8. using Microsoft.AspNetCore.Mvc;
  9. using Microsoft.Extensions.Caching.Memory;
  10. using Microsoft.Extensions.Configuration;
  11. using System.Linq.Expressions;
  12. using System.Net.Http;
  13. using System.Text.Json;
  14. using System.Text.Json.Nodes;
  15. namespace IES.ExamServer.Controllers
  16. {
  17. [ApiController]
  18. [Route("manage")]
  19. public class ManageController:BaseController
  20. {
  21. private readonly IConfiguration _configuration;
  22. private readonly IHttpClientFactory _httpClientFactory;
  23. private readonly IMemoryCache _memoryCache;
  24. private readonly ILogger _logger;
  25. private readonly LiteDBFactory _liteDBFactory;
  26. private readonly DataCenterConnectionService _connectionService;
  27. private readonly int DelayMicro = 10;//微观数据延迟
  28. private readonly int DelayMacro = 100;//宏观数据延迟
  29. private readonly SignalRExamServerHub _signalRExamServerHub;
  30. public ManageController(LiteDBFactory liteDBFactory,ILogger logger, IConfiguration configuration,
  31. IHttpClientFactory httpClientFactory, IMemoryCache memoryCache, DataCenterConnectionService connectionService,SignalRExamServerHub signalRExamServerHub)
  32. {
  33. _logger = logger;
  34. _configuration=configuration;
  35. _httpClientFactory=httpClientFactory;
  36. _memoryCache=memoryCache;
  37. _liteDBFactory=liteDBFactory;
  38. _connectionService=connectionService;
  39. _signalRExamServerHub=signalRExamServerHub;
  40. }
  41. [HttpPost("download-package")]
  42. [AuthToken("admin","teacher")]
  43. public async Task<IActionResult> DownloadPackage(JsonNode json)
  44. {
  45. //C#.NET 6 后端与前端流式通信
  46. //https://www.doubao.com/chat/collection/687687510791426?type=Thread
  47. //下载日志记录:1.步骤,检查,2.获取描述信息,3.分类型,4下载文件,5.前端处理,6.返回结果 , 正在下载...==> [INFO]https://www.doubao.com/chat/collection/687687510791426?type=Thread [Size=180kb] Ok...
  48. //进度条 展示下载文件总大小和已下载,末尾展示 文件总个数和已下载个数
  49. //https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js
  50. return Ok();
  51. }
  52. [HttpPost("check-short-code")]
  53. public async Task<IActionResult> CheckShortCode(JsonNode json)
  54. {
  55. string shortCode = $"{json["shortCode"]}";
  56. string evaluationId = $"{json["evaluationId"]}";
  57. string deviceId = $"{json["deviceId"]}";
  58. Expression<Func<EvaluationClient, bool>> predicate = x => true;
  59. if (!string.IsNullOrEmpty(shortCode))
  60. {
  61. var codePredicate = ExpressionHelper.Or<EvaluationClient>(
  62. x => !string.IsNullOrWhiteSpace(x.shortCode) && x.shortCode == shortCode,
  63. x => !string.IsNullOrWhiteSpace(x.password) && x.password == shortCode
  64. );
  65. predicate= predicate.And(codePredicate);
  66. }
  67. else {
  68. return Ok(new { code = 400,msg="必须输入开卷码" });
  69. }
  70. if (!string.IsNullOrWhiteSpace(evaluationId))
  71. {
  72. predicate= predicate.And(x => x.id!.Equals(evaluationId));
  73. }
  74. IEnumerable<EvaluationClient>? evaluationClients = _liteDBFactory.GetLiteDatabase().GetCollection<EvaluationClient>().Find(predicate);
  75. EvaluationClient? evaluationLocal = null;
  76. EvaluationClient? evaluationCloud = null;
  77. if (evaluationClients!=null && evaluationClients.Count()>0)
  78. {
  79. evaluationLocal= evaluationClients.First();
  80. }
  81. //如果要访问中心,则需要教师登录联网。
  82. var token = GetAuthTokenInfo();
  83. if (token.scope.Equals(ExamConstant.ScopeTeacher))
  84. {
  85. if ( _connectionService.dataCenterIsConnected)
  86. {
  87. Teacher? teacher= _liteDBFactory.GetLiteDatabase().GetCollection<Teacher>().FindOne(x => x.id!.Equals(token.id));
  88. if (teacher != null)
  89. {
  90. string? CenterUrl = _configuration.GetValue<string>("ExamServer:CenterUrl");
  91. var client = _httpClientFactory.CreateClient();
  92. if (client.DefaultRequestHeaders.Contains(Constant._X_Auth_AuthToken))
  93. {
  94. client.DefaultRequestHeaders.Remove(Constant._X_Auth_AuthToken);
  95. }
  96. client.DefaultRequestHeaders.Add(Constant._X_Auth_AuthToken, teacher.x_auth_token);
  97. HttpResponseMessage message = await client.PostAsJsonAsync($"{CenterUrl}/evaluation-sync/find-sync-info", new { shortCode, evaluationId });
  98. if (message.IsSuccessStatusCode)
  99. {
  100. string content = await message.Content.ReadAsStringAsync();
  101. JsonNode? jsonNode = content.ToObject<JsonNode>();
  102. if (jsonNode != null)
  103. {
  104. evaluationCloud = jsonNode["evaluation"]?.ToObject<EvaluationClient>();
  105. }
  106. }
  107. }
  108. }
  109. }
  110. //数据,文件,页面 0 没有更新,1 有更新
  111. int data = 0,blob=0,webview=0, groupList=0, status=0;
  112. long dataSize = 0, blobSize=0 , webviewSize=0, studentCount=0;
  113. if (evaluationLocal== null && evaluationCloud==null)
  114. {
  115. //线上线下没有数据
  116. status=1;
  117. }
  118. else if (evaluationLocal!=null && evaluationCloud!=null)
  119. {
  120. //线上线下有数据
  121. status = 2;
  122. if ((!string.IsNullOrWhiteSpace(evaluationLocal.blobHash) && !evaluationLocal.blobHash.Equals(evaluationCloud.blobHash))
  123. ||(evaluationLocal.blobTime<evaluationCloud.blobTime)
  124. ||(evaluationLocal.blobCount!= evaluationCloud.blobCount)
  125. ||(evaluationLocal.blobSize!= evaluationCloud.blobSize))
  126. {
  127. blob=1;
  128. blobSize=evaluationCloud.blobSize;
  129. }
  130. if ((evaluationLocal.dataTime<evaluationCloud.dataTime)
  131. ||(evaluationLocal.dataSize!=evaluationCloud.dataSize)
  132. ||(evaluationLocal.paperCount!= evaluationCloud.paperCount)
  133. )
  134. {
  135. data=1;
  136. dataSize=evaluationCloud.dataSize;
  137. }
  138. if ((evaluationLocal.webviewCount!=evaluationCloud.webviewCount)
  139. ||(evaluationLocal.webviewSize!= evaluationCloud.webviewSize)
  140. ||(evaluationLocal.webviewTime!= evaluationCloud.webviewTime)
  141. ||(!string.IsNullOrWhiteSpace(evaluationLocal.webviewPath)&& !evaluationLocal.webviewPath.Equals(evaluationCloud.webviewPath)))
  142. {
  143. webview=1;
  144. webviewSize=evaluationCloud.webviewSize;
  145. }
  146. if ((evaluationLocal.studentCount!= evaluationCloud.studentCount)||(!$"{evaluationLocal.grouplistHash}".Equals(evaluationCloud.grouplistHash)))
  147. {
  148. groupList=1;
  149. studentCount=evaluationCloud.studentCount;
  150. }
  151. }
  152. else if (evaluationLocal!=null && evaluationCloud==null)
  153. {
  154. //线下有数据,线上没有数据,可能没联网。
  155. status = 3;
  156. }
  157. else if (evaluationLocal==null && evaluationCloud!=null)
  158. {
  159. //线下没有数据,线上有数据
  160. evaluationLocal= evaluationCloud;
  161. blob=1;
  162. data=1;
  163. webview=1;
  164. groupList=1;
  165. blobSize=evaluationCloud.blobSize;
  166. dataSize=evaluationCloud.dataSize;
  167. webviewSize=evaluationCloud.webviewSize;
  168. studentCount=evaluationCloud.studentCount;
  169. status = 4;
  170. _liteDBFactory.GetLiteDatabase().GetCollection<EvaluationClient>().Insert(evaluationLocal);
  171. }
  172. List<string> file_error = new List<string>();
  173. if (evaluationLocal!=null)
  174. {
  175. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  176. new MessageContent {dataId=evaluationLocal.id,dataName=evaluationLocal.name,messageType=Constant._Message_type_message, status=0, content="开始检查评测信息文件.." });
  177. //校验本地文件数据
  178. string packagePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "package");
  179. if (!Directory.Exists(packagePath))
  180. Directory.CreateDirectory(packagePath);
  181. string evaluationPath = Path.Combine(packagePath, evaluationLocal.id!);
  182. // await Task.Delay(DelayMacro);
  183. int msg_status = Constant._Message_status_info;
  184. string path_evaluation = Path.Combine(evaluationPath, "evaluation.json");
  185. if (!System.IO.File.Exists(path_evaluation))
  186. {
  187. file_error.Add("evaluation");
  188. msg_status=Constant._Message_status_error;
  189. }
  190. else
  191. {
  192. msg_status=Constant._Message_status_success;
  193. //string jsonData = await System.IO.File.ReadAllTextAsync(path_evaluation);
  194. //EvaluationClient? evaluationFile =jsonData.ToObject<EvaluationClient>();
  195. //if (evaluationFile!=null)
  196. //{
  197. // if (evaluationFile.dataSize==evaluationLocal.dataSize )
  198. // {
  199. // file_error.Add("evaluation");
  200. // msg_status=Constant._Message_status_error;
  201. // }
  202. // else
  203. // {
  204. // msg_status=Constant._Message_status_success;
  205. // }
  206. //}
  207. //else
  208. //{
  209. // file_error.Add("evaluation");
  210. // msg_status=Constant._Message_status_error;
  211. //}
  212. }
  213. //数据格式: [消息][信息/错误/警告][15:43]=>[开始检查评测信息文件...]
  214. //数据格式: [检查][成功/失败][15:43]=>[评测数据文件:/wwwroot/package/623a9fe6-5445-0938-ff77-aeb80066ef27/evaluation.json]
  215. //数据格式: [下载][成功/失败][15:43]=>[评测数据文件:/wwwroot/package/623a9fe6-5445-0938-ff77-aeb80066ef27/evaluation.json][1024kb][15ms]
  216. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  217. new MessageContent { dataId=evaluationLocal.id, dataName=evaluationLocal.name, messageType= Constant._Message_type_check, status=msg_status,content=$"评测数据文件:{path_evaluation}" });
  218. //await Task.Delay(DelayMacro);
  219. string path_groupList = Path.Combine(evaluationPath, "groupList.json");
  220. msg_status =Constant._Message_status_info;
  221. if (!System.IO.File.Exists(path_groupList))
  222. {
  223. file_error.Add("groupList");
  224. msg_status=Constant._Message_status_error;
  225. }
  226. else
  227. {
  228. msg_status=Constant._Message_status_success;
  229. }
  230. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  231. new MessageContent { dataId=evaluationLocal.id, dataName=evaluationLocal.name, messageType= Constant._Message_type_check, status=msg_status, content=$"评测名单文件:{path_groupList}" });
  232. //await Task.Delay(DelayMacro);
  233. string path_source = Path.Combine(evaluationPath, "source.json");
  234. msg_status = Constant._Message_status_info;
  235. if (!System.IO.File.Exists(path_source))
  236. {
  237. file_error.Add("source");
  238. msg_status=Constant._Message_status_error;
  239. }
  240. else
  241. {
  242. msg_status=Constant._Message_status_success;
  243. }
  244. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  245. new MessageContent { dataId=evaluationLocal.id, dataName=evaluationLocal.name, messageType= Constant._Message_type_check, status=msg_status, content=$"评测原始数据:{path_source}" });
  246. // await Task.Delay(DelayMacro);
  247. msg_status =Constant._Message_status_info;
  248. try {
  249. string evaluation_str = await System.IO.File.ReadAllTextAsync(path_evaluation);
  250. JsonNode? evaluation_data = evaluation_str.ToObject<JsonNode>();
  251. if (evaluation_data!=null)
  252. {
  253. EvaluationClient? evaluationClient = evaluation_data["evaluationClient"]?.ToObject<EvaluationClient>();
  254. if (evaluationClient!=null)
  255. {
  256. if ((!string.IsNullOrWhiteSpace(evaluationLocal.blobHash) && evaluationLocal.blobHash.Equals(evaluationClient.blobHash))
  257. &&(evaluationLocal.blobTime==evaluationClient.blobTime)
  258. &&(evaluationLocal.blobCount== evaluationClient.blobCount)
  259. &&(evaluationLocal.blobSize== evaluationClient.blobSize)&& (evaluationLocal.dataTime==evaluationClient.dataTime)
  260. &&(evaluationLocal.dataSize==evaluationClient.dataSize)&&(evaluationLocal.webviewCount==evaluationClient.webviewCount)
  261. &&(evaluationLocal.webviewSize== evaluationClient.webviewSize)
  262. &&(evaluationLocal.webviewTime== evaluationClient.webviewTime)
  263. &&(!string.IsNullOrWhiteSpace(evaluationLocal.webviewPath)&& evaluationLocal.webviewPath.Equals(evaluationClient.webviewPath)))
  264. {
  265. msg_status=1;
  266. }
  267. else
  268. {
  269. msg_status=Constant._Message_status_error;
  270. }
  271. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  272. new MessageContent { dataId=evaluationLocal.id, dataName=evaluationLocal.name, messageType=Constant._Message_type_message, status=msg_status, content="校验本地数据文件..." });
  273. }
  274. List<EvaluationExam>? evaluationExams = evaluation_data["evaluationExams"]?.ToObject<List<EvaluationExam>>();
  275. if (evaluationExams.IsNotEmpty())
  276. {
  277. string path_papers = Path.Combine(evaluationPath, "papers");
  278. var papers_files = FileHelper.ListAllFiles(path_papers);
  279. foreach (var evaluationExam in evaluationExams!)
  280. {
  281. int paperIndex = 0;
  282. foreach (var paper in evaluationExam.papers)
  283. {
  284. paperIndex++;
  285. List<MessageContent> contents = new List<MessageContent>();
  286. int paper_error_count = 0;
  287. foreach (var blobInfo in paper.blobs)
  288. {
  289. msg_status=Constant._Message_status_info;
  290. if (!string.IsNullOrWhiteSpace(blobInfo.path))
  291. {
  292. var file = papers_files.Find(x => x.Contains(blobInfo.path));
  293. if (file!=null)
  294. {
  295. msg_status=1;
  296. msg_status=Constant._Message_status_success;
  297. }
  298. else {
  299. msg_status=Constant._Message_status_error;
  300. paper_error_count++;
  301. }
  302. }
  303. else {
  304. msg_status=Constant._Message_status_warning; ;
  305. paper_error_count++;
  306. }
  307. contents.Add(new MessageContent {
  308. dataId=evaluationLocal.id,
  309. dataName=evaluationLocal.name,
  310. messageType=Constant._Message_type_check, status=msg_status, content=$"试卷文件信息:{paper.paperName}" });
  311. }
  312. int paper_msg_status = Constant. _Message_status_info;
  313. if (paper_error_count>0)
  314. {
  315. paper_msg_status=Constant._Message_status_error;
  316. }
  317. else {
  318. paper_msg_status=Constant._Message_status_success;
  319. }
  320. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  321. new MessageContent {
  322. dataId=evaluationLocal.id,
  323. dataName=evaluationLocal.name,
  324. messageType=Constant._Message_type_message,
  325. status=paper_msg_status,
  326. content=$"试卷名称:[{paperIndex}]{evaluationExam.examName}-{evaluationExam.subjectName}-{paper.paperName}\r\n文件数量:{paper.blobs.Count()},检测成功数量:{contents.Count(x => x.status==Constant._Message_status_success)},检测异常数量{contents.Count(x => x.status==Constant._Message_status_error)}",
  327. contents=contents
  328. });
  329. }
  330. }
  331. }
  332. }
  333. }
  334. catch (Exception e) {
  335. }
  336. //检测参考名单
  337. await _signalRExamServerHub.SendMessage(deviceId, Constant._Message_grant_type_check_file,
  338. new MessageContent {
  339. dataId=evaluationLocal.id,
  340. dataName=evaluationLocal.name,
  341. messageType=Constant._Message_type_message, status=0, content="提取评测数据文件..." });
  342. }
  343. return Ok(new {code=200, evaluation= evaluationLocal,data,blob,webview,dataSize,blobSize,webviewSize,status ,groupList,studentCount});
  344. }
  345. /// <summary>
  346. /// 激活考试
  347. /// </summary>
  348. /// <param name="json"></param>
  349. /// <returns></returns>
  350. [HttpPost("activate-evaluation")]
  351. public IActionResult ActivateEvaluation(JsonNode json)
  352. {
  353. string id = $"{json["id"]}";
  354. string shortCode = $"{json["shortCode"]}";
  355. if (!string.IsNullOrWhiteSpace(id) && !string.IsNullOrWhiteSpace(shortCode))
  356. {
  357. EvaluationClient? evaluationClient = _liteDBFactory.GetLiteDatabase().GetCollection<EvaluationClient>().FindOne(x => x.id!.Equals(id) && !string.IsNullOrWhiteSpace(x.shortCode) && x.shortCode.Equals(shortCode));
  358. if (evaluationClient != null)
  359. {
  360. }
  361. }
  362. return Ok();
  363. }
  364. /// <summary>
  365. /// 加载本地的活动列表
  366. /// </summary>
  367. /// <param name="json"></param>
  368. /// <returns></returns>
  369. [HttpPost("list-local-evaluation")]
  370. public IActionResult ListLocalEvaluation(JsonNode json)
  371. {
  372. IEnumerable<EvaluationClient>? evaluationClients = _liteDBFactory.GetLiteDatabase().GetCollection<EvaluationClient>().FindAll().OrderByDescending(x=>x.activate).ThenByDescending(x=>x.stime);
  373. if (evaluationClients != null)
  374. {
  375. var result = evaluationClients.Select(client =>
  376. {
  377. var properties = client.GetType().GetProperties();
  378. var anonymousObject = new Dictionary<string, object?>();
  379. foreach (var property in properties)
  380. {
  381. if (!property.Name.Equals("password") && !property.Name.Equals("shortCode"))
  382. {
  383. anonymousObject[property.Name] = property.GetValue(client);
  384. }
  385. }
  386. return anonymousObject;
  387. });
  388. return Ok(new { code = 200, evaluation = result });
  389. }
  390. else {
  391. return Ok(new { code = 200, evaluation = new Dictionary<string, object?>() });
  392. }
  393. }
  394. }
  395. }