StudentController.cs 87 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502
  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. GroupChange change = new GroupChange
  143. {
  144. scope = "school",
  145. school = schoolId.GetString(),
  146. type = "student",
  147. originCode = schoolId.GetString(),
  148. listid = classId,
  149. stujoin = new List<Member>
  150. {
  151. new Member
  152. {
  153. id= id,
  154. code=schoolId.GetString(),
  155. type=2,
  156. }
  157. }
  158. };
  159. dictChange.Add(classId, change);
  160. }
  161. foreach (var changed in dictChange.Keys)
  162. {
  163. var change = dictChange[changed];
  164. if (change.stujoin.Count != 0 || change.stuleave.Count != 0)
  165. {
  166. var messageChange = new ServiceBusMessage(change.ToJsonString());
  167. messageChange.ApplicationProperties.Add("name", "GroupChange");
  168. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  169. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChange);
  170. }
  171. }
  172. return this.Ok(new { code = $"Base-{schoolId.GetString()}", id, name, year, classId, no, periodId });
  173. }
  174. else return this.Ok(new { code = $"Base-{schoolId.GetString()}", errorId = id });
  175. }
  176. break;
  177. case "import":
  178. //只有ClassNo可以比對
  179. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  180. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  181. var retUpsert = await StudentService.upsertStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  182. await CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  183. School school_base = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{schoolId}", new PartitionKey("Base"));
  184. //实时处理导入毕业的班级。
  185. await SchoolService.DoGraduateClasses(_httpTrigger, _azureCosmos, null, school_base, _option,_dingDing);
  186. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpsert.studs, retUpsert.classDuplNos, retUpsert.errorIds });
  187. case "read":
  188. //增加毕业查询。当毕业查询字段1时,则需要传入入学年份。
  189. int graduate = 0;
  190. int inyear = 0;
  191. //讀取該間學校所有的學生資訊
  192. if (request.TryGetProperty("graduate", out JsonElement _graduate) && $"{_graduate}".Equals("1")) {
  193. if (request.TryGetProperty("year", out JsonElement _year) && _year.ValueKind.Equals(JsonValueKind.Number))
  194. {
  195. if (int.TryParse($"{_year}", out inyear) && inyear > 0)
  196. {
  197. graduate = 1;
  198. }
  199. else {
  200. return BadRequest("入学年份大于0");
  201. }
  202. }
  203. else {
  204. return BadRequest("请输入毕业学生的入学年份");
  205. }
  206. }
  207. var students = await StudentService.getAllStudent(_azureCosmos, _dingDing, _option, schoolId.GetString(), inyear, graduate);
  208. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students });
  209. case "update":
  210. //更新學生資料,批量密碼重置,基本資訊更新(姓名、教室ID、性別、學年及座號)
  211. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  212. //var cleanImei = false;
  213. // request.TryGetProperty("cleanImei",out JsonElement _cleanImei);
  214. //if (_cleanImei.ValueKind.Equals(JsonValueKind.True))
  215. //{
  216. // cleanImei = true;
  217. //}
  218. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  219. var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  220. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  221. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpdate.studs, retUpdate.classDuplNos, retUpdate.nonexistentIds, retUpdate.errorNos, retUpdate.errorClassId });
  222. case "delete":
  223. //刪除學生資料及從教室學生名單內移除該學生
  224. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  225. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  226. var sucDelIds = await StudentService.deleteStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  227. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  228. return this.Ok(new { code = $"Base-{schoolId.GetString()}", ids = sucDelIds });
  229. case "remove":
  230. //將學生基本資料內的classId、no、groupId及groupName寫入null
  231. List<string> stus = request.GetProperty("students").ToObject<List<string>>();
  232. webStudents = new List<Student>();
  233. foreach (string idstu in stus)
  234. {
  235. webStudents.Add(new Student { id = idstu, code = $"Base-{schoolId}" });
  236. }
  237. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  238. (List<string> studs, List<string> nonexistentIds, List<string> errorIds) retRemove = await StudentService.removeStudentClassInfo(
  239. _azureCosmos, _dingDing, _option,
  240. schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  241. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  242. return Ok(new { code = $"Base-{schoolId.GetString()}", ids = retRemove.studs, retRemove.nonexistentIds, retRemove.errorIds });
  243. case "avatar":
  244. if (request.TryGetProperty("avatar", out JsonElement _avatar) && _avatar.ValueKind.Equals(JsonValueKind.Array))
  245. {
  246. List<StudentInfo> avatars = _avatar.ToObject<List<StudentInfo>>();
  247. if (avatars.IsNotEmpty())
  248. {
  249. List<Student> studentsp = new List<Student>();
  250. string insql = string.Join(',', avatars.Select(x => $"'{x.studentId}'"));
  251. string sql = $"select value(c) from c where c.id in ({insql}) ";
  252. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  253. .GetItemQueryIterator<Student>(sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
  254. {
  255. studentsp.Add(item);
  256. }
  257. (string url, string sas) = _azureStorage.GetBlobContainerSAS99Year($"{schoolId}", BlobContainerSasPermissions.Read);
  258. foreach (Student student in studentsp)
  259. {
  260. StudentInfo avatar = avatars.Find(x => x.studentId.Equals(student.id));
  261. student.picture = avatar != null ? $"{avatar.picture}?{sas}" : null;
  262. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").ReplaceItemAsync<Student>(student, student.id, new PartitionKey(student.code));
  263. }
  264. return Ok(new { students = studentsp.Select(x => new { x.id, x.picture, x.code, x.name }) });
  265. }
  266. else
  267. {
  268. return BadRequest();
  269. }
  270. }
  271. else
  272. {
  273. return BadRequest();
  274. }
  275. case "update-self-info":
  276. if (request.TryGetProperty("studentInfo", out JsonElement _studentInfo))
  277. {
  278. var studentInfo = _studentInfo.ToObject<StudentInfo>();
  279. if (studentInfo.studentId.Equals(stuid) && school.Equals($"{schoolId}"))
  280. {
  281. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  282. .ReadItemAsync<Student>(studentInfo.studentId, new PartitionKey($"Base-{schoolId}"));
  283. student.mail = string.IsNullOrEmpty(studentInfo.mail) ? student.mail : studentInfo.mail;
  284. student.mobile = string.IsNullOrEmpty(studentInfo.mobile) ? student.mobile : studentInfo.mobile;
  285. student.name = string.IsNullOrEmpty(studentInfo.name) ? student.name : studentInfo.name;
  286. (string url, string sas) = _azureStorage.GetBlobContainerSAS99Year($"{schoolId}", BlobContainerSasPermissions.Read);
  287. student.picture = string.IsNullOrEmpty(studentInfo.picture) ? student.picture : $"{studentInfo.picture}?{sas}";
  288. student.gender = string.IsNullOrEmpty(studentInfo.gender) ? student.gender : studentInfo.gender;
  289. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  290. .ReplaceItemAsync<Student>(student, studentInfo.studentId, new PartitionKey($"Base-{schoolId}"));
  291. return Ok(new { studentInfo });
  292. }
  293. else
  294. {
  295. return Ok(new { error = false, status = false, msg = "修改的不是自己的信息" });
  296. }
  297. }
  298. else
  299. {
  300. return BadRequest();
  301. }
  302. case "update-self-password":
  303. if (request.TryGetProperty("newpwd", out JsonElement _newpwd) &&
  304. request.TryGetProperty("oldpwd", out JsonElement _oldpwd) &&
  305. request.TryGetProperty("studentId", out JsonElement _studentId))
  306. {
  307. if ($"{_studentId}".Equals(stuid) && school.Equals($"{schoolId}"))
  308. {
  309. try
  310. {
  311. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  312. .ReadItemAsync<Student>($"{_studentId}", new PartitionKey($"Base-{schoolId}"));
  313. var HashedPW = Utils.HashedPassword($"{_oldpwd}", student.salt);
  314. if (HashedPW.Equals(student.pw))
  315. {
  316. student.pw = Utils.HashedPassword($"{_newpwd}", student.salt);
  317. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  318. .ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"Base-{schoolId}"));
  319. return Ok(new { status = true });
  320. }
  321. else
  322. {
  323. return Ok(new { error = false, status = false, msg = "密码不一致" });
  324. }
  325. }
  326. catch (Exception ex)
  327. {
  328. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  329. return Ok(new { error = false, status = false, msg = "账号不存在" });
  330. }
  331. }
  332. else
  333. {
  334. return Ok(new { error = false, status = false, msg = "修改的不是自己的密码" });
  335. }
  336. }
  337. else
  338. {
  339. return BadRequest();
  340. }
  341. case "read-self-info":
  342. if (request.TryGetProperty("studentId", out JsonElement __studentId))
  343. {
  344. try
  345. {
  346. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  347. .ReadItemAsync<Student>($"{__studentId}", new PartitionKey($"Base-{schoolId}"));
  348. 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 });
  349. }
  350. catch (Exception ex)
  351. {
  352. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  353. }
  354. return Ok();
  355. }
  356. else
  357. {
  358. return BadRequest();
  359. }
  360. default:
  361. return BadRequest();
  362. }
  363. }
  364. catch (Exception ex)
  365. {
  366. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  367. }
  368. return BadRequest();
  369. }
  370. [HttpPost("get-student-info")]
  371. #if !DEBUG
  372. [Authorize(Roles = "IES")]
  373. #endif
  374. public async Task<IActionResult> GetStudentInfo(JsonElement request)
  375. {
  376. var client = _azureCosmos.GetCosmosClient();
  377. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  378. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  379. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  380. if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
  381. var jwt = new JwtSecurityToken(id_token.GetString());
  382. var id = jwt.Payload.Sub;
  383. string school_code = jwt.Payload.Azp;
  384. //權限token
  385. jwt.Payload.TryGetValue("name", out object _name);
  386. jwt.Payload.TryGetValue("picture", out object _picture);
  387. jwt.Payload.TryGetValue("lang", out object _lang);
  388. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  389. School school = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  390. var response = await studentClient.ReadItemStreamAsync(id, new PartitionKey($"Base-{school_code.ToLower()}"));
  391. if (response.Status == 200)
  392. {
  393. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  394. Student student = rjson.ToObject<Student>();
  395. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  396. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  397. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  398. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  399. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  400. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  401. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  402. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  403. (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);
  404. //授权规模数量
  405. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  406. var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
  407. string key = $"Login:School:{school_code}:student-day:{dateDay}";
  408. SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
  409. int countAuthorized = 0;
  410. if (countStudent != null && countStudent.Length > 0)
  411. {
  412. countAuthorized = countStudent.Length;
  413. }
  414. int scale = school.scale;
  415. if (scale<=0)
  416. {
  417. string sql = $" SELECT value s FROM c join s in c.service where c.id='{school_code}' and s.prodCode='3CLYJ6NP' ";
  418. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<SchoolProductSumDataService>(sql, "ProductSum");
  419. if (result.list.IsNotEmpty())
  420. {
  421. SchoolProductSumDataService service = result.list[0];
  422. if (service.avaliable>0)
  423. {
  424. scale= service.avaliable;
  425. }
  426. }
  427. }
  428. 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 } });
  429. }
  430. else
  431. {
  432. return Ok(new { error = 2, message = "無此帳號存在" });
  433. }
  434. }
  435. /// <summary>
  436. /// 學生登入
  437. /// </summary>
  438. /// <param name = "request" ></ param >
  439. [AllowAnonymous]
  440. [HttpPost("login")]
  441. public async Task<IActionResult> Login(JsonElement request)
  442. {
  443. try
  444. {
  445. var client = _azureCosmos.GetCosmosClient();
  446. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  447. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  448. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  449. //參數取得
  450. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  451. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  452. if (!request.TryGetProperty("pw", out JsonElement pw)) return BadRequest();
  453. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  454. School school = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  455. var response = await studentClient.ReadItemStreamAsync(id.GetString(), new PartitionKey($"Base-{school_code.GetString().ToLower()}"));
  456. if (response.Status == 200)
  457. {
  458. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  459. Student student = rjson.ToObject<Student>();
  460. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  461. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  462. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  463. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  464. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  465. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  466. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  467. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  468. rjson.RootElement.TryGetProperty("graduate", out JsonElement graduate);
  469. if (graduate.ValueKind.Equals(JsonValueKind.Number) && $"{graduate}".Equals("1")) {
  470. return Ok(new { error = 3, message = "你已毕业,暂不能登录!" });
  471. }
  472. var HashedPW = Utils.HashedPassword(pw.ToString(), salt.ToString());
  473. if (HashedPW.Equals(dbpw.GetString()))
  474. {
  475. (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);
  476. int countAuthorized = await GetStudentAuthNumByScale($"{school_code}", school);
  477. 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 } });
  478. }
  479. else
  480. {
  481. return Ok(new { error = 1, message = "账号或密码错误" });
  482. }
  483. }
  484. else
  485. {
  486. return Ok(new { error = 2, message = "無此帳號存在" });
  487. }
  488. }
  489. catch (Exception ex)
  490. {
  491. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/login()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  492. return BadRequest();
  493. }
  494. }
  495. 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)
  496. {
  497. //班級課程
  498. object classinfo = null;
  499. List<object> courses = new List<object>();
  500. ////校本
  501. //取得所屬預設班級信息
  502. if (!string.IsNullOrWhiteSpace(classId))
  503. {
  504. var query = $"SELECT c.code, c.id, c.name, c.periodId, c.gradeId FROM c WHERE c.id = '{classId}'";
  505. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  506. {
  507. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  508. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  509. {
  510. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  511. {
  512. classinfo = obj.ToObject<object>();
  513. }
  514. }
  515. }
  516. }
  517. //取得該學生跑班課名單ID
  518. List<string> stulistidsSch = new List<string>();
  519. var querysl = $"SELECT c.id FROM c JOIN members IN c.members WHERE members.id = '{id}' AND members.code = '{school_code}'";
  520. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: querysl, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GroupList-{school_code}") }))
  521. {
  522. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  523. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  524. {
  525. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  526. {
  527. stulistidsSch.Add(obj.GetProperty("id").ToString());
  528. }
  529. }
  530. }
  531. //取得該學生的學校課程名單
  532. 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))";
  533. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: queryc, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{school_code}") }))
  534. {
  535. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  536. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  537. {
  538. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  539. {
  540. courses.Add(obj.ToObject<object>());
  541. }
  542. }
  543. }
  544. ////個人
  545. //取得該學生跑班課名單ID
  546. Dictionary<string, Dictionary<string, string>> stulistidsTea = new Dictionary<string, Dictionary<string, string>>();
  547. 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}'";
  548. await foreach (var item in teacherClient.GetItemQueryStreamIterator(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("GroupList") }))
  549. {
  550. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  551. var js = json.RootElement.ToJsonString();
  552. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  553. {
  554. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  555. {
  556. string courseCode = "";
  557. if (obj.TryGetProperty("courseCode", out var code))
  558. {
  559. courseCode = code.GetString();
  560. }
  561. string courseId = "";
  562. if (obj.TryGetProperty("courseId", out var cosid))
  563. {
  564. courseId = cosid.GetString();
  565. }
  566. string stulistId = "";
  567. if (obj.TryGetProperty("id", out var listId))
  568. {
  569. stulistId = listId.GetString();
  570. }
  571. if (!string.IsNullOrEmpty(courseCode))
  572. {
  573. if (!stulistidsTea.ContainsKey(courseCode))
  574. {
  575. Dictionary<string, string> pCourseIdDic = new Dictionary<string, string>();
  576. pCourseIdDic.Add(courseId, stulistId);
  577. stulistidsTea.Add(courseCode, pCourseIdDic);
  578. }
  579. else
  580. {
  581. if (!stulistidsTea[courseCode].ContainsKey(courseId))
  582. {
  583. stulistidsTea[courseCode].Add(courseId, stulistId);
  584. }
  585. }
  586. }
  587. }
  588. }
  589. }
  590. //取得該學生的老師個人課程名單
  591. foreach (KeyValuePair<string, Dictionary<string, string>> item in stulistidsTea)
  592. {
  593. string courseCode = item.Key;
  594. Dictionary<string, string> courseIdDic = item.Value;
  595. string stucourseWhere = string.Empty;
  596. foreach (KeyValuePair<string, string> itemDic in courseIdDic)
  597. {
  598. string courseId = itemDic.Key;
  599. string stuListId = itemDic.Value;
  600. if (!string.IsNullOrWhiteSpace(stucourseWhere))
  601. {
  602. stucourseWhere += " OR ";
  603. }
  604. stucourseWhere += $"( c.id = '{courseId}' AND schedule.stulist = '{stuListId}' )";
  605. }
  606. 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}";
  607. await foreach (var itemcs in teacherClient.GetItemQueryStreamIterator(queryText: querycst, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{courseCode}") }))
  608. {
  609. using var json = await JsonDocument.ParseAsync(itemcs.ContentStream);
  610. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  611. {
  612. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  613. {
  614. courses.Add(obj.ToObject<object>());
  615. }
  616. }
  617. }
  618. }
  619. // BLOB(學校,唯讀)
  620. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(school_code.ToLower(), BlobContainerSasPermissions.Read);
  621. //換取AuthToken,提供給前端
  622. var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name, picture, _option.JwtSecretKey, scope: Constant.ScopeStudent, Website: "IES", areaId: areaId, schoolID: school_code, roles: new[] { "student" }, expire: 1,year: student.year);
  623. //用户在线记录
  624. try
  625. {
  626. _ = _httpTrigger.RequestHttpTrigger(new { school = school_code, scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  627. }
  628. catch {}
  629. await cosmosClient.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, id, new PartitionKey($"Base-{school_code}"));
  630. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  631. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  632. var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, _option.Location.Replace("-Dep", "").Replace("-Test", ""));
  633. return (auth_token, blob_uri, blob_sas, classinfo, courses, token);
  634. }
  635. /// <summary>
  636. /// 學生教育雲登入
  637. /// </summary>
  638. /// <param name = "request" ></ param >
  639. [AllowAnonymous]
  640. [HttpPost("login-open")]
  641. public async Task<IActionResult> OpenIDLogin(JsonElement request)
  642. {
  643. try
  644. {
  645. var client = _azureCosmos.GetCosmosClient();
  646. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  647. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  648. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  649. //參數取得
  650. string location = _option.Location;
  651. if (!request.TryGetProperty("open_code", out JsonElement _open_code)) return BadRequest();
  652. if (!location.Contains("Global")) return BadRequest();
  653. string grant_type = "educloudtw";
  654. string client_id = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  655. string redirect_uri = _configuration.GetValue<string>("HaBookAuth:CoreAccountAPI");
  656. string nonce = RandomString(16);
  657. string lang = "zh-tw";
  658. string open_code = _open_code.GetString();
  659. if(!open_code.Contains("EduCloudTWL")) return BadRequest();
  660. bool is_extrnal_id = true;
  661. //向CS取得OpenData
  662. stuOpenData openData = new stuOpenData();
  663. string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  664. string csv2Url = $"{csv2Domain}/oauth2/Login";
  665. Dictionary<string, object> dict = new() {
  666. { "grant_type", grant_type },
  667. { "client_id", client_id },
  668. { "redirect_uri", $"{redirect_uri}/" },
  669. { "nonce", nonce },
  670. { "lang", lang },
  671. { "open_code", open_code },
  672. { "is_extrnal_id", is_extrnal_id }
  673. };
  674. var httpClient = _httpClient.CreateClient();
  675. HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  676. HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  677. if (httpResponse.StatusCode == HttpStatusCode.OK)
  678. {
  679. string responseContent = await httpResponse.Content.ReadAsStringAsync();
  680. openData = responseContent.ToObject<stuOpenData>();
  681. if (string.IsNullOrWhiteSpace(openData.open_id) || string.IsNullOrWhiteSpace(openData.schoolCode))
  682. {
  683. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  684. }
  685. }
  686. else
  687. {
  688. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  689. }
  690. //用OpenData取得學生資訊
  691. Student stuinfo = new Student();
  692. var queryLogin = $"SELECT * FROM c WHERE c.pk = 'Base' AND IS_DEFINED(c.openId) AND c.openId = '{openData.open_id}'";
  693. await foreach (var item in studentClient.GetItemQueryStreamIterator(queryText: queryLogin, requestOptions: null))
  694. {
  695. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  696. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16().Equals(1))
  697. {
  698. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  699. {
  700. stuinfo = obj.ToObject<Student>();
  701. }
  702. }
  703. }
  704. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  705. string schShortCode = string.Empty;
  706. School school = new School();
  707. //分歧1 有此學生 => Login流程
  708. if (!string.IsNullOrWhiteSpace(stuinfo.id))
  709. {
  710. if (stuinfo.graduate.Equals(1))
  711. {
  712. return Ok(new { error = 3, message = "Graduate already!" });
  713. }
  714. //取得學校資訊
  715. schShortCode = stuinfo.schoolId;
  716. try
  717. {
  718. school = await schoolClient.ReadItemAsync<School>($"{schShortCode}", new PartitionKey("Base"));
  719. }
  720. catch (CosmosException ex)
  721. {
  722. return Ok(new { error = 2, message = "Can not find school data." });
  723. }
  724. //Login流程回傳值
  725. (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);
  726. int countAuthorized = await GetStudentAuthNumByScale($"{school.id}", school);
  727. 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 } });
  728. }
  729. //分歧2 無此學生 => 取得該校同名學生資訊
  730. else
  731. {
  732. //把OpenData的schoolCode換成學校簡碼
  733. string AccessToken = await getCoreAccessToken();
  734. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  735. string csv2SchoolUrl = $"{csv2Domain}/service/SchoolData";
  736. Dictionary<string, object> csv2SchDict = new() {
  737. { "code", openData.schoolCode }
  738. };
  739. HttpContent csv2SchContent = new StringContent(csv2SchDict.ToJsonString(), Encoding.UTF8, "application/json");
  740. HttpResponseMessage csv2SchHttpResponse = await httpClient.PostAsync(csv2SchoolUrl, csv2SchContent);
  741. if (csv2SchHttpResponse.StatusCode == HttpStatusCode.OK)
  742. {
  743. string responseContent = await csv2SchHttpResponse.Content.ReadAsStringAsync();
  744. if (!string.IsNullOrWhiteSpace(responseContent))
  745. {
  746. List<csSchApiResponse> csSchResult = responseContent.ToObject<List<csSchApiResponse>>();
  747. if (!csSchResult.Count.Equals(1))
  748. {
  749. return Ok(new { error = 2, message = "Can not find school data." });
  750. }
  751. foreach (csSchApiResponse csv2Sch in csSchResult)
  752. {
  753. schShortCode = csv2Sch.code;
  754. }
  755. }
  756. }
  757. else
  758. {
  759. return Ok(new { error = 2, message = "Can not find school data." });
  760. }
  761. if (string.IsNullOrWhiteSpace(schShortCode))
  762. {
  763. return Ok(new { error = 2, message = "Can not find school data." });
  764. }
  765. //用OpenData取得學校資訊
  766. try
  767. {
  768. school = await schoolClient.ReadItemAsync<School>($"{schShortCode}", new PartitionKey("Base"));
  769. }
  770. catch (CosmosException ex)
  771. {
  772. return Ok(new { error = 2, message = "Can not find school data." });
  773. }
  774. //學段
  775. Dictionary<string, string> periodDic = new Dictionary<string, string>();
  776. foreach(Period stuSchoolPeriod in school.period)
  777. {
  778. periodDic.Add(stuSchoolPeriod.id, stuSchoolPeriod.name);
  779. }
  780. //取得同名學生
  781. HashSet<string> classIds = new HashSet<string>();
  782. List<Student> stuList = new List<Student>();
  783. var queryStuSame = $"SELECT * FROM c WHERE c.name = '{openData.open_name}'";
  784. await foreach (var item in studentClient.GetItemQueryStreamIterator(queryText: queryStuSame, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{schShortCode}") }))
  785. {
  786. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  787. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  788. {
  789. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  790. {
  791. Student stuRow = obj.ToObject<Student>();
  792. stuList.Add(stuRow);
  793. if(!string.IsNullOrWhiteSpace(stuRow.classId))
  794. {
  795. classIds.Add(stuRow.classId);
  796. }
  797. }
  798. }
  799. }
  800. if(stuList.Count.Equals(0))
  801. {
  802. return Ok(new { error = 4, message = "Can not find any student." });
  803. }
  804. if (classIds.Count.Equals(0))
  805. {
  806. return Ok(new { error = 6, message = "Can not find any class." });
  807. }
  808. //取得學校班級
  809. Dictionary<string, string> classDic = new Dictionary<string, string>();
  810. string classIdJsonStr = JsonSerializer.Serialize(classIds);
  811. var query = $"SELECT c.code, c.id, c.name, c.periodId, c.gradeId FROM c WHERE ARRAY_CONTAINS({classIdJsonStr}, c.id)";
  812. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{schShortCode}") }))
  813. {
  814. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  815. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  816. {
  817. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  818. {
  819. Class classinfo = obj.ToObject<Class>();
  820. classDic.Add(classinfo.id, classinfo.name);
  821. }
  822. }
  823. }
  824. if (classDic.Count.Equals(0))
  825. {
  826. return Ok(new { error = 6, message = "Can not find any class." });
  827. }
  828. //OpenID AES加密
  829. string aeskey = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  830. string aesiv = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  831. openToken openToken = new openToken() { openId = openData.open_id, shortCode = schShortCode };
  832. string encOpenToken = LoginService.AesEncrypt(JsonSerializer.Serialize(openToken), aeskey, aesiv);
  833. //回傳值
  834. var schResult = new { schoolId = school.id, schoolName = school.name };
  835. List<stuOpenDataOrientation> stuResult = new List<stuOpenDataOrientation>();
  836. foreach (Student studata in stuList)
  837. {
  838. stuOpenDataOrientation stuResultRow = new stuOpenDataOrientation();
  839. stuResultRow.id = studata.id;
  840. stuResultRow.name = studata.name;
  841. stuResultRow.classId = studata.classId;
  842. stuResultRow.className = (!string.IsNullOrWhiteSpace(studata.classId) && classDic.ContainsKey(studata.classId)) ? classDic[studata.classId] : string.Empty;
  843. stuResultRow.periodId = studata.periodId;
  844. stuResultRow.periodName = (!string.IsNullOrWhiteSpace(studata.periodId) && periodDic.ContainsKey(studata.periodId)) ? periodDic[studata.periodId] : string.Empty;
  845. stuResultRow.year = studata.year;
  846. stuResultRow.no = studata.no;
  847. Period curPeriod = new Period();
  848. if (!string.IsNullOrWhiteSpace(studata.periodId))
  849. {
  850. curPeriod = school.period.Where(p => p.id.Equals(studata.periodId)).FirstOrDefault();
  851. }
  852. ExamSimple gradeInfo = new ExamSimple();
  853. if (!string.IsNullOrWhiteSpace(curPeriod.id))
  854. {
  855. gradeInfo = getGradeInfoByYear(studata.year, curPeriod);
  856. }
  857. stuResultRow.gradeIndex = (!string.IsNullOrWhiteSpace(gradeInfo.id)) ? gradeInfo.id : string.Empty;
  858. stuResultRow.gradeName = (!string.IsNullOrWhiteSpace(gradeInfo.name)) ? gradeInfo.name : string.Empty;
  859. stuResult.Add(stuResultRow);
  860. }
  861. //回傳值
  862. return Ok(new { openToken = encOpenToken, school = schResult, students = stuResult } );
  863. }
  864. }
  865. catch (Exception ex)
  866. {
  867. //await _dingDing.SendBotMsg($"OS,{_option.Location},student/openlogin()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  868. return BadRequest();
  869. }
  870. }
  871. /// <summary>
  872. /// 學生追加教育雲ID
  873. /// </summary>
  874. /// <param name = "request" ></ param >
  875. [AllowAnonymous]
  876. [HttpPost("add-open-stu")]
  877. public async Task<IActionResult> addOpenidToStudent(JsonElement request)
  878. {
  879. try
  880. {
  881. //參數取得
  882. string location = _option.Location;
  883. string openToken = (request.TryGetProperty("open_token", out JsonElement _open_token)) ? _open_token.GetString() : string.Empty;
  884. if(string.IsNullOrWhiteSpace(openToken)) return BadRequest();
  885. if (!location.Contains("Global")) return BadRequest();
  886. //string schoolCode = (request.TryGetProperty("school_code", out JsonElement _school_code)) ? _school_code.GetString() : string.Empty;
  887. //if (string.IsNullOrWhiteSpace(schoolCode)) return BadRequest();
  888. string stuid = (request.TryGetProperty("stuid", out JsonElement _stuid)) ? _stuid.GetString() : string.Empty;
  889. if (string.IsNullOrWhiteSpace(stuid)) return BadRequest();
  890. //OpenID AES解密
  891. string aeskey = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  892. string aesiv = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  893. string openTokenString = LoginService.AesDecrypt(openToken, aeskey, aesiv);
  894. if(string.IsNullOrWhiteSpace(openTokenString)) return BadRequest();
  895. openToken decOpenToken = openTokenString.ToObject<openToken>();
  896. if(string.IsNullOrWhiteSpace(decOpenToken.openId) || string.IsNullOrWhiteSpace(decOpenToken.shortCode)) return BadRequest();
  897. string schoolCode = decOpenToken.shortCode;
  898. string openId = decOpenToken.openId;
  899. var client = _azureCosmos.GetCosmosClient();
  900. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  901. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  902. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  903. //取得學校基本資料
  904. School school = new School();
  905. try
  906. {
  907. school = await schoolClient.ReadItemAsync<School>($"{schoolCode}", new PartitionKey("Base"));
  908. }
  909. catch (CosmosException ex)
  910. {
  911. return Ok(new { error = 1, message = "Can not find school data." });
  912. }
  913. //取得學生基本資料
  914. Student student = new Student();
  915. try
  916. {
  917. student = await studentClient.ReadItemAsync<Student>($"{stuid}", new PartitionKey($"Base-{schoolCode}"));
  918. }
  919. catch (CosmosException ex)
  920. {
  921. return Ok(new { error = 2, message = "Can not find student data." });
  922. }
  923. if (student.graduate.Equals(1))
  924. {
  925. return Ok(new { error = 3, message = "Graduate already!" });
  926. }
  927. //1.向CS詢問是否已綁定
  928. stuOpenData openData = new stuOpenData();
  929. var httpClient = _httpClient.CreateClient();
  930. string AccessToken = await getCoreAccessToken();
  931. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  932. string grant_type = "bind";
  933. string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  934. string csv2Url = $"{csv2Domain}/oauth2/EduCloudTWBingManage";
  935. Dictionary<string, object> dict = new() {
  936. { "grant_type", grant_type },
  937. { "open_id", openId },
  938. { "id", $"Base-{school.id},{student.id}" }
  939. };
  940. HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  941. HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  942. if (httpResponse.StatusCode == HttpStatusCode.OK)
  943. {
  944. string responseContent = await httpResponse.Content.ReadAsStringAsync();
  945. if(!string.IsNullOrWhiteSpace(responseContent))
  946. {
  947. csApiResponse csResult = responseContent.ToObject<csApiResponse>();
  948. if(csResult.error.Equals(2))
  949. {
  950. return Ok(new { error = 5, message = "This id already exist." });
  951. }
  952. }
  953. }
  954. else
  955. {
  956. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  957. }
  958. //2.將OpenID放入學生Base
  959. student.openId = openId;
  960. //await studentClient.ReplaceItemAsync(student, student.id);
  961. //3.學生登入流程(包含學生資料更新)
  962. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  963. (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);
  964. int countAuthorized = await GetStudentAuthNumByScale($"{schoolCode}", school);
  965. 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 } });
  966. }
  967. catch (Exception ex)
  968. {
  969. //await _dingDing.SendBotMsg($"OS,{_option.Location},student/addOpenidToStudent()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  970. return BadRequest();
  971. }
  972. }
  973. /// <summary>
  974. /// 學生解綁教育雲ID
  975. /// </summary>
  976. /// <param name = "request" ></ param >
  977. [AllowAnonymous]
  978. [HttpPost("rmv-open-stu")]
  979. public async Task<IActionResult> rmvOpenidToStudent(JsonElement request)
  980. {
  981. if (!request.TryGetProperty("school_code", out JsonElement _school_code)) return BadRequest();
  982. string schoolCode = _school_code.GetString();
  983. if(string.IsNullOrWhiteSpace(schoolCode)) return BadRequest();
  984. if (!request.TryGetProperty("stuid", out JsonElement _stuid)) return BadRequest();
  985. string stuId = _stuid.GetString();
  986. if(string.IsNullOrWhiteSpace(stuId)) return BadRequest();
  987. string openType = (request.TryGetProperty("type", out JsonElement _type)) ? _type.GetString().ToLower() : "educloudtwl"; //educloudtwl: 教育雲
  988. var client = _azureCosmos.GetCosmosClient();
  989. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  990. //取得學生基本資料
  991. Student student = new Student();
  992. try
  993. {
  994. student = await studentClient.ReadItemAsync<Student>($"{stuId}", new PartitionKey($"Base-{schoolCode}"));
  995. }
  996. catch (CosmosException ex)
  997. {
  998. return Ok(new { error = 2, message = "Can not find student data." });
  999. }
  1000. //CSV解綁
  1001. bool rmvSuccess = false;
  1002. if (openType.Equals("educloudtwl")) //教育雲
  1003. {
  1004. string openId = student.openId;
  1005. if(string.IsNullOrWhiteSpace(openId)) //學生無OpenID,視為解綁成功
  1006. {
  1007. return Ok(new { error = 0, message = string.Empty });
  1008. }
  1009. stuOpenData openData = new stuOpenData();
  1010. var httpClient = _httpClient.CreateClient();
  1011. string AccessToken = await getCoreAccessToken();
  1012. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
  1013. string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  1014. string csv2Url = $"{csv2Domain}/oauth2/EduCloudTWBingManage";
  1015. Dictionary<string, object> dict = new() {
  1016. { "grant_type", "unbind" },
  1017. { "open_id", openId },
  1018. { "id", $"Base-{schoolCode},{student.id}" }
  1019. };
  1020. HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
  1021. HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
  1022. if (httpResponse.StatusCode == HttpStatusCode.OK)
  1023. {
  1024. string responseContent = await httpResponse.Content.ReadAsStringAsync();
  1025. if (string.IsNullOrWhiteSpace(responseContent))
  1026. {
  1027. rmvSuccess = true;
  1028. }
  1029. else
  1030. {
  1031. csApiResponse csResult = responseContent.ToObject<csApiResponse>();
  1032. if (!string.IsNullOrWhiteSpace(csResult.message))
  1033. {
  1034. return Ok(new { error = csResult.error, message = csResult.message });
  1035. }
  1036. }
  1037. }
  1038. else
  1039. {
  1040. return Ok(new { error = 1, message = "Can not get opendata from CS." });
  1041. }
  1042. }
  1043. //學生資料變更
  1044. if(rmvSuccess)
  1045. {
  1046. if (openType.Equals("educloudtwl")) //教育雲
  1047. {
  1048. student.openId = null;
  1049. }
  1050. //DB更新
  1051. await studentClient.ReplaceItemAsync(student, student.id);
  1052. return Ok(new { error = 0, message = string.Empty });
  1053. }
  1054. return Ok(new { error = 9, message = "Can not unbind student." });
  1055. }
  1056. //查询学生名单详情
  1057. [ProducesDefaultResponseType]
  1058. //[AuthToken(Roles = "teacher")]
  1059. [HttpPost("get-summary-student")]
  1060. [AuthToken(Roles = "teacher,admin,student")]
  1061. #if !DEBUG
  1062. [Authorize(Roles = "IES")]
  1063. #endif
  1064. public async Task<IActionResult> getSummary(JsonElement request)
  1065. {
  1066. try
  1067. {
  1068. request.TryGetProperty("students", out JsonElement students);
  1069. request.TryGetProperty("tmdIds", out JsonElement tmdIds);
  1070. List<TmdInfo> tmdinfos = new List<TmdInfo>();
  1071. List<object> stus = new List<object>();
  1072. var client = _azureCosmos.GetCosmosClient();
  1073. if (students.ValueKind.Equals(JsonValueKind.Array))
  1074. {
  1075. List<Students> stuList = students.ToObject<List<Students>>();
  1076. if (stuList.IsNotEmpty())
  1077. {
  1078. foreach (Students stu in stuList)
  1079. {
  1080. 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}'";
  1081. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{stu.code}") }))
  1082. {
  1083. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1084. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1085. {
  1086. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1087. {
  1088. stus.Add(obj.ToObject<object>());
  1089. }
  1090. }
  1091. }
  1092. }
  1093. }
  1094. }
  1095. if (tmdIds.ValueKind.Equals(JsonValueKind.Array))
  1096. {
  1097. List<string> tmdids = tmdIds.ToObject<List<string>>();
  1098. if (tmdids.IsNotEmpty())
  1099. {
  1100. List<string> inids = new List<string>();
  1101. tmdids.ForEach(x => { inids.Add($"'{x}'"); });
  1102. var insql = string.Join(",", inids);
  1103. var queryslt = $"SELECT c.id,c.name,c.picture FROM c where c.id in ({insql})";
  1104. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryIterator<TmdInfo>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
  1105. {
  1106. tmdinfos.Add(item);
  1107. }
  1108. }
  1109. }
  1110. return Ok(new { stus, tmdinfos });
  1111. }
  1112. catch (Exception ex)
  1113. {
  1114. await _dingDing.SendBotMsg($"OS,{_option.Location},student/get-summary-student()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  1115. return BadRequest();
  1116. }
  1117. }
  1118. /// <summary>
  1119. /// 學生簡易登入
  1120. /// </summary>
  1121. /// <param name = "request" ></ param >
  1122. [AllowAnonymous]
  1123. [HttpPost("login-simple")]
  1124. public async Task<IActionResult> LoginSimple(JsonElement request)
  1125. {
  1126. try
  1127. {
  1128. var client = _azureCosmos.GetCosmosClient();
  1129. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  1130. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  1131. //參數取得
  1132. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  1133. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  1134. if (!request.TryGetProperty("pw", out JsonElement pw)) return BadRequest();
  1135. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  1136. var response = await studentClient.ReadItemStreamAsync(id.GetString(), new PartitionKey($"Base-{school_code.GetString().ToLower()}"));
  1137. if (response.Status == 200)
  1138. {
  1139. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  1140. Student student = rjson.ToObject<Student>();
  1141. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  1142. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  1143. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  1144. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  1145. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  1146. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  1147. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  1148. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  1149. dynamic user = new ExpandoObject();
  1150. user.no = no;
  1151. user.groupId = groupId;
  1152. user.groupName = groupName;
  1153. var HashedPW = Utils.HashedPassword(pw.ToString(), salt.ToString());
  1154. if (HashedPW.Equals(dbpw.GetString()))
  1155. {
  1156. School schoolInfo = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  1157. //取得所屬預設班級信息
  1158. object classinfo = null;
  1159. if (!classId.ValueKind.Equals(JsonValueKind.Null) && classId.ValueKind.Equals(JsonValueKind.String))
  1160. {
  1161. var query = $"SELECT c.id, c.no, c.name FROM c WHERE c.id = '{classId.GetString()}'";
  1162. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  1163. {
  1164. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  1165. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  1166. {
  1167. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  1168. {
  1169. classinfo = obj.ToObject<object>();
  1170. }
  1171. }
  1172. }
  1173. }
  1174. //換取AuthToken,提供給前端
  1175. var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, Website: "IES", areaId: schoolInfo.areaId, scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" }, expire: 1);
  1176. //用户在线记录
  1177. try
  1178. {
  1179. _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  1180. }
  1181. catch { }
  1182. //保存学生登录信息
  1183. await client.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"{student.code}"));
  1184. //其他訊息
  1185. dynamic school = new ExpandoObject();
  1186. //回傳
  1187. return Ok(new { error = 0, auth_token, classinfo, user });
  1188. }
  1189. else
  1190. {
  1191. return Ok(new { error = 1, message = "Invalid account or password" });
  1192. }
  1193. }
  1194. else
  1195. {
  1196. return Ok(new { error = 2, message = "Invalid account" });
  1197. }
  1198. }
  1199. catch (Exception ex)
  1200. {
  1201. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/login-simple()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  1202. return BadRequest();
  1203. }
  1204. }
  1205. //TODO 此API需處理對應前端返回的相關數據
  1206. [ProducesDefaultResponseType]
  1207. [AuthToken(Roles = "student,teacher")]
  1208. [HttpPost("get-school-info")]
  1209. public async Task<IActionResult> GetSchoolInfo(JsonElement request)
  1210. {
  1211. try
  1212. {
  1213. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  1214. var client = _azureCosmos.GetCosmosClient();
  1215. /// tmdid, schoolid
  1216. var userType = "schoolid";
  1217. if (request.TryGetProperty("userType", out JsonElement usertype))
  1218. {
  1219. if (!usertype.ValueKind.Equals(JsonValueKind.Undefined) && !usertype.ValueKind.Equals(JsonValueKind.Null) && usertype.ValueKind.Equals(JsonValueKind.String))
  1220. {
  1221. userType = usertype.GetString();
  1222. }
  1223. }
  1224. if (string.IsNullOrEmpty(school))
  1225. {
  1226. if (userType.Equals("tmdid"))
  1227. {
  1228. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
  1229. if (teacher.schools.IsNotEmpty())
  1230. {
  1231. var tech = teacher.schools.Find(x => x.status.Equals("join"));
  1232. if (tech == null)
  1233. {
  1234. school = teacher.schools[0].schoolId;
  1235. }
  1236. else
  1237. {
  1238. school = tech.schoolId;
  1239. }
  1240. }
  1241. }
  1242. }
  1243. if (!string.IsNullOrEmpty(school))
  1244. {
  1245. object school_base = null;
  1246. var response = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(school, new PartitionKey("Base"));
  1247. if (response.Status == 200)
  1248. {
  1249. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  1250. school_base = json.RootElement.ToObject<object>();
  1251. }
  1252. //取得班级
  1253. List<object> school_classes = new List<object>();
  1254. 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}") }))
  1255. {
  1256. var jsonc = await JsonDocument.ParseAsync(item.ContentStream);
  1257. foreach (var classeinfo in jsonc.RootElement.GetProperty("Documents").EnumerateArray())
  1258. {
  1259. school_classes.Add(classeinfo.ToObject<object>());
  1260. }
  1261. }
  1262. //取得教室
  1263. List<Room> school_rooms = new List<Room>();
  1264. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Room>(queryText: $"select value(c) from c ",
  1265. requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Room-{school}") }))
  1266. {
  1267. school_rooms.Add(item);
  1268. }
  1269. return Ok(new { school_base, school_classes, school_rooms, status = 200 });
  1270. }
  1271. else
  1272. {
  1273. return Ok(new { status = 404 }); ;
  1274. }
  1275. }
  1276. catch (CosmosException ex)
  1277. {
  1278. return Ok(new { status = ex.Status }); ;
  1279. }
  1280. catch (Exception ex)
  1281. {
  1282. await _dingDing.SendBotMsg($"IES5,{_option.Location},Student/get-school-info()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  1283. return BadRequest();
  1284. }
  1285. }
  1286. /**
  1287. * 根据学年获取年级信息
  1288. * @param year 学年
  1289. * @param Period 学段資料
  1290. */
  1291. private ExamSimple getGradeInfoByYear(int year, Period curPeriod)
  1292. {
  1293. ExamSimple result = new ExamSimple();
  1294. if (year > 0)
  1295. {
  1296. DateTime date = DateTime.UtcNow;
  1297. int curYear = date.Year;
  1298. int month = date.Month;
  1299. Semester semesterStart = curPeriod.semesters.Where((Semester x) => x.start.Equals(1)).FirstOrDefault();
  1300. if (semesterStart != null)
  1301. {
  1302. if (month < semesterStart.month)
  1303. {
  1304. curYear--;
  1305. }
  1306. int gradeIndex = curYear - year;
  1307. result.id = gradeIndex.ToString();
  1308. result.name = (gradeIndex >= curPeriod.grades.Count) ? "graduated" : (gradeIndex >= 0) ? curPeriod.grades[gradeIndex] : "not-enrollment";
  1309. }
  1310. }
  1311. return result;
  1312. }
  1313. //學生登入後根據學校規模取得授權數
  1314. private async Task<int> GetStudentAuthNumByScale(string school_code, School school)
  1315. {
  1316. //授权规模数量
  1317. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  1318. var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
  1319. string key = $"Login:School:{school_code}:student-day:{dateDay}";
  1320. SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
  1321. int countAuthorized = 0;
  1322. if (countStudent != null && countStudent.Length > 0)
  1323. {
  1324. bool notify = false;
  1325. countAuthorized = countStudent.Length;
  1326. if (school.scale > 0 && school.scale - countAuthorized <= 0)
  1327. {
  1328. //登录人数已达授权规模数上限
  1329. if (!string.IsNullOrWhiteSpace(school.areaId))
  1330. {
  1331. AreaSetting areaSetting = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemAsync<AreaSetting>(school.areaId, new PartitionKey("AreaSetting"));
  1332. if (areaSetting.ignoreScaleExpire > dateTime.ToUnixTimeMilliseconds())
  1333. {
  1334. //将人数控制在最大规模数以下。
  1335. countAuthorized = school.scale - 1;
  1336. }
  1337. else
  1338. {
  1339. notify = true;
  1340. }
  1341. }
  1342. else
  1343. {
  1344. notify = true;
  1345. }
  1346. if (notify)
  1347. {
  1348. //通知key 一天只通知一次
  1349. string scaleNotifykey = $"Login:School:{school.id}:student-scale-notify:{dateDay}";
  1350. bool Exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync(scaleNotifykey);
  1351. if (!Exists)
  1352. {
  1353. //获取学校管理员
  1354. List<IdNameCode> ids = new List<IdNameCode>();
  1355. string sql = $"select value c from c where c.code='Teacher-{school.id}' and c.status='join' and array_contains(c.roles,'admin') ";
  1356. List<SchoolTeacher> adminTeachers = new List<SchoolTeacher>();
  1357. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  1358. .GetItemQueryIterator<SchoolTeacher>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Teacher-{school.id}") }))
  1359. {
  1360. adminTeachers.Add(item);
  1361. }
  1362. if (adminTeachers.IsNotEmpty())
  1363. {
  1364. string sqlAdmin = $"select c.id,c.lang as code ,c.name from c where c.id in ({string.Join(",", adminTeachers.Select(z => $"'{z.id}'"))}) ";
  1365. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
  1366. .GetItemQueryIterator<IdNameCode>(queryText: sqlAdmin, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
  1367. {
  1368. ids.Add(item);
  1369. }
  1370. foreach (var uds in ids)
  1371. {
  1372. _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);
  1373. }
  1374. await _azureRedis.GetRedisClient(8).StringSetAsync(scaleNotifykey, scaleNotifykey, new TimeSpan(hours: 24, minutes: 0, seconds: 0));
  1375. }
  1376. }
  1377. }
  1378. }
  1379. }
  1380. return countAuthorized;
  1381. }
  1382. private async Task<string> getCoreAccessToken()
  1383. {
  1384. string AccessToken = "";
  1385. try
  1386. {
  1387. string Url = _configuration.GetValue<string>("HaBookAuth:CoreAPI") + "/oauth2/token";
  1388. string GrantType = "device";
  1389. string ClientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  1390. string Secret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  1391. var content = new { grant_type = GrantType, client_id = ClientID, client_secret = Secret };
  1392. var response = await _httpClient.CreateClient().PostAsJsonAsync($"{Url}", content);
  1393. if (response.IsSuccessStatusCode)
  1394. {
  1395. string responseBody = response.Content.ReadAsStringAsync().Result;
  1396. using (JsonDocument document = JsonDocument.Parse(responseBody.ToString()))
  1397. {
  1398. if (document.RootElement.TryGetProperty("access_token", out JsonElement AccessTokenObj))
  1399. {
  1400. AccessToken = AccessTokenObj.ToString();
  1401. }
  1402. }
  1403. }
  1404. return AccessToken;
  1405. }
  1406. catch (Exception ex)
  1407. {
  1408. return AccessToken;
  1409. }
  1410. }
  1411. public static string RandomString(int length)
  1412. {
  1413. Random random = new Random();
  1414. const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  1415. return new string(Enumerable.Repeat(chars, length)
  1416. .Select(s => s[random.Next(s.Length)]).ToArray());
  1417. }
  1418. //取得學生OpenData
  1419. private class stuOpenData
  1420. {
  1421. public string open_id { get; set; }
  1422. public string open_name { get; set; }
  1423. public string open_mail { get; set; }
  1424. public string schoolCode { get; set; }
  1425. }
  1426. //CS API 返回架構
  1427. private class csApiResponse
  1428. {
  1429. public int error { get; set; }
  1430. public string message { get; set; }
  1431. }
  1432. //CSV2 學校資料庫 API 返回架構
  1433. private class csSchApiResponse
  1434. {
  1435. public string code { get; set; }
  1436. public string name { get; set; }
  1437. public string type { get; set; }
  1438. }
  1439. //把openId打包成openToken架構
  1440. private class openToken
  1441. {
  1442. public string openId { get; set; }
  1443. public string shortCode { get; set; }
  1444. }
  1445. //無法取得OpenID搜尋學生姓名回傳的學生資料
  1446. private class stuOpenDataOrientation
  1447. {
  1448. public string id { get; set; }
  1449. public string name { get; set; }
  1450. public string classId { get; set; }
  1451. public string className { get; set; }
  1452. public string periodId { get; set; }
  1453. public string periodName { get; set; }
  1454. public int year { get; set; }
  1455. public string gradeIndex { get; set; }
  1456. public string gradeName { get; set; }
  1457. public string no { get; set; }
  1458. }
  1459. }
  1460. }