ThirdService.cs 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  1. using Microsoft.Azure.Cosmos;
  2. using Azure.Storage.Blobs.Models;
  3. using Microsoft.Extensions.Configuration;
  4. using Newtonsoft.Json;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Net;
  10. using System.Net.Http;
  11. using System.Net.Http.Headers;
  12. using System.Text;
  13. using System.Text.Json;
  14. using System.Threading.Tasks;
  15. using TEAMModelOS.Models;
  16. using TEAMModelOS.SDK.DI;
  17. using TEAMModelOS.SDK.Extension;
  18. using TEAMModelOS.SDK.Models.Cosmos;
  19. using TEAMModelOS.SDK.Models.Cosmos.Student;
  20. using static TEAMModelOS.SDK.Models.Teacher;
  21. namespace TEAMModelOS.SDK.Models
  22. {
  23. public static class ThirdService
  24. {
  25. //自动加入学校,加入培训名单,并根据学科进行分组
  26. public static async Task<ScTeacher> GetScTeacher(ScBindData scBind, Teacher teacher, AzureStorageFactory _azureStorage, AzureCosmosFactory _azureCosmos, AzureServiceBusFactory _serviceBus, IConfiguration _configuration, DingDing _dingDing)
  27. {
  28. var table = _azureStorage.GetCloudTableClient().GetTableReference("ScYxpt");
  29. List<ScSchool> schools = await table.FindListByDict<ScSchool>(new Dictionary<string, object> { { "PartitionKey", "ScSchool" }, { "RowKey", scBind.sid } });
  30. List<ScTeacher> scTeachers = await table.FindListByDict<ScTeacher>(new Dictionary<string, object> { { "PartitionKey", "ScTeacher" }, { "TID", scBind.userid }, { "RowKey", $"{scBind.pxid}" } });
  31. if (schools.IsNotEmpty())
  32. {
  33. ScSchool scSchool = schools[0];
  34. if (!string.IsNullOrEmpty(scSchool.schoolCode))
  35. {
  36. try
  37. {
  38. School school = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(scSchool.schoolCode, new PartitionKey("Base"));
  39. if (school != null)
  40. {
  41. if (scTeachers.IsNotEmpty())
  42. {
  43. if (string.IsNullOrEmpty(scTeachers[0].tmdid))
  44. {
  45. scTeachers[0].tmdid = teacher.id;
  46. scTeachers[0].schoolCode = scSchool.schoolCode;
  47. scTeachers[0].areaId = school.areaId;
  48. await table.SaveOrUpdate<ScTeacher>(scTeachers[0]);
  49. }
  50. }
  51. var sc = teacher.schools.Find(x => x.schoolId.Equals(scSchool.schoolCode));
  52. if (sc != null)
  53. {
  54. if (string.IsNullOrEmpty(sc.status) || !sc.status.Equals("join"))
  55. {
  56. sc.status = "join";
  57. try
  58. {
  59. SchoolTeacher schoolTeacher = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<SchoolTeacher>(teacher.id, new PartitionKey($"Teacher-{school.id}"));
  60. if (schoolTeacher != null)
  61. {
  62. if (!schoolTeacher.roles.IsEmpty())
  63. {
  64. if (!schoolTeacher.roles.Contains("teacher"))
  65. {
  66. schoolTeacher.roles.Add("teacher");
  67. }
  68. }
  69. else
  70. {
  71. schoolTeacher.roles = new List<string> { "teacher" };
  72. }
  73. schoolTeacher.status = "join";
  74. schoolTeacher.pk = "Teacher";
  75. schoolTeacher.name = teacher.name;
  76. schoolTeacher.picture = teacher.picture;
  77. schoolTeacher.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  78. schoolTeacher.ttl = -1;
  79. schoolTeacher.permissions = schoolTeacher.permissions.IsNotEmpty() ? schoolTeacher.permissions : new List<string>();
  80. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").UpsertItemAsync(schoolTeacher, new PartitionKey(schoolTeacher.code));
  81. }
  82. }
  83. catch (CosmosException)
  84. {
  85. SchoolTeacher schoolTeacher = new SchoolTeacher
  86. {
  87. id = teacher.id,
  88. code = $"Teacher-{school.id}",
  89. roles = new List<string> { "teacher" },
  90. permissions = new List<string>(),
  91. pk = "Teacher",
  92. name = teacher.name,
  93. picture = teacher.picture,
  94. status = "join",
  95. createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
  96. ttl = -1
  97. };
  98. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").UpsertItemAsync(schoolTeacher, new PartitionKey(schoolTeacher.code));
  99. }
  100. }
  101. }
  102. else
  103. {
  104. teacher.schools.Add(new Teacher.TeacherSchool { schoolId = school.id, name = school.name, areaId = school.areaId, picture = school.picture, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), status = "join" });
  105. SchoolTeacher schoolTeacher = new SchoolTeacher
  106. {
  107. id = teacher.id,
  108. code = $"Teacher-{school.id}",
  109. roles = new List<string> { "teacher" },
  110. permissions = new List<string>(),
  111. pk = "Teacher",
  112. name = teacher.name,
  113. picture = teacher.picture,
  114. status = "join",
  115. createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
  116. };
  117. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").UpsertItemAsync(schoolTeacher, new PartitionKey(schoolTeacher.code));
  118. }
  119. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync(teacher, teacher.id, new PartitionKey("Base"));
  120. //处理培训名单
  121. StringBuilder queryText = new StringBuilder($"SELECT distinct value(c) FROM c where c.type='yxtrain'");
  122. List<GroupList> yxtrain = new List<GroupList>();
  123. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<GroupList>(queryText: queryText.ToString(),
  124. requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GroupList-{scSchool.schoolCode}") }))
  125. {
  126. yxtrain.Add(item);
  127. }
  128. if (yxtrain.IsNotEmpty())
  129. {
  130. string nickname = teacher.name;
  131. string groupName = null;
  132. string groupId = Guid.NewGuid().ToString();
  133. if (scTeachers.IsNotEmpty())
  134. {
  135. if (scBind.pid.Equals("1249"))
  136. {
  137. groupName = $"第三批教师培训组";
  138. }
  139. nickname = scTeachers[0].TeacherName;
  140. if (!string.IsNullOrEmpty(groupName))
  141. {
  142. var mebers = yxtrain.SelectMany(x => x.members).Where(y => !string.IsNullOrEmpty(y.groupName) && y.groupName.Equals(groupName));
  143. if (mebers != null && mebers.Count() > 0)
  144. {
  145. groupId = mebers.First().groupId;
  146. }
  147. }
  148. else { groupId = null; }
  149. }
  150. else { groupId = null; }
  151. var meber = yxtrain.SelectMany(x => x.members).Where(y => y.id.Equals(teacher.id));
  152. //不在研修名单
  153. if (meber == null || !meber.Any())
  154. {
  155. yxtrain[0].members.Add(new Member { id = teacher.id, type = 1, groupId = groupId, groupName = groupName, nickname = nickname });
  156. await GroupListService.UpsertList(yxtrain[0], _azureCosmos, _configuration, _serviceBus, "web");
  157. }
  158. else
  159. {
  160. if (string.IsNullOrEmpty(meber.First().groupId) || string.IsNullOrEmpty(meber.First().groupName))
  161. {
  162. meber.ToList().ForEach(x => { x.groupId = groupId; x.groupName = groupName; x.nickname = string.IsNullOrWhiteSpace(x.nickname) ? nickname : x.nickname; });
  163. await GroupListService.UpsertList(yxtrain[0], _azureCosmos, _configuration, _serviceBus, "web");
  164. }
  165. }
  166. }
  167. else
  168. {
  169. string nickname = teacher.name;
  170. string groupName = null;
  171. if (scTeachers.IsNotEmpty())
  172. {
  173. groupName = scTeachers[0].TeacherXK;
  174. nickname = scTeachers[0].TeacherName;
  175. }
  176. string groupId = null;
  177. if (!string.IsNullOrEmpty(groupName))
  178. {
  179. groupId = Guid.NewGuid().ToString();
  180. }
  181. GroupList groupList = new()
  182. {
  183. id = Guid.NewGuid().ToString(),
  184. code = $"GroupList-{scSchool.schoolCode}",
  185. creatorId = teacher.id,
  186. type = "yxtrain",
  187. year = DateTimeOffset.UtcNow.Year,
  188. expire = 0,
  189. members = new List<Member> { new Member { id = teacher.id, type = 1, groupId = groupId, groupName = groupName, nickname = nickname } },
  190. scope = "school",
  191. school = scSchool.schoolCode,
  192. name = "研修名单",
  193. pk = "GroupList",
  194. ttl = -1
  195. };
  196. await GroupListService.UpsertList(groupList, _azureCosmos, _configuration, _serviceBus, "web");
  197. }
  198. }
  199. }
  200. catch (Exception ex)
  201. {
  202. await _dingDing.SendBotMsg($"OS\n自动加入学校,加入研修名单出现异常:,{ex.Message}\n{ex.StackTrace}GetScTeacher", GroupNames.醍摩豆服務運維群組);
  203. }
  204. }
  205. }
  206. return null;
  207. }
  208. public static async Task<(string accessConfig, Area area, AreaSetting setting)> GetAccessConfig(CosmosClient client, string standard)
  209. {
  210. Area area = null;
  211. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Normal").
  212. GetItemQueryIteratorSql<Area>($"select value(c) from c where c.standard='{standard}'", requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Base-Area") }))
  213. {
  214. area = item;
  215. break;
  216. }
  217. AreaSetting setting = null;
  218. if (area != null)
  219. {
  220. try
  221. {
  222. setting = await client.GetContainer(Constant.TEAMModelOS, "Normal").ReadItemAsync<AreaSetting>(area.id, new PartitionKey("AreaSetting"));
  223. }
  224. catch (CosmosException)
  225. {
  226. setting = null;
  227. }
  228. }
  229. if (setting == null || string.IsNullOrEmpty(setting.accessConfig))
  230. {
  231. return (null, null, null);
  232. }
  233. else
  234. {
  235. return (setting.accessConfig, area, setting);
  236. }
  237. }
  238. public static async Task<List<Ability>> GetDiagnosisList(CosmosClient client, string standard, DingDing _dingDing, AreaSetting setting, HttpClient _httpClient, Teacher teacher, TEAMModelOS.Models.Option _option, AzureStorageFactory _azureStorage)
  239. {
  240. List<string> abilityNos = new();
  241. var table = _azureStorage.GetCloudTableClient().GetTableReference("ScYxpt");
  242. setting.accessConfig.ToObject<JsonElement>().TryGetProperty("config", out JsonElement _config);
  243. if ($"{_config}".Equals("scsyxpt"))
  244. {
  245. var binds = teacher.binds.FindAll(x => x.type.Equals("scsyxpt"));
  246. var datas = binds.SelectMany(x => x.data).Where(y => y.Contains("scsyxpt"));
  247. HashSet<string> pxids = new();
  248. if (datas != null)
  249. {
  250. datas.ToList().ForEach(x =>
  251. {
  252. var data = x.ToObject<ScBindData>();
  253. if (!string.IsNullOrEmpty(data?.pxid))
  254. {
  255. pxids.Add(data.pxid);
  256. }
  257. });
  258. }
  259. foreach (var pxid in pxids)
  260. {
  261. List<ScTeacher> teachers = await table.FindListByDict<ScTeacher>(new Dictionary<string, object> { { "PartitionKey", "ScTeacher" }, { "PXID", pxid } });
  262. Dictionary<string, object> dict = new();
  263. if (teachers.IsNotEmpty())
  264. {
  265. dict = new Dictionary<string, object>() { { "accessConfig", setting.accessConfig }, { "pxid", pxid }, { "areaId", setting.id }, { "schoolCode", teachers[0].schoolCode } };
  266. }
  267. else
  268. {
  269. dict = new Dictionary<string, object>() { { "accessConfig", setting.accessConfig }, { "pxid", pxid }, { "areaId", setting.id } };
  270. }
  271. (int status, string json) = await ScsStudyApisService.GetDiagnosisListByProject_V2(_httpClient, _dingDing, _azureStorage, setting.id, setting.accessConfig, pxid, teachers[0].schoolCode);
  272. //(int status, string json) = await httpTrigger.RequestHttpTrigger(dict, _option.Location, "GetDiagnosisListByProject_V2");
  273. if (status == 200)
  274. {
  275. List<string> nos = json.ToObject<List<string>>();
  276. if (nos.IsNotEmpty())
  277. {
  278. abilityNos.AddRange(nos);
  279. }
  280. }
  281. }
  282. }
  283. //获取能力点
  284. List<Ability> abilities = null;
  285. if (abilityNos.IsNotEmpty())
  286. {
  287. abilities = new List<Ability>();
  288. StringBuilder sql = new StringBuilder($"select value(c) from c where c.no in ({string.Join(",", abilityNos.Select(x => $"'{x}'"))})");
  289. await foreach (var item in client.GetContainer("TEAMModelOS", "Normal")
  290. .GetItemQueryIteratorSql<Ability>(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Ability-{standard}") }))
  291. {
  292. abilities.Add(item);
  293. }
  294. }
  295. return abilities;
  296. }
  297. //5.3.1.22学员校本教研PDF(每人可以返回多条)批量回写-UploadSBTARPDFListV2
  298. public async static Task<(int t53122OK, List<CodeValue> msgs, List<OfflineRecord> allRightOfflineRecords)> check53122(TeacherTrain teacherTrain, List<CodeValue> msgs, string school,
  299. string schoolPrefix, string sas, AzureStorageFactory _azureStorage)
  300. {
  301. int t53122OK = 1;
  302. if (teacherTrain.offlineRecords.Count <= 0)
  303. {
  304. t53122OK = 0;
  305. msgs.Add(new CodeValue("offlineRecord-count", $"文件个数为0"));
  306. }
  307. List<OfflineRecord> allRightOfflineRecords = new List<OfflineRecord>();
  308. var hasUrl = teacherTrain.offlineRecords.Where(x => !string.IsNullOrWhiteSpace(x.url) && x.size > 0);
  309. if (!hasUrl.Any())
  310. {
  311. t53122OK = 0;
  312. msgs.Add(new CodeValue("offlineRecord-url", $"需要上传的校本研修作业至少有一个。"));
  313. }
  314. if (teacherTrain.offlineReport == null)
  315. {
  316. t53122OK = 0;
  317. msgs.Add(new CodeValue("offlineReport", $"校本研修汇总报告未生成。"));
  318. }
  319. List<string> unexistUrl = new List<string>();
  320. foreach (var url in hasUrl)
  321. {
  322. string blobItem = url.url.Replace($"{schoolPrefix}/", "");
  323. bool Exist = await _azureStorage.GetBlobContainerClient(school).GetBlobClient(blobItem).ExistsAsync();
  324. if (!Exist)
  325. {
  326. unexistUrl.Add($"{url.url}?{sas}");
  327. }
  328. else
  329. {
  330. allRightOfflineRecords.Add(url);
  331. }
  332. }
  333. if (unexistUrl.Any() && hasUrl.Count() > 0 && hasUrl.Count() == unexistUrl.Count)
  334. {
  335. t53122OK = 0;
  336. msgs.Add(new CodeValue("offlineRecord-url-unexist", $"校本研修文件不存在,{string.Join(" , ", unexistUrl)}"));
  337. }
  338. //不需要检查每一个校本研修的文件记录。
  339. //teacherTrain.offlineRecords.ForEach(x => {
  340. // if (string.IsNullOrEmpty(x.url)) {
  341. // msgs.Add(new CodeValue("offlineRecord-url", $"链接为空"));
  342. // }
  343. // if (x.size<=0)
  344. // {
  345. // msgs.Add(new CodeValue("offlineRecord-size", $"文件大小"));
  346. // }
  347. //});
  348. return (t53122OK, msgs, allRightOfflineRecords);
  349. }
  350. //5.3.1.17学员课堂实录批量回写-UploadKTSLList
  351. public async static Task<(int t53117OK, List<CodeValue> msgs)> check53117(TeacherTrain teacherTrain, List<CodeValue> msgs, string school,
  352. string schoolPrefix, string sas, AzureStorageFactory _azureStorage)
  353. {
  354. //校验 基本情况是否满足
  355. int t53117OK = 1;
  356. if (teacherTrain.classTime <= 0)
  357. {
  358. msgs.Add(new CodeValue("classTime", $"未获得学时:{teacherTrain.classTime}"));
  359. t53117OK = 0;
  360. }
  361. if (teacherTrain.teacherClasses.Count() <= 0)
  362. {
  363. msgs.Add(new CodeValue("teacherClasses", $"未上传课堂实录:{teacherTrain.teacherClasses.Count()}个视频"));
  364. t53117OK = 0;
  365. }
  366. teacherTrain.teacherClasses.ForEach(x =>
  367. {
  368. if (string.IsNullOrWhiteSpace(x.url))
  369. {
  370. t53117OK = 0;
  371. msgs.Add(new CodeValue("teacherClasses", $"课堂实录链接无效"));
  372. }
  373. });
  374. List<string> unexistUrl = new List<string>();
  375. foreach (var url in teacherTrain.teacherClasses)
  376. {
  377. string blobItem = url.url.Replace($"{schoolPrefix}/", "");
  378. bool Exist = await _azureStorage.GetBlobContainerClient(school).GetBlobClient(blobItem).ExistsAsync();
  379. if (!Exist)
  380. {
  381. unexistUrl.Add($"{url.url}?{sas}");
  382. }
  383. }
  384. if (unexistUrl.Any())
  385. {
  386. t53117OK = 0;
  387. msgs.Add(new CodeValue("teacherClasses-url-unexist", $"课堂实录文件不存在,{string.Join(" , ", unexistUrl)}"));
  388. }
  389. return (t53117OK, msgs);
  390. }
  391. //5.3.1.12学员培训基本情况批量回写-UpdateTeacherListSituation
  392. public static (int t53112OK, List<CodeValue> msgs) check53112(TeacherTrain teacherTrain, List<CodeValue> msgs)
  393. {
  394. //校验 基本情况是否满足
  395. int t53112OK = 1;
  396. if (teacherTrain.finalScore <= 0)
  397. {
  398. //总体认定结果0、未认定 1、合格 2、优秀 3、不合格 4、其他
  399. msgs.Add(new CodeValue("finalScore", $"最终评定结果参数:{teacherTrain.finalScore}"));
  400. t53112OK = 0;
  401. }
  402. //if (string.IsNullOrEmpty(teacherTrain.summary) || teacherTrain.summary.Length > 300)
  403. //{
  404. // string msg = string.IsNullOrEmpty(teacherTrain.summary) ? "未填写" : teacherTrain.summary.Length > 300 ? "字数超过300." : "";
  405. // msgs.Add(new CodeValue("summary", $"教师培训总结:{msg}"));
  406. // t53112OK = 0;
  407. //}
  408. if (!string.IsNullOrEmpty(teacherTrain.summary) && teacherTrain.summary.Length > 300)
  409. {
  410. //string msg = string.IsNullOrEmpty(teacherTrain.summary) ? "未填写" : teacherTrain.summary.Length > 300 ? "字数超过300." : "";
  411. msgs.Add(new CodeValue("summary", $"教师培训总结:字数超过300."));
  412. t53112OK = 0;
  413. }
  414. if (teacherTrain.totalTime <= 0)
  415. {
  416. msgs.Add(new CodeValue("totalTime", $"未获得学时:{teacherTrain.totalTime}"));
  417. t53112OK = 0;
  418. }
  419. return (t53112OK, msgs);
  420. }
  421. //5.3.1.13学员能力点测评结果批量回写-UpdateTeacherListDiagnosis
  422. public async static Task<(int t53113OK, List<CodeValue> msgs, List<AbilitySub> abilitySubs, List<AbilitySub> allRightAbility)> check53113(AzureCosmosFactory _azureCosmos, TeacherTrain teacherTrain, ScTeacherDiagnosis diagnosis,
  423. List<CodeValue> msgs, string school,
  424. string schoolPrefix, string sas, AzureStorageFactory _azureStorage)
  425. {
  426. //校验 基本情况是否满足
  427. int t53113OK = 1;
  428. List<AbilitySub> allRightAbility = new List<AbilitySub>();
  429. List<AbilitySub> abilitySubs = new List<AbilitySub>();
  430. if (teacherTrain.currency.videoTime <= 0)
  431. {
  432. msgs.Add(new CodeValue("videoTime", $"视频学习时长:{teacherTrain.currency.videoTime}"));
  433. t53113OK = 0;
  434. }
  435. if (teacherTrain.currency.submitTime <= 0)
  436. {
  437. msgs.Add(new CodeValue("submitTime", $"认证材料学习:{teacherTrain.currency.submitTime}"));
  438. t53113OK = 0;
  439. }
  440. if (teacherTrain.currency.teacherAilities.Count <= 0)
  441. {
  442. msgs.Add(new CodeValue("teacherAilities", $"已学习能力点:0"));
  443. t53113OK = 0;
  444. }
  445. try
  446. {
  447. if (diagnosis != null)
  448. {
  449. if (!string.IsNullOrWhiteSpace(diagnosis.abilityNos))
  450. {
  451. List<string> nos = diagnosis.abilityNos.ToObject<List<string>>();
  452. if (nos.Count > 0 && teacherTrain.currency.teacherAilities.Count > 0)
  453. {
  454. var notin = nos.Except(teacherTrain.currency.teacherAilities.Select(x => x.no).Where(z => !string.IsNullOrWhiteSpace(z)));
  455. if (notin.Any())
  456. {
  457. msgs.Add(new CodeValue("diagnosisNos", $"省平台勾选的能力点编号为学习完成:省平台:{string.Join(",", nos.OrderBy(x => x))}" + $" ,已学习:{string.Join(",", teacherTrain.currency.teacherAilities.Select(x => x.no).OrderBy(x => x))} "));
  458. t53113OK = 0;
  459. }
  460. else
  461. {
  462. string insql = "";
  463. if (teacherTrain.currency.teacherAilities.IsNotEmpty())
  464. {
  465. var abilites = teacherTrain.currency.teacherAilities.Where(c => !string.IsNullOrWhiteSpace(c.no) && nos.Contains(c.no));
  466. insql = $" where c.id in ({string.Join(",", abilites.Select(o => $"'{o.id}'"))})";
  467. }
  468. //认证材料
  469. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher")
  470. .GetItemQueryIteratorSql<AbilitySub>(queryText: $"select value(c) from c {insql}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilitySub-{teacherTrain.school}-{teacherTrain.id}") }))
  471. {
  472. abilitySubs.Add(item);
  473. }
  474. if (abilitySubs.Count() <= 3)
  475. {
  476. abilitySubs.ForEach(ab =>
  477. {
  478. var x = teacherTrain.currency.teacherAilities.Find(x => x.id.Equals(ab.id));
  479. if (x == null || !ab.uploads.Any())
  480. {
  481. t53113OK = 0;
  482. msgs.Add(new CodeValue("uploads", $"未上传认证材料:{x.no},{x.name}"));
  483. }
  484. if (x.zpscore <= 0)
  485. {
  486. t53113OK = 0;
  487. msgs.Add(new CodeValue("zpscore", $"认证材料,没有完成自评:{x.no},{x.name},{x.zpscore}"));
  488. }
  489. if (x.hpscore <= 0)
  490. {
  491. x.hpscore = 1;
  492. }
  493. if (x.xzscore <= 0)
  494. {
  495. x.xzscore = 1;
  496. }
  497. });
  498. foreach (AbilitySub abilitySub in abilitySubs)
  499. {
  500. //当前能力点上传的文件是否完全有效
  501. bool isAllRight = true;
  502. List<string> urlUn = new List<string>();
  503. foreach (var subUpload in abilitySub.uploads)
  504. {
  505. foreach (var url in subUpload.urls)
  506. {
  507. string blobItem = url.url.Replace($"{schoolPrefix}/", "");
  508. bool Exist = await _azureStorage.GetBlobContainerClient(school).GetBlobClient(blobItem).ExistsAsync();
  509. if (!Exist)
  510. {
  511. isAllRight = false;
  512. urlUn.Add($"{url.url}?{sas}");
  513. }
  514. }
  515. }
  516. if (isAllRight)
  517. {
  518. allRightAbility.Add(abilitySub);
  519. }
  520. else
  521. {
  522. t53113OK = 0;
  523. var x = teacherTrain.currency.teacherAilities.Find(x => x.id.Equals(abilitySub.id));
  524. msgs.Add(new CodeValue("uploads-url", $"{x.no},{x.name}上传的认证材料文件失效:{string.Join(" , ", urlUn)}"));
  525. }
  526. }
  527. }
  528. else
  529. {
  530. //一个都没上传
  531. if (!abilitySubs.SelectMany(upsl => upsl.uploads).Any())
  532. {
  533. t53113OK = 0;
  534. msgs.Add(new CodeValue("uploads-all", $"没有上传认证材料。"));
  535. }
  536. else
  537. {
  538. //检查上传了认证材料的能力点,超过三个的。
  539. var uploaded = abilitySubs.FindAll(x => x.uploads.Count > 0);
  540. //不足三个的则需要记录
  541. if (uploaded.Count < 3)
  542. {
  543. t53113OK = 0;
  544. ///少于三个的,需要判断另外的不满足情况的
  545. abilitySubs.RemoveAll(x => x.uploads.Count > 0);
  546. abilitySubs.ForEach(ab =>
  547. {
  548. var x = teacherTrain.currency.teacherAilities.Find(x => x.id.Equals(ab.id));
  549. if (x == null || !ab.uploads.Any())
  550. {
  551. t53113OK = 0;
  552. msgs.Add(new CodeValue("uploads", $"未上传认证材料:{x.no},{x.name}"));
  553. }
  554. if (x.zpscore <= 0)
  555. {
  556. t53113OK = 0;
  557. msgs.Add(new CodeValue("zpscore", $"认证材料,没有完成自评:{x.no},{x.name},{x.zpscore}"));
  558. }
  559. if (x.hpscore <= 0)
  560. {
  561. t53113OK = 0;
  562. //如果只有三个,且互评为未评状态,则直接为合格。
  563. x.hpscore = 1;
  564. msgs.Add(new CodeValue("hpscore", $"认证材料,没有完成互评:{x.no},{x.name},{x.hpscore}"));
  565. }
  566. if (x.xzscore <= 0)
  567. {
  568. t53113OK = 0;
  569. msgs.Add(new CodeValue("xzscore", $"认证材料,没有完成小组评:{x.no},{x.name},{x.xzscore}"));
  570. }
  571. });
  572. }
  573. ///如果上传的文件,失效导致不满足3个能力点
  574. List<CodeValue> un_msg = new List<CodeValue>();
  575. //检查已经上传的文件是否正确。
  576. foreach (AbilitySub abilitySub in uploaded)
  577. {
  578. //当前能力点上传的文件是否完全有效
  579. bool isAllRight = true;
  580. List<string> urlUn = new List<string>();
  581. foreach (var subUpload in abilitySub.uploads)
  582. {
  583. foreach (var url in subUpload.urls)
  584. {
  585. string blobItem = url.url.Replace($"{schoolPrefix}/", "");
  586. bool Exist = await _azureStorage.GetBlobContainerClient(school).GetBlobClient(blobItem).ExistsAsync();
  587. if (!Exist)
  588. {
  589. isAllRight = false;
  590. urlUn.Add($"{url.url}?{sas}");
  591. }
  592. }
  593. }
  594. if (isAllRight)
  595. {
  596. allRightAbility.Add(abilitySub);
  597. }
  598. else
  599. {
  600. var x = teacherTrain.currency.teacherAilities.Find(x => x.id.Equals(abilitySub.id));
  601. un_msg.Add(new CodeValue("uploads-url", $"{x.no},{x.name}上传的认证材料文件失效:{string.Join(" , ", urlUn)}"));
  602. }
  603. }
  604. if (allRightAbility.Count < 3)
  605. {
  606. t53113OK = 0;
  607. msgs.AddRange(un_msg);
  608. }
  609. }
  610. }
  611. }
  612. }
  613. else
  614. {
  615. msgs.Add(new CodeValue("teacherAilities", $"未同步省平台挑选的能力点"));
  616. t53113OK = 0;
  617. }
  618. }
  619. else
  620. {
  621. msgs.Add(new CodeValue("teacherAilities", $"未同步省平台挑选的能力点"));
  622. t53113OK = 0;
  623. }
  624. }
  625. else
  626. {
  627. msgs.Add(new CodeValue("teacherAilities", $"未同步省平台挑选的能力点"));
  628. t53113OK = 0;
  629. }
  630. }
  631. catch (Exception ex)
  632. {
  633. throw new Exception($"{ex.StackTrace},{ex.Message}");
  634. }
  635. if (allRightAbility.Count < 3)
  636. {
  637. t53113OK = 0;
  638. msgs.Add(new CodeValue("uploads-count", $"完整上传且有效的认证材料的 能力点数量小于3,当前数量:{allRightAbility.Count}"));
  639. }
  640. return (t53113OK, msgs, abilitySubs, allRightAbility);
  641. }
  642. //推送作答数据
  643. public static async Task<(string id, int code)> pushAnswers(CosmosClient client, AzureStorageFactory _azureStorage, IHttpClientFactory _httpClient, string activityId, string code)
  644. {
  645. if (string.IsNullOrEmpty(activityId))
  646. {
  647. try
  648. {
  649. ExamInfo info = null;
  650. var response = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemStreamAsync(activityId.ToString(), new PartitionKey($"Exam-{code}"));
  651. if (response.Status == 200)
  652. {
  653. using var json = await JsonDocument.ParseAsync(response.Content);
  654. info = json.ToObject<ExamInfo>();
  655. }
  656. List<ExamClassResult> classResults = new();
  657. if (info.scope.Equals("school", StringComparison.OrdinalIgnoreCase))
  658. {
  659. var queryResult = $"select value(c) where c.examId ='{activityId}' and c.pk = 'ExamClassResult' ";
  660. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<ExamClassResult>(queryText: queryResult,
  661. requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{info.school}") }))
  662. {
  663. classResults.Add(item);
  664. }
  665. }
  666. else
  667. {
  668. var queryResult = $"select value(c) where c.examId ='{activityId}' and c.pk = 'ExamClassResult' ";
  669. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<ExamClassResult>(queryText: queryResult,
  670. requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{code}") }))
  671. {
  672. classResults.Add(item);
  673. }
  674. }
  675. //如果单个活动包含多个科目
  676. var (grade, per) = await GetGradeAsync(client, info);
  677. var (currSemester, studyYear, currSemesterDate, date, nextSemester) = SchoolService.GetSemester(per, info.startTime);
  678. int no = 0;
  679. foreach (var subject in info.subjects)
  680. {
  681. //获取试题详细信息
  682. BlobDownloadResult index_json;
  683. if (info.scope.Equals("school"))
  684. {
  685. index_json = await _azureStorage.GetBlobContainerClient($"{info.school}").GetBlobClient($"{info.papers[no].blob}/index.json").DownloadContentAsync();
  686. }
  687. else
  688. {
  689. index_json = await _azureStorage.GetBlobContainerClient($"{info.creatorId}").GetBlobClient($"{info.papers[no].blob}/index.json").DownloadContentAsync();
  690. }
  691. JsonElement RecordingJson = JsonDocument.Parse(new MemoryStream(Encoding.UTF8.GetBytes(index_json.Content.ToString()))).RootElement;
  692. RecordingJson.TryGetProperty("slides", out JsonElement slides);
  693. var sdes = slides.ToObject<List<Slides>>();
  694. List<string> urls = new();
  695. foreach (var ne in sdes)
  696. {
  697. if (!ne.type.Equals("compose"))
  698. {
  699. urls.Add(ne.url);
  700. }
  701. }
  702. // 获取整体的题目ID集合
  703. List<string> ids = new();
  704. List<(string id, string type, double score, int difficulty, string choices, string answer)> itemInfos = new();
  705. int index = 1;
  706. foreach (string url in urls)
  707. {
  708. string id = url.Replace(".json", "");
  709. BlobDownloadResult index_item_json;
  710. if (info.scope.Equals("school"))
  711. {
  712. index_item_json = await _azureStorage.GetBlobContainerClient($"{info.school}").GetBlobClient($"{info.papers[no].blob}/{url}").DownloadContentAsync();
  713. }
  714. else
  715. {
  716. index_item_json = await _azureStorage.GetBlobContainerClient($"{info.creatorId}").GetBlobClient($"{info.papers[no].blob}/{url}").DownloadContentAsync();
  717. }
  718. JsonElement itemJson = JsonDocument.Parse(new MemoryStream(Encoding.UTF8.GetBytes(index_item_json.Content.ToString()))).RootElement;
  719. itemJson.TryGetProperty("exercise", out JsonElement exercise);
  720. var item_json = exercise.ToObject<Exercise>();
  721. string type = item_json.type;
  722. int level = item_json.level;
  723. double score = item_json.score;
  724. var ans = item_json.answer;
  725. if (itemJson.TryGetProperty("item", out JsonElement item))
  726. {
  727. var itemInfo_json = item.ToObject<List<itemInfo>>();
  728. StringBuilder sb = new();
  729. StringBuilder an = new();
  730. itemInfo_json.FirstOrDefault().option.Select(c => c.code).ToList().ForEach(z =>
  731. {
  732. sb.Append(z);
  733. });
  734. ans.ForEach(z =>
  735. {
  736. an.Append(z);
  737. });
  738. itemInfos.Add((index.ToString(), type, score, level, sb.ToString(), an.ToString()));
  739. }
  740. index++;
  741. }
  742. //学生作答信息
  743. List<(string stuId, List<(string questionNo, double score, string answer)> ansDt)> stuAns = new();
  744. foreach (ExamClassResult classResult in classResults)
  745. {
  746. if (classResult.subjectId.Equals(subject.id)) {
  747. int stuCount = 0;
  748. foreach (var stu in classResult.studentIds)
  749. {
  750. int itemCount = 0;
  751. List<(string questionNo, double score, string answer)> ansDt = new();
  752. foreach (var itemNo in classResult.studentAnswers[stuCount])
  753. {
  754. ansDt.Add(((itemCount + 1).ToString(), classResult.studentScores[stuCount][itemCount], itemNo));
  755. itemCount++;
  756. }
  757. stuAns.Add((stu, ansDt));
  758. stuCount++;
  759. }
  760. }
  761. }
  762. var jsonString = new
  763. {
  764. examDate = info.startTime,
  765. //todo 不同学段年级数量不一致
  766. grade,
  767. subName = subject.name,
  768. totalScore = info.papers[no].point.Sum(),
  769. termCode = currSemester.start == 1 ? 1 : 2,
  770. data = new
  771. {
  772. items = itemInfos.Select(c => new
  773. {
  774. questionNo = c.id,
  775. c.type,
  776. c.score,
  777. c.difficulty,
  778. c.choices,
  779. c.answer
  780. }),
  781. stuDate = stuAns.Select(c => new
  782. {
  783. stuNo = c.stuId,
  784. scoreData = c.ansDt.Select(z => new
  785. {
  786. z.questionNo,
  787. z.score,
  788. z.answer
  789. })
  790. })
  791. }
  792. };
  793. var key = Md5Hash.GetMd5String("TMD" + info.school);
  794. string connect = $"http://www.moofen.net/esi/tmd/exam/{info.school}/{key}";
  795. var htc = _httpClient.CreateClient();
  796. string paramJson = JsonConvert.SerializeObject(jsonString);
  797. var content = new StringContent(paramJson, Encoding.UTF8, "application/json");
  798. var ansResponse = await htc.PostAsync(connect, content);
  799. if ((int)ansResponse.StatusCode == 200)
  800. {
  801. return (activityId, 200);
  802. }
  803. no++;
  804. }
  805. }
  806. catch (CosmosException)
  807. {
  808. return (activityId, 500);
  809. }
  810. }
  811. return (activityId, 404);
  812. }
  813. public static async Task<(string gId, Period per)> GetGradeAsync(CosmosClient client, ExamInfo info)
  814. {
  815. if (info.grades.Count > 0)
  816. {
  817. var schresponse = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(info.school, new PartitionKey("Base"));
  818. string grade = "";
  819. School sc = new();
  820. if (schresponse.Status == 200)
  821. {
  822. using var schjson = await JsonDocument.ParseAsync(schresponse.Content);
  823. sc = schjson.ToObject<School>();
  824. }
  825. var period = sc.period.Where(x => x.id.Equals(info.period.id)).FirstOrDefault();
  826. var pType = sc.period.FirstOrDefault(c => c.id.Equals(info.period.id)).periodType;
  827. //int pcount = (int)(sc.period.Where(c => c.periodType.Equals("primary")).FirstOrDefault()?.grades.Count);
  828. //int jcount = (int)(sc.period.Where(c => c.periodType.Equals("junior")).FirstOrDefault()?.grades.Count);
  829. if (pType.Equals("primary"))
  830. {
  831. grade = (int.Parse(info.grades.FirstOrDefault().id) + 1).ToString();
  832. }
  833. else if (pType.Equals("junior"))
  834. {
  835. grade = (int.Parse(info.grades.FirstOrDefault().id) + 7).ToString();
  836. }
  837. else
  838. {
  839. grade = (int.Parse(info.grades.FirstOrDefault().id) + 10).ToString();
  840. }
  841. return (grade, period);
  842. }
  843. else
  844. {
  845. return ("", new Period());
  846. }
  847. }
  848. public static async Task<string> CreateMoofenExam(IHttpClientFactory _httpClient, JsonElement json,DingDing _ding)
  849. {
  850. string content = string.Empty;
  851. try {
  852. Dictionary<string, string> dict = new Dictionary<string, string>();
  853. if (json.TryGetProperty("examName", out JsonElement examName) && !string.IsNullOrWhiteSpace($"{examName}"))
  854. {
  855. dict.Add("examName", $"{examName}");
  856. }
  857. if (json.TryGetProperty("examCategory", out JsonElement examCategory) && !string.IsNullOrWhiteSpace($"{examCategory}"))
  858. {
  859. dict.Add("examCategory", $"{examCategory}");
  860. }
  861. if (json.TryGetProperty("grade", out JsonElement grade) && !string.IsNullOrWhiteSpace($"{grade}"))
  862. {
  863. dict.Add("grade", $"{grade}");
  864. }
  865. if (json.TryGetProperty("term", out JsonElement term) && !string.IsNullOrWhiteSpace($"{term}"))
  866. {
  867. dict.Add("term", $"{term}");
  868. }
  869. if (json.TryGetProperty("examType", out JsonElement examType) && !string.IsNullOrWhiteSpace($"{examType}"))
  870. {
  871. dict.Add("examType", $"{examType}");
  872. }
  873. if (json.TryGetProperty("examDate", out JsonElement examDate) && !string.IsNullOrWhiteSpace($"{examDate}"))
  874. {
  875. dict.Add("examDate", $"{examDate}");
  876. }
  877. if (json.TryGetProperty("schId", out JsonElement schId) && !string.IsNullOrWhiteSpace($"{schId}"))
  878. {
  879. dict.Add("schId", $"{schId}");
  880. }
  881. if (json.TryGetProperty("boeId", out JsonElement boeId) && !string.IsNullOrWhiteSpace($"{boeId}"))
  882. {
  883. dict.Add("boeId", $"{boeId}");
  884. }
  885. if (json.TryGetProperty("subjects", out JsonElement subjects) && subjects.ValueKind.Equals(JsonValueKind.Array))
  886. {
  887. dict.Add("subjects", $"{subjects}");
  888. }
  889. dict.Add("signKey", "TMD");
  890. var keys = dict.Keys.OrderBy(x => x);
  891. var parmas = string.Join("&", keys.Select(x => $"{x}={dict[x]}"));
  892. //var signtime = DateTimeOffset.UtcNow.GetGMTTime(8).ToUnixTimeSeconds();
  893. //parmas = $"{parmas}&signtime={signtime}";
  894. string sign = Md5Hash.Encrypt(parmas);
  895. dict.Add("sign", $"{sign}");
  896. dict.Remove("signKey");
  897. //var pkeys = dict.Keys;
  898. string url = "https://www.moofen.net/oss/esi/exam/tmodel/create";
  899. //parmas = string.Join("&", pkeys.Select(x => $"{x}={dict[x]}"));
  900. var httpClient = _httpClient.CreateClient();
  901. var request = new HttpRequestMessage
  902. {
  903. Method = new HttpMethod("POST"),
  904. RequestUri = new Uri(url),
  905. Content = new StringContent(JsonConvert.SerializeObject(dict))
  906. };
  907. // 设置请求头中的Content-Type
  908. // httpClient.DefaultRequestHeaders.Add("Content-Type", "application/x-www-form-urlencoded");
  909. var mediaTypeHeader = new MediaTypeHeaderValue("application/json")
  910. {
  911. CharSet = "UTF-8"
  912. };
  913. httpClient.Timeout = new TimeSpan(0,0,10);
  914. request.Content.Headers.ContentType = mediaTypeHeader;
  915. HttpResponseMessage response = await _httpClient.CreateClient().SendAsync(request);
  916. if (response.StatusCode.Equals(HttpStatusCode.OK)) {
  917. content = await response.Content.ReadAsStringAsync();
  918. var data = content.ToObject<JsonElement>();
  919. if (data.TryGetProperty("data", out JsonElement _data)) {
  920. return _data.ToString();
  921. }
  922. }
  923. } catch (Exception e) {
  924. await _ding.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")},ThirdService/CreateMoofenExam()\n{e.Message}\n{e.StackTrace}", GroupNames.成都开发測試群組);
  925. }
  926. return "";
  927. }
  928. public static async Task<List<stuAns>> CreateMoofenExamResult(IHttpClientFactory _httpClient, JsonElement json, DingDing _ding)
  929. {
  930. string content = string.Empty;
  931. List<stuAns> ans = new();
  932. try
  933. {
  934. Dictionary<string, string> dict = new Dictionary<string, string>();
  935. if (json.TryGetProperty("examCode", out JsonElement examCode) && !string.IsNullOrWhiteSpace($"{examCode}"))
  936. {
  937. dict.Add("examCode", $"{examCode}");
  938. }
  939. if (json.TryGetProperty("subjects", out JsonElement subjects) && subjects.ValueKind.Equals(JsonValueKind.Array))
  940. {
  941. dict.Add("subjects", $"{subjects}");
  942. }
  943. dict.Add("signKey", "TMD");
  944. var keys = dict.Keys.OrderBy(x => x);
  945. var parmas = string.Join("&", keys.Select(x => $"{x}={dict[x]}"));
  946. //var signtime = DateTimeOffset.UtcNow.GetGMTTime(8).ToUnixTimeSeconds();
  947. //parmas = $"{parmas}&signtime={signtime}";
  948. string sign = Md5Hash.Encrypt(parmas);
  949. dict.Add("sign", $"{sign}");
  950. dict.Remove("signKey");
  951. //var pkeys = dict.Keys;
  952. string url = "https://www.moofen.net/oss/esi/exam/tmodel/result";
  953. //parmas = string.Join("&", pkeys.Select(x => $"{x}={dict[x]}"));
  954. var httpClient = _httpClient.CreateClient();
  955. var request = new HttpRequestMessage
  956. {
  957. Method = new HttpMethod("POST"),
  958. RequestUri = new Uri(url),
  959. Content = new StringContent(JsonConvert.SerializeObject(dict))
  960. };
  961. // 设置请求头中的Content-Type
  962. // httpClient.DefaultRequestHeaders.Add("Content-Type", "application/x-www-form-urlencoded");
  963. var mediaTypeHeader = new MediaTypeHeaderValue("application/json")
  964. {
  965. CharSet = "UTF-8"
  966. };
  967. httpClient.Timeout = new TimeSpan(0, 0, 10);
  968. request.Content.Headers.ContentType = mediaTypeHeader;
  969. HttpResponseMessage response = await _httpClient.CreateClient().SendAsync(request);
  970. if (response.StatusCode.Equals(HttpStatusCode.OK))
  971. {
  972. content = await response.Content.ReadAsStringAsync();
  973. var data = content.ToObject<JsonElement>();
  974. if (data.TryGetProperty("data", out JsonElement _data))
  975. {
  976. ans = _data.ToObject<List<stuAns>>();
  977. return ans;
  978. }
  979. }
  980. }
  981. catch (Exception e)
  982. {
  983. await _ding.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")},ThirdService/CreateMoofenExamResult()\n{e.Message}\n{e.StackTrace}", GroupNames.成都开发測試群組);
  984. }
  985. return ans;
  986. }
  987. /* private class item
  988. {
  989. public string questionNo { get; set; }
  990. public string type { get; set; }
  991. public double score { get; set; }
  992. public int difficulty { get; set; }
  993. public string choices { get; set; }
  994. public string answer { get; set; }
  995. }*/
  996. private class itemInfo
  997. {
  998. public List<opt> option { get; set; } = new();
  999. }
  1000. private class opt
  1001. {
  1002. public string code { get; set; }
  1003. public double value { get; set; }
  1004. }
  1005. public class stuAns {
  1006. public string stuNo { get; set; }
  1007. public List<moofenAns> scoreData { get; set; } = new List<moofenAns>();
  1008. public string subject { get; set; }
  1009. }
  1010. public class moofenAns
  1011. {
  1012. public string questionNo { get; set; }
  1013. public double score { get; set; }
  1014. public string answer { get; set; }
  1015. }
  1016. }
  1017. }