ManageController.cs 24 KB

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