StudentController.cs 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  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.Text;
  10. using System.Text.Json;
  11. using System.Threading.Tasks;
  12. using Azure;
  13. using Azure.Cosmos;
  14. using Azure.Messaging.ServiceBus;
  15. using Azure.Storage.Sas;
  16. using HTEXLib.COMM.Helpers;
  17. using Microsoft.AspNetCore.Authorization;
  18. using Microsoft.AspNetCore.Cryptography.KeyDerivation;
  19. using Microsoft.AspNetCore.Mvc;
  20. using Microsoft.Extensions.Configuration;
  21. using Microsoft.Extensions.Options;
  22. using Microsoft.Identity.Client;
  23. using TEAMModelOS.Filter;
  24. using TEAMModelOS.Models;
  25. using TEAMModelOS.SDK;
  26. using TEAMModelOS.SDK.DI;
  27. using TEAMModelOS.SDK.Extension;
  28. using TEAMModelOS.SDK.Models;
  29. using TEAMModelOS.SDK.Models.Cosmos;
  30. using TEAMModelOS.SDK.Models.Cosmos.Common;
  31. using TEAMModelOS.SDK.Models.Service;
  32. using static TEAMModelOS.SDK.StudentService;
  33. namespace TEAMModelOS.Controllers
  34. {
  35. [Route("student")]
  36. [ApiController]
  37. public class StudentController : Controller
  38. {
  39. private readonly AzureCosmosFactory _azureCosmos;
  40. private readonly AzureStorageFactory _azureStorage;
  41. private readonly AzureRedisFactory _azureRedis;
  42. private readonly DingDing _dingDing;
  43. private readonly Option _option;
  44. private readonly IPSearcher _searcher;
  45. private readonly HttpTrigger _httpTrigger;
  46. public IConfiguration _configuration { get; set; }
  47. private readonly AzureServiceBusFactory _serviceBus;
  48. public StudentController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IPSearcher searcher, IOptionsSnapshot<Option> option,IConfiguration configuration, AzureServiceBusFactory serviceBus, HttpTrigger httpTrigger
  49. )
  50. {
  51. _searcher = searcher;
  52. _azureCosmos = azureCosmos;
  53. _azureStorage = azureStorage;
  54. _azureRedis = azureRedis;
  55. _dingDing = dingDing;
  56. _option = option?.Value;
  57. _configuration = configuration;
  58. _serviceBus = serviceBus;
  59. _httpTrigger = httpTrigger;
  60. }
  61. /// <summary>
  62. /// 學生帳號管理
  63. /// </summary>
  64. /// <param name="request"></param>
  65. /// <returns></returns>
  66. [HttpPost("student-manage")]
  67. [Authorize(Roles = "IES")]
  68. [AuthToken(Roles = "admin,student,teacher")]
  69. public async Task<IActionResult> StudentManage(JsonElement request)
  70. {
  71. try
  72. {
  73. //TODO : 權限檢查、學校檢查。
  74. if (!request.TryGetProperty("grant_type", out JsonElement grant_type) || !request.TryGetProperty("schoolId", out JsonElement schoolId)) return BadRequest();
  75. List<Student> preStudents = null;
  76. List<Student> webStudents = null;
  77. var (stuid, _, _, school) = HttpContext.GetAuthTokenInfo();
  78. switch (grant_type.GetString())
  79. {
  80. case "create":
  81. //單人創建 創建學生->將學生加入教室 檢查學生ID是否重複,欲加入的班級存不存在,座號是否重複。
  82. //id pw name classId no year
  83. //retrun 如果有重複則回{ existNo } , 成功則{ id, name, year, classId, no }
  84. var importStuds = request.GetProperty("students").EnumerateArray();
  85. Dictionary<string, GroupChange> dictChange = new Dictionary<string, GroupChange>();
  86. while (importStuds.MoveNext())
  87. {
  88. JsonElement currStud = importStuds.Current;
  89. string id = null, name = null, pw = null, no = null, classId = null, periodId = null, imei = null ;
  90. List<StudentGuardian> guardians = null;
  91. int year = 0;
  92. //讀取輸入的資料
  93. if (!currStud.TryGetProperty("id", out var tmpId) || !currStud.TryGetProperty("name", out var tmpName)) continue;
  94. id = tmpId.GetString();
  95. name = tmpName.GetString();
  96. if (currStud.TryGetProperty("pw", out var tmpPw)) pw = tmpPw.GetString();
  97. if (currStud.TryGetProperty("year", out var tmpYear)) year = tmpYear.GetInt32();
  98. if (currStud.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
  99. if (currStud.TryGetProperty("classId", out var tmpClassId)) classId = tmpClassId.GetString();
  100. if (currStud.TryGetProperty("imei", out var tmpImei)) imei = $"{tmpImei}";
  101. if (currStud.TryGetProperty("guardians", out var tmpGuardians)) {
  102. guardians = tmpGuardians.Deserialize<List<StudentGuardian>>();
  103. }
  104. //要檢查座號使否已被使用
  105. var existNo = await StudentService.checkStudNo(_azureCosmos, _dingDing, _option, schoolId.GetString(), classId, new List<string>() { id });
  106. if (existNo.Count != 0) return this.Ok(new { code = $"Base-{schoolId.GetString()}", existNo = existNo.Select(o => o.id).ToList() });
  107. if (currStud.TryGetProperty("periodId", out var tempPeriodId)) periodId = tempPeriodId.GetString();
  108. //建立學生
  109. studCreateInfo studCreateInfo = new studCreateInfo(id, name, "M", year, pw, classId, no, periodId,imei,guardians);
  110. var isCreateSuc = await createStudent(_azureCosmos, _dingDing, _option, schoolId.GetString(), studCreateInfo);
  111. if (isCreateSuc)
  112. {
  113. if (dictChange.ContainsKey(classId))
  114. {
  115. dictChange[classId].stujoin.Add(
  116. new Member
  117. {
  118. id = id,
  119. code = $"{schoolId.GetString()}",
  120. type = 2,
  121. });
  122. }
  123. else
  124. {
  125. GroupChange change = new GroupChange
  126. {
  127. scope = "school",
  128. school = schoolId.GetString(),
  129. type = "student",
  130. originCode = schoolId.GetString(),
  131. listid = classId,
  132. stujoin = new List<Member>
  133. {
  134. new Member
  135. {
  136. id= id,
  137. code=schoolId.GetString(),
  138. type=2,
  139. }
  140. }
  141. };
  142. dictChange.Add(classId, change);
  143. }
  144. foreach (var changed in dictChange.Keys)
  145. {
  146. var change = dictChange[changed];
  147. if (change.stujoin.Count != 0 || change.stuleave.Count != 0)
  148. {
  149. var messageChange = new ServiceBusMessage(change.ToJsonString());
  150. messageChange.ApplicationProperties.Add("name", "GroupChange");
  151. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  152. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageChange);
  153. }
  154. }
  155. return this.Ok(new { code = $"Base-{schoolId.GetString()}", id, name, year, classId, no, periodId });
  156. }
  157. else return this.Ok(new { code = $"Base-{schoolId.GetString()}", errorId = id });
  158. }
  159. break;
  160. case "import":
  161. //只有ClassNo可以比對
  162. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  163. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  164. var retUpsert = await StudentService.upsertStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  165. await CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  166. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpsert.studs, retUpsert.classDuplNos, retUpsert.errorIds });
  167. case "read":
  168. //讀取該間學校所有的學生資訊
  169. var students = await StudentService.getAllStudent(_azureCosmos, _dingDing, _option, schoolId.GetString());
  170. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students });
  171. case "update":
  172. //更新學生資料,批量密碼重置,基本資訊更新(姓名、教室ID、性別、學年及座號)
  173. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  174. var cleanImei = false;
  175. if (request.GetProperty("cleanImei").ValueKind.Equals(JsonValueKind.True))
  176. {
  177. cleanImei = true;
  178. }
  179. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  180. var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray(), cleanImei);
  181. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  182. return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpdate.studs, retUpdate.classDuplNos, retUpdate.nonexistentIds, retUpdate.errorNos, retUpdate.errorClassId });
  183. case "delete":
  184. //刪除學生資料及從教室學生名單內移除該學生
  185. webStudents = request.GetProperty("students").ToObject<List<Student>>();
  186. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  187. var sucDelIds = await StudentService.deleteStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  188. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  189. return this.Ok(new { code = $"Base-{schoolId.GetString()}", ids = sucDelIds });
  190. case "remove":
  191. //將學生基本資料內的classId、no、groupId及groupName寫入null
  192. List<string> stus = request.GetProperty("students").ToObject<List<string>>();
  193. webStudents = new List<Student>();
  194. foreach (string idstu in stus)
  195. {
  196. webStudents.Add(new Student { id = idstu, code = $"Base-{schoolId}" });
  197. }
  198. preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
  199. (List<string> studs, List<string> nonexistentIds, List<string> errorIds) retRemove = await StudentService.removeStudentClassInfo(
  200. _azureCosmos, _dingDing, _option,
  201. schoolId.GetString(), request.GetProperty("students").EnumerateArray());
  202. await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
  203. return Ok(new { code = $"Base-{schoolId.GetString()}", ids = retRemove.studs, retRemove.nonexistentIds, retRemove.errorIds });
  204. case "avatar":
  205. if (request.TryGetProperty("avatar", out JsonElement _avatar) && _avatar.ValueKind.Equals(JsonValueKind.Array))
  206. {
  207. List<StudentInfo> avatars = _avatar.ToObject<List<StudentInfo>>();
  208. if (avatars.IsNotEmpty())
  209. {
  210. List<Student> studentsp = new List<Student>();
  211. string insql = string.Join(',', avatars.Select(x => $"'{x.studentId}'"));
  212. string sql = $"select value(c) from c where c.id in ({insql}) ";
  213. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  214. .GetItemQueryIterator<Student>(sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
  215. {
  216. studentsp.Add(item);
  217. }
  218. (string url, string sas) = _azureStorage.GetBlobContainerSAS99Year($"{schoolId}", BlobContainerSasPermissions.Read);
  219. foreach (Student student in studentsp)
  220. {
  221. StudentInfo avatar = avatars.Find(x => x.studentId.Equals(student.id));
  222. student.picture = avatar != null ? $"{avatar.picture}?{sas}" : null;
  223. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").ReplaceItemAsync<Student>(student, student.id, new PartitionKey(student.code));
  224. }
  225. return Ok(new { students = studentsp.Select(x => new { x.id, x.picture, x.code, x.name }) });
  226. }
  227. else
  228. {
  229. return BadRequest();
  230. }
  231. }
  232. else
  233. {
  234. return BadRequest();
  235. }
  236. case "update-self-info":
  237. if (request.TryGetProperty("studentInfo", out JsonElement _studentInfo))
  238. {
  239. var studentInfo = _studentInfo.ToObject<StudentInfo>();
  240. if (studentInfo.studentId.Equals(stuid) && school.Equals($"{schoolId}"))
  241. {
  242. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  243. .ReadItemAsync<Student>(studentInfo.studentId, new PartitionKey($"Base-{schoolId}"));
  244. student.mail = string.IsNullOrEmpty(studentInfo.mail) ? student.mail : studentInfo.mail;
  245. student.mobile = string.IsNullOrEmpty(studentInfo.mobile) ? student.mobile : studentInfo.mobile;
  246. student.name = string.IsNullOrEmpty(studentInfo.name) ? student.name : studentInfo.name;
  247. (string url, string sas) = _azureStorage.GetBlobContainerSAS99Year($"{schoolId}", BlobContainerSasPermissions.Read);
  248. student.picture = string.IsNullOrEmpty(studentInfo.picture) ? student.picture : $"{studentInfo.picture}?{sas}";
  249. student.gender = string.IsNullOrEmpty(studentInfo.gender) ? student.gender : studentInfo.gender;
  250. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  251. .ReplaceItemAsync<Student>(student, studentInfo.studentId, new PartitionKey($"Base-{schoolId}"));
  252. return Ok(new { studentInfo });
  253. }
  254. else
  255. {
  256. return Ok(new { error = false, status = false, msg = "修改的不是自己的信息" });
  257. }
  258. }
  259. else
  260. {
  261. return BadRequest();
  262. }
  263. case "update-self-password":
  264. if (request.TryGetProperty("newpwd", out JsonElement _newpwd) &&
  265. request.TryGetProperty("oldpwd", out JsonElement _oldpwd) &&
  266. request.TryGetProperty("studentId", out JsonElement _studentId))
  267. {
  268. if ($"{_studentId}".Equals(stuid) && school.Equals($"{schoolId}"))
  269. {
  270. try
  271. {
  272. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  273. .ReadItemAsync<Student>($"{_studentId}", new PartitionKey($"Base-{schoolId}"));
  274. var HashedPW = Utils.HashedPassword($"{_oldpwd}", student.salt);
  275. if (HashedPW.Equals(student.pw))
  276. {
  277. student.pw = Utils.HashedPassword($"{_newpwd}", student.salt);
  278. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  279. .ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"Base-{schoolId}"));
  280. return Ok(new { status = true });
  281. }
  282. else
  283. {
  284. return Ok(new { error = false, status = false, msg = "密码不一致" });
  285. }
  286. }
  287. catch (Exception ex)
  288. {
  289. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  290. return Ok(new { error = false, status = false, msg = "账号不存在" });
  291. }
  292. }
  293. else
  294. {
  295. return Ok(new { error = false, status = false, msg = "修改的不是自己的密码" });
  296. }
  297. }
  298. else
  299. {
  300. return BadRequest();
  301. }
  302. case "read-self-info":
  303. if (request.TryGetProperty("studentId", out JsonElement __studentId))
  304. {
  305. try
  306. {
  307. Student student = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
  308. .ReadItemAsync<Student>($"{__studentId}", new PartitionKey($"Base-{schoolId}"));
  309. 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 });
  310. }
  311. catch (Exception ex)
  312. {
  313. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  314. }
  315. return Ok();
  316. }
  317. else
  318. {
  319. return BadRequest();
  320. }
  321. default:
  322. return BadRequest();
  323. }
  324. }
  325. catch (Exception ex)
  326. {
  327. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/StudentManage()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  328. }
  329. return BadRequest();
  330. }
  331. [HttpPost("get-student-info")]
  332. [Authorize(Roles = "IES")]
  333. public async Task<IActionResult> GetStudentInfo(JsonElement request)
  334. {
  335. var client = _azureCosmos.GetCosmosClient();
  336. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  337. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  338. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  339. if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
  340. var jwt = new JwtSecurityToken(id_token.GetString());
  341. var id = jwt.Payload.Sub;
  342. string school_code = jwt.Payload.Azp;
  343. //權限token
  344. jwt.Payload.TryGetValue("name", out object _name);
  345. jwt.Payload.TryGetValue("picture", out object _picture);
  346. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  347. School school = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  348. var response = await studentClient.ReadItemStreamAsync(id, new PartitionKey($"Base-{school_code.ToLower()}"));
  349. if (response.Status == 200)
  350. {
  351. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  352. Student student = rjson.ToObject<Student>();
  353. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  354. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  355. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  356. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  357. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  358. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  359. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  360. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  361. (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck($"{id}", $"{classId}", $"{school_code}", $"{picture}", $"{name}", schoolClient, teacherClient, school.areaId, ip, client, student);
  362. return Ok(new { 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 } });
  363. }
  364. else
  365. {
  366. return Ok(new { error = 2, message = "無此帳號存在" });
  367. }
  368. }
  369. /// <summary>
  370. /// 學生登入
  371. /// </summary>
  372. /// <param name = "request" ></ param >
  373. [AllowAnonymous]
  374. [HttpPost("login")]
  375. public async Task<IActionResult> Login(JsonElement request)
  376. {
  377. try
  378. {
  379. var client = _azureCosmos.GetCosmosClient();
  380. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  381. var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
  382. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  383. //參數取得
  384. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  385. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  386. if (!request.TryGetProperty("pw", out JsonElement pw)) return BadRequest();
  387. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  388. School school = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  389. var response = await studentClient.ReadItemStreamAsync(id.GetString(), new PartitionKey($"Base-{school_code.GetString().ToLower()}"));
  390. if (response.Status == 200)
  391. {
  392. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  393. Student student = rjson.ToObject<Student>();
  394. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  395. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  396. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  397. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  398. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  399. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  400. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  401. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  402. var HashedPW = Utils.HashedPassword(pw.ToString(), salt.ToString());
  403. if (HashedPW.Equals(dbpw.GetString()))
  404. {
  405. (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck($"{id}", $"{classId}", $"{school_code}", $"{picture}", $"{name}", schoolClient, teacherClient, school.areaId,ip, client, student);
  406. return Ok(new { 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 } });
  407. }
  408. else
  409. {
  410. return Ok(new { error = 1, message = "账号或密码错误" });
  411. }
  412. }
  413. else
  414. {
  415. return Ok(new { error = 2, message = "無此帳號存在" });
  416. }
  417. }
  418. catch (Exception ex)
  419. {
  420. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/login()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  421. return BadRequest();
  422. }
  423. }
  424. private async Task<(string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token)> StudentCheck(string id, string classId, string school_code, string picture, string name, CosmosContainer schoolClient, CosmosContainer teacherClient, string areaId,string ip,CosmosClient cosmosClient,Student student)
  425. {
  426. //班級課程
  427. object classinfo = null;
  428. List<object> courses = new List<object>();
  429. ////校本
  430. //取得所屬預設班級信息
  431. if (!string.IsNullOrWhiteSpace(classId))
  432. {
  433. var query = $"SELECT c.code, c.id, c.name, c.periodId, c.gradeId FROM c WHERE c.id = '{classId}'";
  434. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  435. {
  436. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  437. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  438. {
  439. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  440. {
  441. classinfo = obj.ToObject<object>();
  442. }
  443. }
  444. }
  445. }
  446. //取得該學生跑班課名單ID
  447. List<string> stulistidsSch = new List<string>();
  448. var querysl = $"SELECT c.id FROM c JOIN members IN c.members WHERE members.id = '{id}' AND members.code = '{school_code}'";
  449. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: querysl, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GroupList-{school_code}") }))
  450. {
  451. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  452. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  453. {
  454. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  455. {
  456. stulistidsSch.Add(obj.GetProperty("id").ToString());
  457. }
  458. }
  459. }
  460. //取得該學生的學校課程名單
  461. 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))";
  462. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: queryc, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{school_code}") }))
  463. {
  464. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  465. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  466. {
  467. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  468. {
  469. courses.Add(obj.ToObject<object>());
  470. }
  471. }
  472. }
  473. ////個人
  474. //取得該學生跑班課名單ID
  475. Dictionary<string, Dictionary<string, string>> stulistidsTea = new Dictionary<string, Dictionary<string, string>>();
  476. 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}'";
  477. await foreach (var item in teacherClient.GetItemQueryStreamIterator(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("GroupList") }))
  478. {
  479. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  480. var js = json.RootElement.ToJsonString();
  481. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  482. {
  483. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  484. {
  485. string courseCode = "";
  486. if (obj.TryGetProperty("courseCode", out var code))
  487. {
  488. courseCode = code.GetString();
  489. }
  490. string courseId = "";
  491. if (obj.TryGetProperty("courseId", out var cosid))
  492. {
  493. courseId = cosid.GetString();
  494. }
  495. string stulistId = "";
  496. if (obj.TryGetProperty("id", out var listId))
  497. {
  498. stulistId = listId.GetString();
  499. }
  500. if (!string.IsNullOrEmpty(courseCode))
  501. {
  502. if (!stulistidsTea.ContainsKey(courseCode))
  503. {
  504. Dictionary<string, string> pCourseIdDic = new Dictionary<string, string>();
  505. pCourseIdDic.Add(courseId, stulistId);
  506. stulistidsTea.Add(courseCode, pCourseIdDic);
  507. }
  508. else
  509. {
  510. if (!stulistidsTea[courseCode].ContainsKey(courseId))
  511. {
  512. stulistidsTea[courseCode].Add(courseId, stulistId);
  513. }
  514. }
  515. }
  516. }
  517. }
  518. }
  519. //取得該學生的老師個人課程名單
  520. foreach (KeyValuePair<string, Dictionary<string, string>> item in stulistidsTea)
  521. {
  522. string courseCode = item.Key;
  523. Dictionary<string, string> courseIdDic = item.Value;
  524. string stucourseWhere = string.Empty;
  525. foreach (KeyValuePair<string, string> itemDic in courseIdDic)
  526. {
  527. string courseId = itemDic.Key;
  528. string stuListId = itemDic.Value;
  529. if (!string.IsNullOrWhiteSpace(stucourseWhere))
  530. {
  531. stucourseWhere += " OR ";
  532. }
  533. stucourseWhere += $"( c.id = '{courseId}' AND schedule.stulist = '{stuListId}' )";
  534. }
  535. 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}";
  536. await foreach (var itemcs in teacherClient.GetItemQueryStreamIterator(queryText: querycst, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{courseCode}") }))
  537. {
  538. using var json = await JsonDocument.ParseAsync(itemcs.ContentStream);
  539. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  540. {
  541. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  542. {
  543. courses.Add(obj.ToObject<object>());
  544. }
  545. }
  546. }
  547. }
  548. // BLOB(學校,唯讀)
  549. var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(school_code.ToLower(), BlobContainerSasPermissions.Read);
  550. //換取AuthToken,提供給前端
  551. 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);
  552. //用户在线记录
  553. try
  554. {
  555. _ = _httpTrigger.RequestHttpTrigger(new { school = school_code, scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  556. }
  557. catch {}
  558. await cosmosClient.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, id, new PartitionKey($"Base-{school_code}"));
  559. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  560. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  561. var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, _option.Location.Replace("-Dep", "").Replace("-Test", ""));
  562. return (auth_token, blob_uri, blob_sas, classinfo, courses, token);
  563. }
  564. //查询学生名单详情
  565. [ProducesDefaultResponseType]
  566. //[AuthToken(Roles = "teacher")]
  567. [HttpPost("get-summary-student")]
  568. [Authorize(Roles = "IES")]
  569. public async Task<IActionResult> getSummary(JsonElement requert)
  570. {
  571. try
  572. {
  573. requert.TryGetProperty("students", out JsonElement students);
  574. requert.TryGetProperty("tmdIds", out JsonElement tmdIds);
  575. List<TmdInfo> tmdinfos = new List<TmdInfo>();
  576. List<object> stus = new List<object>();
  577. var client = _azureCosmos.GetCosmosClient();
  578. if (students.ValueKind.Equals(JsonValueKind.Array))
  579. {
  580. List<Students> stuList = students.ToObject<List<Students>>();
  581. if (stuList.IsNotEmpty())
  582. {
  583. foreach (Students stu in stuList)
  584. {
  585. 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}'";
  586. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{stu.code}") }))
  587. {
  588. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  589. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  590. {
  591. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  592. {
  593. stus.Add(obj.ToObject<object>());
  594. }
  595. }
  596. }
  597. }
  598. }
  599. }
  600. if (tmdIds.ValueKind.Equals(JsonValueKind.Array))
  601. {
  602. List<string> tmdids = tmdIds.ToObject<List<string>>();
  603. if (tmdids.IsNotEmpty())
  604. {
  605. List<string> inids = new List<string>();
  606. tmdids.ForEach(x => { inids.Add($"'{x}'"); });
  607. var insql = string.Join(",", inids);
  608. var queryslt = $"SELECT c.id,c.name,c.picture FROM c where c.id in ({insql})";
  609. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryIterator<TmdInfo>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
  610. {
  611. tmdinfos.Add(item);
  612. }
  613. }
  614. }
  615. return Ok(new { stus, tmdinfos });
  616. }
  617. catch (Exception ex)
  618. {
  619. await _dingDing.SendBotMsg($"OS,{_option.Location},student/get-summary-student()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  620. return BadRequest();
  621. }
  622. }
  623. /// <summary>
  624. /// 學生簡易登入
  625. /// </summary>
  626. /// <param name = "request" ></ param >
  627. [AllowAnonymous]
  628. [HttpPost("login-simple")]
  629. public async Task<IActionResult> LoginSimple(JsonElement request)
  630. {
  631. try
  632. {
  633. var client = _azureCosmos.GetCosmosClient();
  634. var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
  635. var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
  636. //參數取得
  637. if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
  638. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  639. if (!request.TryGetProperty("pw", out JsonElement pw)) return BadRequest();
  640. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  641. var response = await studentClient.ReadItemStreamAsync(id.GetString(), new PartitionKey($"Base-{school_code.GetString().ToLower()}"));
  642. if (response.Status == 200)
  643. {
  644. var rjson = await JsonDocument.ParseAsync(response.ContentStream);
  645. Student student = rjson.ToObject<Student>();
  646. rjson.RootElement.TryGetProperty("salt", out JsonElement salt);
  647. rjson.RootElement.TryGetProperty("pw", out JsonElement dbpw);
  648. rjson.RootElement.TryGetProperty("name", out JsonElement name);
  649. rjson.RootElement.TryGetProperty("picture", out JsonElement picture);
  650. rjson.RootElement.TryGetProperty("classId", out JsonElement classId);
  651. rjson.RootElement.TryGetProperty("no", out JsonElement no);
  652. rjson.RootElement.TryGetProperty("groupId", out JsonElement groupId);
  653. rjson.RootElement.TryGetProperty("groupName", out JsonElement groupName);
  654. dynamic user = new ExpandoObject();
  655. user.no = no;
  656. user.groupId = groupId;
  657. user.groupName = groupName;
  658. var HashedPW = Utils.HashedPassword(pw.ToString(), salt.ToString());
  659. if (HashedPW.Equals(dbpw.GetString()))
  660. {
  661. School schoolInfo = await schoolClient.ReadItemAsync<School>($"{school_code}", new PartitionKey("Base"));
  662. //取得所屬預設班級信息
  663. object classinfo = null;
  664. if (!classId.ValueKind.Equals(JsonValueKind.Null) && classId.ValueKind.Equals(JsonValueKind.String))
  665. {
  666. var query = $"SELECT c.id, c.no, c.name FROM c WHERE c.id = '{classId.GetString()}'";
  667. await foreach (var item in schoolClient.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
  668. {
  669. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  670. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  671. {
  672. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  673. {
  674. classinfo = obj.ToObject<object>();
  675. }
  676. }
  677. }
  678. }
  679. //換取AuthToken,提供給前端
  680. 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);
  681. //用户在线记录
  682. try
  683. {
  684. _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
  685. }
  686. catch { }
  687. //保存学生登录信息
  688. await client.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"{student.code}"));
  689. //其他訊息
  690. dynamic school = new ExpandoObject();
  691. //回傳
  692. return Ok(new { error = 0, auth_token, classinfo, user });
  693. }
  694. else
  695. {
  696. return Ok(new { error = 1, message = "Invalid account or password" });
  697. }
  698. }
  699. else
  700. {
  701. return Ok(new { error = 2, message = "Invalid account" });
  702. }
  703. }
  704. catch (Exception ex)
  705. {
  706. await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/login-simple()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  707. return BadRequest();
  708. }
  709. }
  710. //TODO 此API需處理對應前端返回的相關數據
  711. [ProducesDefaultResponseType]
  712. [AuthToken(Roles = "student,teacher")]
  713. [HttpPost("get-school-info")]
  714. public async Task<IActionResult> GetSchoolInfo(JsonElement request)
  715. {
  716. try
  717. {
  718. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  719. var client = _azureCosmos.GetCosmosClient();
  720. /// tmdid, schoolid
  721. var userType = "schoolid";
  722. if (request.TryGetProperty("userType", out JsonElement usertype))
  723. {
  724. if (!usertype.ValueKind.Equals(JsonValueKind.Undefined) && !usertype.ValueKind.Equals(JsonValueKind.Null) && usertype.ValueKind.Equals(JsonValueKind.String))
  725. {
  726. userType = usertype.GetString();
  727. }
  728. }
  729. if (string.IsNullOrEmpty(school))
  730. {
  731. if (userType.Equals("tmdid"))
  732. {
  733. Teacher teacher = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
  734. if (teacher.schools.IsNotEmpty())
  735. {
  736. var tech = teacher.schools.Find(x => x.status.Equals("join"));
  737. if (tech == null)
  738. {
  739. school = teacher.schools[0].schoolId;
  740. }
  741. else
  742. {
  743. school = tech.schoolId;
  744. }
  745. }
  746. }
  747. }
  748. if (!string.IsNullOrEmpty(school))
  749. {
  750. object school_base = null;
  751. var response = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(school, new PartitionKey("Base"));
  752. if (response.Status == 200)
  753. {
  754. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  755. school_base = json.RootElement.ToObject<object>();
  756. }
  757. //取得班级
  758. List<object> school_classes = new List<object>();
  759. 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}") }))
  760. {
  761. var jsonc = await JsonDocument.ParseAsync(item.ContentStream);
  762. foreach (var classeinfo in jsonc.RootElement.GetProperty("Documents").EnumerateArray())
  763. {
  764. school_classes.Add(classeinfo.ToObject<object>());
  765. }
  766. }
  767. //取得教室
  768. List<Room> school_rooms = new List<Room>();
  769. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Room>(queryText: $"select value(c) from c ",
  770. requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Room-{school}") }))
  771. {
  772. school_rooms.Add(item);
  773. }
  774. return Ok(new { school_base, school_classes, school_rooms, status = 200 });
  775. }
  776. else
  777. {
  778. return Ok(new { status = 404 }); ;
  779. }
  780. }
  781. catch (CosmosException ex)
  782. {
  783. return Ok(new { status = ex.Status }); ;
  784. }
  785. catch (Exception ex)
  786. {
  787. await _dingDing.SendBotMsg($"IES5,{_option.Location},Student/get-school-info()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  788. return BadRequest();
  789. }
  790. }
  791. }
  792. }