ExamController.cs 28 KB

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