CoreController.cs 50 KB

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