ExamController.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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("{scope}")]
  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. [HttpPost("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}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  99. return BadRequest();
  100. }
  101. }
  102. [ProducesDefaultResponseType]
  103. [HttpPost("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}\n{e.StackTrace}", 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. result.status[newIndex] = 0;
  202. for (int i = 0; i < ans.Count; i++)
  203. {
  204. if (ans[i] == null)
  205. {
  206. continue;
  207. //ans[i] = new List<string>();
  208. }
  209. var ac = ans[i].Count;
  210. var sc = standard[i].Count;
  211. //记录次数
  212. int n = 0;
  213. //算分处理
  214. if (sc > 0)
  215. {
  216. result.ans[newIndex][i] = ans[i];
  217. if (ac == sc && sc == 1)
  218. {
  219. foreach (string right in ans[i])
  220. {
  221. if (standard[i].Contains(right))
  222. {
  223. result.studentScores[newIndex][i] = points[i];
  224. }
  225. else
  226. {
  227. result.studentScores[newIndex][i] = 0;
  228. }
  229. }
  230. }
  231. else
  232. {
  233. if (rule > 0)
  234. {
  235. int falseCount = 0;
  236. if (ac > 0)
  237. {
  238. foreach (string obj in ans[i])
  239. {
  240. if (!standard[i].Contains(obj))
  241. {
  242. falseCount++;
  243. }
  244. }
  245. switch (rule)
  246. {
  247. case 1:
  248. if (ac == sc)
  249. {
  250. if (falseCount == 0)
  251. {
  252. result.studentScores[newIndex][i] = points[i];
  253. }
  254. else
  255. {
  256. result.studentScores[newIndex][i] = 0;
  257. }
  258. }
  259. else
  260. {
  261. result.studentScores[newIndex][i] = 0;
  262. }
  263. break;
  264. case 2:
  265. if (falseCount > 0)
  266. {
  267. result.studentScores[newIndex][i] = 0;
  268. }
  269. else
  270. {
  271. if (ac == sc)
  272. {
  273. result.studentScores[newIndex][i] = points[i];
  274. }
  275. else
  276. {
  277. result.studentScores[newIndex][i] = points[i] / 2;
  278. }
  279. }
  280. break;
  281. case 3:
  282. if (falseCount > 0)
  283. {
  284. result.studentScores[newIndex][i] = 0;
  285. }
  286. else
  287. {
  288. if (ac == sc)
  289. {
  290. result.studentScores[newIndex][i] = points[i];
  291. }
  292. else
  293. {
  294. result.studentScores[newIndex][i] = System.Math.Round((double)ac / sc * points[i], 1);
  295. }
  296. }
  297. break;
  298. case 4:
  299. if (ac == sc)
  300. {
  301. result.studentScores[newIndex][i] = points[i];
  302. }
  303. else
  304. {
  305. double persent = (double)(sc - 2 * falseCount) / sc;
  306. if (persent <= 0)
  307. {
  308. result.studentScores[newIndex][i] = 0;
  309. }
  310. else
  311. {
  312. result.studentScores[newIndex][i] = System.Math.Round(persent * points[i], 1);
  313. }
  314. }
  315. break;
  316. }
  317. }
  318. else
  319. {
  320. result.studentScores[newIndex][i] = 0;
  321. }
  322. }
  323. }
  324. }
  325. }
  326. bool flag = true;
  327. foreach (List<double> scores in result.studentScores)
  328. {
  329. foreach (double score in scores)
  330. {
  331. if (score == -1)
  332. {
  333. flag = false;
  334. break;
  335. }
  336. }
  337. }
  338. if (flag)
  339. {
  340. result.progress = true;
  341. info.subjects.ForEach(s =>
  342. {
  343. if (s.id.Equals(subjectId.ToString()))
  344. {
  345. s.classCount += 1;
  346. }
  347. });
  348. await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(info, info.id.ToString(), new PartitionKey($"{info.code}"));
  349. }
  350. result.sum[newIndex] = result.studentScores[newIndex].Sum();
  351. await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(result, result.id, new PartitionKey($"{result.code}"));
  352. break;
  353. }
  354. }
  355. }
  356. yield return (code, value);
  357. }
  358. }
  359. [ProducesDefaultResponseType]
  360. [HttpPost("parse-word")]
  361. [ApiToken(Auth = "1104", Name = "录入试卷数据", Limit = false)]
  362. public async Task<IActionResult> ParseWord([FromForm] FileDto fileDto)
  363. {
  364. if (!FileType.GetExtention(fileDto.file.FileName).ToLower().Equals("docx"))
  365. {
  366. return BadRequest(new Dictionary<string, object> { { "msg", "type is not docx!" }, { "code", 404 } });
  367. }
  368. try
  369. {
  370. var client = _azureCosmos.GetCosmosClient();
  371. var response = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemStreamAsync(fileDto.examId, new PartitionKey($"Exam-{fileDto.code}"));
  372. ExamInfo examInfo;
  373. if (response.Status == 200)
  374. {
  375. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  376. examInfo = json.ToObject<ExamInfo>();
  377. }
  378. else
  379. {
  380. return Ok(new { error = 404, msg = "请先导入评测信息" });
  381. }
  382. foreach (PaperSimple paper in examInfo.papers)
  383. {
  384. if (paper.id.Contains(fileDto.subjectId))
  385. {
  386. return Ok(new { error = 500, msg = "该试卷信息已存在" });
  387. }
  388. }
  389. PaperSimple simple = new();
  390. var doc = _DOXC2HTMLTranslator.Translate(fileDto.file.OpenReadStream());
  391. (List<HTEXLib.DOCX.Models.ItemInfo> tests, List<string> error) = _HTML2ITEMV3Translator.Translate(doc);
  392. List<Task<string>> tasks = new List<Task<string>>();
  393. PaperDto paperDto = new()
  394. {
  395. id = Guid.NewGuid().ToString(),
  396. name = fileDto.name,
  397. code = fileDto.code,
  398. scope = "school",
  399. multipleRule = fileDto.multipleRule,
  400. gradeIds = fileDto.gradeIds,
  401. subjectId = fileDto.subjectId,
  402. periodId = fileDto.periodId
  403. };
  404. foreach (HTEXLib.DOCX.Models.ItemInfo item in tests)
  405. {
  406. Slides slides = new();
  407. ItemDto dto = new();
  408. Scoring scoring = new();
  409. dto.id = Guid.NewGuid().ToString();
  410. dto.exercise.answer = item.answer;
  411. dto.exercise.explain = item.explain;
  412. dto.exercise.type = item.type;
  413. dto.exercise.opts = item.option.Count;
  414. dto.exercise.knowledge = item.knowledge;
  415. dto.exercise.field = item.field;
  416. dto.exercise.level = item.level;
  417. dto.exercise.subjectId = fileDto.subjectId;
  418. dto.exercise.periodId = fileDto.periodId;
  419. dto.exercise.gradeIds = fileDto.gradeIds;
  420. slides.url = dto.id + ".json";
  421. slides.type = dto.exercise.type;
  422. scoring.ans = dto.exercise.answer;
  423. scoring.score = dto.exercise.score;
  424. scoring.knowledge = dto.exercise.knowledge;
  425. scoring.field = dto.exercise.field;
  426. slides.scoring = scoring;
  427. //添加试卷信息
  428. paperDto.slides.Add(slides);
  429. if (!slides.type.Equals("compose"))
  430. {
  431. simple.point.Add(dto.exercise.score);
  432. simple.answers.Add(dto.exercise.answer);
  433. simple.knowledge.Add(dto.exercise.knowledge);
  434. simple.type.Add(dto.exercise.type);
  435. simple.field.Add((int)dto.exercise.field);
  436. }
  437. if (item.children.Count > 0)
  438. {
  439. foreach (HTEXLib.DOCX.Models.ItemInfo its in item.children)
  440. {
  441. Slides cslides = new();
  442. Scoring cscoring = new();
  443. ItemDto dtoChildren = new ItemDto();
  444. dtoChildren.id = Guid.NewGuid().ToString();
  445. dtoChildren.pid = dto.id;
  446. dtoChildren.exercise.answer = its.answer;
  447. dtoChildren.exercise.explain = its.explain;
  448. dtoChildren.exercise.type = its.type;
  449. dtoChildren.exercise.opts = its.option.Count;
  450. dtoChildren.exercise.knowledge = its.knowledge;
  451. dtoChildren.exercise.field = its.field;
  452. dtoChildren.exercise.level = its.level;
  453. dtoChildren.exercise.scope = "school";
  454. dtoChildren.exercise.score = its.score;
  455. dtoChildren.exercise.subjectId = fileDto.subjectId;
  456. dtoChildren.exercise.periodId = fileDto.periodId;
  457. dtoChildren.exercise.gradeIds = fileDto.gradeIds;
  458. dtoChildren.exercise.children.Add(dtoChildren.id);
  459. info info1 = new();
  460. info1.uid = dtoChildren.id;
  461. info1.question = its.question;
  462. info1.option = its.option;
  463. dtoChildren.item.Add(info1);
  464. dto.exercise.children.Add(dtoChildren.id);
  465. //处理子题的slides
  466. cslides.url = dtoChildren.id + ".json";
  467. cslides.type = dtoChildren.exercise.type;
  468. cscoring.ans = dtoChildren.exercise.answer;
  469. cscoring.score = dtoChildren.exercise.score;
  470. cscoring.knowledge = dtoChildren.exercise.knowledge;
  471. cscoring.field = dtoChildren.exercise.field;
  472. cslides.scoring = scoring;
  473. paperDto.slides.Add(cslides);
  474. //添加试卷信息
  475. simple.point.Add(dtoChildren.exercise.score);
  476. simple.answers.Add(dto.exercise.answer);
  477. simple.knowledge.Add(dto.exercise.knowledge);
  478. simple.type.Add(dto.exercise.type);
  479. simple.field.Add((int)dto.exercise.field);
  480. StringBuilder stringBuilder = new StringBuilder();
  481. stringBuilder.Append(fileDto.examId).Append("/");
  482. stringBuilder.Append("paper").Append("/");
  483. stringBuilder.Append(fileDto.subjectId).Append("/");
  484. stringBuilder.Append(dtoChildren.id + ".json");
  485. tasks.Add(_azureStorage.GetBlobContainerClient(fileDto.code).UploadFileByContainer( dtoChildren.ToJsonString(), "exam", stringBuilder.ToString(), false));
  486. }
  487. }
  488. info @info = new();
  489. @info.uid = dto.id;
  490. @info.question = item.question;
  491. @info.option = item.option;
  492. dto.item.Add(@info);
  493. dto.exercise.scope = "school";
  494. dto.exercise.score = item.score;
  495. StringBuilder builder = new StringBuilder();
  496. builder.Append(fileDto.examId).Append('/');
  497. builder.Append("paper").Append('/');
  498. builder.Append(fileDto.subjectId).Append('/');
  499. builder.Append(dto.id + ".json");
  500. tasks.Add(_azureStorage.GetBlobContainerClient(fileDto.code).UploadFileByContainer(dto.ToJsonString(), "exam", builder.ToString(), false));
  501. }
  502. StringBuilder paperBuilder = new StringBuilder();
  503. paperBuilder.Append(fileDto.examId).Append('/');
  504. paperBuilder.Append("paper").Append('/');
  505. paperBuilder.Append(fileDto.subjectId).Append('/');
  506. paperBuilder.Append("index.json");
  507. tasks.Add(_azureStorage.GetBlobContainerClient(fileDto.code).UploadFileByContainer(paperDto.ToJsonString(), "exam", paperBuilder.ToString(), false));
  508. //开始给ExamInfo paper赋值
  509. simple.id = fileDto.subjectId;
  510. simple.code = "Paper-" + fileDto.code;
  511. simple.name = fileDto.name;
  512. simple.blob = paperBuilder.ToString().Replace("index.json", "");
  513. simple.scope = "school";
  514. simple.multipleRule = fileDto.multipleRule;
  515. examInfo.papers.Add(simple);
  516. await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(examInfo, examInfo.id, new PartitionKey($"{examInfo.code}"));
  517. await Task.WhenAll(tasks);
  518. return Ok(new { code = 200 });
  519. }
  520. catch (Exception e)
  521. {
  522. await _dingDing.SendBotMsg($"OS,{_option.Location},analysis/word()\n{e.Message}\n{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
  523. return BadRequest();
  524. }
  525. }
  526. private class students
  527. {
  528. public string id { get; set; }
  529. public List<List<string>> answer { get; set; }
  530. }
  531. public class FileDto
  532. {
  533. public string periodId { get; set; }
  534. public string code { get; set; }
  535. public string name { get; set; }
  536. public int multipleRule { get; set; }
  537. public string examId { get; set; }
  538. public string subjectId { get; set; }
  539. public List<string> gradeIds { get; set; }
  540. public IFormFile file { get; set; }
  541. }
  542. }
  543. }