CoreController.cs 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  1. using Microsoft.Azure.Cosmos;
  2. using Azure.Storage.Blobs.Models;
  3. using FastJSON;
  4. using Microsoft.AspNetCore.Authorization;
  5. using Microsoft.AspNetCore.Hosting;
  6. using Microsoft.AspNetCore.Http;
  7. using Microsoft.AspNetCore.Mvc;
  8. using Microsoft.Extensions.Configuration;
  9. using Microsoft.Extensions.Hosting;
  10. using Microsoft.Extensions.Options;
  11. using Microsoft.OData.UriParser;
  12. using StackExchange.Redis;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.Drawing.Imaging;
  16. using System.IdentityModel.Tokens.Jwt;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Net;
  20. using System.Net.Http;
  21. using System.Reflection;
  22. using System.Runtime.InteropServices;
  23. using System.Security.Policy;
  24. using System.Text;
  25. using System.Text.Json;
  26. using System.Text.RegularExpressions;
  27. using System.Threading.Tasks;
  28. using System.Web;
  29. using TEAMModelOS.Models;
  30. using TEAMModelOS.Models.Request;
  31. using TEAMModelOS.SDK;
  32. using TEAMModelOS.SDK.DI;
  33. using TEAMModelOS.SDK.Extension;
  34. using TEAMModelOS.SDK.Models;
  35. using TEAMModelOS.SDK.Models.Dtos;
  36. using TEAMModelOS.SDK.Models.Service;
  37. using TEAMModelOS.SDK.Models.Service.BI;
  38. using TEAMModelOS.SDK.PngQuant;
  39. using Top.Api;
  40. using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
  41. using static TEAMModelOS.SDK.CoreAPIHttpService;
  42. using Pipelines.Sockets.Unofficial.Arenas;
  43. using System.Text.Json.Nodes;
  44. using TEAMModelOS.Services;
  45. namespace TEAMModelOS.Controllers
  46. {
  47. [Route("core")]
  48. [ApiController]
  49. public class CoreController : ControllerBase
  50. {
  51. private readonly AzureCosmosFactory _azureCosmos;
  52. private readonly AzureRedisFactory _azureRedis;
  53. private readonly AzureStorageFactory _azureStorage;
  54. private readonly DingDing _dingDing;
  55. private readonly Option _option;
  56. private readonly IHttpClientFactory _httpClientFactory;
  57. private readonly IPSearcher _searcher;
  58. private readonly CoreAPIHttpService _coreAPIHttpService;
  59. private readonly IConfiguration _configuration;
  60. private readonly SnowflakeId _snowflakeId;
  61. private readonly IWebHostEnvironment _environment;
  62. private readonly HttpTrigger _httpTrigger;
  63. public CoreController(HttpTrigger httpTrigger, IWebHostEnvironment environment, IHttpClientFactory httpClientFactory,AzureCosmosFactory azureCosmos, SnowflakeId snowflakeId,CoreAPIHttpService coreAPIHttpService,IConfiguration configuration,IPSearcher searcher, AzureRedisFactory azureRedis, AzureStorageFactory azureStorage, DingDing dingDing, IOptionsSnapshot<Option> option)
  64. {
  65. _azureCosmos = azureCosmos;
  66. _searcher = searcher;
  67. _azureStorage = azureStorage;
  68. _dingDing = dingDing;
  69. _option = option?.Value;
  70. _azureRedis = azureRedis;
  71. _configuration = configuration;
  72. _coreAPIHttpService = coreAPIHttpService;
  73. _snowflakeId = snowflakeId;
  74. _httpClientFactory = httpClientFactory;
  75. _environment= environment;
  76. _httpTrigger = httpTrigger;
  77. }
  78. public class NotifyData {
  79. public string notifyCode { get; set; }
  80. public string data { get; set; }
  81. public int notifyEvent { get; set; }
  82. public string ticket { get; set; }
  83. public string token { get; set; }
  84. }
  85. [HttpGet("process-notify")]
  86. public async Task<IActionResult> ProcessNotify([FromQuery] NotifyData notifyData) {
  87. string msg = "";
  88. /// code =-1 参数异常,0 凭据失效 1不是管理员,2操作成功,3服务端异常
  89. int code = -1;
  90. string scope = "";
  91. string lang = _option.Location.Contains("China") ? "zh-cn" : "en-us";
  92. string path = Path.Combine("", $"Lang/{lang}.json");
  93. var jsonAuth = System.IO.File.ReadAllText(path, Encoding.UTF8);
  94. var jsonData = jsonAuth.ToObject<JsonElement>();
  95. var status = jsonData.GetProperty("notify-status");
  96. Teacher teacher = null;
  97. try {
  98. string opttmdid = "";
  99. if (!string.IsNullOrWhiteSpace(notifyData.ticket))
  100. {
  101. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  102. (HttpStatusCode statusCode, CoreAPIToken token) = await _coreAPIHttpService.GetCoreAPIoAuth2Token(
  103. new Dictionary<string, object> { { "client_id", clientID }, { "grant_type", "authorization_code" }, { "code", notifyData.ticket } }, _option.Location, _configuration, _dingDing);
  104. if (statusCode.Equals(HttpStatusCode.OK))
  105. {
  106. var jwt = new JwtSecurityToken(token.id_token);
  107. opttmdid = jwt.Payload.Sub;
  108. }
  109. else
  110. {
  111. code = 0;
  112. msg = "凭据失效";
  113. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  114. }
  115. scope = "HiTA";
  116. }
  117. else if (!string.IsNullOrWhiteSpace(notifyData.token) && string.IsNullOrWhiteSpace(opttmdid))
  118. {
  119. var jwt = new JwtSecurityToken(notifyData.token);
  120. opttmdid = jwt.Payload.Sub;
  121. scope = "IES";
  122. }
  123. Dictionary<string, object> dict = new Dictionary<string, object>();
  124. if (string.IsNullOrWhiteSpace(opttmdid)) {
  125. code = 1;
  126. msg = "参数异常";
  127. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  128. }
  129. var rdata = HttpUtility.UrlDecode(notifyData.data, Encoding.UTF8);
  130. //取消base64,但是旧数据需要处理
  131. var d= rdata.Replace("\\", "");
  132. try {
  133. dict = d.ToObject<Dictionary<string, object>>();
  134. }
  135. catch {
  136. var dataByte = Convert.FromBase64String(rdata);
  137. string data = Encoding.Unicode.GetString(dataByte);
  138. data.ToObject<Dictionary<string, object>>();
  139. }
  140. dict.TryGetValue("schoolId", out object _schoolId);
  141. dict.TryGetValue("tmdid", out object _tmdid);
  142. if (!string.IsNullOrWhiteSpace($"{_schoolId}") && !string.IsNullOrWhiteSpace($"{_tmdid}") && !string.IsNullOrWhiteSpace(opttmdid))
  143. {
  144. ResponseMessage teacherResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
  145. .ReadItemStreamAsync($"{_tmdid}", new PartitionKey("Base"));
  146. if (teacherResponse.StatusCode==System.Net.HttpStatusCode.OK) {
  147. teacher = JsonDocument.Parse(teacherResponse.Content).RootElement.Deserialize<Teacher>();
  148. if (!string.IsNullOrWhiteSpace(teacher.lang) &&(teacher.lang.Equals("zh-cn")||teacher.lang.Equals("zh-tw") || teacher.lang.Equals("en-us"))) {
  149. lang= teacher.lang;
  150. path = Path.Combine("", $"Lang/{lang}.json");
  151. jsonAuth = System.IO.File.ReadAllText(path, Encoding.UTF8);
  152. jsonData = jsonAuth.ToObject<JsonElement>();
  153. status = jsonData.GetProperty("notify-status");
  154. }
  155. }
  156. switch (true)
  157. {
  158. case bool when $"{notifyData.notifyCode}".Equals("quit_school"):
  159. {
  160. ResponseMessage adminResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  161. .ReadItemStreamAsync(opttmdid, new PartitionKey($"Teacher-{_schoolId}"));
  162. //是否是管理员
  163. if (adminResponse.StatusCode==System.Net.HttpStatusCode.OK)
  164. {
  165. var adminTeacher = JsonDocument.Parse(adminResponse.Content).RootElement.Deserialize<SchoolTeacher>();
  166. if (adminTeacher.roles.Contains("admin"))
  167. {
  168. ResponseMessage schoolTeacherResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  169. .ReadItemStreamAsync($"{_tmdid}", new PartitionKey($"Teacher-{_schoolId}"));
  170. if (teacher!=null && schoolTeacherResponse.StatusCode==System.Net.HttpStatusCode.OK)
  171. {
  172. // var teacher = JsonDocument.Parse(teacherResponse.Content).RootElement.Deserialize<Teacher>();
  173. var schoolTeacher = JsonDocument.Parse(schoolTeacherResponse.Content).RootElement.Deserialize<SchoolTeacher>();
  174. //同意
  175. if (notifyData.notifyEvent == 1)
  176. {
  177. teacher.schools.RemoveAll(z =>!string.IsNullOrWhiteSpace($"{_schoolId}") && !string.IsNullOrWhiteSpace(z.schoolId) && z.schoolId.Equals($"{_schoolId}"));
  178. try
  179. {
  180. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).DeleteItemStreamAsync( schoolTeacher.id, new PartitionKey(schoolTeacher.code));
  181. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReplaceItemAsync(teacher, teacher.id, new PartitionKey(teacher.code));
  182. School qschool = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<School>($"{_schoolId}", new PartitionKey("Base"));
  183. _coreAPIHttpService.PushNotify(new List<IdNameCode> { new IdNameCode {id= teacher.id,name= teacher.name,code= teacher.lang } }, $"quited_school", Constant.NotifyType_IES5_Management,
  184. new Dictionary<string, object> { { "schoolName", qschool.name }, { "schoolId", $"{_schoolId}" } }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
  185. }
  186. catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
  187. {
  188. }
  189. code = 2;
  190. msg = "操作成功";
  191. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  192. }
  193. //拒绝退出学校,直接不管。
  194. if (notifyData.notifyEvent == 2)
  195. {
  196. code = 2;
  197. msg = "操作成功";
  198. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  199. }
  200. }
  201. else
  202. {
  203. code = 5;
  204. msg = "老师已离开学校";
  205. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  206. }
  207. }
  208. else
  209. {
  210. code = 3;
  211. msg = "您已不是管理员";
  212. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  213. }
  214. }
  215. else
  216. {
  217. code = 3;
  218. msg = "您已不是管理员";
  219. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  220. }
  221. }
  222. break;
  223. case bool when $"{notifyData.notifyCode}".Equals("request_school"):
  224. {
  225. ResponseMessage adminResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  226. .ReadItemStreamAsync(opttmdid, new PartitionKey($"Teacher-{_schoolId}"));
  227. //是否是管理员
  228. if (adminResponse.StatusCode==System.Net.HttpStatusCode.OK)
  229. {
  230. var adminTeacher = JsonDocument.Parse(adminResponse.Content).RootElement.Deserialize<SchoolTeacher>();
  231. if (adminTeacher.roles.Contains("admin"))
  232. {
  233. ResponseMessage schoolTeacherResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  234. .ReadItemStreamAsync($"{_tmdid}", new PartitionKey($"Teacher-{_schoolId}"));
  235. if (teacher!=null && schoolTeacherResponse.StatusCode==System.Net.HttpStatusCode.OK)
  236. {
  237. var schoolTeacher = JsonDocument.Parse(schoolTeacherResponse.Content).RootElement.Deserialize<SchoolTeacher>();
  238. //同意
  239. if (notifyData.notifyEvent == 1)
  240. {
  241. teacher.schools.ForEach(school =>
  242. {
  243. if (school.schoolId.Equals($"{_schoolId}"))
  244. {
  245. school.status = "join";
  246. }
  247. });
  248. schoolTeacher.status = "join";
  249. try {
  250. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync(schoolTeacher, schoolTeacher.id, new PartitionKey(schoolTeacher.code));
  251. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReplaceItemAsync(teacher, teacher.id, new PartitionKey(teacher.code));
  252. } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) {
  253. }
  254. code = 2;
  255. msg = "操作成功";
  256. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  257. await BIStats.SetTypeAddStats(_azureCosmos.GetCosmosClient(), _dingDing, $"{_schoolId}", "Teacher", 1);//BI统计增/减量
  258. }
  259. //拒绝
  260. int count = 0;
  261. if (notifyData.notifyEvent == 2)
  262. {
  263. //只有在未正式加入成功才能拒绝成功,防止HiTA消息未清除,再次操作。
  264. count=teacher.schools.RemoveAll(z => z.schoolId.Equals($"{_schoolId}") && (z.status.Equals("request") || z.status.Equals("invite")));
  265. if (count > 0)
  266. {
  267. try {
  268. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).DeleteItemStreamAsync($"{opttmdid}", new PartitionKey($"Teacher-{_schoolId}"));
  269. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReplaceItemAsync(teacher, teacher.id, new PartitionKey(teacher.code));
  270. }
  271. catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
  272. {
  273. }
  274. code = 2;
  275. msg = "操作成功";
  276. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  277. }
  278. else
  279. {
  280. code = 6;
  281. msg = "已加入学校,请勿重复操作";
  282. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  283. }
  284. }
  285. }
  286. else
  287. {
  288. code = 5;
  289. msg = "老师已离开学校";
  290. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  291. }
  292. }
  293. else
  294. {
  295. code = 3;
  296. msg = "您已不是管理员";
  297. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  298. }
  299. }
  300. else
  301. {
  302. code = 3;
  303. msg = "您已不是管理员";
  304. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  305. }
  306. }
  307. break;
  308. case bool when $"{notifyData.notifyCode}".Equals("invite_school"):
  309. {
  310. ResponseMessage schoolTeacherResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
  311. .ReadItemStreamAsync($"{opttmdid}", new PartitionKey($"Teacher-{_schoolId}"));
  312. ResponseMessage optTeacherResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
  313. .ReadItemStreamAsync($"{opttmdid}", new PartitionKey($"Base"));
  314. if (optTeacherResponse.StatusCode == HttpStatusCode.OK && schoolTeacherResponse.StatusCode==System.Net.HttpStatusCode.OK)
  315. {
  316. var optteacher = JsonDocument.Parse(optTeacherResponse.Content).RootElement.Deserialize<Teacher>();
  317. var schoolTeacher = JsonDocument.Parse(schoolTeacherResponse.Content).RootElement.Deserialize<SchoolTeacher>();
  318. //同意
  319. if (notifyData.notifyEvent == 1)
  320. {
  321. optteacher.schools.ForEach(school =>
  322. {
  323. if (school.schoolId.Equals($"{_schoolId}"))
  324. {
  325. school.status = "join";
  326. }
  327. });
  328. schoolTeacher.status = "join";
  329. try {
  330. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync(schoolTeacher, schoolTeacher.id, new PartitionKey(schoolTeacher.code));
  331. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReplaceItemAsync(optteacher, optteacher.id, new PartitionKey(optteacher.code));
  332. }
  333. catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
  334. {
  335. }
  336. code = 2;
  337. msg = "操作成功";
  338. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  339. await BIStats.SetTypeAddStats(_azureCosmos.GetCosmosClient(), _dingDing, $"{_schoolId}", "Teacher", 1);//BI统计增/减量
  340. }
  341. //拒绝
  342. int count = 0;
  343. if (notifyData.notifyEvent == 2)
  344. {
  345. //只有在未正式加入成功才能拒绝成功,防止HiTA消息未清除,再次操作。
  346. count = optteacher.schools.RemoveAll(z => z.schoolId.Equals($"{_schoolId}") && (z.status.Equals("request") || z.status.Equals("invite")));
  347. if (count >0)
  348. {
  349. try {
  350. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).DeleteItemStreamAsync($"{opttmdid}", new PartitionKey($"Teacher-{_schoolId}"));
  351. await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReplaceItemAsync(optteacher, optteacher.id, new PartitionKey(optteacher.code));
  352. }
  353. catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
  354. {
  355. }
  356. code = 2;
  357. msg = "操作成功";
  358. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  359. }
  360. else
  361. {
  362. code = 6;
  363. msg = "已加入学校,请勿重复操作";
  364. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  365. }
  366. }
  367. }
  368. else
  369. {
  370. code = 5;
  371. msg = "教师不存在";
  372. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  373. }
  374. }
  375. break;
  376. }
  377. }
  378. else
  379. {
  380. code = 1;
  381. msg = "参数异常";
  382. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  383. }
  384. } catch (Exception ex) {
  385. await _dingDing.SendBotMsg($"{ex.Message}\n{ex.StackTrace}\n{notifyData.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  386. code = 4;
  387. msg = "服务端异常";
  388. msg = status.TryGetProperty($"code{code}", out JsonElement text) ? $"{text}" : msg;
  389. }
  390. if (scope.Equals("HiTA"))
  391. {
  392. string HostName = HttpContext.GetHostName();
  393. var rurl = $"https://{HostName}/resultPage?code={code}&msg={HttpUtility.UrlEncode(msg)}";
  394. return Redirect(rurl);
  395. }
  396. else {
  397. return Ok(new { code, msg });
  398. }
  399. }
  400. /// <summary>
  401. ///{"grant_type":"create","client_id":"c7317f88-7cea-4e48-ac57-a16071f7b884","nonce":"habook","name":"User","pin_code":"985395","account":"+86-15283771540"}
  402. ///{"error":2,"message":"帳號已存在"}
  403. ///{"error":3,"message":"驗證PIN碼失敗"}
  404. ///{
  405. ///"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhY2NvdW50LnRlYW1tb2RlbCIsInN1YiI6IjY3MzQ2NjQ4NTUiLCJhdWQiOiJjNzMxN2Y4OC03Y2VhLTRlNDgtYWM1Ny1hMTYwNzFmN2I4ODQiLCJleHAiOjE3MzQ3NTEyNTUsImlhdCI6MTczNDY2NDg1NSwibm9uY2UiOiJoYWJvb2siLCJuYW1lIjoiVXNlciIsImlzQWN0aXZhdGUiOiJmYWxzZSIsImlzTW9iaWxlIjoidHJ1ZSIsImlzTWFpbCI6ImZhbHNlIiwiYXJlYSI6ImNoaW5hIiwibmJmIjoxNzM0NjY0ODU1fQ.8P2IKq44ML13vYb03GbbrDSJqGpjkIQjiztNYSnbBRg",
  406. ///"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InF4dklNdDk1OEpHWFFrWjF1QzF2RHQ1N3dRTSJ9.eyJhdWQiOiI3MjY0MzcwNC1iMmU3LTRiMjYtYjg4MS1iZDU4NjVlN2E3YTUiLCJpc3MiOiJodHRwczovL2xvZ2luLnBhcnRuZXIubWljcm9zb2Z0b25saW5lLmNuLzQ4MDdlOWNmLTg3YjgtNDE3NC1hYTViLWU3NjQ5N2Q3MzkyYi92Mi4wIiwiaWF0IjoxNzM0NjY0NTU1LCJuYmYiOjE3MzQ2NjQ1NTUsImV4cCI6MTczNDY2ODQ1NSwiYWlvIjoiNDJOZ1lBalJsZGM2OVcvZjQ2ZnRPak16K1BVT3ZVeTJ2TCtzTjZTeU15NytZeE5IbndZQSIsImF6cCI6ImM3MzE3Zjg4LTdjZWEtNGU0OC1hYzU3LWExNjA3MWY3Yjg4NCIsImF6cGFjciI6IjEiLCJvaWQiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJyaCI6IjAuREFJQXota0hTTGlIZEVHcVctZGtsOWM1S3dRM1pITG5zaVpMdUlHOVdHWG5wNlVCQUFBLiIsInJvbGVzIjpbIklFUyJdLCJzdWIiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJ0aWQiOiI0ODA3ZTljZi04N2I4LTQxNzQtYWE1Yi1lNzY0OTdkNzM5MmIiLCJ1dGkiOiJEV0VtaGxUeXcwT295YVdGaU04MEFRIiwidmVyIjoiMi4wIn0.CaikbZjaVASy_E7_S4L8ZVjP8FipNqr9vLOdt-FQj9cmeoBXl2sQtb5F-vpkJ-QNfN1J3h8qJRYrGdyrMp8FBNliYFX7Mwij8pIXsdTI00DNSNDJ6b9trdiS4DUW9c7R3wzvQ-UEyhNkNcI4b8gJCNcxYDIEiCL358svYs_8jz4vyCUhuDDLdJMJimVFCRezvoG8kFE-ZO82Cj5VL6pBN8q9ZZs74k4C94tE1TBs4ixm-3jQOGrwovkdFkHqG2g7R7-F1V_w5-RqfRDCQR-mr1cGpShDlOyUx6oph6-xi5lCZJPgmQsW0vIAAVpzqI_t62-IuBekmDKNqK35xkjG5w",
  407. ///"expires_in": "2024-12-20T04:20:54.1635029+00:00",
  408. ///"token_type": "Bearer"
  409. ///}
  410. /// </summary>
  411. /// <param name="request"></param>
  412. /// <returns></returns>
  413. [HttpPost("sendsms/check")]
  414. public async Task<IActionResult> SendSmsCheck(JsonNode request)
  415. {
  416. string msg = string.Empty;
  417. int timezone = 8;
  418. if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
  419. {
  420. timezone=tz;
  421. }
  422. var pin_code = request["pin_code"];
  423. var account = request["account"];
  424. TmdidImplicit implicit_token = null;
  425. var loginData= await _coreAPIHttpService.Auth2Login($"{pin_code}", $"{account}", _option.Location, _configuration, _dingDing);
  426. if (loginData.code.Equals(HttpStatusCode.OK))
  427. {
  428. var node = JsonSerializer.Deserialize<JsonNode>(loginData.content);
  429. if (node?["id_token"]!=null)
  430. {
  431. implicit_token= JsonSerializer.Deserialize<TmdidImplicit>(loginData.content);
  432. }
  433. else
  434. {
  435. if (node?["error"]!=null && $"{node?["error"]}".Equals("2"))
  436. {
  437. string mobile = $"{account}".Split("-").Last();
  438. var coreUser = await _coreAPIHttpService.GetUserInfo(new Dictionary<string, string> { { "key", $"{mobile}" } }, _option.Location, _configuration);
  439. if (coreUser!=null)
  440. {
  441. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  442. var location = _option.Location;
  443. implicit_token = await _coreAPIHttpService.Implicit(
  444. new Dictionary<string, string>()
  445. {
  446. { "grant_type", "implicit" },
  447. { "client_id",clientID },
  448. { "account",coreUser.id },
  449. { "nonce",Guid.NewGuid().ToString()}
  450. }, location, _configuration);
  451. }
  452. }
  453. else {
  454. msg=$"验证失败!{loginData.content}";
  455. }
  456. }
  457. if (implicit_token!=null)
  458. {
  459. object name = null, picture = null;
  460. var jwt = new JwtSecurityToken(implicit_token.id_token);
  461. jwt.Payload.TryGetValue("name", out name);
  462. jwt.Payload.TryGetValue("picture", out picture);
  463. jwt.Payload.TryGetValue("lang", out object _jwtlang);
  464. var tmdid = jwt.Payload.Sub;
  465. IEnumerable<dynamic> schools = new List<dynamic>();
  466. var response = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemStreamAsync(tmdid, new PartitionKey("Base"));
  467. if (response.StatusCode==System.Net.HttpStatusCode.OK)
  468. {
  469. Teacher teacher = JsonDocument.Parse(response.Content).RootElement.ToObject<Teacher>();
  470. schools= teacher.schools.Where(x => x.status.Equals("join")).Select(x => new { id = x.schoolId, name = x.name, picture = x.picture });
  471. }
  472. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  473. TeacherInfo teacherInfo = await TeacherService.TeacherInfoLite(_azureCosmos, $"{name}", $"{picture}", $"{jwt.Payload.Sub}", _azureStorage, _option, _azureRedis, "", _httpTrigger, $"{_jwtlang}", timezone);
  474. string x_auth_token = string.Empty;
  475. if (teacherInfo!=null)
  476. {
  477. x_auth_token= teacherInfo.auth_token;
  478. }
  479. return Ok(new { code = 200, implicit_token, schools, x_auth_token });
  480. }
  481. else {
  482. msg=$"验证失败!隐式登录未找到用户{account}";
  483. }
  484. }
  485. else {
  486. msg=$"验证失败,{loginData.code}";
  487. }
  488. return Ok(new { code=400,msg= msg });
  489. }
  490. [HttpPost("sendsms/pin")]
  491. public async Task<IActionResult> SendSmsPinCode(JsonElement request)
  492. {
  493. (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
  494. //获取投票活动的选项及投票数
  495. string ipkey = $"Ip:Pin:Count:{ip}";
  496. bool ipkeyexist = await _azureRedis.GetRedisClient(8).KeyExistsAsync(ipkey);
  497. if (ipkeyexist)
  498. {
  499. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync(ipkey, ip, 1);
  500. }
  501. else {
  502. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync(ipkey, ip, 1);
  503. var Expire = DateTime.UtcNow.AddSeconds(600);
  504. _azureRedis.GetRedisClient(8).KeyExpire(ipkey, Expire);
  505. }
  506. var counts = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(ipkey);
  507. long sum = 0;
  508. if (counts != null && counts.Length > 0)
  509. {
  510. foreach (var count in counts)
  511. {
  512. sum += (int)count.Score;
  513. }
  514. }
  515. int limit = 1000;
  516. if (sum > limit) {
  517. await _dingDing.SendBotMsg($"{_option.Location}\nIp:{ip}\n位置:{region}\n 短信验证码10分钟内访问次数超过:{limit}次!",GroupNames.成都开发測試群組);
  518. return Ok(new { send = 2 });
  519. }
  520. if (!request.TryGetProperty("area", out JsonElement _area)) return BadRequest();
  521. if (!request.TryGetProperty("to", out JsonElement _to)) return BadRequest();
  522. if (!request.TryGetProperty("lang", out JsonElement _lang)) return BadRequest();
  523. request.TryGetProperty("HasUser", out JsonElement _HasUser);
  524. string code=$"{_area}{_to}";
  525. int exp = 120;
  526. string key = $"Random:Code:PinCode-{_area}{_to}";
  527. bool exist = await _azureRedis.GetRedisClient(8).KeyExistsAsync(key);
  528. if (!exist)
  529. {
  530. //不存在则发送请求。
  531. Dictionary<string, object> dict = null;
  532. if (_HasUser.ValueKind.Equals(JsonValueKind.True))
  533. {
  534. dict = new Dictionary<string, object> { { "country", $"{_area}" }, { "to", $"{_to}" }, { "lang", $"{_lang}" }, { "HasUser", true } };
  535. }
  536. else if (_HasUser.ValueKind.Equals(JsonValueKind.False)) {
  537. dict = new Dictionary<string, object> { { "country", $"{_area}" }, { "to", $"{_to}" }, { "lang", $"{_lang}" }, { "HasUser", false } };
  538. }
  539. else
  540. {
  541. dict = new Dictionary<string, object> { { "country", $"{_area}" }, { "to", $"{_to}" }, { "lang", $"{_lang}" } };
  542. }
  543. var httpresp = await _coreAPIHttpService.SendSmsPin(dict, _option.Location, _configuration, _dingDing);
  544. if (httpresp.code.Equals(HttpStatusCode.OK))
  545. {
  546. var Expire = DateTime.UtcNow.AddSeconds(exp);
  547. //send=1 表示已发送
  548. await _azureRedis.GetRedisClient(8).StringSetAsync(key, new { code = code, send = 1, Expire = Expire.Ticks }.ToJsonString());
  549. _azureRedis.GetRedisClient(8).KeyExpire(key, Expire);
  550. if (!string.IsNullOrWhiteSpace(httpresp.content))
  551. {
  552. return Ok(httpresp.content.ToObject<JsonElement>());
  553. }
  554. else {
  555. return Ok(new { send = 1 });
  556. }
  557. }
  558. else
  559. {
  560. return Ok(new { send = 0 });
  561. }
  562. }
  563. else
  564. {
  565. //检查当前key是否已经发送了.
  566. RedisValue value = await _azureRedis.GetRedisClient(8).StringGetAsync(key);
  567. JsonElement element = value.ToString().ToObject<JsonElement>();
  568. int send = 0;
  569. if (element.TryGetProperty("send", out JsonElement _send))
  570. {
  571. if (_send.ValueKind.Equals(JsonValueKind.Number))
  572. {
  573. if (int.Parse($"{_send}") == 1)
  574. {
  575. send = 1;
  576. }
  577. else if (int.Parse($"{_send}") == 0)
  578. {
  579. send = 0;
  580. }
  581. }
  582. }
  583. if (send == 0)
  584. {
  585. await _azureRedis.GetRedisClient(8).KeyDeleteAsync(key);
  586. return Ok(new { send = 0 });
  587. }
  588. else
  589. {
  590. TimeSpan? timeSpan = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync(key);
  591. if (timeSpan != null && timeSpan.HasValue)
  592. {
  593. int seconds = timeSpan.Value.Seconds;
  594. return Ok(new { send = 1 });
  595. }
  596. else
  597. {
  598. return Ok(new { send = 0 });
  599. }
  600. }
  601. }
  602. }
  603. [HttpPost("apply-school")]
  604. public async Task<IActionResult> ApplySchool(ApplySchool request)
  605. {
  606. if (_option.Location.Equals("China"))
  607. {
  608. string msg = $"有新学校申请。\n" +
  609. $"申请站点:{_option.Location}\n" +
  610. $"申请学校:{request.name}\n" +
  611. $"学制:{string.Join(",", request.period)}\n" +
  612. $"机构代码:{request.orgCode}\n" +
  613. $"所在国家/地区:{request.area}\n" +
  614. $"申请人:{request.tmdname}({request.tmdid})\n" +
  615. $"联系电话:{request.cellphone}\n" +
  616. $"联系邮箱:{request.email}\n"+
  617. $"备注:{request.content}";
  618. await _dingDing.SendBotMsg(msg, GroupNames.大陸客戶聯繫通知群);
  619. }
  620. else if (_option.Location.Equals("Global"))
  621. {
  622. string msg = $"有新學校申請。\n" +
  623. $"申請站點:{_option.Location}\n" +
  624. $"申請學校:{request.name}\n" +
  625. $"學制:{string.Join(",", request.period)}\n" +
  626. $"機構代碼:{request.orgCode}\n" +
  627. $"所在國家/地區:{request.area}\n" +
  628. $"申請人:{request.tmdname}({request.tmdid})\n" +
  629. $"聯繫電話:{request.cellphone}\n" +
  630. $"聯繫郵箱:{request.email}\n" +
  631. $"備註:{request.content}";
  632. await _dingDing.SendBotMsg(msg, GroupNames.國際客戶聯繫通知群);
  633. }
  634. else
  635. {
  636. string devmsg = _option.Location.Contains("Test") || _option.Location.Contains("Dep") ? "(测试消息)" : "";
  637. string msg = $"有新学校申请。{devmsg}\n" +
  638. $"申请站点:{_option.Location}\n" +
  639. $"申请学校:{request.name}\n" +
  640. $"学制:{string.Join(",", request.period)}\n" +
  641. $"机构代码:{request.orgCode}\n" +
  642. $"所在国家/地区:{request.area}\n" +
  643. $"申请人:{request.tmdname}({request.tmdid})\n" +
  644. $"联系电话:{request.cellphone}\n" +
  645. $"联系邮箱:{request.email}\n" +
  646. $"备注:{request.content}";
  647. await _dingDing.SendBotMsg(msg, GroupNames.成都开发測試群組);
  648. }
  649. return Ok();
  650. }
  651. [HttpPost("apply-manager")]
  652. public async Task<IActionResult> ApplyManager(ApplySchool request)
  653. {
  654. if (_option.Location.Equals("China"))
  655. {
  656. string msg = $"有新的申请管理员咨询。\n" +
  657. $"站点:{_option.Location}\n" +
  658. //$"所在国家/地区:{request.area}\n" +
  659. $"咨询学校:{request.name}\n" +
  660. $"联系人:{request.tmdname} - {request.gender}({request.tmdid})\n " +
  661. $"联系电话:{request.cellphone}\n" +
  662. $"联系邮箱:{request.email}\n";
  663. await _dingDing.SendBotMsg(msg, GroupNames.大陸客戶聯繫通知群);
  664. }
  665. else if (_option.Location.Equals("Global"))
  666. {
  667. string msg = $"有新的申請管理員諮詢。\n" +
  668. $"站點:{_option.Location}\n" +
  669. //$"所在国家/地区:{request.area}\n" +
  670. $"諮詢學校:{request.name}\n" +
  671. $"聯繫人:{request.tmdname} - {request.gender}({request.tmdid})\n" +
  672. $"聯繫電話:{request.cellphone}\n" +
  673. $"聯繫郵箱:{request.email}\n";
  674. await _dingDing.SendBotMsg(msg, GroupNames.國際客戶聯繫通知群);
  675. }
  676. else
  677. {
  678. string devmsg = _option.Location.Contains("Test") || _option.Location.Contains("Dep") ? "(测试消息)" : "";
  679. string msg = $"有新的申请管理员咨询。{devmsg}\n" +
  680. $"站点:{_option.Location}\n" +
  681. //$"所在国家/地区:{request.area}\n" +
  682. $"咨询学校:{request.name}\n" +
  683. $"联系人:{request.tmdname} - {request.gender}({request.tmdid})\n" +
  684. $"联系电话:{request.cellphone}\n" +
  685. $"联系邮箱:{request.email}\n";
  686. await _dingDing.SendBotMsg(msg, GroupNames.成都开发測試群組);
  687. }
  688. return Ok();
  689. }
  690. [HttpPost("apply-service")]
  691. public async Task<IActionResult> ApplyService(ApplySchool request)
  692. {
  693. if (_option.Location.Equals("China"))
  694. {
  695. string msg = $"有新的產品咨询。\n" +
  696. $"站点:{_option.Location}\n" +
  697. //$"所在国家/地区:{request.area}\n" +
  698. $"咨询学校:{request.name}\n" +
  699. $"联系人:{request.tmdname} - {request.gender}({request.tmdid})\n " +
  700. $"联系电话:{request.cellphone}\n" +
  701. $"联系邮箱:{request.email}\n" +
  702. $"咨询内容:{request.content}";
  703. await _dingDing.SendBotMsg(msg, GroupNames.大陸客戶聯繫通知群);
  704. }
  705. else if (_option.Location.Equals("Global"))
  706. {
  707. string msg = $"有新的產品諮詢。\n" +
  708. $"站點:{_option.Location}\n" +
  709. //$"所在国家/地区:{request.area}\n" +
  710. $"諮詢學校:{request.name}\n" +
  711. $"聯繫人:{request.tmdname} - {request.gender}({request.tmdid})\n" +
  712. $"聯繫電話:{request.cellphone}\n" +
  713. $"聯繫郵箱:{request.email}\n" +
  714. $"諮詢內容:{request.content}";
  715. await _dingDing.SendBotMsg(msg, GroupNames.國際客戶聯繫通知群);
  716. }
  717. else
  718. {
  719. string devmsg = _option.Location.Contains("Test") || _option.Location.Contains("Dep") ? "(测试消息)" : "";
  720. string msg = $"有新的產品咨询。{devmsg}\n" +
  721. $"站点:{_option.Location}\n" +
  722. //$"所在国家/地区:{request.area}\n" +
  723. $"咨询学校:{request.name}\n" +
  724. $"联系人:{request.tmdname} - {request.gender}({request.tmdid})\n" +
  725. $"联系电话:{request.cellphone}\n" +
  726. $"联系邮箱:{request.email}\n" +
  727. $"咨询内容:{request.content}";
  728. await _dingDing.SendBotMsg(msg, GroupNames.成都开发測試群組);
  729. }
  730. return Ok();
  731. }
  732. [HttpPost("random-code")]
  733. [RequestSizeLimit(100_000_000)] //最大100m左右
  734. public async Task<IActionResult> RandomCode(JsonElement request)
  735. {
  736. if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();//学校编码 或者醍摩豆id
  737. request.TryGetProperty("snowflakeId", out JsonElement snowflakeId);
  738. string _num09 = "123456789";
  739. string no = $"{Utils.CreatSaltString(6, _num09)}";
  740. var Expire = DateTime.UtcNow.AddHours(1);
  741. string key = $"Random:Code:{no}-{code}";
  742. await _azureRedis.GetRedisClient(8).StringSetAsync(key, new { code = code, no = no, Expire = Expire.Ticks }.ToJsonString());
  743. _azureRedis.GetRedisClient(8).KeyExpire(key, Expire);
  744. long time=0;
  745. if (JsonValueKind.Number.Equals(snowflakeId.ValueKind) && !string.IsNullOrWhiteSpace($"{snowflakeId}"))
  746. {
  747. long id = long.Parse($"{snowflakeId}");
  748. time = _snowflakeId.ParseIdToTimeStamp(id);
  749. }
  750. return Ok(new { random = no , time });
  751. }
  752. [HttpPost("system-info")]
  753. [RequestSizeLimit(100_000_000)] //最大100m左右
  754. public async Task<IActionResult> SystemInfo(JsonElement request)
  755. {
  756. Type attr = this.GetType();
  757. string currentDirectory = Path.GetDirectoryName(attr.Assembly.Location);
  758. Assembly assembly = Assembly.LoadFrom($"{currentDirectory}\\TEAMModelOS.dll");
  759. var description = assembly.GetCustomAttribute<AssemblyDescriptionAttribute>().Description;
  760. var version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
  761. long.TryParse(version.PadRight(12, '0').Replace(".", ""),out long aver);
  762. long.TryParse(_option.Version.PadRight(12, '0').Replace(".", ""),out long bver );
  763. if (bver > aver) {
  764. version = _option.Version;
  765. }
  766. var date = DateTimeOffset.UtcNow;
  767. long nowtime = date.ToUnixTimeMilliseconds();
  768. var IpPort = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
  769. if (string.IsNullOrEmpty(IpPort))
  770. {
  771. IpPort = HttpContext.Connection.RemoteIpAddress.ToString();
  772. }
  773. if (IpPort.Contains("::"))
  774. {
  775. IpPort = "127.0.0.1";
  776. }
  777. string ip = IpPort.Split(":")[0];
  778. //string os = "";
  779. //if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
  780. //{//Linux
  781. // os = "Linux";
  782. //}
  783. //else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  784. //{//Windows
  785. // os = "Windows";
  786. //}
  787. //else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  788. //{//OSX
  789. // os = "OSX";
  790. //}
  791. string region = await _searcher.SearchIpAsync(ip);
  792. return Ok(new { version, description, nowtime, region, ip, date });
  793. }
  794. /// <summary>
  795. /// 等待P1V3,啟動Dcoker,支持EMF GDI+
  796. /// </summary>
  797. /// <param name="request"></param>
  798. /// <returns></returns>
  799. [HttpPost("convert-emf")]
  800. [RequestSizeLimit(100_000_000)] //最大100m左右
  801. public async Task<IActionResult> ConvertEmf(ImageQuangRequest request)
  802. {
  803. try
  804. {
  805. if (string.IsNullOrWhiteSpace(request.base64)) return BadRequest();
  806. byte[] obase64data = Convert.FromBase64String(request.base64);
  807. using var obase64ms = new MemoryStream(obase64data);
  808. var (isimg, type, dupe) = Utils.ImageValidateByStream(obase64ms);
  809. if (isimg && type.Equals("emf"))
  810. {
  811. var pngimagebase64 = Utils.ConvertEMFtoPNG(obase64ms);
  812. return Ok(pngimagebase64);
  813. }
  814. else
  815. {
  816. return BadRequest("非EMF圖片格式");
  817. }
  818. }
  819. catch (Exception ex)
  820. {
  821. await _dingDing.SendBotMsg($"Core,{_option.Location},convert-emf()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
  822. return BadRequest();
  823. }
  824. }
  825. /// <summary>
  826. /// PNG JPF 圖片輕量化
  827. /// </summary>
  828. /// <param name="request">支持html base64標籤解析</param>
  829. /// <returns></returns>
  830. [HttpPost("image-quant")]
  831. //
  832. [RequestSizeLimit(100_000_000)] //最大100m左右
  833. public async Task<IActionResult> ImageQuang(List<ImageQuangRequest> request)
  834. {
  835. if (request.Count < 1) return BadRequest();
  836. Regex rex = new(@"data:(?<key1>image.+?);base64,(?<key2>.+)");
  837. List<object> respons = new List<object>();
  838. var quantizer = new PngQuantizer();
  839. foreach (var item in request)
  840. {
  841. //正則處理,移除空白
  842. string trim = Regex.Replace(item.base64, @"\s", "");
  843. Match match = rex.Match(trim);
  844. string otype = match.Groups["key1"].Value;
  845. string obase64 = match.Groups["key2"].Value;
  846. byte[] obase64data = Convert.FromBase64String(obase64);
  847. using var obase64ms = new MemoryStream(obase64data);
  848. //驗證圖片格式及位深,判斷是否要處理量化演算
  849. var (isimg, type, dupe) = Utils.ImageValidateByStream(obase64ms);
  850. if (isimg && (type.Equals("png") || type.Equals("jpg")) && dupe > 8)
  851. {
  852. string img = string.Empty;
  853. using var quantized = quantizer.QuantizeImage(obase64ms, item.width, item.height); //JPEG,PNG輕量化
  854. if (quantized != null)
  855. {
  856. //檢查是否需要保存原圖到blob
  857. if (!string.IsNullOrWhiteSpace(item.blob))
  858. {
  859. img = $"{Guid.NewGuid():N}.{type}"; //命名新圖片名稱
  860. string Container = item.blob.Substring(0, item.blob.IndexOf("/")); //取得容器名稱
  861. string blobpath = $"{item.blob[(item.blob.Trim('/').IndexOf("/") + 1)..]}/{img}"; //處理路徑,避免多餘的字符
  862. obase64ms.Position = 0;
  863. await _azureStorage.GetBlobContainerClient(Container).GetBlobClient(blobpath).UploadAsync(obase64ms, new BlobHttpHeaders { ContentType = otype });
  864. }
  865. using var nbase64ms = new MemoryStream();
  866. quantized.Save(nbase64ms, ImageFormat.Png); //保存為PNG格式
  867. byte[] data = nbase64ms.ToArray();
  868. string base64 = $"data:image/png;base64,{Convert.ToBase64String(data)}"; //創建新的img src base64
  869. respons.Add(new { base64, blob = img });
  870. // TODO 下面註解,無法解決保存時,真正改變Format32bppArgb為Format8bppIndexed,需要再研究
  871. //ImageCodecInfo imageCodecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(info => info.MimeType == "image/png");
  872. //var parameters = new EncoderParameters(1);
  873. //parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8);
  874. //quantized.Save(nbase64ms, imageCodecInfo, parameters); //保存為PNG格式
  875. }
  876. else
  877. {
  878. respons.Add(new { item.base64, blob = "" }); //異常的圖片,原Base64返回
  879. }
  880. }
  881. else
  882. {
  883. respons.Add(new { item.base64, blob = "" }); //不符合的圖片,原Base64返回
  884. }
  885. }
  886. return Ok(respons);
  887. }
  888. /// <summary>
  889. /// 取得短網址
  890. /// </summary>
  891. [HttpPost("get-short-url")]
  892. public async Task<IActionResult> GetShortUrl(JsonElement request)
  893. {
  894. if (!request.TryGetProperty("url", out JsonElement url)) return BadRequest();
  895. try
  896. {
  897. var uri = new Uri(url.GetString());
  898. string md5url = Md5Hash.GetMd5String(uri.OriginalString);
  899. char[] chars = new char[]
  900. {
  901. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
  902. 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
  903. 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
  904. 'y', 'z', '0', '1', '2', '3', '4', '5',
  905. '6', '7', '8', '9', 'A', 'B', 'C', 'D',
  906. 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
  907. 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
  908. 'U', 'V', 'W', 'X', 'Y', 'Z'
  909. };
  910. //把加密字元按照8位一組16進位制與0x3FFFFFFF進行位與運算
  911. int hexint = 0x3FFFFFFF & Convert.ToInt32(string.Concat("0x", md5url.AsSpan(0, 8)), 16);
  912. StringBuilder outShort = new();
  913. for (int j = 0; j < 6; j++)
  914. {
  915. //把得到的值與0x0000003D進行位與運算,取得字元陣列chars索引
  916. int index = 0x0000003D & hexint;
  917. //把取得的字元相加
  918. outShort.Append(chars[index]);
  919. //每次迴圈按位右移5位
  920. hexint >>= 5;
  921. }
  922. var shortid = outShort.ToString();
  923. var table = _azureStorage.GetCloudTableClient().GetTableReference("ShortUrl");
  924. ShortUrl su = new() { PartitionKey = uri.Host, RowKey = shortid, Url = uri.AbsoluteUri };
  925. await table.SaveOrUpdate<ShortUrl>(su);
  926. var result = new UriBuilder() { Scheme = uri.Scheme, Host = uri.Host, Path = shortid }.ToString();
  927. return Ok(new { result });
  928. }
  929. catch
  930. {
  931. return Ok(new { error = 1, message = "无法转换" });
  932. }
  933. }
  934. /// <summary>
  935. /// 短網址換長網址
  936. /// </summary>
  937. [HttpPost("get-long-url")]
  938. public IActionResult GetLongUrl(JsonElement request)
  939. {
  940. if (!request.TryGetProperty("url", out JsonElement url)) return BadRequest();
  941. try
  942. {
  943. var uri = new Uri(url.GetString());
  944. var table = _azureStorage.GetCloudTableClient().GetTableReference("ShortUrl");
  945. var shortUrl = table.Get<ShortUrl>(uri.Host, uri.AbsolutePath.Replace("/", string.Empty));
  946. if (shortUrl is not null)
  947. {
  948. return Ok(new { result = shortUrl.Url });
  949. }
  950. else
  951. {
  952. return Ok(new { error = 1, message = "无法转换" });
  953. }
  954. }
  955. catch
  956. {
  957. return Ok(new { error = 1, message = "无法转换" });
  958. }
  959. }
  960. }
  961. public record ApplySchool
  962. {
  963. /// <summary>
  964. /// 学校名称
  965. /// </summary>
  966. public string name { get; set; }
  967. /// <summary>
  968. /// 性别
  969. /// </summary>
  970. public string gender { get; set; }
  971. /// <summary>
  972. /// 站点
  973. /// </summary>
  974. public string site { get; set; }
  975. public string area { get; set; }
  976. public string tmdname { get; set; }
  977. public string tmdid { get; set; }
  978. /// <summary>
  979. /// 手机号
  980. /// </summary>
  981. public string cellphone { get; set; }
  982. public string email { get; set; }
  983. /// <summary>
  984. /// 备注
  985. /// </summary>
  986. public string content { get; set; }
  987. /// <summary>
  988. /// 学制
  989. /// </summary>
  990. public List<string> period { get; set; } = new List<string>();
  991. /// <summary>
  992. /// 机构代码
  993. /// </summary>
  994. public string orgCode { get; set; }
  995. }
  996. }