ExamController.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. using Microsoft.AspNetCore.Mvc;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using TEAMModelOS.Models;
  7. using TEAMModelOS.SDK;
  8. using TEAMModelOS.SDK.DI;
  9. using System.Text.Json;
  10. using TEAMModelOS.SDK.Models;
  11. using TEAMModelOS.SDK.Extension;
  12. using Azure.Cosmos;
  13. using Microsoft.AspNetCore.Http;
  14. using Microsoft.Extensions.Options;
  15. using System.IO;
  16. using System.Dynamic;
  17. using System.Net.Http;
  18. using System.Net;
  19. using Newtonsoft.Json;
  20. using System.Linq;
  21. using StackExchange.Redis;
  22. using static TEAMModelOS.SDK.Models.Teacher;
  23. using Microsoft.Extensions.Configuration;
  24. using TEAMModelOS.Filter;
  25. using Microsoft.AspNetCore.Authorization;
  26. using HTEXLib.COMM.Helpers;
  27. using HTEXLib.Translator;
  28. using TEAMModelOS.Models.Dto;
  29. namespace TEAMModelAPI.Controllers
  30. {
  31. [ProducesResponseType(StatusCodes.Status200OK)]
  32. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  33. [ApiController]
  34. [Route("school")]
  35. public class ExamController : ControllerBase
  36. {
  37. public AzureCosmosFactory _azureCosmos;
  38. private readonly AzureStorageFactory _azureStorage;
  39. private readonly AzureRedisFactory _azureRedis;
  40. private readonly DingDing _dingDing;
  41. private readonly Option _option;
  42. private readonly IConfiguration _configuration;
  43. public DOXC2HTMLTranslator _DOXC2HTMLTranslator { get; set; }
  44. //public PPTX2HTEXTranslator _PPTX2HTEXTranslator { get; set; }
  45. public HTML2ITEMV3Translator _HTML2ITEMV3Translator { get; set; }
  46. public ExamController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IOptionsSnapshot<Option> option, IConfiguration configuration)
  47. {
  48. _azureCosmos = azureCosmos;
  49. _azureStorage = azureStorage;
  50. _azureRedis = azureRedis;
  51. _dingDing = dingDing;
  52. _option = option?.Value;
  53. _configuration = configuration;
  54. }
  55. [ProducesDefaultResponseType]
  56. [HttpGet("import-exam")]
  57. [ApiToken(Auth = "201", Name = "汇入评测基础数据", Limit = false)]
  58. public async Task<IActionResult> importExam(JsonElement request)
  59. {
  60. //获取评测的ID
  61. if (!request.TryGetProperty("exam", out JsonElement exam)) return BadRequest();
  62. if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
  63. try
  64. {
  65. var client = _azureCosmos.GetCosmosClient();
  66. ExamInfo info = exam.ToObject<ExamInfo>();
  67. info.progress = "going";
  68. await client.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync(info, new PartitionKey($"Exam-{code}"));
  69. return Ok(new { info });
  70. }
  71. catch (Exception e)
  72. {
  73. await _dingDing.SendBotMsg($"OS,{_option.Location},analysis/import-exam()\n{e.Message}", GroupNames.醍摩豆服務運維群組);
  74. return BadRequest();
  75. }
  76. }
  77. [ProducesDefaultResponseType]
  78. [HttpGet("upsert-record")]
  79. [ApiToken(Auth = "202", Name = "批量汇入作答数据", Limit = false)]
  80. public async Task<IActionResult> upsertRecord(JsonElement request)
  81. {
  82. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  83. if (!request.TryGetProperty("students", out JsonElement students)) return BadRequest();
  84. if (!request.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
  85. //根据不同评测的类型返回对应的编码
  86. if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
  87. try
  88. {
  89. var client = _azureCosmos.GetCosmosClient();
  90. //List<string> ids = students.ToObject<List<string>>();
  91. List<students> stus = students.ToObject<List<students>>();
  92. ExamInfo info = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ExamInfo>(id.GetString(), new PartitionKey($"Exam-{code}"));
  93. string classCode = info.scope.Equals("school") ? info.school : info.creatorId;
  94. List<ExamClassResult> examClassResults = new();
  95. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIterator<ExamClassResult>(
  96. queryText: $"select value(c) from c where c.examId = '{id}' and c.subjectId = '{subjectId}'",
  97. requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{classCode}") }))
  98. {
  99. examClassResults.Add(item);
  100. }
  101. int n = 0;
  102. foreach (ExamSubject subject in info.subjects)
  103. {
  104. if (!subject.id.Equals(subjectId.GetString()))
  105. {
  106. n++;
  107. }
  108. else
  109. {
  110. break;
  111. }
  112. }
  113. //获取试卷信息
  114. PaperSimple standerAnswers = new PaperSimple();
  115. List<List<string>> standard = new List<List<string>>();
  116. List<double> points = new List<double>();
  117. standerAnswers = info.papers[n];
  118. standard = standerAnswers.answers;
  119. points = standerAnswers.point;
  120. int rule = standerAnswers.multipleRule;
  121. List<string> value = new List<string>();
  122. await foreach (var s in stuTask(stus, examClassResults, standard, points, rule, info, subjectId.GetString(), client))
  123. {
  124. if (s.code == 1)
  125. {
  126. value.Add(s.value);
  127. }
  128. }
  129. if (value.Count > 0)
  130. {
  131. return Ok(new { code = 1, msg = "学生ID异常", value = value });
  132. }
  133. else
  134. {
  135. return Ok(new { code = 0 });
  136. }
  137. }
  138. catch (Exception e)
  139. {
  140. await _dingDing.SendBotMsg($"OS,{_option.Location},activity/upsert-record()\n{e.Message}", GroupNames.醍摩豆服務運維群組);
  141. return BadRequest();
  142. }
  143. }
  144. private async IAsyncEnumerable<(int code, string value)> stuTask(List<students> stus, List<ExamClassResult> examClassResults, List<List<string>> standard,
  145. List<double> points, int rule, ExamInfo info, string subjectId, CosmosClient client)
  146. {
  147. foreach (var s in stus)
  148. {
  149. string value = "";
  150. int code = 0;
  151. List<List<string>> ans = s.answer;
  152. bool isExist = examClassResults.Exists(e => e.studentIds.Contains(s.id));
  153. if (!isExist)
  154. {
  155. value = s.id;
  156. code = 1;
  157. }
  158. else
  159. {
  160. foreach (ExamClassResult result in examClassResults)
  161. {
  162. if (!result.studentIds.Contains(s.id))
  163. {
  164. continue;
  165. }
  166. else
  167. {
  168. int newIndex = result.studentIds.IndexOf(s.id);
  169. StringBuilder builder = new StringBuilder();
  170. builder.Append(result.examId).Append('/');
  171. builder.Append(result.subjectId).Append('/');
  172. builder.Append(s.id).Append('/');
  173. builder.Append("ans.json");
  174. result.studentAnswers[newIndex].Clear();
  175. result.studentAnswers[newIndex].Add(builder.ToString());
  176. for (int i = 0; i < ans.Count; i++)
  177. {
  178. if (ans[i] == null)
  179. {
  180. continue;
  181. //ans[i] = new List<string>();
  182. }
  183. var ac = ans[i].Count;
  184. var sc = standard[i].Count;
  185. //记录次数
  186. int n = 0;
  187. //算分处理
  188. if (sc > 0)
  189. {
  190. result.ans[newIndex][i] = ans[i];
  191. if (ac == sc && sc == 1)
  192. {
  193. foreach (string right in ans[i])
  194. {
  195. if (standard[i].Contains(right))
  196. {
  197. result.studentScores[newIndex][i] = points[i];
  198. }
  199. else
  200. {
  201. result.studentScores[newIndex][i] = 0;
  202. }
  203. }
  204. }
  205. else
  206. {
  207. if (rule > 0)
  208. {
  209. int falseCount = 0;
  210. if (ac > 0)
  211. {
  212. foreach (string obj in ans[i])
  213. {
  214. if (!standard[i].Contains(obj))
  215. {
  216. falseCount++;
  217. }
  218. }
  219. switch (rule)
  220. {
  221. case 1:
  222. if (ac == sc)
  223. {
  224. if (falseCount == 0)
  225. {
  226. result.studentScores[newIndex][i] = points[i];
  227. }
  228. else
  229. {
  230. result.studentScores[newIndex][i] = 0;
  231. }
  232. }
  233. else
  234. {
  235. result.studentScores[newIndex][i] = 0;
  236. }
  237. break;
  238. case 2:
  239. if (falseCount > 0)
  240. {
  241. result.studentScores[newIndex][i] = 0;
  242. }
  243. else
  244. {
  245. if (ac == sc)
  246. {
  247. result.studentScores[newIndex][i] = points[i];
  248. }
  249. else
  250. {
  251. result.studentScores[newIndex][i] = points[i] / 2;
  252. }
  253. }
  254. break;
  255. case 3:
  256. if (falseCount > 0)
  257. {
  258. result.studentScores[newIndex][i] = 0;
  259. }
  260. else
  261. {
  262. if (ac == sc)
  263. {
  264. result.studentScores[newIndex][i] = points[i];
  265. }
  266. else
  267. {
  268. result.studentScores[newIndex][i] = System.Math.Round((double)ac / sc * points[i], 1);
  269. }
  270. }
  271. break;
  272. case 4:
  273. if (ac == sc)
  274. {
  275. result.studentScores[newIndex][i] = points[i];
  276. }
  277. else
  278. {
  279. double persent = (double)(sc - 2 * falseCount) / sc;
  280. if (persent <= 0)
  281. {
  282. result.studentScores[newIndex][i] = 0;
  283. }
  284. else
  285. {
  286. result.studentScores[newIndex][i] = System.Math.Round(persent * points[i], 1);
  287. }
  288. }
  289. break;
  290. }
  291. }
  292. else
  293. {
  294. result.studentScores[newIndex][i] = 0;
  295. }
  296. }
  297. }
  298. }
  299. }
  300. bool flag = true;
  301. foreach (List<double> scores in result.studentScores)
  302. {
  303. foreach (double score in scores)
  304. {
  305. if (score == -1)
  306. {
  307. flag = false;
  308. break;
  309. }
  310. }
  311. }
  312. if (flag)
  313. {
  314. result.progress = true;
  315. info.subjects.ForEach(s =>
  316. {
  317. if (s.id.Equals(subjectId.ToString()))
  318. {
  319. s.classCount += 1;
  320. }
  321. });
  322. await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(info, info.id.ToString(), new PartitionKey($"{info.code}"));
  323. }
  324. result.sum[newIndex] = result.studentScores[newIndex].Sum();
  325. await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(result, result.id, new PartitionKey($"{result.code}"));
  326. break;
  327. }
  328. }
  329. }
  330. yield return (code, value);
  331. }
  332. }
  333. [ProducesDefaultResponseType]
  334. [HttpGet("parse-word")]
  335. [ApiToken(Auth = "203", Name = "录入试卷数据", Limit = false)]
  336. public async Task<IActionResult> ParseWord([FromForm] FileDto fileDto)
  337. {
  338. if (!FileType.GetExtention(fileDto.file.FileName).ToLower().Equals("docx"))
  339. {
  340. return BadRequest(new Dictionary<string, object> { { "msg", "type is not docx!" }, { "code", 404 } });
  341. }
  342. try
  343. {
  344. var client = _azureCosmos.GetCosmosClient();
  345. var response = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemStreamAsync(fileDto.examId, new PartitionKey($"Exam-{fileDto.code}"));
  346. ExamInfo examInfo;
  347. if (response.Status == 200)
  348. {
  349. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  350. examInfo = json.ToObject<ExamInfo>();
  351. }
  352. else
  353. {
  354. return Ok(new { error = 404, msg = "请先导入评测信息" });
  355. }
  356. foreach (PaperSimple paper in examInfo.papers)
  357. {
  358. if (paper.id.Contains(fileDto.subjectId))
  359. {
  360. return Ok(new { error = 500, msg = "该试卷信息已存在" });
  361. }
  362. }
  363. PaperSimple simple = new();
  364. var doc = _DOXC2HTMLTranslator.Translate(fileDto.file.OpenReadStream());
  365. (List<HTEXLib.DOCX.Models.ItemInfo> tests, List<string> error) = _HTML2ITEMV3Translator.Translate(doc);
  366. List<Task<string>> tasks = new List<Task<string>>();
  367. PaperDto paperDto = new()
  368. {
  369. id = Guid.NewGuid().ToString(),
  370. name = fileDto.name,
  371. code = fileDto.code,
  372. scope = "school",
  373. multipleRule = fileDto.multipleRule,
  374. gradeIds = fileDto.gradeIds,
  375. subjectId = fileDto.subjectId,
  376. periodId = fileDto.periodId
  377. };
  378. foreach (HTEXLib.DOCX.Models.ItemInfo item in tests)
  379. {
  380. Slides slides = new();
  381. ItemDto dto = new();
  382. Scoring scoring = new();
  383. dto.id = Guid.NewGuid().ToString();
  384. dto.exercise.answer = item.answer;
  385. dto.exercise.explain = item.explain;
  386. dto.exercise.type = item.type;
  387. dto.exercise.opts = item.option.Count;
  388. dto.exercise.knowledge = item.knowledge;
  389. dto.exercise.field = item.field;
  390. dto.exercise.level = item.level;
  391. dto.exercise.subjectId = fileDto.subjectId;
  392. dto.exercise.periodId = fileDto.periodId;
  393. dto.exercise.gradeIds = fileDto.gradeIds;
  394. slides.url = dto.id + ".json";
  395. slides.type = dto.exercise.type;
  396. scoring.ans = dto.exercise.answer;
  397. scoring.score = dto.exercise.score;
  398. scoring.knowledge = dto.exercise.knowledge;
  399. scoring.field = dto.exercise.field;
  400. slides.scoring = scoring;
  401. //添加试卷信息
  402. paperDto.slides.Add(slides);
  403. if (!slides.type.Equals("compose"))
  404. {
  405. simple.point.Add(dto.exercise.score);
  406. simple.answers.Add(dto.exercise.answer);
  407. simple.knowledge.Add(dto.exercise.knowledge);
  408. simple.type.Add(dto.exercise.type);
  409. simple.field.Add((int)dto.exercise.field);
  410. }
  411. if (item.children.Count > 0)
  412. {
  413. foreach (HTEXLib.DOCX.Models.ItemInfo its in item.children)
  414. {
  415. Slides cslides = new();
  416. Scoring cscoring = new();
  417. ItemDto dtoChildren = new ItemDto();
  418. dtoChildren.id = Guid.NewGuid().ToString();
  419. dtoChildren.pid = dto.id;
  420. dtoChildren.exercise.answer = its.answer;
  421. dtoChildren.exercise.explain = its.explain;
  422. dtoChildren.exercise.type = its.type;
  423. dtoChildren.exercise.opts = its.option.Count;
  424. dtoChildren.exercise.knowledge = its.knowledge;
  425. dtoChildren.exercise.field = its.field;
  426. dtoChildren.exercise.level = its.level;
  427. dtoChildren.exercise.scope = "school";
  428. dtoChildren.exercise.score = its.score;
  429. dtoChildren.exercise.subjectId = fileDto.subjectId;
  430. dtoChildren.exercise.periodId = fileDto.periodId;
  431. dtoChildren.exercise.gradeIds = fileDto.gradeIds;
  432. dtoChildren.exercise.children.Add(dtoChildren.id);
  433. info info1 = new();
  434. info1.uid = dtoChildren.id;
  435. info1.question = its.question;
  436. info1.option = its.option;
  437. dtoChildren.item.Add(info1);
  438. dto.exercise.children.Add(dtoChildren.id);
  439. //处理子题的slides
  440. cslides.url = dtoChildren.id + ".json";
  441. cslides.type = dtoChildren.exercise.type;
  442. cscoring.ans = dtoChildren.exercise.answer;
  443. cscoring.score = dtoChildren.exercise.score;
  444. cscoring.knowledge = dtoChildren.exercise.knowledge;
  445. cscoring.field = dtoChildren.exercise.field;
  446. cslides.scoring = scoring;
  447. paperDto.slides.Add(cslides);
  448. //添加试卷信息
  449. simple.point.Add(dtoChildren.exercise.score);
  450. simple.answers.Add(dto.exercise.answer);
  451. simple.knowledge.Add(dto.exercise.knowledge);
  452. simple.type.Add(dto.exercise.type);
  453. simple.field.Add((int)dto.exercise.field);
  454. StringBuilder stringBuilder = new StringBuilder();
  455. stringBuilder.Append(fileDto.examId).Append("/");
  456. stringBuilder.Append("paper").Append("/");
  457. stringBuilder.Append(fileDto.subjectId).Append("/");
  458. stringBuilder.Append(dtoChildren.id + ".json");
  459. tasks.Add(_azureStorage.UploadFileByContainer(fileDto.code, dtoChildren.ToJsonString(), "exam", stringBuilder.ToString(), false));
  460. }
  461. }
  462. info @info = new();
  463. @info.uid = dto.id;
  464. @info.question = item.question;
  465. @info.option = item.option;
  466. dto.item.Add(@info);
  467. dto.exercise.scope = "school";
  468. dto.exercise.score = item.score;
  469. StringBuilder builder = new StringBuilder();
  470. builder.Append(fileDto.examId).Append('/');
  471. builder.Append("paper").Append('/');
  472. builder.Append(fileDto.subjectId).Append('/');
  473. builder.Append(dto.id + ".json");
  474. tasks.Add(_azureStorage.UploadFileByContainer(fileDto.code, dto.ToJsonString(), "exam", builder.ToString(), false));
  475. }
  476. StringBuilder paperBuilder = new StringBuilder();
  477. paperBuilder.Append(fileDto.examId).Append('/');
  478. paperBuilder.Append("paper").Append('/');
  479. paperBuilder.Append(fileDto.subjectId).Append('/');
  480. paperBuilder.Append("index.json");
  481. tasks.Add(_azureStorage.UploadFileByContainer(fileDto.code, paperDto.ToJsonString(), "exam", paperBuilder.ToString(), false));
  482. //开始给ExamInfo paper赋值
  483. simple.id = fileDto.subjectId;
  484. simple.code = "Paper-" + fileDto.code;
  485. simple.name = fileDto.name;
  486. simple.blob = paperBuilder.ToString().Replace("index.json", "");
  487. simple.scope = "school";
  488. simple.multipleRule = fileDto.multipleRule;
  489. examInfo.papers.Add(simple);
  490. await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(examInfo, examInfo.id, new PartitionKey($"{examInfo.code}"));
  491. await Task.WhenAll(tasks);
  492. return Ok(new { code = 200 });
  493. }
  494. catch (Exception e)
  495. {
  496. await _dingDing.SendBotMsg($"OS,{_option.Location},analysis/word()\n{e.Message}", GroupNames.醍摩豆服務運維群組);
  497. return BadRequest();
  498. }
  499. }
  500. private class students
  501. {
  502. public string id { get; set; }
  503. public List<List<string>> answer { get; set; }
  504. }
  505. public class FileDto
  506. {
  507. public string periodId { get; set; }
  508. public string code { get; set; }
  509. public string name { get; set; }
  510. public int multipleRule { get; set; }
  511. public string examId { get; set; }
  512. public string subjectId { get; set; }
  513. public List<string> gradeIds { get; set; }
  514. public IFormFile file { get; set; }
  515. }
  516. }
  517. }