StudentController.cs 99 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Dynamic;
  5. using System.IdentityModel.Tokens.Jwt;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Http.Headers;
  11. using System.Net.Http.Json;
  12. using System.Text;
  13. using System.Text.Json;
  14. using System.Threading.Tasks;
  15. using Azure;
  16. using Azure.Cosmos;
  17. using Azure.Messaging.ServiceBus;
  18. using Azure.Storage.Sas;
  19. using HTEXLib.COMM.Helpers;
  20. using Microsoft.AspNetCore.Authorization;
  21. using Microsoft.AspNetCore.Cryptography.KeyDerivation;
  22. using Microsoft.AspNetCore.Hosting;
  23. using Microsoft.AspNetCore.Http;
  24. using Microsoft.AspNetCore.Mvc;
  25. using Microsoft.Azure.Amqp.Framing;
  26. using Microsoft.Extensions.Configuration;
  27. using Microsoft.Extensions.Hosting;
  28. using Microsoft.Extensions.Options;
  29. using Microsoft.Identity.Client;
  30. using Org.BouncyCastle.Asn1.Tsp;
  31. using StackExchange.Redis;
  32. using TEAMModelOS.Filter;
  33. using TEAMModelOS.Models;
  34. using TEAMModelOS.SDK;
  35. using TEAMModelOS.SDK.DI;
  36. using TEAMModelOS.SDK.Extension;
  37. using TEAMModelOS.SDK.Models;
  38. using TEAMModelOS.SDK.Models.Cosmos;
  39. using TEAMModelOS.SDK.Models.Cosmos.Common;
  40. using TEAMModelOS.SDK.Models.Service;
  41. using static TEAMModelOS.SDK.Services.BlobService;
  42. using static TEAMModelOS.SDK.StudentService;
  43. namespace TEAMModelOS.Controllers
  44. {
  45. [Route("student")]
  46. [ApiController]
  47. public class StudentController : Controller
  48. {
  49. private readonly AzureCosmosFactory _azureCosmos;
  50. private readonly AzureStorageFactory _azureStorage;
  51. private readonly AzureRedisFactory _azureRedis;
  52. private readonly DingDing _dingDing;
  53. private readonly Option _option;
  54. private readonly IPSearcher _searcher;
  55. private readonly HttpTrigger _httpTrigger;
  56. private readonly CoreAPIHttpService _coreAPIHttpService;
  57. private readonly IWebHostEnvironment _environment;
  58. public IConfiguration _configuration { get; set; }
  59. private readonly AzureServiceBusFactory _serviceBus;
  60. private readonly IHttpClientFactory _httpClient;
  61. public StudentController(IWebHostEnvironment environment, CoreAPIHttpService coreAPIHttpService, AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IPSearcher searcher, IOptionsSnapshot<Option> option,IConfiguration configuration, AzureServiceBusFactory serviceBus, HttpTrigger httpTrigger, IHttpClientFactory httpClient
  62. )
  63. {
  64. _searcher = searcher;
  65. _azureCosmos = azureCosmos;
  66. _azureStorage = azureStorage;
  67. _azureRedis = azureRedis;
  68. _dingDing = dingDing;
  69. _option = option?.Value;
  70. _configuration = configuration;
  71. _serviceBus = serviceBus;
  72. _httpTrigger = httpTrigger;
  73. _coreAPIHttpService = coreAPIHttpService;
  74. _environment = environment;
  75. _httpClient = httpClient;
  76. }
  77. /// <summary>
  78. /// 學生帳號管理
  79. /// </summary>
  80. /// <param name="request"></param>
  81. /// <returns></returns>
  82. [HttpPost("student-manage")]
  83. [Authorize(Roles = "IES")]
  84. [AuthToken(Roles = "admin,student,teacher")]
  85. public async Task<IActionResult> StudentManage(JsonElement request)
  86. {
  87. try
  88. {
  89. //TODO : 權限檢查、學校檢查。
  90. if (!request.TryGetProperty("grant_type", out JsonElement grant_type) || !request.TryGetProperty("schoolId", out JsonElement schoolId)) return BadRequest();
  91. List<Student> preStudents = null;
  92. List<Student> webStudents = null;
  93. var (stuid, _, _, school) = HttpContext.GetAuthTokenInfo();
  94. switch (grant_type.GetString())
  95. {
  96. case "create":
  97. //單人創建 創建學生->將學生加入教室 檢查學生ID是否重複,欲加入的班級存不存在,座號是否重複。
  98. //id pw name classId no year
  99. //retrun 如果有重複則回{ existNo } , 成功則{ id, name, year, classId, no }
  100. var importStuds = request.GetProperty("students").EnumerateArray();
  101. Dictionary<string, GroupChange> dictChange = new Dictionary<string, GroupChange>();
  102. while (importStuds.MoveNext())
  103. {
  104. JsonElement currStud = importStuds.Current;
  105. string id = null, name = null, pw = null, no = null, classId = null, periodId = null, imei = null , gender= null;
  106. List<StudentGuardian> guardians = null;
  107. int year = 0;
  108. //讀取輸入的資料
  109. if (!currStud.TryGetProperty("id", out var tmpId) || !currStud.TryGetProperty("name", out var tmpName)) continue;
  110. id = tmpId.GetString();
  111. name = tmpName.GetString();
  112. if (currStud.TryGetProperty("pw", out var tmpPw)) pw = tmpPw.GetString();
  113. if (currStud.TryGetProperty("year", out var tmpYear)) year = tmpYear.GetInt32();
  114. if (currStud.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
  115. if (currStud.TryGetProperty("classId", out var tmpClassId)) classId = tmpClassId.GetString();
  116. if (currStud.TryGetProperty("imei", out var tmpImei)) imei = $"{tmpImei}";
  117. if (currStud.TryGetProperty("gender", out var tmpGender)) gender = $"{tmpGender}";
  118. if (currStud.TryGetProperty("guardians", out var tmpGuardians)) {
  119. guardians = tmpGuardians.Deserialize<List<StudentGuardian>>();
  120. }
  121. //要檢查座號使否已被使用
  122. var existNo = await StudentService.checkStudNo(_azureCosmos, _dingDing, _option, schoolId.GetString(), classId, new List<string>() { id });
  123. if (existNo.Count != 0) return this.Ok(new { code = $"Base-{schoolId.GetString()}", existNo = existNo.Select(o => o.id).ToList() });
  124. if (currStud.TryGetProperty("periodId", out var tempPeriodId)) periodId = tempPeriodId.GetString();
  125. //建立學生
  126. studCreateInfo studCreateInfo = new studCreateInfo(id, name, gender, year, pw, classId, no, periodId,imei,guardians);
  127. var isCreateSuc = await createStudent(_azureCosmos, _dingDing, _option, schoolId.GetString(), studCreateInfo);
  128. if (isCreateSuc)
  129. {
  130. if (dictChange.ContainsKey(classId))
  131. {
  132. dictChange[classId].stujoin.Add(
  133. new Member
  134. {
  135. id = id,
  136. code = $"{schoolId.GetString()}",
  137. type = 2,
  138. });
  139. }
  140. else
  141. {
  142. Class clazz =await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<Class>(classId, new PartitionKey($"Class-{schoolId}"));
  143. GroupChange change = new GroupChange
  144. {
  145. client="web",
  146. name=clazz.name,
  147. scope = "school",
  148. school = schoolId.GetString(),
  149. type = "student",
  150. originCode = schoolId.GetString(),
  151. listid = classId,
  152. stujoin = new List<Member>
  153. {
  154. new Member
  155. {
  156. id= id,
  157. code=schoolId.GetString(),
  158. type=2,
  159. }
  160. }
  161. };
  162. dictChange.Add(classId, change);
  163. }
  164. foreach (var changed in dictChange.Keys)
  165. {
  166. var change = dictChange[changed];
  167. if (change.stujoin.Count != 0 || change.stuleave.Count != 0)
  168. {
  169. var messageChange = new ServiceBusMessage(change.ToJsonString());
  170. messageChange.ApplicationProperties.Add("name", "GroupChange");
  171. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  172. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChange);
  173. }
  174. }
  175. return this.Ok(new { code = $"Base-{schoolId.GetString()}", id, name, year, classId, no, periodId });
  176. }
  177. else return this.Ok(new { code = $"Base-{schoolId.GetString()}", errorId = id });
  178. }
  179. break;
  180. case "import":
  181. //只有ClassNo可以比對
  182. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  183. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  184. var retUpsert = await StudentService.upsertStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  185. await CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  186. School school_base = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{schoolId}", new PartitionKey("Base"));
  187. //实时处理导入毕业的班级。
  188. await SchoolService.DoGraduateClasses(_httpTrigger, _azureCosmos, null, school_base, _option,_dingDing);
  189. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpsert.studs, retUpsert.classDuplNos, retUpsert.errorIds });
  190. case "read":
  191. //增加毕业查询。当毕业查询字段1时,则需要传入入学年份。
  192. int graduate = 0;
  193. int inyear = 0;
  194. //讀取該間學校所有的學生資訊
  195. if (request.TryGetProperty("graduate", out JsonElement _graduate) && $"{_graduate}".Equals("1")) {
  196. if (request.TryGetProperty("year", out JsonElement _year) && _year.ValueKind.Equals(JsonValueKind.Number))
  197. {
  198. if (int.TryParse($"{_year}", out inyear) && inyear > 0)
  199. {
  200. graduate = 1;
  201. }
  202. else {
  203. return BadRequest("入学年份大于0");
  204. }
  205. }
  206. else {
  207. return BadRequest("请输入毕业学生的入学年份");
  208. }
  209. }
  210. var students = await StudentService.getAllStudent(_azureCosmos, _dingDing, _option, schoolId.GetString(), inyear, graduate);
  211. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students });
  212. case "update":
  213. //更新學生資料,批量密碼重置,基本資訊更新(姓名、教室ID、性別、學年及座號)
  214. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  215. //var cleanImei = false;
  216. // request.TryGetProperty("cleanImei",out JsonElement _cleanImei);
  217. //if (_cleanImei.ValueKind.Equals(JsonValueKind.True))
  218. //{
  219. // cleanImei = true;
  220. //}
  221. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  222. var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  223. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  224. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpdate.studs, retUpdate.classDuplNos, retUpdate.nonexistentIds, retUpdate.errorNos, retUpdate.errorClassId });
  225. case "delete":
  226. //刪除學生資料及從教室學生名單內移除該學生
  227. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  228. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  229. var sucDelIds = await StudentService.deleteStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  230. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  231. return this.Ok(new { code = $"Base-{schoolId.GetString()}", ids = sucDelIds });
  232. case "remove":
  233. //將學生基本資料內的classId、no、groupId及groupName寫入null
  234. List<string> stus = request.GetProperty("students").ToObject<List<string>>();
  235. webStudents = new List<Student>();
  236. foreach (string idstu in stus)
  237. {
  238. webStudents.Add(new Student { id = idstu, code = $"Base-{schoolId}" });
  239. }
  240. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  241. (List<string> studs, List<string> nonexistentIds, List<string> errorIds) retRemove = await StudentService.removeStudentClassInfo(
  242. _azureCosmos, _dingDing, _option,
  243. schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  244. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  245. return Ok(new { code = $"Base-{schoolId.GetString()}", ids = retRemove.studs, retRemove.nonexistentIds, retRemove.errorIds });
  246. case "avatar":
  247. if (request.TryGetProperty("avatar", out JsonElement _avatar) && _avatar.ValueKind.Equals(JsonValueKind.Array))
  248. {
  249. List<StudentInfo> avatars = _avatar.ToObject<List<StudentInfo>>();
  250. if (avatars.IsNotEmpty())
  251. {
  252. List<Student> studentsp = new List<Student>();
  253. string insql = string.Join(',', avatars.Select(x => $"'{x.studentId}'"));
  254. string sql = $"select value(c) from c where c.id in ({insql}) ";
  255. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  256. .GetItemQueryIterator<Student>(sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
  257. {
  258. studentsp.Add(item);
  259. }
  260. (string url, string sas) = _azureStorage.GetBlobContainerSAS99Year($"{schoolId}", BlobContainerSasPermissions.Read);
  261. foreach (Student student in studentsp)
  262. {
  263. StudentInfo avatar = avatars.Find(x => x.studentId.Equals(student.id));
  264. student.picture = avatar != null ? $"{avatar.picture}?{sas}" : null;
  265. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").ReplaceItemAsync<Student>(student, student.id, new PartitionKey(student.code));
  266. }
  267. return Ok(new { students = studentsp.Select(x => new { x.id, x.picture, x.code, x.name }) });
  268. }
  269. else
  270. {
  271. return BadRequest();
  272. }
  273. }
  274. else
  275. {
  276. return BadRequest();
  277. }
  278. case "update-self-info":
  279. if (request.TryGetProperty("studentInfo", out JsonElement _studentInfo))
  280. {
  281. var studentInfo = _studentInfo.ToObject<StudentInfo>();
  282. if (studentInfo.studentId.Equals(stuid) && school.Equals($"{schoolId}"))
  283. {
  284. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  285. .ReadItemAsync<Student>(studentInfo.studentId, new PartitionKey($"Base-{schoolId}"));
  286. student.mail = string.IsNullOrEmpty(studentInfo.mail) ? student.mail : studentInfo.mail;
  287. student.mobile = string.IsNullOrEmpty(studentInfo.mobile) ? student.mobile : studentInfo.mobile;
  288. student.name = string.IsNullOrEmpty(studentInfo.name) ? student.name : studentInfo.name;
  289. (string url, string sas) = _azureStorage.GetBlobContainerSAS99Year($"{schoolId}", BlobContainerSasPermissions.Read);
  290. student.picture = string.IsNullOrEmpty(studentInfo.picture) ? student.picture : $"{studentInfo.picture}?{sas}";
  291. student.gender = string.IsNullOrEmpty(studentInfo.gender) ? student.gender : studentInfo.gender;
  292. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  293. .ReplaceItemAsync<Student>(student, studentInfo.studentId, new PartitionKey($"Base-{schoolId}"));
  294. return Ok(new { studentInfo });
  295. }
  296. else
  297. {
  298. return Ok(new { error = false, status = false, msg = "修改的不是自己的信息" });
  299. }
  300. }
  301. else
  302. {
  303. return BadRequest();
  304. }
  305. case "update-self-password":
  306. if (request.TryGetProperty("newpwd", out JsonElement _newpwd) &&
  307. request.TryGetProperty("oldpwd", out JsonElement _oldpwd) &&
  308. request.TryGetProperty("studentId", out JsonElement _studentId))
  309. {
  310. if ($"{_studentId}".Equals(stuid) && school.Equals($"{schoolId}"))
  311. {
  312. try
  313. {
  314. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  315. .ReadItemAsync<Student>($"{_studentId}", new PartitionKey($"Base-{schoolId}"));
  316. var HashedPW = Utils.HashedPassword($"{_oldpwd}", student.salt);
  317. if (HashedPW.Equals(student.pw))
  318. {
  319. student.pw = Utils.HashedPassword($"{_newpwd}", student.salt);
  320. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  321. .ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"Base-{schoolId}"));
  322. return Ok(new { status = true });
  323. }
  324. else
  325. {
  326. return Ok(new { error = false, status = false, msg = "密码不一致" });
  327. }
  328. }
  329. catch (Exception ex)
  330. {
  331. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  332. return Ok(new { error = false, status = false, msg = "账号不存在" });
  333. }
  334. }
  335. else
  336. {
  337. return Ok(new { error = false, status = false, msg = "修改的不是自己的密码" });
  338. }
  339. }
  340. else
  341. {
  342. return BadRequest();
  343. }
  344. case "read-self-info":
  345. if (request.TryGetProperty("studentId", out JsonElement __studentId))
  346. {
  347. try
  348. {
  349. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  350. .ReadItemAsync<Student>($"{__studentId}", new PartitionKey($"Base-{schoolId}"));
  351. return Ok(new { student.mail, student.mobile, student.name, student.picture, student.gender, student.id, student.schoolId, student.year, student.no, student.classId, student.periodId, irs = student.irs });
  352. }
  353. catch (Exception ex)
  354. {
  355. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  356. }
  357. return Ok();
  358. }
  359. else
  360. {
  361. return BadRequest();
  362. }
  363. case "unbind":
  364. List<string> ubStuIdList = new List<string>();
  365. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  366. string openType = (request.TryGetProperty("type", out JsonElement _type)) ? _type.GetString().ToLower() : "educloudtwl"; //educloudtwl: 教育雲
  367. var httpClient = _httpClient.CreateClient();
  368. string AccessToken = await getCoreAccessToken();
  369. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  370. string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  371. string csv2Url = $"{csv2Domain}/oauth2/EduCloudTWBingManage";
  372. var client = _azureCosmos.GetCosmosClient();
  373. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  374. foreach (Student studata in webStudents)
  375. {
  376. string stuId = studata.id;
  377. //取得學生基本資料
  378. Student student = new Student();
  379. try
  380. {
  381. student = await studentClient.ReadItemAsync<Student>($"{stuId}", new PartitionKey($"Base-{schoolId}"));
  382. }
  383. catch (CosmosException ex)
  384. {
  385. continue;
  386. }
  387. //CSV解綁
  388. bool rmvSuccess = false;
  389. if (openType.Equals("educloudtwl")) //教育雲
  390. {
  391. string openId = student.openId;
  392. if (string.IsNullOrWhiteSpace(openId)) //學生無OpenID,視為解綁成功
  393. {
  394. continue;
  395. }
  396. stuOpenData openData = new stuOpenData();
  397. Dictionary<string, object> dict = new() {
  398. { "grant_type", "unbind" },
  399. { "open_id", openId },
  400. { "id", $"Base-{schoolId},{student.id}" }
  401. };
  402. HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  403. HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  404. if (httpResponse.StatusCode == HttpStatusCode.OK)
  405. {
  406. string responseContent = await httpResponse.Content.ReadAsStringAsync();
  407. if (string.IsNullOrWhiteSpace(responseContent))
  408. {
  409. rmvSuccess = true;
  410. }
  411. else
  412. {
  413. csApiResponse csResult = responseContent.ToObject<csApiResponse>();
  414. if (!string.IsNullOrWhiteSpace(csResult.message))
  415. {
  416. continue;
  417. //return Ok(new { error = csResult.error, message = csResult.message });
  418. }
  419. }
  420. }
  421. else
  422. {
  423. continue;
  424. //return Ok(new { error = 1, message = "Can not get opendata from CS." });
  425. }
  426. }
  427. //學生資料變更
  428. if (rmvSuccess)
  429. {
  430. if (openType.Equals("educloudtwl")) //教育雲
  431. {
  432. student.openId = null;
  433. }
  434. //DB更新
  435. await studentClient.ReplaceItemAsync(student, student.id);
  436. ubStuIdList.Add(student.id);
  437. }
  438. }
  439. return this.Ok(new { code = $"Base-{schoolId.GetString()}", unbindIds = ubStuIdList });
  440. break;
  441. default:
  442. return BadRequest();
  443. }
  444. }
  445. catch (Exception ex)
  446. {
  447. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  448. }
  449. return BadRequest();
  450. }
  451. [HttpPost("get-student-info")]
  452. #if !DEBUG
  453. [Authorize(Roles = "IES")]
  454. #endif
  455. public async Task<IActionResult> GetStudentInfo(JsonElement request)
  456. {
  457. var client = _azureCosmos.GetCosmosClient();
  458. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  459. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  460. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  461. if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
  462. var jwt = new JwtSecurityToken(id_token.GetString());
  463. var id = jwt.Payload.Sub;
  464. string school_code = jwt.Payload.Azp;
  465. //權限token
  466. jwt.Payload.TryGetValue("name", out object _name);
  467. jwt.Payload.TryGetValue("picture", out object _picture);
  468. jwt.Payload.TryGetValue("lang", out object _lang);
  469. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  470. School school = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  471. var response = await studentClient.ReadItemStreamAsync(id, new PartitionKey($"Base-{school_code.ToLower()}"));
  472. if (response.Status == 200)
  473. {
  474. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  475. Student student = rjson.ToObject<Student>();
  476. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  477. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  478. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  479. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  480. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  481. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  482. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  483. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  484. (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school,$"{id}", $"{classId}", $"{school_code}", $"{picture}", $"{name}", schoolClient, teacherClient, school.areaId, ip, client, student);
  485. //授权规模数量
  486. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  487. var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
  488. string key = $"Login:School:{school_code}:student-day:{dateDay}";
  489. SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
  490. int countAuthorized = 0;
  491. if (countStudent != null && countStudent.Length > 0)
  492. {
  493. countAuthorized = countStudent.Length;
  494. }
  495. int scale = school.scale;
  496. if (scale<=0)
  497. {
  498. string sql = $" SELECT value s FROM c join s in c.service where c.id='{school_code}' and s.prodCode='3CLYJ6NP' ";
  499. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<SchoolProductSumDataService>(sql, "ProductSum");
  500. if (result.list.IsNotEmpty())
  501. {
  502. SchoolProductSumDataService service = result.list[0];
  503. if (service.avaliable>0)
  504. {
  505. scale= service.avaliable;
  506. }
  507. }
  508. }
  509. return Ok(new { scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
  510. }
  511. else
  512. {
  513. return Ok(new { error = 2, message = "無此帳號存在" });
  514. }
  515. }
  516. /// <summary>
  517. /// 學生登入
  518. /// </summary>
  519. /// <param name = "request" ></ param >
  520. [AllowAnonymous]
  521. [HttpPost("login")]
  522. public async Task<IActionResult> Login(JsonElement request)
  523. {
  524. try
  525. {
  526. var client = _azureCosmos.GetCosmosClient();
  527. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  528. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  529. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  530. //參數取得
  531. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  532. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  533. if (!request.TryGetProperty("pw", out JsonElement pw)) return BadRequest();
  534. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  535. School school = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  536. var response = await studentClient.ReadItemStreamAsync(id.GetString(), new PartitionKey($"Base-{school_code.GetString().ToLower()}"));
  537. if (response.Status == 200)
  538. {
  539. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  540. Student student = rjson.ToObject<Student>();
  541. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  542. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  543. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  544. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  545. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  546. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  547. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  548. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  549. rjson.RootElement.TryGetProperty("graduate", out JsonElement graduate);
  550. if (graduate.ValueKind.Equals(JsonValueKind.Number) && $"{graduate}".Equals("1")) {
  551. return Ok(new { error = 3, message = "你已毕业,暂不能登录!" });
  552. }
  553. var HashedPW = Utils.HashedPassword(pw.ToString(), salt.ToString());
  554. if (HashedPW.Equals(dbpw.GetString()))
  555. {
  556. (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school,$"{id}", $"{classId}", $"{school_code}", $"{picture}", $"{name}", schoolClient, teacherClient, school.areaId,ip, client, student);
  557. int countAuthorized = await GetStudentAuthNumByScale($"{school_code}", school);
  558. await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="web", count=1, id=school.id, key="student_login", name=school.name, scope="school", target=school.id });
  559. return Ok(new {school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
  560. }
  561. else
  562. {
  563. return Ok(new { error = 1, message = "账号或密码错误" });
  564. }
  565. }
  566. else
  567. {
  568. return Ok(new { error = 2, message = "無此帳號存在" });
  569. }
  570. }
  571. catch (Exception ex)
  572. {
  573. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/login()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  574. return BadRequest();
  575. }
  576. }
  577. private async Task<(string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token)> StudentCheck(School schoolBase,string id, string classId, string school_code, string picture, string name, CosmosContainer schoolClient, CosmosContainer teacherClient, string areaId,string ip,CosmosClient cosmosClient,Student student)
  578. {
  579. //班級課程
  580. object classinfo = null;
  581. List<object> courses = new List<object>();
  582. ////校本
  583. //取得所屬預設班級信息
  584. if (!string.IsNullOrWhiteSpace(classId))
  585. {
  586. var query = $"SELECT c.code, c.id, c.name, c.periodId, c.gradeId FROM c WHERE c.id = '{classId}'";
  587. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  588. {
  589. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  590. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  591. {
  592. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  593. {
  594. classinfo = obj.ToObject<object>();
  595. }
  596. }
  597. }
  598. }
  599. //取得該學生跑班課名單ID
  600. List<string> stulistidsSch = new List<string>();
  601. var querysl = $"SELECT c.id FROM c JOIN members IN c.members WHERE members.id = '{id}' AND members.code = '{school_code}'";
  602. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: querysl, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GroupList-{school_code}") }))
  603. {
  604. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  605. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  606. {
  607. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  608. {
  609. stulistidsSch.Add(obj.GetProperty("id").ToString());
  610. }
  611. }
  612. }
  613. //取得該學生的學校課程名單
  614. var queryc = $"SELECT DISTINCT c.id, c.name, schedule.class, schedule.time, schedule.notice, c.scope FROM c JOIN schedule IN c.schedule WHERE (schedule.class.id = '{classId}' AND schedule.stulist = null) OR (ARRAY_CONTAINS({JsonSerializer.Serialize(stulistidsSch)}, schedule.stulist, true))";
  615. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: queryc, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{school_code}") }))
  616. {
  617. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  618. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  619. {
  620. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  621. {
  622. courses.Add(obj.ToObject<object>());
  623. }
  624. }
  625. }
  626. ////個人
  627. //取得該學生跑班課名單ID
  628. Dictionary<string, Dictionary<string, string>> stulistidsTea = new Dictionary<string, Dictionary<string, string>>();
  629. var queryslt = $"SELECT c.id, c.course.id as courseId, c.course.code as courseCode FROM c JOIN members IN c.members WHERE members.id = '{id}' AND members.code = '{school_code}'";
  630. await foreach (var item in teacherClient.GetItemQueryStreamIterator(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("GroupList") }))
  631. {
  632. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  633. var js = json.RootElement.ToJsonString();
  634. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  635. {
  636. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  637. {
  638. string courseCode = "";
  639. if (obj.TryGetProperty("courseCode", out var code))
  640. {
  641. courseCode = code.GetString();
  642. }
  643. string courseId = "";
  644. if (obj.TryGetProperty("courseId", out var cosid))
  645. {
  646. courseId = cosid.GetString();
  647. }
  648. string stulistId = "";
  649. if (obj.TryGetProperty("id", out var listId))
  650. {
  651. stulistId = listId.GetString();
  652. }
  653. if (!string.IsNullOrEmpty(courseCode))
  654. {
  655. if (!stulistidsTea.ContainsKey(courseCode))
  656. {
  657. Dictionary<string, string> pCourseIdDic = new Dictionary<string, string>();
  658. pCourseIdDic.Add(courseId, stulistId);
  659. stulistidsTea.Add(courseCode, pCourseIdDic);
  660. }
  661. else
  662. {
  663. if (!stulistidsTea[courseCode].ContainsKey(courseId))
  664. {
  665. stulistidsTea[courseCode].Add(courseId, stulistId);
  666. }
  667. }
  668. }
  669. }
  670. }
  671. }
  672. //取得該學生的老師個人課程名單
  673. foreach (KeyValuePair<string, Dictionary<string, string>> item in stulistidsTea)
  674. {
  675. string courseCode = item.Key;
  676. Dictionary<string, string> courseIdDic = item.Value;
  677. string stucourseWhere = string.Empty;
  678. foreach (KeyValuePair<string, string> itemDic in courseIdDic)
  679. {
  680. string courseId = itemDic.Key;
  681. string stuListId = itemDic.Value;
  682. if (!string.IsNullOrWhiteSpace(stucourseWhere))
  683. {
  684. stucourseWhere += " OR ";
  685. }
  686. stucourseWhere += $"( c.id = '{courseId}' AND schedule.stulist = '{stuListId}' )";
  687. }
  688. var querycst = $"SELECT DISTINCT c.id, c.name, schedule.class, schedule.time, schedule.notice, c.scope FROM c JOIN schedule IN c.schedule WHERE {stucourseWhere}";
  689. await foreach (var itemcs in teacherClient.GetItemQueryStreamIterator(queryText: querycst, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{courseCode}") }))
  690. {
  691. using var json = await JsonDocument.ParseAsync(itemcs.ContentStream);
  692. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  693. {
  694. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  695. {
  696. courses.Add(obj.ToObject<object>());
  697. }
  698. }
  699. }
  700. }
  701. // BLOB(學校,唯讀)
  702. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(school_code.ToLower(), BlobContainerSasPermissions.Read);
  703. int timezone = 8;
  704. if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
  705. {
  706. timezone=tz;
  707. }
  708. if (!string.IsNullOrWhiteSpace(schoolBase.timeZone?.value))
  709. {
  710. string timeZoneOffsetString = schoolBase.timeZone.value;
  711. bool plus = true;
  712. if (timeZoneOffsetString.Contains("-"))
  713. {
  714. plus=false;
  715. }
  716. // 去除时区偏移字符串中的正负号
  717. timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
  718. // 尝试解析格式化后的时区偏移字符串
  719. if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
  720. {
  721. // 将时区偏移转换为小时数
  722. timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
  723. }
  724. }
  725. //換取AuthToken,提供給前端
  726. var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name, picture, _option.JwtSecretKey, scope: Constant.ScopeStudent, Website: "IES", timezone: timezone, areaId: areaId, schoolID: school_code, roles: new[] { "student" }, expire: 4,year: student.year);
  727. //用户在线记录
  728. //try
  729. //{
  730. // _ = _httpTrigger.RequestHttpTrigger(new { school = school_code, scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  731. //}
  732. //catch {}
  733. await cosmosClient.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, id, new PartitionKey($"Base-{school_code}"));
  734. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  735. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  736. var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, _option.Location.Replace("-Dep", "").Replace("-Test", ""));
  737. return (auth_token, blob_uri, blob_sas, classinfo, courses, token);
  738. }
  739. /// <summary>
  740. /// 學生教育雲登入
  741. /// </summary>
  742. /// <param name = "request" ></ param >
  743. [AllowAnonymous]
  744. [HttpPost("login-open")]
  745. public async Task<IActionResult> OpenIDLogin(JsonElement request)
  746. {
  747. try
  748. {
  749. var client = _azureCosmos.GetCosmosClient();
  750. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  751. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  752. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  753. //參數取得
  754. string location = _option.Location;
  755. if (!request.TryGetProperty("open_code", out JsonElement _open_code)) return BadRequest();
  756. if (!location.Contains("Global")) return BadRequest();
  757. string grant_type = "educloudtw";
  758. string client_id = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  759. string redirect_uri = _configuration.GetValue<string>("HaBookAuth:CoreAccountAPI");
  760. string nonce = RandomString(16);
  761. string lang = "zh-tw";
  762. string open_code = _open_code.GetString();
  763. if(!open_code.Contains("EduCloudTWL")) return BadRequest();
  764. bool is_extrnal_id = true;
  765. //向CS取得OpenData
  766. stuOpenData openData = new stuOpenData();
  767. string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  768. string csv2Url = $"{csv2Domain}/oauth2/Login";
  769. Dictionary<string, object> dict = new() {
  770. { "grant_type", grant_type },
  771. { "client_id", client_id },
  772. { "redirect_uri", $"{redirect_uri}/" },
  773. { "nonce", nonce },
  774. { "lang", lang },
  775. { "open_code", open_code },
  776. { "is_extrnal_id", is_extrnal_id }
  777. };
  778. var httpClient = _httpClient.CreateClient();
  779. HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  780. HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  781. if (httpResponse.StatusCode == HttpStatusCode.OK)
  782. {
  783. string responseContent = await httpResponse.Content.ReadAsStringAsync();
  784. openData = responseContent.ToObject<stuOpenData>();
  785. if (string.IsNullOrWhiteSpace(openData.open_id) || string.IsNullOrWhiteSpace(openData.schoolCode))
  786. {
  787. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  788. }
  789. }
  790. else
  791. {
  792. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  793. }
  794. //用OpenData取得學生資訊
  795. Student stuinfo = new Student();
  796. var queryLogin = $"SELECT * FROM c WHERE c.pk = 'Base' AND IS_DEFINED(c.openId) AND c.openId = '{openData.open_id}'";
  797. await foreach (var item in studentClient.GetItemQueryStreamIterator(queryText: queryLogin, requestOptions: null))
  798. {
  799. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  800. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16().Equals(1))
  801. {
  802. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  803. {
  804. stuinfo = obj.ToObject<Student>();
  805. }
  806. }
  807. }
  808. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  809. string schShortCode = string.Empty;
  810. School school = new School();
  811. //分歧1 有此學生 => Login流程
  812. if (!string.IsNullOrWhiteSpace(stuinfo.id))
  813. {
  814. if (stuinfo.graduate.Equals(1))
  815. {
  816. return Ok(new { error = 3, message = "Graduate already!" });
  817. }
  818. //取得學校資訊
  819. schShortCode = stuinfo.schoolId;
  820. try
  821. {
  822. school = await schoolClient.ReadItemAsync<School>($"{schShortCode}", new PartitionKey("Base"));
  823. }
  824. catch (CosmosException ex)
  825. {
  826. return Ok(new { error = 2, message = "Can not find school data." });
  827. }
  828. //Login流程回傳值
  829. (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school, $"{stuinfo.id}", $"{stuinfo.classId}", $"{schShortCode}", $"{stuinfo.picture}", $"{stuinfo.name}", schoolClient, teacherClient, school.areaId, ip, client, stuinfo);
  830. int countAuthorized = await GetStudentAuthNumByScale($"{school.id}", school);
  831. await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="web", count=1, id=school.id, key="student_login", name=school.name, scope="school", target=school.id });
  832. return Ok(new { school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
  833. }
  834. //分歧2 無此學生 => 取得該校同名學生資訊
  835. else
  836. {
  837. //把OpenData的schoolCode換成學校簡碼
  838. string AccessToken = await getCoreAccessToken();
  839. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  840. string csv2SchoolUrl = $"{csv2Domain}/service/SchoolData";
  841. Dictionary<string, object> csv2SchDict = new() {
  842. { "code", openData.schoolCode }
  843. };
  844. HttpContent csv2SchContent = new StringContent(csv2SchDict.ToJsonString(), Encoding.UTF8, "application/json");
  845. HttpResponseMessage csv2SchHttpResponse = await httpClient.PostAsync(csv2SchoolUrl, csv2SchContent);
  846. if (csv2SchHttpResponse.StatusCode == HttpStatusCode.OK)
  847. {
  848. string responseContent = await csv2SchHttpResponse.Content.ReadAsStringAsync();
  849. if (!string.IsNullOrWhiteSpace(responseContent))
  850. {
  851. List<csSchApiResponse> csSchResult = responseContent.ToObject<List<csSchApiResponse>>();
  852. if (!csSchResult.Count.Equals(1))
  853. {
  854. return Ok(new { error = 2, message = "Can not find school data." });
  855. }
  856. foreach (csSchApiResponse csv2Sch in csSchResult)
  857. {
  858. schShortCode = csv2Sch.code;
  859. }
  860. }
  861. }
  862. else
  863. {
  864. return Ok(new { error = 2, message = "Can not find school data." });
  865. }
  866. if (string.IsNullOrWhiteSpace(schShortCode))
  867. {
  868. return Ok(new { error = 2, message = "Can not find school data." });
  869. }
  870. //用OpenData取得學校資訊
  871. try
  872. {
  873. school = await schoolClient.ReadItemAsync<School>($"{schShortCode}", new PartitionKey("Base"));
  874. }
  875. catch (CosmosException ex)
  876. {
  877. return Ok(new { error = 2, message = "Can not find school data." });
  878. }
  879. //學段
  880. Dictionary<string, string> periodDic = new Dictionary<string, string>();
  881. foreach(Period stuSchoolPeriod in school.period)
  882. {
  883. periodDic.Add(stuSchoolPeriod.id, stuSchoolPeriod.name);
  884. }
  885. //取得同名學生
  886. HashSet<string> classIds = new HashSet<string>();
  887. List<Student> stuList = new List<Student>();
  888. var queryStuSame = $"SELECT * FROM c WHERE c.name = '{openData.open_name}'";
  889. await foreach (var item in studentClient.GetItemQueryStreamIterator(queryText: queryStuSame, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{schShortCode}") }))
  890. {
  891. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  892. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  893. {
  894. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  895. {
  896. Student stuRow = obj.ToObject<Student>();
  897. stuList.Add(stuRow);
  898. if(!string.IsNullOrWhiteSpace(stuRow.classId))
  899. {
  900. classIds.Add(stuRow.classId);
  901. }
  902. }
  903. }
  904. }
  905. if(stuList.Count.Equals(0))
  906. {
  907. return Ok(new { error = 4, message = "Can not find any student." });
  908. }
  909. if (classIds.Count.Equals(0))
  910. {
  911. return Ok(new { error = 6, message = "Can not find any class." });
  912. }
  913. //取得學校班級
  914. Dictionary<string, string> classDic = new Dictionary<string, string>();
  915. string classIdJsonStr = JsonSerializer.Serialize(classIds);
  916. var query = $"SELECT c.code, c.id, c.name, c.periodId, c.gradeId FROM c WHERE ARRAY_CONTAINS({classIdJsonStr}, c.id)";
  917. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{schShortCode}") }))
  918. {
  919. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  920. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  921. {
  922. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  923. {
  924. Class classinfo = obj.ToObject<Class>();
  925. classDic.Add(classinfo.id, classinfo.name);
  926. }
  927. }
  928. }
  929. if (classDic.Count.Equals(0))
  930. {
  931. return Ok(new { error = 6, message = "Can not find any class." });
  932. }
  933. //OpenID AES加密
  934. string aeskey = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  935. string aesiv = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  936. openToken openToken = new openToken() { openId = openData.open_id, shortCode = schShortCode };
  937. string encOpenToken = LoginService.AesEncrypt(JsonSerializer.Serialize(openToken), aeskey, aesiv);
  938. //回傳值
  939. var schResult = new { schoolId = school.id, schoolName = school.name };
  940. List<stuOpenDataOrientation> stuResult = new List<stuOpenDataOrientation>();
  941. foreach (Student studata in stuList)
  942. {
  943. stuOpenDataOrientation stuResultRow = new stuOpenDataOrientation();
  944. stuResultRow.id = studata.id;
  945. stuResultRow.name = studata.name;
  946. stuResultRow.classId = studata.classId;
  947. stuResultRow.className = (!string.IsNullOrWhiteSpace(studata.classId) && classDic.ContainsKey(studata.classId)) ? classDic[studata.classId] : string.Empty;
  948. stuResultRow.periodId = studata.periodId;
  949. stuResultRow.periodName = (!string.IsNullOrWhiteSpace(studata.periodId) && periodDic.ContainsKey(studata.periodId)) ? periodDic[studata.periodId] : string.Empty;
  950. stuResultRow.year = studata.year;
  951. stuResultRow.no = studata.no;
  952. Period curPeriod = new Period();
  953. if (!string.IsNullOrWhiteSpace(studata.periodId))
  954. {
  955. curPeriod = school.period.Where(p => p.id.Equals(studata.periodId)).FirstOrDefault();
  956. }
  957. ExamSimple gradeInfo = new ExamSimple();
  958. if (!string.IsNullOrWhiteSpace(curPeriod.id))
  959. {
  960. gradeInfo = getGradeInfoByYear(studata.year, curPeriod);
  961. }
  962. stuResultRow.gradeIndex = (!string.IsNullOrWhiteSpace(gradeInfo.id)) ? gradeInfo.id : string.Empty;
  963. stuResultRow.gradeName = (!string.IsNullOrWhiteSpace(gradeInfo.name)) ? gradeInfo.name : string.Empty;
  964. stuResult.Add(stuResultRow);
  965. }
  966. await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="web", count=1, id=school.id, key="student_login", name=school.name, scope="school", target=school.id });
  967. //回傳值
  968. return Ok(new { openToken = encOpenToken, school = schResult, students = stuResult } );
  969. }
  970. }
  971. catch (Exception ex)
  972. {
  973. //await _dingDing.SendBotMsg($"OS,{_option.Location},student/openlogin()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  974. return BadRequest();
  975. }
  976. }
  977. /// <summary>
  978. /// 學生追加教育雲ID
  979. /// </summary>
  980. /// <param name = "request" ></ param >
  981. [AllowAnonymous]
  982. [HttpPost("add-open-stu")]
  983. public async Task<IActionResult> addOpenidToStudent(JsonElement request)
  984. {
  985. try
  986. {
  987. //參數取得
  988. string location = _option.Location;
  989. string openToken = (request.TryGetProperty("open_token", out JsonElement _open_token)) ? _open_token.GetString() : string.Empty;
  990. if(string.IsNullOrWhiteSpace(openToken)) return BadRequest();
  991. if (!location.Contains("Global")) return BadRequest();
  992. //string schoolCode = (request.TryGetProperty("school_code", out JsonElement _school_code)) ? _school_code.GetString() : string.Empty;
  993. //if (string.IsNullOrWhiteSpace(schoolCode)) return BadRequest();
  994. string stuid = (request.TryGetProperty("stuid", out JsonElement _stuid)) ? _stuid.GetString() : string.Empty;
  995. if (string.IsNullOrWhiteSpace(stuid)) return BadRequest();
  996. //OpenID AES解密
  997. string aeskey = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  998. string aesiv = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  999. string openTokenString = LoginService.AesDecrypt(openToken, aeskey, aesiv);
  1000. if(string.IsNullOrWhiteSpace(openTokenString)) return BadRequest();
  1001. openToken decOpenToken = openTokenString.ToObject<openToken>();
  1002. if(string.IsNullOrWhiteSpace(decOpenToken.openId) || string.IsNullOrWhiteSpace(decOpenToken.shortCode)) return BadRequest();
  1003. string schoolCode = decOpenToken.shortCode;
  1004. string openId = decOpenToken.openId;
  1005. var client = _azureCosmos.GetCosmosClient();
  1006. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  1007. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  1008. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  1009. //取得學校基本資料
  1010. School school = new School();
  1011. try
  1012. {
  1013. school = await schoolClient.ReadItemAsync<School>($"{schoolCode}", new PartitionKey("Base"));
  1014. }
  1015. catch (CosmosException ex)
  1016. {
  1017. return Ok(new { error = 1, message = "Can not find school data." });
  1018. }
  1019. //取得學生基本資料
  1020. Student student = new Student();
  1021. try
  1022. {
  1023. student = await studentClient.ReadItemAsync<Student>($"{stuid}", new PartitionKey($"Base-{schoolCode}"));
  1024. }
  1025. catch (CosmosException ex)
  1026. {
  1027. return Ok(new { error = 2, message = "Can not find student data." });
  1028. }
  1029. if (student.graduate.Equals(1))
  1030. {
  1031. return Ok(new { error = 3, message = "Graduate already!" });
  1032. }
  1033. //1.向CS詢問是否已綁定
  1034. stuOpenData openData = new stuOpenData();
  1035. var httpClient = _httpClient.CreateClient();
  1036. string AccessToken = await getCoreAccessToken();
  1037. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  1038. string grant_type = "bind";
  1039. string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  1040. string csv2Url = $"{csv2Domain}/oauth2/EduCloudTWBingManage";
  1041. Dictionary<string, object> dict = new() {
  1042. { "grant_type", grant_type },
  1043. { "open_id", openId },
  1044. { "id", $"Base-{school.id},{student.id}" }
  1045. };
  1046. HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  1047. HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  1048. if (httpResponse.StatusCode == HttpStatusCode.OK)
  1049. {
  1050. string responseContent = await httpResponse.Content.ReadAsStringAsync();
  1051. if(!string.IsNullOrWhiteSpace(responseContent))
  1052. {
  1053. csApiResponse csResult = responseContent.ToObject<csApiResponse>();
  1054. if(csResult.error.Equals(2))
  1055. {
  1056. return Ok(new { error = 5, message = "This id already exist." });
  1057. }
  1058. }
  1059. }
  1060. else
  1061. {
  1062. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  1063. }
  1064. //2.將OpenID放入學生Base
  1065. student.openId = openId;
  1066. //await studentClient.ReplaceItemAsync(student, student.id);
  1067. //3.學生登入流程(包含學生資料更新)
  1068. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  1069. (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school, $"{student.id}", $"{student.classId}", $"{schoolCode}", $"{student.picture}", $"{student.name}", schoolClient, teacherClient, school.areaId, ip, client, student);
  1070. int countAuthorized = await GetStudentAuthNumByScale($"{schoolCode}", school);
  1071. return Ok(new { school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
  1072. }
  1073. catch (Exception ex)
  1074. {
  1075. //await _dingDing.SendBotMsg($"OS,{_option.Location},student/addOpenidToStudent()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  1076. return BadRequest();
  1077. }
  1078. }
  1079. /// <summary>
  1080. /// 學生解綁教育雲ID
  1081. /// </summary>
  1082. /// <param name = "request" ></ param >
  1083. //[AllowAnonymous]
  1084. //[HttpPost("rmv-open-stu")]
  1085. //public async Task<IActionResult> rmvOpenidToStudent(JsonElement request)
  1086. //{
  1087. // if (!request.TryGetProperty("school_code", out JsonElement _school_code)) return BadRequest();
  1088. // string schoolCode = _school_code.GetString();
  1089. // if(string.IsNullOrWhiteSpace(schoolCode)) return BadRequest();
  1090. // if (!request.TryGetProperty("stuid", out JsonElement _stuid)) return BadRequest();
  1091. // string stuId = _stuid.GetString();
  1092. // if(string.IsNullOrWhiteSpace(stuId)) return BadRequest();
  1093. // string openType = (request.TryGetProperty("type", out JsonElement _type)) ? _type.GetString().ToLower() : "educloudtwl"; //educloudtwl: 教育雲
  1094. // var client = _azureCosmos.GetCosmosClient();
  1095. // var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  1096. // //取得學生基本資料
  1097. // Student student = new Student();
  1098. // try
  1099. // {
  1100. // student = await studentClient.ReadItemAsync<Student>($"{stuId}", new PartitionKey($"Base-{schoolCode}"));
  1101. // }
  1102. // catch (CosmosException ex)
  1103. // {
  1104. // return Ok(new { error = 2, message = "Can not find student data." });
  1105. // }
  1106. // //CSV解綁
  1107. // bool rmvSuccess = false;
  1108. // if (openType.Equals("educloudtwl")) //教育雲
  1109. // {
  1110. // string openId = student.openId;
  1111. // if(string.IsNullOrWhiteSpace(openId)) //學生無OpenID,視為解綁成功
  1112. // {
  1113. // return Ok(new { error = 0, message = string.Empty });
  1114. // }
  1115. // stuOpenData openData = new stuOpenData();
  1116. // var httpClient = _httpClient.CreateClient();
  1117. // string AccessToken = await getCoreAccessToken();
  1118. // httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  1119. // string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  1120. // string csv2Url = $"{csv2Domain}/oauth2/EduCloudTWBingManage";
  1121. // Dictionary<string, object> dict = new() {
  1122. // { "grant_type", "unbind" },
  1123. // { "open_id", openId },
  1124. // { "id", $"Base-{schoolCode},{student.id}" }
  1125. // };
  1126. // HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  1127. // HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  1128. // if (httpResponse.StatusCode == HttpStatusCode.OK)
  1129. // {
  1130. // string responseContent = await httpResponse.Content.ReadAsStringAsync();
  1131. // if (string.IsNullOrWhiteSpace(responseContent))
  1132. // {
  1133. // rmvSuccess = true;
  1134. // }
  1135. // else
  1136. // {
  1137. // csApiResponse csResult = responseContent.ToObject<csApiResponse>();
  1138. // if (!string.IsNullOrWhiteSpace(csResult.message))
  1139. // {
  1140. // return Ok(new { error = csResult.error, message = csResult.message });
  1141. // }
  1142. // }
  1143. // }
  1144. // else
  1145. // {
  1146. // return Ok(new { error = 1, message = "Can not get opendata from CS." });
  1147. // }
  1148. // }
  1149. // //學生資料變更
  1150. // if(rmvSuccess)
  1151. // {
  1152. // if (openType.Equals("educloudtwl")) //教育雲
  1153. // {
  1154. // student.openId = null;
  1155. // }
  1156. // //DB更新
  1157. // await studentClient.ReplaceItemAsync(student, student.id);
  1158. // return Ok(new { error = 0, message = string.Empty });
  1159. // }
  1160. // return Ok(new { error = 9, message = "Can not unbind student." });
  1161. //}
  1162. //查询学生名单详情
  1163. [ProducesDefaultResponseType]
  1164. //[AuthToken(Roles = "teacher")]
  1165. [HttpPost("get-summary-student")]
  1166. [AuthToken(Roles = "teacher,admin,student")]
  1167. #if !DEBUG
  1168. [Authorize(Roles = "IES")]
  1169. #endif
  1170. public async Task<IActionResult> getSummary(JsonElement request)
  1171. {
  1172. try
  1173. {
  1174. request.TryGetProperty("students", out JsonElement students);
  1175. request.TryGetProperty("tmdIds", out JsonElement tmdIds);
  1176. List<TmdInfo> tmdinfos = new List<TmdInfo>();
  1177. List<object> stus = new List<object>();
  1178. var client = _azureCosmos.GetCosmosClient();
  1179. if (students.ValueKind.Equals(JsonValueKind.Array))
  1180. {
  1181. List<Students> stuList = students.ToObject<List<Students>>();
  1182. if (stuList.IsNotEmpty())
  1183. {
  1184. foreach (Students stu in stuList)
  1185. {
  1186. var query = $"select c.id,c.name,c.picture,c.classId,c.code,c.groupId,c.groupName,c.no from c where c.id = '{stu.id}'";
  1187. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{stu.code}") }))
  1188. {
  1189. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1190. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1191. {
  1192. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1193. {
  1194. stus.Add(obj.ToObject<object>());
  1195. }
  1196. }
  1197. }
  1198. }
  1199. }
  1200. }
  1201. if (tmdIds.ValueKind.Equals(JsonValueKind.Array))
  1202. {
  1203. List<string> tmdids = tmdIds.ToObject<List<string>>();
  1204. if (tmdids.IsNotEmpty())
  1205. {
  1206. List<string> inids = new List<string>();
  1207. tmdids.ForEach(x => { inids.Add($"'{x}'"); });
  1208. var insql = string.Join(",", inids);
  1209. var queryslt = $"SELECT c.id,c.name,c.picture FROM c where c.id in ({insql})";
  1210. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryIterator<TmdInfo>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
  1211. {
  1212. tmdinfos.Add(item);
  1213. }
  1214. }
  1215. }
  1216. return Ok(new { stus, tmdinfos });
  1217. }
  1218. catch (Exception ex)
  1219. {
  1220. await _dingDing.SendBotMsg($"OS,{_option.Location},student/get-summary-student()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  1221. return BadRequest();
  1222. }
  1223. }
  1224. /// <summary>
  1225. /// 學生簡易登入
  1226. /// </summary>
  1227. /// <param name = "request" ></ param >
  1228. [AllowAnonymous]
  1229. [HttpPost("login-simple")]
  1230. public async Task<IActionResult> LoginSimple(JsonElement request)
  1231. {
  1232. try
  1233. {
  1234. var client = _azureCosmos.GetCosmosClient();
  1235. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  1236. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  1237. //參數取得
  1238. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  1239. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  1240. if (!request.TryGetProperty("pw", out JsonElement pw)) return BadRequest();
  1241. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  1242. var response = await studentClient.ReadItemStreamAsync(id.GetString(), new PartitionKey($"Base-{school_code.GetString().ToLower()}"));
  1243. if (response.Status == 200)
  1244. {
  1245. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  1246. Student student = rjson.ToObject<Student>();
  1247. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  1248. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  1249. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  1250. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  1251. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  1252. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  1253. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  1254. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  1255. dynamic user = new ExpandoObject();
  1256. user.no = no;
  1257. user.groupId = groupId;
  1258. user.groupName = groupName;
  1259. var HashedPW = Utils.HashedPassword(pw.ToString(), salt.ToString());
  1260. if (HashedPW.Equals(dbpw.GetString()))
  1261. {
  1262. School schoolInfo = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  1263. //取得所屬預設班級信息
  1264. object classinfo = null;
  1265. if (!classId.ValueKind.Equals(JsonValueKind.Null) && classId.ValueKind.Equals(JsonValueKind.String))
  1266. {
  1267. var query = $"SELECT c.id, c.no, c.name FROM c WHERE c.id = '{classId.GetString()}'";
  1268. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  1269. {
  1270. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1271. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1272. {
  1273. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1274. {
  1275. classinfo = obj.ToObject<object>();
  1276. }
  1277. }
  1278. }
  1279. }
  1280. int timezone = 8;
  1281. if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
  1282. {
  1283. timezone=tz;
  1284. }
  1285. if (!string.IsNullOrWhiteSpace(schoolInfo.timeZone?.value))
  1286. {
  1287. string timeZoneOffsetString = schoolInfo.timeZone.value;
  1288. bool plus = true;
  1289. if (timeZoneOffsetString.Contains("-"))
  1290. {
  1291. plus=false;
  1292. }
  1293. // 去除时区偏移字符串中的正负号
  1294. timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
  1295. // 尝试解析格式化后的时区偏移字符串
  1296. if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
  1297. {
  1298. // 将时区偏移转换为小时数
  1299. timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
  1300. }
  1301. }
  1302. //換取AuthToken,提供給前端
  1303. var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, areaId: schoolInfo.areaId, scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" }, expire: 4);
  1304. //用户在线记录
  1305. //try
  1306. //{
  1307. // _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  1308. //}
  1309. //catch { }
  1310. //保存学生登录信息
  1311. await client.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"{student.code}"));
  1312. //其他訊息
  1313. dynamic school = new ExpandoObject();
  1314. //回傳
  1315. await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="web", count=1, id=schoolInfo.id, key="student_login", name=schoolInfo.name , scope="school", target=schoolInfo.id });
  1316. return Ok(new { error = 0, auth_token, classinfo, user });
  1317. }
  1318. else
  1319. {
  1320. return Ok(new { error = 1, message = "Invalid account or password" });
  1321. }
  1322. }
  1323. else
  1324. {
  1325. return Ok(new { error = 2, message = "Invalid account" });
  1326. }
  1327. }
  1328. catch (Exception ex)
  1329. {
  1330. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/login-simple()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  1331. return BadRequest();
  1332. }
  1333. }
  1334. //TODO 此API需處理對應前端返回的相關數據
  1335. [ProducesDefaultResponseType]
  1336. [AuthToken(Roles = "student,teacher")]
  1337. [HttpPost("get-school-info")]
  1338. public async Task<IActionResult> GetSchoolInfo(JsonElement request)
  1339. {
  1340. try
  1341. {
  1342. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  1343. var client = _azureCosmos.GetCosmosClient();
  1344. /// tmdid, schoolid
  1345. var userType = "schoolid";
  1346. if (request.TryGetProperty("userType", out JsonElement usertype))
  1347. {
  1348. if (!usertype.ValueKind.Equals(JsonValueKind.Undefined) && !usertype.ValueKind.Equals(JsonValueKind.Null) && usertype.ValueKind.Equals(JsonValueKind.String))
  1349. {
  1350. userType = usertype.GetString();
  1351. }
  1352. }
  1353. if (string.IsNullOrEmpty(school))
  1354. {
  1355. if (userType.Equals("tmdid"))
  1356. {
  1357. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
  1358. if (teacher.schools.IsNotEmpty())
  1359. {
  1360. var tech = teacher.schools.Find(x => x.status.Equals("join"));
  1361. if (tech == null)
  1362. {
  1363. school = teacher.schools[0].schoolId;
  1364. }
  1365. else
  1366. {
  1367. school = tech.schoolId;
  1368. }
  1369. }
  1370. }
  1371. }
  1372. if (!string.IsNullOrEmpty(school))
  1373. {
  1374. object school_base = null;
  1375. var response = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(school, new PartitionKey("Base"));
  1376. if (response.Status == 200)
  1377. {
  1378. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  1379. school_base = json.RootElement.ToObject<object>();
  1380. }
  1381. //取得班级
  1382. List<object> school_classes = new List<object>();
  1383. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id,c.x,c.y,c.name,c.year,c.teacher,c.periodId,c.gradeId,c.room,c.sn,c.no,c.style,c.status,c.openType,c.scope, ARRAY_LENGTH(c.students) AS studCount FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school}") }))
  1384. {
  1385. var jsonc = await JsonDocument.ParseAsync(item.ContentStream);
  1386. foreach (var classeinfo in jsonc.RootElement.GetProperty("Documents").EnumerateArray())
  1387. {
  1388. school_classes.Add(classeinfo.ToObject<object>());
  1389. }
  1390. }
  1391. //取得教室
  1392. List<Room> school_rooms = new List<Room>();
  1393. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Room>(queryText: $"select value(c) from c ",
  1394. requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Room-{school}") }))
  1395. {
  1396. school_rooms.Add(item);
  1397. }
  1398. return Ok(new { school_base, school_classes, school_rooms, status = 200 });
  1399. }
  1400. else
  1401. {
  1402. return Ok(new { status = 404 }); ;
  1403. }
  1404. }
  1405. catch (CosmosException ex)
  1406. {
  1407. return Ok(new { status = ex.Status }); ;
  1408. }
  1409. catch (Exception ex)
  1410. {
  1411. await _dingDing.SendBotMsg($"IES5,{_option.Location},Student/get-school-info()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  1412. return BadRequest();
  1413. }
  1414. }
  1415. //取得學生各科目的智慧錯題數
  1416. [ProducesDefaultResponseType]
  1417. [AuthToken(Roles = "student")]
  1418. [HttpPost("get-malearn-itsnum-subj")]
  1419. public async Task<IActionResult> GetStuMalearnItemCountBySubj(JsonElement request)
  1420. {
  1421. try
  1422. {
  1423. var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  1424. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  1425. var client = _azureCosmos.GetCosmosClient();
  1426. Dictionary<string, HashSet<string>> qidDic = new Dictionary<string, HashSet<string>>();
  1427. List<object> result = new List<object>();
  1428. //取得錯題
  1429. string queryErr = $"SELECT DISTINCT c.qId, c.unitId FROM c WHERE c.pk = 'MaLearn' AND c.type = 'answer' AND c.nxtRev < {now} AND IS_DEFINED(c.activityId) AND IS_STRING(c.activityId)";
  1430. ///屬學校
  1431. if(!string.IsNullOrWhiteSpace(school))
  1432. {
  1433. string codeSch = $"MaLearn-{school}-{id}";
  1434. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryStreamIterator(queryText: queryErr, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey(codeSch) }))
  1435. {
  1436. var jsons = await JsonDocument.ParseAsync(item.ContentStream);
  1437. foreach (var je in jsons.RootElement.GetProperty("Documents").EnumerateArray())
  1438. {
  1439. string subjectId = je.GetProperty("unitId").GetString();
  1440. string qId = je.GetProperty("qId").GetString();
  1441. if(!qidDic.ContainsKey(subjectId))
  1442. {
  1443. qidDic.Add(subjectId, new HashSet<string>());
  1444. }
  1445. qidDic[subjectId].Add(qId);
  1446. }
  1447. }
  1448. }
  1449. ///屬個人
  1450. string codePri = $"MaLearn-{id}";
  1451. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryStreamIterator(queryText: queryErr, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey(codePri) }))
  1452. {
  1453. var jsons = await JsonDocument.ParseAsync(item.ContentStream);
  1454. foreach (var je in jsons.RootElement.GetProperty("Documents").EnumerateArray())
  1455. {
  1456. string subjectId = je.GetProperty("unitId").GetString();
  1457. string qId = je.GetProperty("qId").GetString();
  1458. if (!qidDic.ContainsKey(subjectId))
  1459. {
  1460. qidDic.Add(subjectId, new HashSet<string>());
  1461. }
  1462. qidDic[subjectId].Add(qId);
  1463. }
  1464. }
  1465. //資料整理
  1466. foreach(var qidDicItem in qidDic)
  1467. {
  1468. string subjectId = qidDicItem.Key;
  1469. int itsNum = qidDicItem.Value.Count;
  1470. result.Add(new { subjectId, itsNum });
  1471. }
  1472. return Ok(result);
  1473. }
  1474. catch (CosmosException ex)
  1475. {
  1476. await _dingDing.SendBotMsg($"IES5,{_option.Location},Student/get-malearn-itsnum-subj()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  1477. return BadRequest();
  1478. }
  1479. }
  1480. /**
  1481. * 根据学年获取年级信息
  1482. * @param year 学年
  1483. * @param Period 学段資料
  1484. */
  1485. private ExamSimple getGradeInfoByYear(int year, Period curPeriod)
  1486. {
  1487. ExamSimple result = new ExamSimple();
  1488. if (year > 0)
  1489. {
  1490. DateTime date = DateTime.UtcNow;
  1491. int curYear = date.Year;
  1492. int month = date.Month;
  1493. Semester semesterStart = curPeriod.semesters.Where((Semester x) => x.start.Equals(1)).FirstOrDefault();
  1494. if (semesterStart != null)
  1495. {
  1496. if (month < semesterStart.month)
  1497. {
  1498. curYear--;
  1499. }
  1500. int gradeIndex = curYear - year;
  1501. result.id = gradeIndex.ToString();
  1502. result.name = (gradeIndex >= curPeriod.grades.Count) ? "graduated" : (gradeIndex >= 0) ? curPeriod.grades[gradeIndex] : "not-enrollment";
  1503. }
  1504. }
  1505. return result;
  1506. }
  1507. //學生登入後根據學校規模取得授權數
  1508. private async Task<int> GetStudentAuthNumByScale(string school_code, School school)
  1509. {
  1510. if (!string.IsNullOrWhiteSpace(school.areaId) && (school.areaId.Equals("7a51072f-b329-4e74-99e0-ba0407ba8926") || school.areaId.Equals("69e3d413-50a1-4f5e-844a-e0f7c9622ea3")) && DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()<1706630400000) {
  1511. return school.scale-5;
  1512. }
  1513. //授权规模数量
  1514. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  1515. var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
  1516. string key = $"Login:School:{school_code}:student-day:{dateDay}";
  1517. SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
  1518. int countAuthorized = 0;
  1519. if (countStudent != null && countStudent.Length > 0)
  1520. {
  1521. bool notify = false;
  1522. countAuthorized = countStudent.Length;
  1523. if (school.scale > 0 && school.scale - countAuthorized <= 0)
  1524. {
  1525. //登录人数已达授权规模数上限
  1526. if (!string.IsNullOrWhiteSpace(school.areaId))
  1527. {
  1528. AreaSetting areaSetting = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemAsync<AreaSetting>(school.areaId, new PartitionKey("AreaSetting"));
  1529. if (areaSetting.ignoreScaleExpire > dateTime.ToUnixTimeMilliseconds())
  1530. {
  1531. //将人数控制在最大规模数以下。
  1532. countAuthorized = school.scale - 1;
  1533. }
  1534. else
  1535. {
  1536. notify = true;
  1537. }
  1538. }
  1539. else
  1540. {
  1541. notify = true;
  1542. }
  1543. if (notify)
  1544. {
  1545. //通知key 一天只通知一次
  1546. string scaleNotifykey = $"Login:School:{school.id}:student-scale-notify:{dateDay}";
  1547. bool Exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync(scaleNotifykey);
  1548. if (!Exists)
  1549. {
  1550. //获取学校管理员
  1551. List<IdNameCode> ids = new List<IdNameCode>();
  1552. string sql = $"select value c from c where c.code='Teacher-{school.id}' and c.status='join' and array_contains(c.roles,'admin') ";
  1553. List<SchoolTeacher> adminTeachers = new List<SchoolTeacher>();
  1554. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  1555. .GetItemQueryIterator<SchoolTeacher>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Teacher-{school.id}") }))
  1556. {
  1557. adminTeachers.Add(item);
  1558. }
  1559. if (adminTeachers.IsNotEmpty())
  1560. {
  1561. string sqlAdmin = $"select c.id,c.lang as code ,c.name from c where c.id in ({string.Join(",", adminTeachers.Select(z => $"'{z.id}'"))}) ";
  1562. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
  1563. .GetItemQueryIterator<IdNameCode>(queryText: sqlAdmin, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
  1564. {
  1565. ids.Add(item);
  1566. }
  1567. foreach (var uds in ids)
  1568. {
  1569. _coreAPIHttpService.PushNotify(new List<IdNameCode> { uds }, $"school-scale-notify", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", uds.name }, { "countAuthorized", $"{countAuthorized}" }, { "scale", $"{school.scale}" }, { "schoolName", school.name }, { "schoolId", $"{school.id}" }, }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
  1570. }
  1571. await _azureRedis.GetRedisClient(8).StringSetAsync(scaleNotifykey, scaleNotifykey, new TimeSpan(hours: 24, minutes: 0, seconds: 0));
  1572. }
  1573. }
  1574. }
  1575. }
  1576. }
  1577. return countAuthorized;
  1578. }
  1579. private async Task<string> getCoreAccessToken()
  1580. {
  1581. string AccessToken = "";
  1582. try
  1583. {
  1584. string Url = _configuration.GetValue<string>("HaBookAuth:CoreAPI") + "/oauth2/token";
  1585. string GrantType = "device";
  1586. string ClientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  1587. string Secret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  1588. var content = new { grant_type = GrantType, client_id = ClientID, client_secret = Secret };
  1589. var response = await _httpClient.CreateClient().PostAsJsonAsync($"{Url}", content);
  1590. if (response.IsSuccessStatusCode)
  1591. {
  1592. string responseBody = response.Content.ReadAsStringAsync().Result;
  1593. using (JsonDocument document = JsonDocument.Parse(responseBody.ToString()))
  1594. {
  1595. if (document.RootElement.TryGetProperty("access_token", out JsonElement AccessTokenObj))
  1596. {
  1597. AccessToken = AccessTokenObj.ToString();
  1598. }
  1599. }
  1600. }
  1601. return AccessToken;
  1602. }
  1603. catch (Exception ex)
  1604. {
  1605. return AccessToken;
  1606. }
  1607. }
  1608. public static string RandomString(int length)
  1609. {
  1610. Random random = new Random();
  1611. const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  1612. return new string(Enumerable.Repeat(chars, length)
  1613. .Select(s => s[random.Next(s.Length)]).ToArray());
  1614. }
  1615. //取得學生OpenData
  1616. private class stuOpenData
  1617. {
  1618. public string open_id { get; set; }
  1619. public string open_name { get; set; }
  1620. public string open_mail { get; set; }
  1621. public string schoolCode { get; set; }
  1622. }
  1623. //CS API 返回架構
  1624. private class csApiResponse
  1625. {
  1626. public int error { get; set; }
  1627. public string message { get; set; }
  1628. }
  1629. //CSV2 學校資料庫 API 返回架構
  1630. private class csSchApiResponse
  1631. {
  1632. public string code { get; set; }
  1633. public string name { get; set; }
  1634. public string type { get; set; }
  1635. }
  1636. //把openId打包成openToken架構
  1637. private class openToken
  1638. {
  1639. public string openId { get; set; }
  1640. public string shortCode { get; set; }
  1641. }
  1642. //無法取得OpenID搜尋學生姓名回傳的學生資料
  1643. private class stuOpenDataOrientation
  1644. {
  1645. public string id { get; set; }
  1646. public string name { get; set; }
  1647. public string classId { get; set; }
  1648. public string className { get; set; }
  1649. public string periodId { get; set; }
  1650. public string periodName { get; set; }
  1651. public int year { get; set; }
  1652. public string gradeIndex { get; set; }
  1653. public string gradeName { get; set; }
  1654. public string no { get; set; }
  1655. }
  1656. }
  1657. }