BINoticeController.cs 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747
  1. using Microsoft.AspNetCore.Hosting;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using Microsoft.Extensions.Configuration;
  5. using Microsoft.Extensions.Options;
  6. using System.Net.Http;
  7. using TEAMModelOS.SDK.DI;
  8. using TEAMModelOS.SDK;
  9. using TEAMModelOS.Models;
  10. using System.Text.Json;
  11. using System.Threading.Tasks;
  12. using TEAMModelOS.SDK.Context.Constant;
  13. using System.Collections.Generic;
  14. using System.Text;
  15. using TEAMModelOS.SDK.Extension;
  16. using TEAMModelBI.Models;
  17. using System.Linq;
  18. using TEAMModelBI.Filter;
  19. using TEAMModelOS.SDK.Models.Cosmos.BI;
  20. using Microsoft.Azure.Cosmos;
  21. using System;
  22. using TEAMModelOS.SDK.Models.Service.BI;
  23. using TEAMModelOS.SDK.Models.Cosmos.BI.BISchool;
  24. using TEAMModelBI.Tool.Extension;
  25. using TEAMModelOS.SDK.Models;
  26. using Pipelines.Sockets.Unofficial.Arenas;
  27. using TEAMModelBI.Tool;
  28. using Azure.Core;
  29. using Azure.Storage.Blobs.Models;
  30. using Microsoft.AspNetCore.Authorization;
  31. using static TEAMModelBI.Models.Extension.GeoRegion;
  32. using System.Diagnostics.Metrics;
  33. using Microsoft.Azure.Cosmos.Table;
  34. using System.Net.Http.Json;
  35. using NotifyData = TEAMModelOS.SDK.CoreAPIHttpService.NotifyData;
  36. using Microsoft.Azure.Cosmos.Linq;
  37. using System.Text.RegularExpressions;
  38. using MathNet.Numerics.Distributions;
  39. using System.Drawing.Drawing2D;
  40. using Microsoft.Azure.Amqp.Framing;
  41. namespace TEAMModelBI.Controllers.BICommon
  42. {
  43. [Route("notice")]
  44. [ApiController]
  45. public class BINoticeController : ControllerBase
  46. {
  47. private readonly AzureCosmosFactory _azureCosmos;
  48. private readonly DingDing _dingDing;
  49. private readonly Option _option;
  50. private readonly AzureStorageFactory _azureStorage;
  51. private readonly IConfiguration _configuration;
  52. private readonly AzureServiceBusFactory _serviceBus;
  53. private readonly IHttpClientFactory _http;
  54. private readonly CoreAPIHttpService _coreAPIHttpService;
  55. private readonly IWebHostEnvironment _environment; //读取文件
  56. private readonly HttpClient _httpClient;
  57. private readonly SnowflakeId _snowflakeId;
  58. public BINoticeController(AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option, IConfiguration configuration, AzureServiceBusFactory serviceBus, IHttpClientFactory http, CoreAPIHttpService coreAPIHttpService, IWebHostEnvironment hostingEnvironment, HttpClient httpClient, SnowflakeId snowflakeId)
  59. {
  60. _azureCosmos = azureCosmos;
  61. _dingDing = dingDing;
  62. _azureStorage = azureStorage;
  63. _option = option?.Value;
  64. _configuration = configuration;
  65. _serviceBus = serviceBus;
  66. _http = http;
  67. _coreAPIHttpService = coreAPIHttpService;
  68. _environment = hostingEnvironment;
  69. _httpClient = httpClient;
  70. _snowflakeId = snowflakeId;
  71. }
  72. /// <summary>
  73. /// 查询账户信息
  74. /// </summary>
  75. /// <param name="jsonElement"></param>
  76. /// <returns></returns>
  77. [AuthToken(Roles = "admin,rdc,assist,sales")]
  78. [HttpPost("get-info")]
  79. public async Task<IActionResult> GetInfo(JsonElement jsonElement)
  80. {
  81. jsonElement.TryGetProperty("ids", out JsonElement _ids);
  82. var cosmosClient = _azureCosmos.GetCosmosClient();
  83. List<string> ids = new();
  84. ids = _ids.ToObject<List<string>>();
  85. List<TmdUserinfo> userinfos = new();
  86. List<string> noFound = new();
  87. if (ids.Count > 0)
  88. {
  89. var content = new StringContent(ids.ToArray().ToJsonString(), Encoding.UTF8, "application/json");
  90. string idJson = await _coreAPIHttpService.GetUserInfos(content);
  91. userinfos = idJson.ToObject<List<TmdUserinfo>>();
  92. foreach (var item in ids)
  93. {
  94. var tempId = userinfos.Where(s => s.id.Equals(item)).ToList();
  95. var tempMail = userinfos.Where(s => !string.IsNullOrEmpty($"{s.mail}") && s.mail.Equals(item)).ToList();
  96. var tempmobile = userinfos.Where(s => !string.IsNullOrEmpty($"{s.mobile}") && s.mobile.Equals(item)).ToList();
  97. if (tempId.Count == 0 && tempMail.Count == 0 && tempmobile.Count == 0)
  98. noFound.Add(item);
  99. }
  100. }
  101. return Ok(new { state = RespondCode.Ok, userinfos, noFound });
  102. }
  103. /// <summary>
  104. /// 依据学校id查询学校教师信息
  105. /// </summary>
  106. /// <param name="jsonElement"></param>
  107. /// <returns></returns>
  108. //[AuthToken(Roles = "admin,rdc,assist,sales")]
  109. [HttpPost("get-tchinfos")]
  110. public async Task<IActionResult> GetSchoolTeacher(JsonElement jsonElement)
  111. {
  112. if (!jsonElement.TryGetProperty("scId", out JsonElement scId)) return BadRequest();
  113. List<IdInfo> idInfos = new();
  114. var cosmosClient = _azureCosmos.GetCosmosClient();
  115. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<IdInfo>(queryText: "select c.id,c.name,c.picture from c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{scId}") }))
  116. {
  117. idInfos.Add(item);
  118. }
  119. return Ok(new { state = RespondCode.Ok, idInfos });
  120. }
  121. /// <summary>
  122. /// BI发布端外消息
  123. /// </summary>
  124. /// <param name="jsonElement"></param>
  125. /// <returns></returns>
  126. [AuthToken(Roles = "admin,rdc,assist,sales")]
  127. [HttpPost("push-info")]
  128. public async Task<IActionResult> PushNotion(JsonElement jsonElement)
  129. {
  130. try
  131. {
  132. var (_tmdId, _tmdName, _, _, _, _) = HttpJwtAnalysis.JwtXAuthBI(HttpContext.GetXAuth("AuthToken"), _option);
  133. jsonElement.TryGetProperty("type", out JsonElement type);
  134. jsonElement.TryGetProperty("jumpUrl", out JsonElement jumpUrl);
  135. jsonElement.TryGetProperty("callbackName", out JsonElement callbackName);
  136. jsonElement.TryGetProperty("refuseName", out JsonElement refuseName);
  137. if (!jsonElement.TryGetProperty("theme", out JsonElement theme)) return BadRequest();
  138. if (!jsonElement.TryGetProperty("content", out JsonElement content)) return BadRequest();
  139. jsonElement.TryGetProperty("tmdIds", out JsonElement _tmdIds);
  140. jsonElement.TryGetProperty("schoolIds", out JsonElement _schoolIds);
  141. jsonElement.TryGetProperty("areaIds", out JsonElement _areaIds);
  142. if (!jsonElement.TryGetProperty("crowdType", out JsonElement _crowdType)) return BadRequest();
  143. if (!jsonElement.TryGetProperty("sendTime", out JsonElement sendTime)) return BadRequest();
  144. jsonElement.TryGetProperty("source", out JsonElement source);
  145. jsonElement.TryGetProperty("isOnlySave", out JsonElement isOnlySave);
  146. var cosmosClient = _azureCosmos.GetCosmosClient();
  147. //DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  148. List<IdNameCode> idNameCodes = new();
  149. BINotice bINotice = new();
  150. List<CrowdInfo> tmdIds = new();
  151. List<CrowdInfo> schoolIds = new();
  152. List<CrowdInfo> areaIds = new();
  153. List<string> tempScIds = new();
  154. //if (!string.IsNullOrEmpty($"{sendTime}"))
  155. // dateTime = DateTimeOffset.Parse($"{sendTime}");
  156. if (string.IsNullOrEmpty($"{_tmdIds}") && string.IsNullOrEmpty($"{_schoolIds}") && string.IsNullOrEmpty($"{_areaIds}"))
  157. return Ok(new { state = RespondCode.ParamsError, msg = "发送人群不能为空" });
  158. if (!string.IsNullOrEmpty($"{_tmdIds}"))
  159. tmdIds = _tmdIds.ToObject<List<CrowdInfo>>();
  160. if (!string.IsNullOrEmpty($"{_schoolIds}"))
  161. schoolIds = _schoolIds.ToObject<List<CrowdInfo>>();
  162. if (!string.IsNullOrEmpty($"{_areaIds}"))
  163. areaIds = _areaIds.ToObject<List<CrowdInfo>>();
  164. Crowd crowd = new()
  165. {
  166. tmdIds = tmdIds.Count > 0 ? tmdIds : new List<CrowdInfo>(),
  167. schoolIds = schoolIds.Count > 0 ? schoolIds : new List<CrowdInfo>(),
  168. areaIds = areaIds.Count > 0 ? areaIds : new List<CrowdInfo>(),
  169. types = $"{_crowdType}"
  170. };
  171. if (tmdIds.Count > 0)
  172. {
  173. StringBuilder tchSql = new($"select c.id, c.name,c.code,c.picture,c.nickname from c ");
  174. if (tmdIds.Count > 0)
  175. {
  176. tchSql.Append(BICommonWay.ManyScSql(" where c.id ", tmdIds.Select(s => s.id).ToList()));
  177. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIteratorSql<IdNameCode>(queryText: tchSql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  178. {
  179. var tempId = idNameCodes.FindAll(fa => fa.id.Equals(item.id)).ToList();
  180. if (tempId.Count == 0)
  181. idNameCodes.Add(item);
  182. }
  183. }
  184. }
  185. if (areaIds.Count > 0)
  186. {
  187. StringBuilder scAreaSql = new($"select value(c.id) from c");
  188. if (schoolIds.Count > 0)
  189. {
  190. scAreaSql.Append(BICommonWay.ManyScSql(" where c.areaId", areaIds.Select(s => s.id).ToList()));
  191. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<string>(queryText: scAreaSql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  192. {
  193. var tempScId = schoolIds.FindAll(fa => fa.Equals(item)).ToList();
  194. if (tempScId.Count == 0)
  195. tempScIds.Add(item);
  196. }
  197. }
  198. }
  199. if (schoolIds.Count > 0)
  200. {
  201. StringBuilder scTchSql = new($"select c.id, c.name, c.picture,c.nickname from c where c.pk='Teacher'");
  202. if (schoolIds.Count > 0)
  203. {
  204. scTchSql.Append(BICommonWay.ManyScSql(" and c.code", schoolIds.Select(s => s.id).ToList(), $"Teacher-"));
  205. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<IdNameCode>(queryText: scTchSql.ToString(), requestOptions: new QueryRequestOptions() { }))
  206. {
  207. var tempId = idNameCodes.FindAll(fa => fa.id.Equals(item.id)).ToList();
  208. if (tempId.Count == 0)
  209. idNameCodes.Add(item);
  210. }
  211. }
  212. }
  213. string tagServiceName = ($"{_crowdType}".ToLower().Contains("hita")) ? "HiTA" : $"{_crowdType}";
  214. bINotice = new()
  215. {
  216. id = Guid.NewGuid().ToString(),
  217. code = "BINotice",
  218. type = !string.IsNullOrEmpty($"{type}") ? type.GetInt32() : 0,
  219. jumpUrl = !string.IsNullOrEmpty($"{jumpUrl}") ? $"{jumpUrl}" : "",
  220. callbackName = !string.IsNullOrEmpty($"{callbackName}") ? $"{callbackName}" : "",
  221. refuseName = !string.IsNullOrEmpty($"{refuseName}") ? $"{refuseName}" : "",
  222. theme = $"{theme}",
  223. content = $"{content}",
  224. crowd = crowd,
  225. crowdIds = idNameCodes.Select(s => $"{s.id}_{tagServiceName}").ToList(),
  226. createId = _tmdId,
  227. sendTime = sendTime.GetInt64(),//发布时间待解决
  228. createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
  229. source = !string.IsNullOrEmpty($"{source}") ? $"{source}" : "BI"
  230. };
  231. bINotice = await cosmosClient.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync<BINotice>(bINotice, new PartitionKey("BINotice"));
  232. if (isOnlySave.GetBoolean())
  233. {
  234. //BI发送端外通知
  235. _coreAPIHttpService.BIPushNotify(bINotice, new Dictionary<string, object> { { "tmdid", _tmdIds }, { "sendId", bINotice.crowdIds } }, _option.Location, _configuration, _dingDing);
  236. }
  237. return Ok(new { state = RespondCode.Ok, bINotice });
  238. }
  239. catch (Exception ex)
  240. {
  241. _ = _dingDing.SendBotMsg($"BI,{_option.Location},notion/PushNotion() \n{ex.Message}\n{ex.StackTrace}\n", GroupNames.成都开发測試群組);
  242. return BadRequest();
  243. }
  244. }
  245. /// <summary>
  246. /// 查询发布的消息信息
  247. /// </summary>
  248. /// <param name="jsonElement"></param>
  249. /// <returns></returns>
  250. [HttpPost("get-infos")]
  251. [AuthToken(Roles = "admin,rdc,assist,sales")]
  252. public async Task<IActionResult> GetInfos(JsonElement jsonElement)
  253. {
  254. jsonElement.TryGetProperty("id", out JsonElement id);
  255. var cosmosClient = _azureCosmos.GetCosmosClient();
  256. List<BINotice> bINotices = new();
  257. StringBuilder sql = new("select value(c) from c");
  258. if (!string.IsNullOrEmpty($"{id}"))
  259. sql.Append($" where c.id='{id}'");
  260. sql.Append($" order by c.sendTime desc ");
  261. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<BINotice>(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("BINotice") }))
  262. {
  263. bINotices.Add(item);
  264. }
  265. return Ok(new { state = RespondCode.Ok, bINotices });
  266. }
  267. /// <summary>
  268. /// 取得學區及所屬學校、教師數
  269. /// </summary>
  270. /// <param name="jsonElement"></param>
  271. /// <returns></returns>
  272. [HttpPost("get-areas")]
  273. #if !DEBUG
  274. [AuthToken(Roles = "admin")]
  275. #endif
  276. public async Task<IActionResult> GetAreas(JsonElement jsonElement)
  277. {
  278. try
  279. {
  280. string reqAreaId = (jsonElement.TryGetProperty("areaId", out JsonElement _areaId)) ? _areaId.GetString() : string.Empty;
  281. bool showList = (jsonElement.TryGetProperty("showList", out JsonElement _showList)) ? _showList.GetBoolean() : false; //是否列出學校
  282. bool hasMail = (jsonElement.TryGetProperty("hasMail", out JsonElement _hasMail)) ? _hasMail.GetBoolean() : false; //篩選是否有Email true:會排除沒有Mail的醍摩豆帳號
  283. var cosmosClient = _azureCosmos.GetCosmosClient();
  284. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
  285. List<AreaInfo> areaInfos = new();
  286. //取得學區
  287. string sqlArea = $"SELECT c.id, c.name FROM c ";
  288. string whereArea = string.Empty;
  289. if (!string.IsNullOrWhiteSpace(reqAreaId))
  290. {
  291. string wherePre = (!string.IsNullOrWhiteSpace(whereArea)) ? " AND " : string.Empty;
  292. whereArea += $"{wherePre} c.id = '{reqAreaId}' ";
  293. }
  294. if (!string.IsNullOrWhiteSpace(whereArea))
  295. {
  296. whereArea = $" WHERE {whereArea}";
  297. sqlArea = sqlArea + whereArea;
  298. }
  299. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", Constant.Normal).GetItemQueryIteratorSql<AreaInfo>(queryText: sqlArea, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base-Area") }))
  300. {
  301. areaInfos.Add(item);
  302. }
  303. //取得學校ID列表
  304. Dictionary<string, string> schAreaDic = new Dictionary<string, string>();
  305. Dictionary<string, string> schNameDic = new Dictionary<string, string>();
  306. List<string> teacherCodes = new List<string>();
  307. List<string> areaIds = areaInfos.Select(a => a.id).ToList();
  308. string sqlSch = $"SELECT c.id, c.name, c.areaId FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(areaIds)}, c.areaId)";
  309. ///實體校
  310. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
  311. {
  312. string schId = item.GetProperty("id").ToString();
  313. string schName = item.GetProperty("name").ToString();
  314. string areaId = item.GetProperty("areaId").ToString();
  315. if (!schAreaDic.ContainsKey(schId))
  316. {
  317. schAreaDic.Add(schId, areaId);
  318. schNameDic.Add(schId, schName);
  319. }
  320. if (!teacherCodes.Contains($"Teacher-{schId}"))
  321. {
  322. teacherCodes.Add($"Teacher-{schId}");
  323. }
  324. }
  325. ///虛擬校
  326. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"VirtualBase") }))
  327. {
  328. string schId = item.GetProperty("id").ToString();
  329. string schName = item.GetProperty("name").ToString();
  330. string areaId = item.GetProperty("areaId").ToString();
  331. if (!schAreaDic.ContainsKey(schId))
  332. {
  333. schAreaDic.Add(schId, areaId);
  334. if (!schNameDic.ContainsKey(schId)) schNameDic.Add(schId, schName);
  335. }
  336. }
  337. //有無Email篩檢
  338. List<string> tmidFilterList = new List<string>();
  339. if (hasMail)
  340. {
  341. List<string> tmidListIes = new List<string>();
  342. string sqlTchHasMailIes = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
  343. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTchHasMailIes, requestOptions: null))
  344. {
  345. string tmid = item.GetProperty("id").ToString();
  346. tmidListIes.Add(tmid);
  347. }
  348. string sqlEx = $"SELECT c.id FROM c WHERE IS_DEFINED(c.mail) AND NOT IS_NULL(c.mail) AND c.mail != '' AND ARRAY_CONTAINS({JsonSerializer.Serialize(tmidListIes)}, c.id) ";
  349. await foreach (var item in cosmosClientCsv2.GetContainer("Core", "ID2").GetItemQueryIteratorSql<JsonElement>(queryText: sqlEx, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base-ex") }))
  350. {
  351. string tmid = item.GetProperty("id").ToString();
  352. tmidFilterList.Add(tmid);
  353. }
  354. }
  355. //取得學校教師數
  356. Dictionary<string, int> schTeacherCntDic = new Dictionary<string, int>();
  357. string sqlTmidFilter = (tmidFilterList.Count > 0) ? $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(tmidFilterList)}, c.id) " : string.Empty;
  358. string sqlTch = $"SELECT REPLACE(c.code, 'Teacher-', '') as schoolId, COUNT(c.id) AS tchCnt FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code) {sqlTmidFilter} GROUP BY c.code";
  359. await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
  360. {
  361. string schId = item.GetProperty("schoolId").ToString();
  362. int tchCnt = item.GetProperty("tchCnt").GetInt32();
  363. schTeacherCntDic.Add(schId, tchCnt);
  364. }
  365. //資料整理1 有教師則記入
  366. foreach (KeyValuePair<string, int> item in schTeacherCntDic)
  367. {
  368. string schId = item.Key;
  369. int tchCnt = item.Value;
  370. string areaId = schAreaDic[schId];
  371. AreaInfo areaInfo = areaInfos.Where(a => a.id.Equals(areaId)).FirstOrDefault();
  372. if (areaInfo != null)
  373. {
  374. areaInfo.scCnt++;
  375. areaInfo.tchCnt += tchCnt;
  376. if (showList)
  377. {
  378. AreaSchoolInfo sch = new AreaSchoolInfo() { id = schId, name = schNameDic[schId], tchCnt = tchCnt };
  379. areaInfo.lists.Add(sch);
  380. }
  381. }
  382. }
  383. //資料整理2 移除無學校學區
  384. var result = areaInfos.Where(a => a.scCnt > 0).ToList();
  385. return Ok(new { state = RespondCode.Ok, result });
  386. }
  387. catch (Exception ex)
  388. {
  389. return BadRequest(new { ex });
  390. }
  391. }
  392. /// <summary>
  393. /// 取得地理位置所屬的學校或TMID
  394. /// </summary>
  395. /// <param name="jsonElement"></param>
  396. /// <returns></returns>
  397. [HttpPost("get-geos")]
  398. #if !DEBUG
  399. [AuthToken(Roles = "admin")]
  400. #endif
  401. public async Task<IActionResult> GetGeos(JsonElement jsonElement)
  402. {
  403. bool showList = (jsonElement.TryGetProperty("showList", out JsonElement _showList)) ? _showList.GetBoolean() : false; //是否列出學校或TMID
  404. string countryId = (jsonElement.TryGetProperty("countryId", out JsonElement _countryId)) ? _countryId.GetString() : string.Empty; //地區ID(國ID)
  405. string provinceId = (jsonElement.TryGetProperty("provinceId", out JsonElement _provinceId)) ? _provinceId.GetString() : string.Empty; //省ID
  406. string cityId = (jsonElement.TryGetProperty("cityId", out JsonElement _cityId)) ? _cityId.GetString() : string.Empty; //市ID
  407. string distId = (jsonElement.TryGetProperty("dist", out JsonElement _distJobj)) ? _distJobj.GetString() : string.Empty; //區ID
  408. string type = (jsonElement.TryGetProperty("type", out JsonElement _type)) ? _type.GetString() : "school"; //類型 tmid、school
  409. bool hasMail = (jsonElement.TryGetProperty("hasMail", out JsonElement _hasMail)) ? _hasMail.GetBoolean() : false; //篩選是否有Email true:會排除沒有Mail的醍摩豆帳號
  410. string countryName = string.Empty;
  411. string provinceName = string.Empty;
  412. string cityName = string.Empty;
  413. string distName = string.Empty;
  414. var (geoInfos, groupKey) = await GetDataByGeo(type, "geo", showList, countryId, provinceId, cityId, hasMail);
  415. return Ok(new { state = 200, dataType = groupKey, data = geoInfos });
  416. }
  417. /// <summary>
  418. /// 取得教育機構所屬的學校或TMID
  419. /// </summary>
  420. /// <param name="jsonElement"></param>
  421. /// <returns></returns>
  422. [HttpPost("get-units")]
  423. #if !DEBUG
  424. [AuthToken(Roles = "admin")]
  425. #endif
  426. public async Task<IActionResult> GetUnitTypes(JsonElement jsonElement)
  427. {
  428. //var cosmosClient = _azureCosmos.GetCosmosClient();
  429. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
  430. var coreCosmosClientCn = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead");
  431. string type = (jsonElement.TryGetProperty("type", out JsonElement _type)) ? _type.GetString() : string.Empty; //教育機構類型 1:基礎教育機構(K-小學) 2:中等教育機構(國中、高中/職) 3:高等教育機構(大學、研究所) 4:其他
  432. bool showList = (jsonElement.TryGetProperty("showList", out JsonElement _showList)) ? _showList.GetBoolean() : false; //是否列出學校
  433. bool hasMail = (jsonElement.TryGetProperty("hasMail", out JsonElement _hasMail)) ? _hasMail.GetBoolean() : false; //篩選是否有Email true:會排除沒有Mail的醍摩豆帳號
  434. //取得ID帳號中所有的歸戶學校ID
  435. List<string> coreSchIds = new List<string>();
  436. Dictionary<string, List<string>> schTmidDic = new Dictionary<string, List<string>>(); //學校ID > TMID 字典
  437. string sqlEx = "SELECT c.id, c.schoolCode, c.schoolCodeW FROM c WHERE IS_DEFINED(c.schoolCode) AND NOT IS_NULL(c.schoolCode) AND IS_DEFINED(c.schoolCodeW) AND NOT IS_NULL(c.schoolCodeW)";
  438. if (hasMail) sqlEx += " AND IS_DEFINED(c.mail) AND NOT IS_NULL(c.mail) ";
  439. await foreach (var item in cosmosClientCsv2.GetContainer("Core", "ID2").GetItemQueryIteratorSql<JsonElement>(queryText: sqlEx, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base-ex") }))
  440. {
  441. string tmid = item.GetProperty("id").ToString();
  442. string shortCode = (item.TryGetProperty("shortCode", out JsonElement _shortCode)) ? _shortCode.ToString() : string.Empty;
  443. string schoolCodeW = (item.TryGetProperty("schoolCodeW", out JsonElement _schoolCodeW)) ? _schoolCodeW.ToString() : string.Empty;
  444. string schId = (!string.IsNullOrWhiteSpace(shortCode)) ? shortCode : (!string.IsNullOrWhiteSpace(schoolCodeW)) ? schoolCodeW : string.Empty;
  445. if (!string.IsNullOrWhiteSpace(schId))
  446. {
  447. if(!coreSchIds.Contains(schId)) coreSchIds.Add(schId);
  448. if (schTmidDic.ContainsKey(schId)) {
  449. if (!schTmidDic[schId].Contains(tmid)) schTmidDic[schId].Add(tmid);
  450. }
  451. else
  452. {
  453. schTmidDic.Add(schId, new List<string>() { tmid });
  454. }
  455. }
  456. }
  457. //Core 篩選取得的學校、學校類型取得
  458. string coreSql = "SELECT c.id, c.name, c.code, c.shortCode, c.unitType, c.unitType = '1' ? '1' : c.unitType = '8' ? '1' : c.unitType = '4' ? '1' : c.unitType = '5' ? '4' : c.unitType = '6' ? '4' : c.unitType = '7' ? '4' : c.unitType = '9' ? '4' : c.unitType as unitTypeFix FROM c";
  459. string coreWhere = $" WHERE 1=1 AND ARRAY_CONTAINS({JsonSerializer.Serialize(coreSchIds)}, c.shortCode) AND IS_DEFINED(c.unitType) AND NOT IS_NULL(c.unitType) ";
  460. switch (type)
  461. {
  462. case "1": ///基礎教育機構(K-小學)
  463. coreWhere += $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(new List<string>() { "1", "8" })}, c.unitType)";
  464. break;
  465. case "2": ///中等教育機構(國中、高中/職)
  466. coreWhere += $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(new List<string>() { "2" })}, c.unitType)";
  467. break;
  468. case "3": ///高等教育機構(大學、研究所)
  469. coreWhere += $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(new List<string>() { "3" })}, c.unitType)";
  470. break;
  471. case "4": ///其他
  472. coreWhere += $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(new List<string>() { "4", "5", "6", "7", "9" })}, c.unitType)";
  473. break;
  474. }
  475. string lang = (_option.Location.Contains("China")) ? "zh_cn" : "zh_tw";
  476. Dictionary<string, string> unitNameDic = getSchoolUnitName(lang);
  477. List<AreaInfo> unitInfos = new(); //輸出結果
  478. string sqlCore = $"{coreSql}{coreWhere}";
  479. await foreach (var item in coreCosmosClientCn.GetContainer("Core", "School").GetItemQueryIteratorSql<JsonElement>(queryText: sqlCore, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base") }))
  480. {
  481. string schName = item.GetProperty("name").ToString();
  482. string shortCode = item.GetProperty("shortCode").ToString();
  483. string unitType = item.GetProperty("unitTypeFix").ToString();
  484. ///結果製作
  485. string unitId = unitType;
  486. string unitName = unitNameDic[unitId];
  487. AreaInfo unitInfo = unitInfos.Where(u => u.id.Equals(unitId)).FirstOrDefault();
  488. if (unitInfo == null)
  489. {
  490. AreaInfo unitInfoCrt = new AreaInfo();
  491. unitInfoCrt.id = unitId;
  492. unitInfoCrt.name = unitName;
  493. unitInfos.Add(unitInfoCrt);
  494. unitInfo = unitInfos.Where(u => u.id.Equals(unitId)).FirstOrDefault();
  495. }
  496. unitInfo.scCnt++;
  497. int tchCnt = (schTmidDic.ContainsKey(shortCode)) ? schTmidDic[shortCode].Count : 0;
  498. unitInfo.tchCnt += tchCnt;
  499. if (showList)
  500. {
  501. AreaSchoolInfo sch = new AreaSchoolInfo() { id = shortCode, name = schName, tchCnt = tchCnt };
  502. unitInfo.lists.Add(sch);
  503. }
  504. }
  505. return Ok(new { state = 200, data = unitInfos });
  506. }
  507. //由地理資訊取得TMID或學校核心邏輯
  508. /// <summary>由地理資訊取得TMID或學校核心邏輯</summary>
  509. /// <param name="type">地理資訊取得對象:tmid、school</param>
  510. /// <param name="output">輸出:tmid、school</param>
  511. /// <param name="showList">輸出項lists是否要有值</param>
  512. /// <param name="countryId"></param>
  513. /// <param name="provinceId"></param>
  514. /// <param name="cityId"></param>
  515. /// <param name="hasMail">醍魔豆帳號是否有Email</param>
  516. /// <returns></returns>
  517. private async Task<(List<AreaInfo>,string)> GetDataByGeo (string type, string output, bool showList, string countryId = "", string provinceId = "", string cityId = "", bool hasMail = false)
  518. {
  519. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
  520. var cosmosClientCsv2Cn = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead");
  521. bool isChina = (_option.Location.Contains("China")) ? true : false;
  522. List<string> comeRemoveStr = GeoRegion.comeRemoveStr;
  523. if (isChina) countryId = "CN";
  524. List<TmidInfo> tmidExInfos = new List<TmidInfo>(); //TMID資訊
  525. List<AreaInfo> geoInfos = new(); //輸出:地理位置為單位
  526. List<AreaInfo> tmidInfos = new(); //輸出:TMID為單位
  527. regiondata regionData = GetRegionData(); //取得國省市區地理資訊架構
  528. regiondata regionDataEn = GetRegionDataEn(); //取得EN版國省市區地理資訊架構
  529. Dictionary<string, string> geoIdNameDic = new Dictionary<string, string>(); //key: geoId val:geoName
  530. Dictionary<string, string> geoCountryIdNameDicEn = new Dictionary<string, string>(); //key: geoId val:geoName
  531. string countryName = string.Empty;
  532. string provinceName = string.Empty;
  533. string cityName = string.Empty;
  534. string distName = string.Empty;
  535. //[TMID地理位置欄位 ID與名稱混雜對策] 取得搜尋的國省市名稱
  536. ///國字典製作
  537. var geoCountryIdNameDic = new Dictionary<string, string>();
  538. foreach (KeyValuePair<string, regionbase> item in regionData.country)
  539. {
  540. geoCountryIdNameDic.Add(item.Value.code, item.Value.name);
  541. }
  542. foreach (KeyValuePair<string, regionbase> item in regionDataEn.country)
  543. {
  544. geoCountryIdNameDicEn.Add(item.Value.code, item.Value.name);
  545. }
  546. if (!string.IsNullOrWhiteSpace(countryId))
  547. {
  548. countryName = (regionData.country.ContainsKey(countryId)) ? regionData.country[countryId].name : string.Empty;
  549. }
  550. ///省字典製作
  551. var geoProvinceIdNameDic = new Dictionary<string, string>();
  552. if (isChina)
  553. {
  554. foreach (KeyValuePair<string, regionbase> item in regionData.province["CN"])
  555. {
  556. geoProvinceIdNameDic.Add(item.Value.code, item.Value.name);
  557. }
  558. }
  559. if (!string.IsNullOrWhiteSpace(provinceId))
  560. {
  561. provinceName = (geoProvinceIdNameDic.ContainsKey(provinceId)) ? geoProvinceIdNameDic[provinceId] : string.Empty;
  562. }
  563. ///市字典製作
  564. var geoCityIdNameDic = new Dictionary<string, string>();
  565. if(isChina)
  566. {
  567. foreach (var dicProvince in regionData.city["CN"])
  568. {
  569. var pNow = dicProvince.Value;
  570. foreach (var item in pNow)
  571. {
  572. geoCityIdNameDic.Add(item.Value.code, item.Value.name);
  573. }
  574. }
  575. }
  576. else
  577. {
  578. foreach (var dicProvince in regionData.city["TW"])
  579. {
  580. var pNow = dicProvince.Value;
  581. foreach (var item in pNow)
  582. {
  583. geoCityIdNameDic.Add(item.Value.code, item.Value.name);
  584. }
  585. }
  586. }
  587. if (!string.IsNullOrWhiteSpace(cityId))
  588. {
  589. cityName = (geoCityIdNameDic.ContainsKey(cityId)) ? geoCityIdNameDic[cityId] : string.Empty;
  590. }
  591. string sqlWhere = string.Empty;
  592. string groupKey = "country";
  593. //地理資訊取得對象:TMID
  594. if (type.Equals("tmid"))
  595. {
  596. List<string> schIds = new List<string>(); //要取得的學校ID
  597. //取得ID資訊
  598. sqlWhere = $" (NOT IS_NULL(c.country) AND c.country != '') ";
  599. ///國
  600. if (!string.IsNullOrWhiteSpace(countryId))
  601. {
  602. groupKey = (!isChina) ? "city" : "province";
  603. string tmpSql = $" c.country = '{countryId}' ";
  604. if (!string.IsNullOrWhiteSpace(countryName))
  605. {
  606. comeRemoveStr.ForEach(c => { countryName = countryName.Replace(c, ""); });
  607. tmpSql += $" OR CONTAINS(c.country, '{countryName}') ";
  608. }
  609. sqlWhere += $" AND ({tmpSql}) ";
  610. }
  611. ///省
  612. if (!string.IsNullOrWhiteSpace(provinceId))
  613. {
  614. groupKey = "city";
  615. string tmpSql = $" c.province = '{provinceId}' ";
  616. if (!string.IsNullOrWhiteSpace(provinceName))
  617. {
  618. comeRemoveStr.ForEach(c => { provinceName = provinceName.Replace(c, ""); });
  619. tmpSql += $" OR CONTAINS(c.province, '{provinceName}') ";
  620. }
  621. sqlWhere += $" AND ({tmpSql}) ";
  622. }
  623. ///市
  624. if (!string.IsNullOrWhiteSpace(cityId))
  625. {
  626. groupKey = "name";
  627. string tmpSql = (!isChina) ? $" c.province = '{cityId}' " : $" c.city = '{cityId}' ";
  628. if (!string.IsNullOrWhiteSpace(cityName))
  629. {
  630. comeRemoveStr.ForEach(c => { cityName = cityName.Replace(c, ""); });
  631. tmpSql += (!isChina) ? $" OR CONTAINS(c.province, '{cityName}') " : $" OR CONTAINS(c.city, '{cityName}') ";
  632. }
  633. sqlWhere += $" AND ({tmpSql}) ";
  634. }
  635. //有Email篩選
  636. if (hasMail)
  637. {
  638. string tmpSql = "IS_DEFINED(c.mail) AND NOT IS_NULL(c.mail)";
  639. sqlWhere += $" AND ({tmpSql}) ";
  640. }
  641. if (!string.IsNullOrWhiteSpace(sqlWhere))
  642. {
  643. Regex regex = new Regex("^[a-zA-Z0-9]{2}$");
  644. string sqlTmidEx = $"SELECT * FROM c WHERE {sqlWhere}";
  645. await foreach (var item in cosmosClientCsv2.GetContainer("Core", "ID2").GetItemQueryIteratorSql<JsonElement>(queryText: sqlTmidEx, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base-ex") }))
  646. {
  647. TmidInfo tmidExInfoTmp = item.ToObject<TmidInfo>();
  648. //地理位置名稱
  649. ///國
  650. if(!string.IsNullOrWhiteSpace(tmidExInfoTmp.country))
  651. {
  652. string countryIdNow = (regex.IsMatch(tmidExInfoTmp.country)) ? tmidExInfoTmp.country : string.Empty;
  653. if (string.IsNullOrWhiteSpace(countryIdNow)) //國欄位填入國名對策
  654. {
  655. var geoCountry = geoCountryIdNameDic.FirstOrDefault(x => x.Value.Contains(tmidExInfoTmp.country));
  656. if (!geoCountry.Equals(default(KeyValuePair<string, string>))) {
  657. countryIdNow = geoCountry.Key;
  658. }
  659. }
  660. if (string.IsNullOrWhiteSpace(countryIdNow)) //國欄位填入國名對策(英文)
  661. {
  662. var geoCountryEn = geoCountryIdNameDicEn.FirstOrDefault(x => x.Value.Equals(tmidExInfoTmp.country));
  663. if (!geoCountryEn.Equals(default(KeyValuePair<string, string>))) {
  664. countryIdNow = geoCountryEn.Key;
  665. }
  666. }
  667. if(!string.IsNullOrWhiteSpace(countryIdNow) && !tmidExInfoTmp.country.Equals(countryIdNow)) tmidExInfoTmp.country = countryIdNow;
  668. if (geoCountryIdNameDic.TryGetValue(countryIdNow, out var countryInfo))
  669. {
  670. tmidExInfoTmp.countryName = countryInfo;
  671. }
  672. }
  673. ///省
  674. if (!string.IsNullOrWhiteSpace(tmidExInfoTmp.province))
  675. {
  676. if (tmidExInfoTmp.country.Equals("TW")) //TW的省欄位填的是市資料
  677. {
  678. tmidExInfoTmp.city = tmidExInfoTmp.province;
  679. tmidExInfoTmp.province = string.Empty;
  680. }
  681. else
  682. {
  683. string provinceIdNow = (regex.IsMatch(tmidExInfoTmp.province)) ? tmidExInfoTmp.province : string.Empty;
  684. if (string.IsNullOrWhiteSpace(provinceIdNow)) //省欄位填入省名對策
  685. {
  686. var geoProvince = geoProvinceIdNameDic.FirstOrDefault(x => x.Value.Equals(tmidExInfoTmp.province));
  687. if (!geoProvince.Equals(default(KeyValuePair<string, string>)))
  688. {
  689. provinceIdNow = geoProvince.Key;
  690. }
  691. }
  692. if (!string.IsNullOrWhiteSpace(provinceIdNow) && !tmidExInfoTmp.province.Equals(provinceIdNow)) tmidExInfoTmp.province = provinceIdNow;
  693. //var provinceDic = regionData.province[tmidExInfoTmp.country];
  694. if (geoProvinceIdNameDic.TryGetValue(tmidExInfoTmp.province, out var provinceInfo))
  695. {
  696. tmidExInfoTmp.provinceName = provinceInfo;
  697. }
  698. }
  699. }
  700. ///市
  701. if (!string.IsNullOrWhiteSpace(tmidExInfoTmp.city))
  702. {
  703. string cityIdNow = (regex.IsMatch(tmidExInfoTmp.city)) ? tmidExInfoTmp.city : string.Empty;
  704. if (string.IsNullOrWhiteSpace(cityIdNow)) //市欄位填入市名對策
  705. {
  706. var geoCity = geoCityIdNameDic.FirstOrDefault(x => x.Value.Equals(tmidExInfoTmp.city));
  707. if (!geoCity.Equals(default(KeyValuePair<string, string>)))
  708. {
  709. cityIdNow = geoCity.Key;
  710. }
  711. }
  712. if (!string.IsNullOrWhiteSpace(cityIdNow) && !tmidExInfoTmp.city.Equals(cityIdNow)) tmidExInfoTmp.city = cityIdNow;
  713. if(geoCityIdNameDic.TryGetValue(tmidExInfoTmp.city, out var cityInfo))
  714. {
  715. tmidExInfoTmp.cityName = cityInfo;
  716. }
  717. }
  718. //要取得的學校ID
  719. if (!string.IsNullOrWhiteSpace(tmidExInfoTmp.schoolCode) && !schIds.Contains(tmidExInfoTmp.schoolCode)) schIds.Add(tmidExInfoTmp.schoolCode);
  720. if (!string.IsNullOrWhiteSpace(tmidExInfoTmp.schoolCodeW) && !schIds.Contains(tmidExInfoTmp.schoolCodeW)) schIds.Add(tmidExInfoTmp.schoolCodeW);
  721. //輸出項生成
  722. tmidExInfos.Add(tmidExInfoTmp);
  723. }
  724. }
  725. //取得學校資訊
  726. Dictionary<string, string> schIdNameDic = new Dictionary<string, string>(); //key: schId val:schName
  727. string sqlWhereSch = $" ARRAY_CONTAINS({JsonSerializer.Serialize(schIds)}, c.shortCode)";
  728. string sqlSch = $"SELECT c.id, c.code, c.shortCode, c.name FROM c WHERE {sqlWhereSch} ";
  729. await foreach (var item in cosmosClientCsv2Cn.GetContainer("Core", "School").GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base") }))
  730. {
  731. string schId = item.GetProperty("shortCode").ToString();
  732. string schName = item.GetProperty("name").ToString();
  733. schIdNameDic.Add(schId, schName);
  734. }
  735. //輸出項整理
  736. foreach (TmidInfo tmidInfo in tmidExInfos)
  737. {
  738. ///tmidInfos
  739. AreaInfo tmidInfoRow = new AreaInfo() { id = tmidInfo.id, name = tmidInfo.name };
  740. tmidInfos.Add(tmidInfoRow);
  741. ///geoInfos
  742. string geoId = tmidInfo.country;
  743. string geoName = tmidInfo.countryName;
  744. if(!string.IsNullOrWhiteSpace(geoName)) comeRemoveStr.ForEach(c => { geoName = ReplaceLastMatch(geoName, c, string.Empty); }); //字串替換
  745. switch (groupKey)
  746. {
  747. case "province":
  748. geoId = tmidInfo.province;
  749. geoName = tmidInfo.provinceName;
  750. if (!string.IsNullOrWhiteSpace(geoName)) comeRemoveStr.ForEach(c => { geoName = ReplaceLastMatch(geoName, c, string.Empty); }); //字串替換
  751. break;
  752. case "city":
  753. geoId = tmidInfo.city;
  754. geoName = tmidInfo.cityName;
  755. if (!string.IsNullOrWhiteSpace(geoName)) comeRemoveStr.ForEach(c => { geoName = ReplaceLastMatch(geoName, c, string.Empty); }); //字串替換
  756. break;
  757. case "name":
  758. geoId = tmidInfo.schoolCode;
  759. if (string.IsNullOrWhiteSpace(geoId)) geoId = tmidInfo.schoolCodeW;
  760. if (!string.IsNullOrWhiteSpace(geoName)) geoName = (!string.IsNullOrWhiteSpace(geoId) && schIdNameDic.ContainsKey(geoId)) ? schIdNameDic[geoId] : string.Empty;
  761. break;
  762. }
  763. //輸出
  764. if (!string.IsNullOrWhiteSpace(geoId))
  765. {
  766. AreaInfo geoInfo = geoInfos.Where(g => g.id.Equals(geoId)).FirstOrDefault();
  767. if (geoInfo == null)
  768. {
  769. AreaInfo geoInfoCrt = new AreaInfo();
  770. geoInfoCrt.id = geoId;
  771. geoInfoCrt.name = (_option.Location.Contains("Global")) ? "無法取得名稱" : "无法取得名称";
  772. if (!string.IsNullOrWhiteSpace(geoName)) geoInfoCrt.name = geoName;
  773. geoInfos.Add(geoInfoCrt);
  774. geoInfo = geoInfos.Where(g => g.id.Equals(geoInfoCrt.id)).FirstOrDefault();
  775. }
  776. string schId = (!string.IsNullOrWhiteSpace(tmidInfo.schoolCode)) ? tmidInfo.schoolCode : (!string.IsNullOrWhiteSpace(tmidInfo.schoolCodeW)) ? tmidInfo.schoolCodeW : string.Empty;
  777. string schName = (!string.IsNullOrWhiteSpace(schId) && schIdNameDic.ContainsKey(schId)) ? schIdNameDic[schId] : string.Empty;
  778. if (!string.IsNullOrWhiteSpace(schId))
  779. {
  780. AreaSchoolInfo schInGeo = geoInfo.lists.FirstOrDefault(s => s.id.Equals(schId));
  781. if (schInGeo == null)
  782. {
  783. geoInfo.lists.Add(new AreaSchoolInfo() { id = schId, name = schName });
  784. schInGeo = geoInfo.lists.FirstOrDefault(s => s.id.Equals(schId));
  785. }
  786. schInGeo.tchCnt++;
  787. geoInfo.scCnt++;
  788. geoInfo.tchCnt++;
  789. }
  790. }
  791. }
  792. ///移除無校列
  793. geoInfos.RemoveAll(g => g.lists.Count.Equals(0));
  794. if (!showList)
  795. {
  796. geoInfos.ForEach(g => g.lists.Clear());
  797. }
  798. }
  799. //地理資訊取得對象:學校
  800. else if (type.Equals("school"))
  801. {
  802. List<string> schIds = new List<string>(); //要取得的學校ID
  803. //取得ID資訊
  804. sqlWhere = $" ((NOT IS_NULL(c.schoolCode) AND c.schoolCode != '') OR (NOT IS_NULL(c.schoolCodeW) AND c.schoolCodeW != ''))";
  805. //有Email篩選
  806. if (hasMail)
  807. {
  808. string tmpSql = "IS_DEFINED(c.mail) AND NOT IS_NULL(c.mail)";
  809. sqlWhere += $" AND ({tmpSql}) ";
  810. }
  811. if (!string.IsNullOrWhiteSpace(sqlWhere))
  812. {
  813. string sqlTmidEx = $"SELECT * FROM c WHERE {sqlWhere}";
  814. await foreach (var item in cosmosClientCsv2.GetContainer("Core", "ID2").GetItemQueryIteratorSql<JsonElement>(queryText: sqlTmidEx, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base-ex") }))
  815. {
  816. TmidInfo tmidEx = item.ToObject<TmidInfo>();
  817. tmidExInfos.Add(tmidEx);
  818. if (!string.IsNullOrWhiteSpace(tmidEx.schoolCode) && !schIds.Contains(tmidEx.schoolCode)) schIds.Add(tmidEx.schoolCode);
  819. if (!string.IsNullOrWhiteSpace(tmidEx.schoolCodeW) && !schIds.Contains(tmidEx.schoolCodeW)) schIds.Add(tmidEx.schoolCodeW);
  820. }
  821. }
  822. //取得學校資訊
  823. if(schIds.Count > 0)
  824. {
  825. sqlWhere = $" (NOT IS_NULL(c.countryId) AND c.countryId != '') ";
  826. ///國
  827. if (!string.IsNullOrWhiteSpace(countryId))
  828. {
  829. groupKey = (isChina) ? "province" : (!countryId.Equals("TW")) ? "name" : "city";
  830. sqlWhere += $" AND c.countryId = '{countryId}' ";
  831. }
  832. ///省
  833. if (!string.IsNullOrWhiteSpace(provinceId))
  834. {
  835. groupKey = "city";
  836. sqlWhere += $" AND c.provinceId = '{provinceId}' ";
  837. }
  838. ///市
  839. if (!string.IsNullOrWhiteSpace(cityId))
  840. {
  841. groupKey = "name";
  842. sqlWhere += $" AND c.cityId = '{cityId}' ";
  843. }
  844. sqlWhere += $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(schIds)}, c.shortCode)";
  845. string sqlSch = $"SELECT c.id, c.code, c.shortCode, c.name, c.countryId, c.provinceId, c.cityId, c.distId FROM c WHERE {sqlWhere} ";
  846. await foreach (var item in cosmosClientCsv2Cn.GetContainer("Core", "School").GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base") }))
  847. {
  848. string schId = item.GetProperty("shortCode").ToString();
  849. string schName = item.GetProperty("name").ToString();
  850. string geoId = Convert.ToString(item.GetProperty("countryId"));
  851. var countryInfo = regionData.country.FirstOrDefault(c => c.Key.Equals(geoId));
  852. string geoName = (!countryInfo.Equals(default(KeyValuePair<string, regionbase>))) ? countryInfo.Value.name : string.Empty;
  853. if (!string.IsNullOrWhiteSpace(geoName)) comeRemoveStr.ForEach(c => { geoName = ReplaceLastMatch(geoName, c, string.Empty); }); //字串替換
  854. switch (groupKey)
  855. {
  856. case "province":
  857. geoId = Convert.ToString(item.GetProperty("provinceId"));
  858. var provinceDic = regionData.province[countryId];
  859. if(provinceDic.TryGetValue(geoId, out var provinceInfo))
  860. {
  861. geoName = provinceInfo.name;
  862. }
  863. if (!string.IsNullOrWhiteSpace(geoName)) comeRemoveStr.ForEach(c => { geoName = ReplaceLastMatch(geoName, c, string.Empty); }); //字串替換
  864. break;
  865. case "city":
  866. geoId = Convert.ToString(item.GetProperty("cityId"));
  867. if(regionData.city.ContainsKey(countryId))
  868. {
  869. if (regionData.city[countryId].ContainsKey("tw"))
  870. {
  871. var cityDic = regionData.city[countryId]["tw"];
  872. if(cityDic.TryGetValue(geoId, out var cityInfo))
  873. {
  874. geoName = cityInfo.name;
  875. }
  876. }
  877. else if (!string.IsNullOrWhiteSpace(provinceId) && regionData.city[countryId].ContainsKey(provinceId))
  878. {
  879. var cityDic = regionData.city[countryId][provinceId];
  880. if (cityDic.TryGetValue(geoId, out var cityInfo))
  881. {
  882. geoName = cityInfo.name;
  883. }
  884. }
  885. }
  886. comeRemoveStr.ForEach(c => { geoName = ReplaceLastMatch(geoName, c, string.Empty); }); //字串替換
  887. break;
  888. case "name":
  889. geoId = Convert.ToString(item.GetProperty("shortCode"));
  890. geoName = Convert.ToString(item.GetProperty("name"));
  891. break;
  892. }
  893. //輸出項製作
  894. if (!string.IsNullOrWhiteSpace(geoId))
  895. {
  896. ///geoInfos
  897. AreaInfo geoInfo = geoInfos.Where(g => g.id.Equals(geoId)).FirstOrDefault();
  898. if (geoInfo == null)
  899. {
  900. AreaInfo geoInfoCrt = new AreaInfo();
  901. geoInfoCrt.id = geoId;
  902. geoInfoCrt.name = (_option.Location.Contains("Global")) ? "無法取得名稱" : "无法取得名称";
  903. if(!string.IsNullOrWhiteSpace(geoName)) geoInfoCrt.name = geoName;
  904. geoInfos.Add(geoInfoCrt);
  905. geoInfo = geoInfos.Where(g => g.id.Equals(geoInfoCrt.id)).FirstOrDefault();
  906. }
  907. geoInfo.scCnt++;
  908. List<TmidInfo> tmidInfoSelected = tmidExInfos.Where(g => (!string.IsNullOrWhiteSpace(g.schoolCode) && g.schoolCode.Equals(schId)) || (!string.IsNullOrWhiteSpace(g.schoolCodeW) && g.schoolCodeW.Equals(schId))).ToList();
  909. geoInfo.tchCnt += tmidInfoSelected.Count;
  910. if (showList && (groupKey.Equals("country") || groupKey.Equals("province") || groupKey.Equals("city")))
  911. {
  912. AreaSchoolInfo sch = new AreaSchoolInfo() { id = schId, name = schName, tchCnt = tmidInfoSelected.Count };
  913. geoInfo.lists.Add(sch);
  914. }
  915. ///tmidInfos
  916. List<AreaInfo> tmidAddToList = tmidInfoSelected.Select(g => new AreaInfo() { id = g.id, name = g.name }).ToList();
  917. tmidInfos.AddRange(tmidAddToList);
  918. }
  919. }
  920. }
  921. }
  922. //輸出
  923. if (output.Equals("tmid"))
  924. {
  925. return (tmidInfos, groupKey);
  926. }
  927. else
  928. {
  929. return (geoInfos, groupKey);
  930. }
  931. }
  932. /// <summary>
  933. /// (個別挑選)取得TMID資訊
  934. /// </summary>
  935. [HttpPost("get-tmidinfo")]
  936. #if !DEBUG
  937. [AuthToken(Roles = "admin")]
  938. #endif
  939. public async Task<IActionResult> GetTmidInfo(GetTmidInfoParam target)
  940. {
  941. try
  942. {
  943. var storageClientCsv2 = _azureStorage.GetCloudTableClient(name: "CoreServiceV2"); //Storage CSV2
  944. var cosmosClientIes = _azureCosmos.GetCosmosClient();
  945. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
  946. var tablePointsClient = storageClientCsv2.GetTableReference("Points");
  947. //各項值取聯集或交集
  948. string modeToAll = !string.IsNullOrWhiteSpace(target.mode) ? target.mode : "and";
  949. string sqlSel = "SELECT c.id FROM c WHERE 1=1 ";
  950. //生成時間
  951. List<string> passTmidCrt = new List<string>();
  952. List<string> sqlWhereListBase = new List<string>();
  953. if (target.creatTime != null)
  954. {
  955. if (target.creatTime.start > 0) sqlWhereListBase.Add($" {target.creatTime.start} <= StringToNumber(c.id) ");
  956. if (target.creatTime.end > 0) sqlWhereListBase.Add($" StringToNumber(c.id) <= {target.creatTime.end} ");
  957. }
  958. ///SQL文生成
  959. bool useCrtFlg = false;
  960. string sqlWhereBase = string.Empty;
  961. foreach (string w in sqlWhereListBase)
  962. {
  963. useCrtFlg = true;
  964. sqlWhereBase += $" AND {w}";
  965. }
  966. if (!string.IsNullOrWhiteSpace(sqlWhereBase))
  967. {
  968. string sqlBase = $"{sqlSel}{sqlWhereBase}";
  969. await foreach (var item in cosmosClientCsv2
  970. .GetContainer("Core", "ID2")
  971. .GetItemQueryIteratorSql<JsonElement>(queryText: sqlBase, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
  972. {
  973. string id = item.GetProperty("id").ToString();
  974. if (!passTmidCrt.Contains(id)) passTmidCrt.Add(id);
  975. }
  976. }
  977. //地理資訊 (base-ex)
  978. bool useGeoFlg = (target.geo != null && (!string.IsNullOrWhiteSpace(target.geo.countryId) || !string.IsNullOrWhiteSpace(target.geo.provinceId) || !string.IsNullOrWhiteSpace(target.geo.cityId))) ? true : false;
  979. List<string> passTmidGeo = new List<string>();
  980. var (geoInfos, _) = await GetDataByGeo("tmid", "tmid", true, target.geo.countryId, target.geo.provinceId, target.geo.cityId, target.hasMail);
  981. foreach(var geoInfo in geoInfos)
  982. {
  983. if (!passTmidGeo.Contains(geoInfo.id)) passTmidGeo.Add(geoInfo.id);
  984. }
  985. //使用產品 ARRAY_CONTAINS(['hiteach', 'hiteachcc', 'sokapp', 'sokrates', 'webirs'], c.type)
  986. bool usePrdFlg = false;
  987. List<string> passTmidPrd = new List<string>();
  988. var tmidProdDic = new Dictionary<string, List<string>>(); //各ID使用過的的產品List
  989. List<string> prodWhereList = new List<string>();
  990. if (target.product != null && target.product.id.Count > 0)
  991. {
  992. usePrdFlg = true;
  993. foreach (string prodCode in target.product.id)
  994. {
  995. prodWhereList.Add(prodCode);
  996. }
  997. }
  998. if (prodWhereList.Count > 0)
  999. {
  1000. string sqlSelCross = "SELECT DISTINCT c.id, c.type FROM c ";
  1001. string sqlWhereCross = $" WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(prodWhereList)}, c.type)";
  1002. string sqlCross = $"{sqlSelCross}{sqlWhereCross}";
  1003. await foreach (var item in cosmosClientCsv2
  1004. .GetContainer("Core", "ID2")
  1005. .GetItemQueryIteratorSql<JsonElement>(sqlCross, requestOptions: null))
  1006. {
  1007. string id = item.GetProperty("id").ToString();
  1008. string prod = item.GetProperty("type").ToString();
  1009. if (!tmidProdDic.ContainsKey(id)) tmidProdDic.Add(id, new List<string>());
  1010. tmidProdDic[id].Add(prod);
  1011. }
  1012. ///生成符合AndOr條件的TMID製作
  1013. if (target.product.mode.Equals("and"))
  1014. {
  1015. foreach (var prodItem in tmidProdDic)
  1016. {
  1017. int hasProdCount = 0;
  1018. string tmid = prodItem.Key;
  1019. ///產品比對
  1020. foreach (string prod in target.product.id)
  1021. {
  1022. if (prodItem.Value.Contains(prod)) hasProdCount++;
  1023. }
  1024. if (hasProdCount.Equals(target.product.id.Count))
  1025. {
  1026. if (!passTmidPrd.Contains(tmid)) passTmidPrd.Add(tmid);
  1027. }
  1028. }
  1029. }
  1030. else if (target.product.mode.Equals("or"))
  1031. {
  1032. foreach (var prodItem in tmidProdDic)
  1033. {
  1034. int hasProdCount = 0;
  1035. string tmid = prodItem.Key;
  1036. ///產品比對
  1037. foreach (string prod in target.product.id)
  1038. {
  1039. if (prodItem.Value.Contains(prod))
  1040. {
  1041. if (!passTmidPrd.Contains(tmid)) passTmidPrd.Add(tmid);
  1042. break;
  1043. }
  1044. }
  1045. }
  1046. }
  1047. }
  1048. //點數
  1049. bool usePntFlg = false;
  1050. List<string> passTmidPnt = new List<string>();
  1051. string filter = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Points");
  1052. string rkFilterCombine = string.Empty;
  1053. if (target.point != null)
  1054. {
  1055. if (target.point.start > 0 || target.point.end > 0)
  1056. {
  1057. usePntFlg = true;
  1058. if (target.point.start > 0)
  1059. {
  1060. string rkFilter = TableQuery.GenerateFilterConditionForInt("Points", QueryComparisons.GreaterThanOrEqual, Convert.ToInt32(target.point.start));
  1061. if (string.IsNullOrWhiteSpace(rkFilterCombine)) rkFilterCombine = rkFilter;
  1062. else rkFilterCombine = TableQuery.CombineFilters(rkFilterCombine, TableOperators.And, rkFilter);
  1063. }
  1064. if (target.point.end > 0)
  1065. {
  1066. string rkFilter = TableQuery.GenerateFilterConditionForInt("Points", QueryComparisons.LessThanOrEqual, Convert.ToInt32(target.point.end));
  1067. if (string.IsNullOrWhiteSpace(rkFilterCombine)) rkFilterCombine = rkFilter;
  1068. else rkFilterCombine = TableQuery.CombineFilters(rkFilterCombine, TableOperators.And, rkFilter);
  1069. }
  1070. filter = TableQuery.CombineFilters(filter, TableOperators.And, rkFilterCombine);
  1071. TableQuery tableQuery = new TableQuery().Where(filter);
  1072. var usersPoints = tablePointsClient.ExecuteQuery(tableQuery);
  1073. if (usersPoints.Any())
  1074. {
  1075. foreach (DynamicTableEntity item in usersPoints)
  1076. {
  1077. string tmid = item.RowKey;
  1078. if (!passTmidPnt.Contains(tmid)) passTmidPnt.Add(tmid);
  1079. }
  1080. }
  1081. }
  1082. }
  1083. //學校
  1084. bool useSchFlg = false;
  1085. List<string> passTmidSch = new List<string>();
  1086. if (target.school != null && target.school.Count > 0)
  1087. {
  1088. useSchFlg = true;
  1089. List<string> schTeacherCodeList = target.school.Select(s => $"Teacher-{s}").ToList();
  1090. string sqlWhereSch = $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(schTeacherCodeList)}, c.code)";
  1091. string sqlSch = $"{sqlSel}{sqlWhereSch}";
  1092. await foreach (var item in cosmosClientIes
  1093. .GetContainer(Constant.TEAMModelOS, Constant.School)
  1094. .GetItemQueryIteratorSql<JsonElement>(sqlSch, requestOptions: null))
  1095. {
  1096. string id = item.GetProperty("id").ToString();
  1097. if (!passTmidSch.Contains(id)) passTmidSch.Add(id);
  1098. }
  1099. }
  1100. //TMID
  1101. bool useTmidFlg = false;
  1102. List<string> passTmidMid = new List<string>();
  1103. if (target.tmid != null && target.tmid.Count > 0)
  1104. {
  1105. useTmidFlg = true;
  1106. string sqlWhereTmid = $" AND ARRAY_CONTAINS({JsonSerializer.Serialize(target.tmid)}, c.id)";
  1107. string sqlTmid = $"{sqlSel}{sqlWhereTmid}";
  1108. await foreach (var item in cosmosClientCsv2
  1109. .GetContainer("Core", "ID2")
  1110. .GetItemQueryIteratorSql<JsonElement>(queryText: sqlTmid, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
  1111. {
  1112. string id = item.GetProperty("id").ToString();
  1113. if (!passTmidMid.Contains(id)) passTmidMid.Add(id);
  1114. }
  1115. }
  1116. //所有TMID做聯集交集整合
  1117. List<string> passTmid = new List<string>(); //最終條件符合的TMID列表
  1118. ///初始化
  1119. if (passTmidCrt.Count > 0) passTmid = passTmidCrt;
  1120. else if (passTmidGeo.Count > 0) passTmid = passTmidGeo;
  1121. else if (passTmidPrd.Count > 0) passTmid = passTmidPrd;
  1122. else if (passTmidPnt.Count > 0) passTmid = passTmidPnt;
  1123. else if (passTmidSch.Count > 0) passTmid = passTmidSch;
  1124. else if (passTmidMid.Count > 0) passTmid = passTmidMid;
  1125. if (modeToAll.Equals("or")) //聯集
  1126. {
  1127. if (useCrtFlg) passTmid = passTmid.Union(passTmidCrt).ToList();
  1128. if (useGeoFlg) passTmid = passTmid.Union(passTmidGeo).ToList();
  1129. if (usePrdFlg) passTmid = passTmid.Union(passTmidPrd).ToList();
  1130. if (usePntFlg) passTmid = passTmid.Union(passTmidPnt).ToList();
  1131. if (useSchFlg) passTmid = passTmid.Union(passTmidSch).ToList();
  1132. if (useTmidFlg) passTmid = passTmid.Union(passTmidMid).ToList();
  1133. }
  1134. else //交集
  1135. {
  1136. if (useCrtFlg) passTmid = passTmid.Intersect(passTmidCrt).ToList();
  1137. if (useGeoFlg) passTmid = passTmid.Intersect(passTmidGeo).ToList();
  1138. if (usePrdFlg) passTmid = passTmid.Intersect(passTmidPrd).ToList();
  1139. if (usePntFlg) passTmid = passTmid.Intersect(passTmidPnt).ToList();
  1140. if (useSchFlg) passTmid = passTmid.Intersect(passTmidSch).ToList();
  1141. if (useTmidFlg) passTmid = passTmid.Intersect(passTmidMid).ToList();
  1142. }
  1143. //最終ID基本資訊取得
  1144. List<IdName> result = new List<IdName>();
  1145. string sqlBaseInfo = $"SELECT c.id, c.name FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(passTmid)}, c.id)";
  1146. await foreach (var item in cosmosClientCsv2
  1147. .GetContainer("Core", "ID2")
  1148. .GetItemQueryIteratorSql<JsonElement>(queryText: sqlBaseInfo, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
  1149. {
  1150. string id = item.GetProperty("id").ToString();
  1151. string name = item.GetProperty("name").ToString();
  1152. IdName resultRow = new IdName() { id = id, name = name };
  1153. result.Add(resultRow);
  1154. }
  1155. return Ok(new { state = RespondCode.Ok, data = result });
  1156. }
  1157. catch (Exception ex)
  1158. {
  1159. return BadRequest(new { state = RespondCode.NotFound, data = new List<IdName>() });
  1160. }
  1161. }
  1162. /// <summary>
  1163. /// 寄發訊息API
  1164. /// </summary>
  1165. /// <param name="target"></param>
  1166. /// <param name="type">發送類型</param>
  1167. /// <param name="method">挑選方式</param>
  1168. /// <param name="title">標題</param>
  1169. /// <param name="body">內文</param>
  1170. /// <param name="data">傳遞資料</param>
  1171. /// <param name="action">傳遞動作</param>
  1172. [HttpPost("send-message")]
  1173. #if !DEBUG
  1174. [AuthToken(Roles = "admin")]
  1175. #endif
  1176. public async Task<IActionResult> SendMessage(JsonElement jsonElement)
  1177. {
  1178. SendMessageParam target = (jsonElement.TryGetProperty("target", out JsonElement _target)) ? _target.ToObject<SendMessageParam>() : null; //發送對象
  1179. string type = (jsonElement.TryGetProperty("type", out JsonElement _type)) ? _type.ToString() : string.Empty; //發送類型 mail:郵件、notify:端外、sms:簡訊
  1180. string method = (jsonElement.TryGetProperty("method", out JsonElement _method)) ? _method.ToString() : string.Empty; //挑選方式 single:個別 multi:批次
  1181. string subject = (jsonElement.TryGetProperty("subject", out JsonElement _subject)) ? _subject.ToString() : string.Empty; //主題 (Email用)
  1182. string title = (jsonElement.TryGetProperty("title", out JsonElement _title)) ? _title.ToString() : string.Empty; //標題
  1183. string body = (jsonElement.TryGetProperty("body", out JsonElement _body)) ? _body.ToString() : string.Empty; //內文
  1184. string sender = (jsonElement.TryGetProperty("sender", out JsonElement _sender)) ? _sender.ToString() : string.Empty; //發送者
  1185. string hubName = (jsonElement.TryGetProperty("hubName", out JsonElement _hubName)) ? _hubName.ToString() : "hita5"; //訊息中樞
  1186. string data = (jsonElement.TryGetProperty("data", out JsonElement _data)) ? _data.ToString() : string.Empty; //額外資料
  1187. long send = (jsonElement.TryGetProperty("send", out JsonElement _send)) ? _send.GetInt64() : 0; //發送時間 0:立即發送
  1188. string template = (jsonElement.TryGetProperty("template", out JsonElement _template)) ? _template.ToString() : string.Empty; //模板ID
  1189. if (target == null || string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(method) || string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(body) || string.IsNullOrWhiteSpace(sender)) return BadRequest();
  1190. string eventKey = "bi-gen-notify";
  1191. string eventId = $"{eventKey}_{_snowflakeId.NextId()}";
  1192. string eventName = "BI send notification";
  1193. var result = await SendMessageCore(target, type, method, subject, title, body, data, sender, hubName, template, send, eventId, eventName);
  1194. return Ok(new { state = RespondCode.Ok, result });
  1195. }
  1196. ///寄發訊息核心邏輯
  1197. private async Task<object> SendMessageCore(SendMessageParam target, string type, string method, string subject, string title, string body, string data, string sender, string hubName, string template, long send, string eventId = "", string eventName = "")
  1198. {
  1199. #region target 內容例
  1200. //{
  1201. // "area":[
  1202. // "1234"
  1203. // ],
  1204. // "geo":[
  1205. // {
  1206. // "countryId":"TW",
  1207. // "provinceId":"",
  1208. // "cityId":"30",
  1209. // "type":"tmid"
  1210. // },
  1211. // {
  1212. // "countryId":"TW",
  1213. // "provinceId":"",
  1214. // "cityId":"30",
  1215. // "type":"school"
  1216. // }
  1217. // ],
  1218. // "unit":[
  1219. // "1"
  1220. // ],
  1221. // "school":[
  1222. // "1234"
  1223. // ],
  1224. // "tmid": [
  1225. // "1234"
  1226. // ]
  1227. //}
  1228. #endregion
  1229. var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
  1230. List<string> tmids_area = await GetTmidByAreaId(target.area);
  1231. List<string> tmids_geo = await GetTmidByGeo(target.geo);
  1232. List<string> tmids_unit = await GetTmidByUnitId(target.unit);
  1233. List<string> tmids_school = await GetTmidBySchoolId(target.school);
  1234. List<string> tmids_direct = target.tmid;
  1235. List<string> tmids = new List<string>(); //聯集化
  1236. tmids = tmids.Union(tmids_area).ToList();
  1237. tmids = tmids.Union(tmids_geo).ToList();
  1238. tmids = tmids.Union(tmids_unit).ToList();
  1239. tmids = tmids.Union(tmids_school).ToList();
  1240. tmids = tmids.Union(tmids_direct).ToList();
  1241. tmids = tmids.Distinct().ToList(); //唯一化
  1242. //取得TMID資料
  1243. List<IdInfo> tmidInfos = new List<IdInfo>();
  1244. string sqlBaseInfo = $"SELECT c.id, c.name, c.mail, c.mobile FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(tmids)}, c.id)";
  1245. if(type.Equals("mail")) //發送類型 mail:郵件
  1246. {
  1247. sqlBaseInfo += $" AND (IS_DEFINED(c.mail) AND NOT IS_NULL(c.mail) AND c.mail != '') ";
  1248. }
  1249. else if(type.Equals("sms")) //發送類型 sms:簡訊
  1250. {
  1251. sqlBaseInfo += $" AND (IS_DEFINED(c.mobile) AND NOT IS_NULL(c.mobile) AND c.mobile != '') ";
  1252. }
  1253. await foreach (var item in cosmosClientCsv2
  1254. .GetContainer("Core", "ID2")
  1255. .GetItemQueryIteratorSql<JsonElement>(queryText: sqlBaseInfo, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base-ex") }))
  1256. {
  1257. IdInfo idInfo = new IdInfo();
  1258. idInfo.id = item.GetProperty("id").ToString();
  1259. idInfo.name = (item.TryGetProperty("name", out JsonElement _name)) ? _name.ToString() : string.Empty;
  1260. idInfo.mail = (item.TryGetProperty("mail", out JsonElement _mail)) ? _mail.ToString() : string.Empty;
  1261. idInfo.mobile = (item.TryGetProperty("mobile", out JsonElement _mobile)) ? _mobile.ToString() : string.Empty;
  1262. tmidInfos.Add(idInfo);
  1263. }
  1264. //寄發訊息
  1265. if (type.Equals("notify")) //端內外通知
  1266. {
  1267. List<string> tmIds = tmidInfos.Select(i => i.id).ToList();
  1268. if(send.Equals(0)) //立即寄送
  1269. {
  1270. HttpResponseMessage response = CallPushNotifyApi(tmIds, title, body, sender, hubName, template, data, eventId, eventName);
  1271. var result = new { status = response.StatusCode, content = await response.Content.ReadAsStringAsync() };
  1272. return result;
  1273. }
  1274. else //定時寄送 [待做]
  1275. {
  1276. }
  1277. }
  1278. else if(type.Equals("mail")) //Email
  1279. {
  1280. List<string> tmIds = tmidInfos.Where(i => !string.IsNullOrWhiteSpace(i.mail)).Select(i => i.id).ToList();
  1281. //呼叫Email API [未]
  1282. ///模板設定
  1283. if(_option.Location.Contains("Global"))
  1284. {
  1285. if (string.IsNullOrWhiteSpace(template)) template = "d-f1c5abd8247f4be79ceaecdd327e9a68"; //若未指定模板ID,用預設模板
  1286. }
  1287. var result = new { };
  1288. return result;
  1289. }
  1290. else if (type.Equals("sms")) //短訊
  1291. {
  1292. List<string> tmIds = tmidInfos.Where(i => !string.IsNullOrWhiteSpace(i.mobile)).Select(i => i.id).ToList();
  1293. //呼叫短訊API [未]
  1294. var result = new { };
  1295. return result;
  1296. }
  1297. return new { };
  1298. }
  1299. //取得學區所屬學校教師
  1300. private async Task<List<string>> GetTmidByAreaId(List<string> areaIds)
  1301. {
  1302. var cosmosClientIes = _azureCosmos.GetCosmosClient();
  1303. //取得學校ID列表
  1304. List<string> schIds = new List<string>();
  1305. string sqlSch = $"SELECT c.id FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(areaIds)}, c.areaId)";
  1306. ///實體校
  1307. await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
  1308. {
  1309. string schId = item.GetProperty("id").ToString();
  1310. schIds.Add(schId);
  1311. }
  1312. List<string> teacherCodes = schIds.Select(s => $"Teacher-{s}").ToList();
  1313. //取得學校教師ID列表
  1314. List<string> tmids = new List<string>();
  1315. string sqlTch = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
  1316. await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
  1317. {
  1318. string tmid = item.GetProperty("id").ToString();
  1319. tmids.Add(tmid);
  1320. }
  1321. return tmids;
  1322. }
  1323. //取得地理資訊所屬TMID
  1324. private async Task<List<string>> GetTmidByGeo(List<Geo> geos)
  1325. {
  1326. List<string> tmids = new List<string>();
  1327. foreach (Geo geo in geos)
  1328. {
  1329. if(!string.IsNullOrWhiteSpace(geo.type) && geo.type.Equals("tmid"))
  1330. {
  1331. var (geoInfos, _) = await GetDataByGeo("tmid", "tmid", true, geo.countryId, geo.provinceId, geo.cityId);
  1332. foreach (var geoInfo in geoInfos)
  1333. {
  1334. if (!tmids.Contains(geoInfo.id)) tmids.Add(geoInfo.id);
  1335. }
  1336. }
  1337. else if(!string.IsNullOrWhiteSpace(geo.type) && geo.type.Equals("school"))
  1338. {
  1339. var (geoInfos, _) = await GetDataByGeo("school", "tmid", true, geo.countryId, geo.provinceId, geo.cityId);
  1340. foreach (var geoInfo in geoInfos)
  1341. {
  1342. if (!tmids.Contains(geoInfo.id)) tmids.Add(geoInfo.id);
  1343. }
  1344. }
  1345. }
  1346. return tmids;
  1347. }
  1348. //取得學校機構所屬學校教師
  1349. private async Task<List<string>> GetTmidByUnitId(List<string> units)
  1350. {
  1351. if (units.Count.Equals(0)) return new List<string>();
  1352. var cosmosClientIes = _azureCosmos.GetCosmosClient();
  1353. var coreCosmosClientCn = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead");
  1354. //取得IES5學校ID
  1355. string iesSql = "SELECT c.id, c.type FROM c WHERE 1=1 ";
  1356. string iesWhere = string.Empty;
  1357. //IES學校ID取得、學校類型取得
  1358. List<string> iesSchIds = new List<string>();
  1359. Dictionary<string, string> schTypeDic = new Dictionary<string, string>(); //IES5學校ID與學校類型對照表
  1360. List<string> teacherCodes = new List<string>(); //用來取得老師資料的分區鍵
  1361. string sqlSch = $"{iesSql}{iesWhere}";
  1362. await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
  1363. {
  1364. string schId = item.GetProperty("id").ToString();
  1365. int schType = item.GetProperty("type").GetInt32();
  1366. iesSchIds.Add(schId);
  1367. if (!schTypeDic.ContainsKey(schId)) schTypeDic.Add(schId, schType.ToString());
  1368. }
  1369. //取得Core學校ID及類型
  1370. string coreSql = "SELECT c.shortCode, c.unitType FROM c";
  1371. string coreWhere = $" WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(iesSchIds)}, c.shortCode) ";
  1372. string coreWhereUnittype = string.Empty;
  1373. if (units.Contains("1"))
  1374. {
  1375. if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
  1376. coreWhereUnittype += $" ARRAY_CONTAINS(['1','8'], c.unitType) ";
  1377. }
  1378. if (units.Contains("2"))
  1379. {
  1380. if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
  1381. coreWhereUnittype += $" c.unitType = '2' ";
  1382. }
  1383. if (units.Contains("3"))
  1384. {
  1385. if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
  1386. coreWhereUnittype += $" c.unitType = '3' ";
  1387. }
  1388. if (units.Contains("4"))
  1389. {
  1390. if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
  1391. coreWhereUnittype += $" ARRAY_CONTAINS(['4','5','6','7','9'], c.unitType) ";
  1392. }
  1393. if (!string.IsNullOrWhiteSpace(coreWhereUnittype))
  1394. {
  1395. coreWhere += $" AND ({coreWhereUnittype})";
  1396. }
  1397. Dictionary<string, string> coreSchUnitTypeDic = new Dictionary<string, string>(); //Core學校ID與學校類型對照表
  1398. string sqlCore = $"{coreSql}{coreWhere}";
  1399. await foreach (var item in coreCosmosClientCn.GetContainer("Core", "School").GetItemQueryIteratorSql<JsonElement>(queryText: sqlCore, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base") }))
  1400. {
  1401. string shortCode = item.GetProperty("shortCode").ToString();
  1402. string unitType = item.GetProperty("unitType").ToString();
  1403. if (!teacherCodes.Contains($"Teacher-{shortCode}")) teacherCodes.Add($"Teacher-{shortCode}");
  1404. if (!coreSchUnitTypeDic.ContainsKey(shortCode)) coreSchUnitTypeDic.Add(shortCode, unitType);
  1405. }
  1406. //學校類型轉換
  1407. Dictionary<string, string> finalSchUnitTypeDic = new Dictionary<string, string>();
  1408. foreach (KeyValuePair<string, string> item in coreSchUnitTypeDic)
  1409. {
  1410. string schId = item.Key;
  1411. string coreUnitType = (coreSchUnitTypeDic.ContainsKey(schId)) ? coreSchUnitTypeDic[schId] : string.Empty;
  1412. string iesUnitType = (schTypeDic.ContainsKey(schId)) ? schTypeDic[schId] : string.Empty;
  1413. string unitTypeF = string.Empty; //機構類型(最終判斷)
  1414. if (!string.IsNullOrWhiteSpace(coreUnitType))
  1415. {
  1416. switch (coreUnitType)
  1417. {
  1418. case "1": //基礎教育機構
  1419. case "8": //學前教育
  1420. unitTypeF = "1"; // => 基礎教育機構(K-小學)
  1421. break;
  1422. case "2": //中等教育機構
  1423. unitTypeF = "2"; // => 中等教育機構(國中、高中/職)
  1424. break;
  1425. case "3": //高等教育機構
  1426. unitTypeF = "3"; // => 高等教育機構(大學、研究所)
  1427. break;
  1428. case "4": //政府單位機構
  1429. case "5": //NGO機構
  1430. case "6": //企業機構
  1431. case "7": //其他
  1432. case "9": //特殊教育
  1433. unitTypeF = "4"; // => 其他
  1434. break;
  1435. }
  1436. }
  1437. else if (!string.IsNullOrWhiteSpace(iesUnitType))
  1438. {
  1439. switch (iesUnitType) //1 普教,2 高职教
  1440. {
  1441. case "1": //國教(K-12)
  1442. unitTypeF = "1"; // => 基礎教育機構(K-小學)
  1443. break;
  1444. case "2": //大專院校
  1445. unitTypeF = "3"; // => 高等教育機構(大學、研究所)
  1446. break;
  1447. default: //未設定
  1448. unitTypeF = "4"; // => 其他
  1449. break;
  1450. }
  1451. }
  1452. finalSchUnitTypeDic.Add(schId, unitTypeF);
  1453. }
  1454. //取得學校所屬老師TMID
  1455. teacherCodes = finalSchUnitTypeDic.Select(s => $"Teacher-{s.Key}").ToList();
  1456. List<string> tmids = new List<string>();
  1457. string sqlTch = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
  1458. await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
  1459. {
  1460. string tmid = item.GetProperty("id").ToString();
  1461. tmids.Add(tmid);
  1462. }
  1463. return tmids;
  1464. }
  1465. //取得學校所屬學校教師
  1466. private async Task<List<string>> GetTmidBySchoolId(List<string> schIds)
  1467. {
  1468. var cosmosClientIes = _azureCosmos.GetCosmosClient();
  1469. //取得學校ID列表
  1470. List<string> teacherCodes = schIds.Select(s => $"Teacher-{s}").ToList();
  1471. //取得學校教師ID列表
  1472. List<string> tmids = new List<string>();
  1473. string sqlTch = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
  1474. await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
  1475. {
  1476. string tmid = item.GetProperty("id").ToString();
  1477. tmids.Add(tmid);
  1478. }
  1479. return tmids;
  1480. }
  1481. /// <summary>
  1482. /// 寄發端外通知
  1483. /// </summary>
  1484. /// <param name="tmIds"></param>
  1485. /// <param name="title"></param>
  1486. /// <param name="body"></param>
  1487. /// <param name="eventId"></param>
  1488. /// <param name="eventName"></param>
  1489. /// <param name="data"></param>
  1490. /// <returns></returns>
  1491. private HttpResponseMessage CallPushNotifyApi(List<string> tmIds, string title, string body, string sender, string hubName, string template, string data, string eventId = "", string eventName = "")
  1492. {
  1493. NotifyData notify = new NotifyData();
  1494. notify.hubName = hubName;
  1495. notify.sender = sender;
  1496. //#if DEBUG //測試模式時限制TMID帳號,正式站佈署時不生效
  1497. // List<string> filterTmid = new List<string>() { "1522758684", "1595321354" };
  1498. // tmIds = tmIds.Intersect(filterTmid).ToList();
  1499. //#endif
  1500. notify.tags = tmIds.Select(s => $"{s}_{sender}").ToList();
  1501. notify.title = title;
  1502. notify.body = body;
  1503. notify.eventId = eventId;
  1504. notify.eventName = eventName;
  1505. notify.data = data;
  1506. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  1507. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  1508. var url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  1509. var client = _httpClient;
  1510. string sendSite = (_option.Location.Contains("China")) ? "China" : "Global";
  1511. var token = CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, sendSite).Result;
  1512. if (client.DefaultRequestHeaders.Contains("Authorization"))
  1513. {
  1514. client.DefaultRequestHeaders.Remove("Authorization");
  1515. }
  1516. client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
  1517. HttpResponseMessage responseMessage = client.PostAsJsonAsync($"{url}/service/PushNotify", notify).Result;
  1518. return responseMessage;
  1519. }
  1520. //BI訊息推送DB記入
  1521. private async Task<BINotice> CrtBiNoticeData(BINotice bINotice)
  1522. {
  1523. var cosmosClientIes = _azureCosmos.GetCosmosClient(); //IES
  1524. bINotice.id = Guid.NewGuid().ToString();
  1525. BINotice result = await cosmosClientIes.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync<BINotice>(bINotice, new PartitionKey("BINotice"));
  1526. return result;
  1527. }
  1528. private string GetDictionaryKeyByValue(Dictionary<string, string> dic, string value)
  1529. {
  1530. string result = string.Empty;
  1531. foreach(KeyValuePair<string, string> d in dic)
  1532. {
  1533. if(d.Value.Equals(value))
  1534. {
  1535. result = d.Key;
  1536. }
  1537. }
  1538. return result;
  1539. }
  1540. /// <summary>
  1541. /// 取得TMID資訊 接收參數class
  1542. /// </summary>
  1543. public record GetTmidInfoParam
  1544. {
  1545. public string mode { get; set; } //and or
  1546. public StartEnd creatTime { get; set; }
  1547. public Geo geo { get; set; }
  1548. public Product product { get; set; }
  1549. public StartEnd point { get; set; }
  1550. public List<string> school { get; set; }
  1551. public List<string> tmid { get; set; }
  1552. public bool hasMail { get; set; } = false;
  1553. public record StartEnd
  1554. {
  1555. public long start { get; set; }
  1556. public long end { get; set; }
  1557. }
  1558. public record Product
  1559. {
  1560. public string mode { get; set; } //and or
  1561. public List<string> id { get; set; } = new(); //產品類型
  1562. }
  1563. }
  1564. public record Geo
  1565. {
  1566. public string countryId { get; set; }
  1567. public string provinceId { get; set; }
  1568. public string cityId { get; set; }
  1569. public string distId { get; set; }
  1570. public string type { get; set; } //tmid、school
  1571. }
  1572. /// <summary>
  1573. /// 發送訊息核心邏輯 接收參數class
  1574. /// </summary>
  1575. public record SendMessageParam
  1576. {
  1577. public List<string> area { get; set; } = new(); //學區ID
  1578. public List<Geo> geo { get; set; } = new();
  1579. public List<string> unit { get; set; } = new(); //學校機構
  1580. public List<string> school { get; set; } = new(); //學校ID
  1581. public List<string> tmid { get; set; } = new(); //TMID
  1582. }
  1583. //取得學校機構ID及名稱
  1584. public Dictionary<string, string> getSchoolUnitName(string lang)
  1585. {
  1586. Dictionary<string, string> tw = new Dictionary<string, string>();
  1587. tw.Add("1", "基礎教育機構(K-小學)");
  1588. tw.Add("2", "中等教育機構(國中、高中/職)");
  1589. tw.Add("3", "高等教育機構(大學、研究所)");
  1590. tw.Add("4", "其他");
  1591. Dictionary<string, string> cn = new Dictionary<string, string>();
  1592. cn.Add("1", "基础教育机构(K-小学)");
  1593. cn.Add("2", "中等教育机构(初中、高中/职)");
  1594. cn.Add("3", "高等教育机构(大学、研究生院)");
  1595. cn.Add("4", "其他");
  1596. if(lang.Equals("zh-cn")) return cn;
  1597. else return tw;
  1598. }
  1599. /// <summary>
  1600. /// TMID基本進階資訊(base-ex)
  1601. /// </summary>
  1602. private class TmidInfo
  1603. {
  1604. public string id { get; set; }
  1605. public string name { get; set; }
  1606. public string mobile { get; set; }
  1607. public string mail { get; set; }
  1608. public string countryCode { get; set; }
  1609. public string country { get; set; }
  1610. public string countryName { get; set; }
  1611. public string province { get; set; }
  1612. public string provinceName { get; set; }
  1613. public string city { get; set; }
  1614. public string cityName { get; set; }
  1615. public string dist { get; set; }
  1616. public string distName { get; set; }
  1617. public string schoolCode { get; set; }
  1618. public string schoolCodeW { get; set; }
  1619. }
  1620. /// <summary>
  1621. /// 學區、地理位置、機構類型 回傳信息
  1622. /// </summary>
  1623. public record AreaInfo
  1624. {
  1625. public string id { get; set; }
  1626. public string name { get; set; }
  1627. public int scCnt { get; set; } = 0;
  1628. public int tchCnt { get; set; } = 0;
  1629. public List<AreaSchoolInfo> lists { get; set; } = new();
  1630. }
  1631. /// <summary>
  1632. /// 學區附屬學校
  1633. /// </summary>
  1634. public record AreaSchoolInfo
  1635. {
  1636. public string id { get; set; }
  1637. public string name { get; set; }
  1638. public int tchCnt { get; set; } = 0;
  1639. }
  1640. /// <summary>
  1641. /// TMID個人積分
  1642. /// </summary>
  1643. private record TmidPoints
  1644. {
  1645. public int points { get; set; } = 0; //累積的點數(只加不減)
  1646. public int balance { get; set; } = 0; //可用的點數
  1647. public int level { get; set; } = 0; //等級
  1648. }
  1649. private record IdName
  1650. {
  1651. public string id { get; set; }
  1652. public string name { get; set; }
  1653. }
  1654. private record IdInfo : IdName
  1655. {
  1656. public string mail { get; set; }
  1657. public string mobile { get; set; }
  1658. }
  1659. /// <summary>
  1660. /// 替換字串最後一個字
  1661. /// </summary>
  1662. /// <param name="str">目標字串</param>
  1663. /// <param name="target">被替換的字串</param>
  1664. /// <param name="alternative">要換上的字串</param>
  1665. /// <returns></returns>
  1666. public string ReplaceLastMatch(string str, string target, string alternative)
  1667. {
  1668. var pos = str.LastIndexOf(target);
  1669. if (pos >= 0)
  1670. return str.Substring(0, pos) + alternative + str.Substring(pos + target.Length);
  1671. return str;
  1672. }
  1673. //地區要去除的特殊字
  1674. public List<string> comeRemoveStr = new List<string>() { "省", "市", "区", "自治州", "县", "旗", "盟", "回族", "藏族", "羌族", "哈尼族", "彝族", "壮族", "苗族", "维吾尔", "自治", "地區", "區", "縣" };
  1675. }
  1676. }