浏览代码

發送訊息核心邏輯

jeff 5 月之前
父节点
当前提交
2a8f9403c6
共有 2 个文件被更改,包括 479 次插入49 次删除
  1. 403 49
      TEAMModelBI/Controllers/BICommon/BINoticeController.cs
  2. 76 0
      TEAMModelBI/Controllers/BITest/TestController.cs

+ 403 - 49
TEAMModelBI/Controllers/BICommon/BINoticeController.cs

@@ -31,6 +31,9 @@ using Microsoft.AspNetCore.Authorization;
 using static TEAMModelBI.Models.Extension.GeoRegion;
 using System.Diagnostics.Metrics;
 using Microsoft.Azure.Cosmos.Table;
+using System.Net.Http.Json;
+using NotifyData = TEAMModelOS.SDK.CoreAPIHttpService.NotifyData;
+using Microsoft.Azure.Cosmos.Linq;
 
 
 namespace TEAMModelBI.Controllers.BICommon
@@ -49,8 +52,9 @@ namespace TEAMModelBI.Controllers.BICommon
         private readonly CoreAPIHttpService _coreAPIHttpService;
         private readonly IWebHostEnvironment _environment; //读取文件
         private readonly HttpClient _httpClient;
+        private readonly SnowflakeId _snowflakeId;
 
-        public BINoticeController(AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option, IConfiguration configuration, AzureServiceBusFactory serviceBus, IHttpClientFactory http, CoreAPIHttpService coreAPIHttpService, IWebHostEnvironment hostingEnvironment, HttpClient httpClient)
+        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)
         {
             _azureCosmos = azureCosmos;
             _dingDing = dingDing;
@@ -62,6 +66,7 @@ namespace TEAMModelBI.Controllers.BICommon
             _coreAPIHttpService = coreAPIHttpService;
             _environment = hostingEnvironment;
             _httpClient = httpClient;
+            _snowflakeId = snowflakeId;
         }
 
         /// <summary>
@@ -242,7 +247,7 @@ namespace TEAMModelBI.Controllers.BICommon
                 };
 
                 bINotice = await cosmosClient.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync<BINotice>(bINotice, new PartitionKey("BINotice"));
-                if (isOnlySave.GetBoolean()) 
+                if (isOnlySave.GetBoolean())
                 {
                     //BI发送端外通知
                     _coreAPIHttpService.BIPushNotify(bINotice, new Dictionary<string, object> { { "tmdid", _tmdIds }, { "sendId", bINotice.crowdIds } }, _option.Location, _configuration, _dingDing);
@@ -305,13 +310,13 @@ namespace TEAMModelBI.Controllers.BICommon
 
                 //取得學區
                 string sqlArea = $"SELECT c.id, c.name FROM c ";
-                string whereArea = string.Empty ;
+                string whereArea = string.Empty;
                 if (!string.IsNullOrWhiteSpace(reqAreaId))
                 {
-                    string wherePre = (!string.IsNullOrWhiteSpace(whereArea)) ? " AND " : string.Empty ;
+                    string wherePre = (!string.IsNullOrWhiteSpace(whereArea)) ? " AND " : string.Empty;
                     whereArea += $"{wherePre} c.id = '{reqAreaId}' ";
                 }
-                if(!string.IsNullOrWhiteSpace(whereArea))
+                if (!string.IsNullOrWhiteSpace(whereArea))
                 {
                     whereArea = $" WHERE {whereArea}";
                     sqlArea = sqlArea + whereArea;
@@ -332,12 +337,12 @@ namespace TEAMModelBI.Controllers.BICommon
                     string schId = item.GetProperty("id").ToString();
                     string schName = item.GetProperty("name").ToString();
                     string areaId = item.GetProperty("areaId").ToString();
-                    if(!schAreaDic.ContainsKey(schId))
+                    if (!schAreaDic.ContainsKey(schId))
                     {
                         schAreaDic.Add(schId, areaId);
                         schNameDic.Add(schId, schName);
                     }
-                    if(!teacherCodes.Contains($"Teacher-{schId}"))
+                    if (!teacherCodes.Contains($"Teacher-{schId}"))
                     {
                         teacherCodes.Add($"Teacher-{schId}");
                     }
@@ -351,7 +356,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     if (!schAreaDic.ContainsKey(schId))
                     {
                         schAreaDic.Add(schId, areaId);
-                        if(!schNameDic.ContainsKey(schId)) schNameDic.Add(schId, schName);
+                        if (!schNameDic.ContainsKey(schId)) schNameDic.Add(schId, schName);
                     }
                 }
                 //取得學校教師數
@@ -370,7 +375,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     int tchCnt = item.Value;
                     string areaId = schAreaDic[schId];
                     AreaInfo areaInfo = areaInfos.Where(a => a.id.Equals(areaId)).FirstOrDefault();
-                    if(areaInfo != null)
+                    if (areaInfo != null)
                     {
                         areaInfo.scCnt++;
                         areaInfo.tchCnt += tchCnt;
@@ -427,10 +432,10 @@ namespace TEAMModelBI.Controllers.BICommon
             }
             else
             {
-                if(isGlobal)
+                if (isGlobal)
                 {
                     provinceId = string.Empty;
-                    if(countryId.Equals("TW"))
+                    if (countryId.Equals("TW"))
                     {
                         foreach (KeyValuePair<string, regionbase> item in regionData.city[countryId][countryId.ToLower()])
                         {
@@ -446,7 +451,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     }
                 }
             }
-            if(regionData.province.ContainsKey(countryId) && !string.IsNullOrWhiteSpace(provinceId))
+            if (regionData.province.ContainsKey(countryId) && !string.IsNullOrWhiteSpace(provinceId))
             {
                 provinceName = (regionData.province[countryId].ContainsKey(provinceId)) ? regionData.province[countryId][provinceId].name : string.Empty;
                 foreach (KeyValuePair<string, regionbase> item in regionData.city[countryId][provinceId])
@@ -454,13 +459,13 @@ namespace TEAMModelBI.Controllers.BICommon
                     geoIdNameDic.Add(item.Value.code, item.Value.name);
                 }
             }
-            if(isGlobal)
+            if (isGlobal)
             {
-                if(regionData.city.ContainsKey(countryId))
+                if (regionData.city.ContainsKey(countryId))
                 {
-                    if(countryId.ToLower().Equals("tw"))
+                    if (countryId.ToLower().Equals("tw"))
                     {
-                        if(!string.IsNullOrWhiteSpace(cityId) && regionData.city[countryId][countryId.ToLower()].ContainsKey(cityId))
+                        if (!string.IsNullOrWhiteSpace(cityId) && regionData.city[countryId][countryId.ToLower()].ContainsKey(cityId))
                         {
                             cityName = regionData.city[countryId][countryId.ToLower()][cityId].name;
                         }
@@ -468,7 +473,7 @@ namespace TEAMModelBI.Controllers.BICommon
                 }
             } else
             {
-                if (regionData.city.ContainsKey(countryId) && 
+                if (regionData.city.ContainsKey(countryId) &&
                     !string.IsNullOrWhiteSpace(provinceId) && regionData.city[countryId].ContainsKey(provinceId) &&
                     !string.IsNullOrWhiteSpace(cityId) && regionData.city[countryId][provinceId].ContainsKey(cityId)
                     )
@@ -484,20 +489,20 @@ namespace TEAMModelBI.Controllers.BICommon
                 if (!string.IsNullOrWhiteSpace(sqlWhere)) sqlWhere += $" AND";
                 comeRemoveStr.ForEach(c => { countryName = countryName.Replace(c, ""); });
                 sqlWhere += $" CONTAINS(c.region, '{countryName}') ";
-                groupKey = (isGlobal) ? "city" : "province";                
+                groupKey = (isGlobal) ? "city" : "province";
             }
             if (!string.IsNullOrWhiteSpace(provinceName))
             {
                 if (!string.IsNullOrWhiteSpace(sqlWhere)) sqlWhere += $" AND";
                 comeRemoveStr.ForEach(c => { provinceName = provinceName.Replace(c, ""); });
-                sqlWhere += $" CONTAINS(c.province, '{provinceName}') "; 
+                sqlWhere += $" CONTAINS(c.province, '{provinceName}') ";
                 groupKey = "city";
             }
             if (!string.IsNullOrWhiteSpace(cityName))
             {
                 if (!string.IsNullOrWhiteSpace(sqlWhere)) sqlWhere += $" AND";
                 comeRemoveStr.ForEach(c => { cityName = cityName.Replace(c, ""); });
-                sqlWhere += $" CONTAINS(c.city, '{cityName}') "; 
+                sqlWhere += $" CONTAINS(c.city, '{cityName}') ";
                 groupKey = "name";
             }
             Dictionary<string, string> schGeoDic = new Dictionary<string, string>(); //key: schoolId val:統計的geoName
@@ -515,6 +520,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     string schId = item.GetProperty("id").ToString();
                     string schName = item.GetProperty("name").ToString();
                     string geoName = item.GetProperty(groupKey).ToString();
+                    comeRemoveStr.ForEach(c => { geoName = geoName.Replace(c, ""); });
                     if (!schGeoDic.ContainsKey(schId))
                     {
                         schGeoDic.Add(schId, geoName);
@@ -555,7 +561,7 @@ namespace TEAMModelBI.Controllers.BICommon
                 int tchCnt = item.Value;
                 string geoName = schGeoDic[schId];
                 AreaInfo geoInfo = geoInfos.Where(g => g.name.Equals(geoName)).FirstOrDefault();
-                if(geoInfo == null)
+                if (geoInfo == null)
                 {
                     AreaInfo geoInfoCrt = new AreaInfo();
                     geoInfoCrt.id = (groupKey.Equals("city") || groupKey.Equals("province")) ? geoIdNameDic.FirstOrDefault(x => x.Value.Contains(geoName)).Key : schId;
@@ -590,7 +596,7 @@ namespace TEAMModelBI.Controllers.BICommon
         public async Task<IActionResult> GetUnitTypes(JsonElement jsonElement)
         {
             var cosmosClient = _azureCosmos.GetCosmosClient();
-            var coreCosmosClientCn = _azureCosmos.GetCosmosClient(name:"CoreServiceV2CnRead");
+            var coreCosmosClientCn = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead");
 
             string type = (jsonElement.TryGetProperty("type", out JsonElement _type)) ? _type.GetString() : string.Empty; //教育機構類型 1:基礎教育機構(K-小學) 2:中等教育機構(國中、高中/職) 3:高等教育機構(大學、研究所) 4:其他
             bool showSchool = (jsonElement.TryGetProperty("showSchool", out JsonElement _showSchool)) ? _showSchool.GetBoolean() : false; //是否列出學校
@@ -609,7 +615,7 @@ namespace TEAMModelBI.Controllers.BICommon
             {
                 string schId = item.GetProperty("id").ToString();
                 string schName = item.GetProperty("name").ToString();
-                int schType =  item.GetProperty("type").GetInt32();
+                int schType = item.GetProperty("type").GetInt32();
                 iesSchIds.Add(schId);
                 if (!schTypeDic.ContainsKey(schId)) schTypeDic.Add(schId, schType.ToString());
                 if (!schNameDic.ContainsKey(schId)) schNameDic.Add(schId, schName);
@@ -669,7 +675,7 @@ namespace TEAMModelBI.Controllers.BICommon
                             break;
                     }
                 }
-                else if(!string.IsNullOrWhiteSpace(iesUnitType))
+                else if (!string.IsNullOrWhiteSpace(iesUnitType))
                 {
                     switch (iesUnitType)  //1 普教,2 高职教
                     {
@@ -705,7 +711,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     unitInfo.schools.Add(sch);
                 }
             }
-            if(!string.IsNullOrWhiteSpace(type))
+            if (!string.IsNullOrWhiteSpace(type))
             {
                 unitInfos = unitInfos.Where(u => u.id.Equals(type)).ToList();
             }
@@ -715,9 +721,9 @@ namespace TEAMModelBI.Controllers.BICommon
         }
 
         /// <summary>
-        /// 取得TMID資訊 ※個別挑選用
+        /// (個別挑選)取得TMID資訊後繼送訊息
         /// </summary>
-        [HttpPost("get-tmidinfo")]
+        [HttpPost("get-tmidinfo-single")]
 #if !DEBUG
         [Authorize(Roles = "IES")]
         [AuthToken(Roles = "admin")]
@@ -728,10 +734,9 @@ namespace TEAMModelBI.Controllers.BICommon
             {
                 var storageClientCsv2 = _azureStorage.GetCloudTableClient(name: "CoreServiceV2"); //Storage CSV2
                 var cosmosClientIes = _azureCosmos.GetCosmosClient();
-                var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name:"CoreServiceV2");
+                var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
                 var tablePointsClient = storageClientCsv2.GetTableReference("Points");
 
-                
                 //各項值取聯集或交集
                 string modeToAll = !string.IsNullOrWhiteSpace(target.mode) ? target.mode : "and";
 
@@ -739,7 +744,7 @@ namespace TEAMModelBI.Controllers.BICommon
                 //生成時間
                 List<string> passTmidCrt = new List<string>();
                 List<string> sqlWhereListBase = new List<string>();
-                if(target.creatTime != null)
+                if (target.creatTime != null)
                 {
                     if (target.creatTime.start > 0) sqlWhereListBase.Add($" {target.creatTime.start} <= StringToNumber(c.id) ");
                     if (target.creatTime.end > 0) sqlWhereListBase.Add($" StringToNumber(c.id) <= {target.creatTime.end} ");
@@ -752,7 +757,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     useCrtFlg = true;
                     sqlWhereBase += $" AND {w}";
                 }
-                if(!string.IsNullOrWhiteSpace(sqlWhereBase))
+                if (!string.IsNullOrWhiteSpace(sqlWhereBase))
                 {
                     string sqlBase = $"{sqlSel}{sqlWhereBase}";
                     await foreach (var item in cosmosClientCsv2
@@ -760,7 +765,7 @@ namespace TEAMModelBI.Controllers.BICommon
                     .GetItemQueryIteratorSql<JsonElement>(queryText: sqlBase, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
                     {
                         string id = item.GetProperty("id").ToString();
-                        if(!passTmidCrt.Contains(id)) passTmidCrt.Add(id);
+                        if (!passTmidCrt.Contains(id)) passTmidCrt.Add(id);
                     }
                 }
 
@@ -797,8 +802,8 @@ namespace TEAMModelBI.Controllers.BICommon
                 bool usePrdFlg = false;
                 List<string> passTmidPrd = new List<string>();
                 var tmidProdDic = new Dictionary<string, List<string>>(); //各ID使用過的的產品List
-                List<string> prodWhereList = new List<string>(); 
-                if(target.product != null && target.product.id.Count > 0)
+                List<string> prodWhereList = new List<string>();
+                if (target.product != null && target.product.id.Count > 0)
                 {
                     usePrdFlg = true;
                     foreach (string prodCode in target.product.id)
@@ -806,7 +811,7 @@ namespace TEAMModelBI.Controllers.BICommon
                         prodWhereList.Add(prodCode);
                     }
                 }
-                if(prodWhereList.Count > 0)
+                if (prodWhereList.Count > 0)
                 {
                     string sqlSelCross = "SELECT DISTINCT c.id, c.type FROM c ";
                     string sqlWhereCross = $" WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(prodWhereList)}, c.type)";
@@ -823,22 +828,22 @@ namespace TEAMModelBI.Controllers.BICommon
                     ///生成符合AndOr條件的TMID製作
                     if (target.product.mode.Equals("and"))
                     {
-                        foreach(var prodItem in tmidProdDic)
+                        foreach (var prodItem in tmidProdDic)
                         {
                             int hasProdCount = 0;
                             string tmid = prodItem.Key;
                             ///產品比對
-                            foreach(string prod in target.product.id)
+                            foreach (string prod in target.product.id)
                             {
-                                if(prodItem.Value.Contains(prod)) hasProdCount++;
+                                if (prodItem.Value.Contains(prod)) hasProdCount++;
                             }
-                            if(hasProdCount.Equals(target.product.id.Count))
+                            if (hasProdCount.Equals(target.product.id.Count))
                             {
                                 if (!passTmidPrd.Contains(tmid)) passTmidPrd.Add(tmid);
                             }
                         }
                     }
-                    else if(target.product.mode.Equals("or"))
+                    else if (target.product.mode.Equals("or"))
                     {
                         foreach (var prodItem in tmidProdDic)
                         {
@@ -854,7 +859,7 @@ namespace TEAMModelBI.Controllers.BICommon
                                 }
                             }
                         }
-                    }                
+                    }
                 }
 
                 //點數
@@ -862,7 +867,7 @@ namespace TEAMModelBI.Controllers.BICommon
                 List<string> passTmidPnt = new List<string>();
                 string filter = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Points");
                 string rkFilterCombine = string.Empty;
-                if(target.point != null)
+                if (target.point != null)
                 {
                     if (target.point.start > 0 || target.point.end > 0)
                     {
@@ -928,12 +933,11 @@ namespace TEAMModelBI.Controllers.BICommon
                     }
                 }
 
-
                 //所有TMID做聯集交集整合
                 List<string> passTmid = new List<string>(); //最終條件符合的TMID列表
                 ///初始化
                 if (passTmidCrt.Count > 0) passTmid = passTmidCrt;
-                else if(passTmidGeo.Count > 0) passTmid = passTmidGeo;
+                else if (passTmidGeo.Count > 0) passTmid = passTmidGeo;
                 else if (passTmidPrd.Count > 0) passTmid = passTmidPrd;
                 else if (passTmidPnt.Count > 0) passTmid = passTmidPnt;
                 else if (passTmidSch.Count > 0) passTmid = passTmidSch;
@@ -983,19 +987,45 @@ namespace TEAMModelBI.Controllers.BICommon
         }
 
         /// <summary>
-        /// 發送訊息核心邏輯
+        /// 寄發訊息API
         /// </summary>
+        /// <param name="target"></param>
+        /// <param name="type">發送類型</param>
+        /// <param name="method">挑選方式</param>
+        /// <param name="title">標題</param>
+        /// <param name="body">內文</param>
+        /// <param name="data">傳遞資料</param>
+        /// <param name="action">傳遞動作</param>
         [HttpPost("send-message")]
 #if !DEBUG
         [Authorize(Roles = "IES")]
         [AuthToken(Roles = "admin")]
 #endif
-        public async Task<IActionResult> SendMessage(SendMessageParam target)
+        public async Task<IActionResult> SendMessage(JsonElement jsonElement)
         {
+            SendMessageParam target = (jsonElement.TryGetProperty("target", out JsonElement _target)) ? _target.ToObject<SendMessageParam>() : null; //發送對象
+            string type = (jsonElement.TryGetProperty("type", out JsonElement _type)) ? _type.ToString() : string.Empty; //發送類型 mail:郵件、notify:端外、sms:簡訊
+            string method = (jsonElement.TryGetProperty("method", out JsonElement _method)) ? _method.ToString() : string.Empty; //挑選方式 single:個別 multi:批次
+            string title = (jsonElement.TryGetProperty("title", out JsonElement _title)) ? _title.ToString() : string.Empty; //標題
+            string body = (jsonElement.TryGetProperty("body", out JsonElement _body)) ? _body.ToString() : string.Empty; //內文
+            var dataParam = (jsonElement.TryGetProperty("data", out JsonElement _data)) ? _data : new JsonElement { };
+            List<NotifyAction> action = (jsonElement.TryGetProperty("action", out JsonElement _action)) ? _action.ToObject<List<NotifyAction>>() : new List<NotifyAction>();
 
-
-
-            //target 內容例
+            if (target == null || string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(method) || string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(body)) return BadRequest();
+            string eventKey = "bi-gen-notify";
+            string eventId = $"{eventKey}_{_snowflakeId.NextId()}";
+            string eventName = "BI send notification";
+            var data = new {
+                value = dataParam,
+                action = action
+            };
+            await SendMessageCore(target, type, method, title, body, eventId, eventName, data);
+            return Ok(new { state = RespondCode.Ok });
+        }
+        ///寄發訊息核心邏輯
+        private async Task<IActionResult> SendMessageCore(SendMessageParam target, string type, string method, string title, string body, string eventId = "", string eventName = "", object data = null)
+        {
+            #region target 內容例
             //{
             //     "area":[
             //      "1234"
@@ -1024,10 +1054,323 @@ namespace TEAMModelBI.Controllers.BICommon
             //      "1234"
             //     ]
             //}
+            #endregion
+            var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
+
+            List<string> tmids_area = await GetTmidByAreaId(target.area);
+            List<string> tmids_geo = await GetTmidByGeo(target.geo);
+            List<string> tmids_unit = await GetTmidByUnitId(target.unit);
+            List<string> tmids_school = await GetTmidBySchoolId(target.school);
+            List<string> tmids_direct = target.tmid;
+
+            List<string> tmids = new List<string>(); //聯集化
+            tmids = tmids.Union(tmids_area).ToList();
+            tmids = tmids.Union(tmids_geo).ToList();
+            tmids = tmids.Union(tmids_unit).ToList();
+            tmids = tmids.Union(tmids_school).ToList();
+            tmids = tmids.Union(tmids_direct).ToList();
+            tmids = tmids.Distinct().ToList();  //唯一化
 
+            //取得TMID資料
+            List<IdInfo> tmidInfos = new List<IdInfo>();
+            string sqlBaseInfo = $"SELECT c.id, c.name, c.mail, c.mobile FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(tmids)}, c.id)";
+            await foreach (var item in cosmosClientCsv2
+                .GetContainer("Core", "ID2")
+                .GetItemQueryIteratorSql<JsonElement>(queryText: sqlBaseInfo, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base") }))
+            {
+                IdInfo idInfo = new IdInfo();
+                idInfo.id = item.GetProperty("id").ToString();
+                idInfo.name = item.GetProperty("name").ToString();
+                idInfo.mail = item.GetProperty("mail").ToString();
+                idInfo.mobile = item.GetProperty("mobile").ToString();
+                tmidInfos.Add(idInfo);
+            }
+
+            //寄發訊息
+            if(type.Equals("notify")) //端內外通知
+            {
+                List<string> tmIds = tmidInfos.Select(i => i.id).ToList();
+                CallPushNotifyApi(tmIds, title, body, eventId, eventName, data);
+            }
+            else if(type.Equals("mail")) //Email
+            {
+                List<string> tmIds = tmidInfos.Where(i => !string.IsNullOrWhiteSpace(i.mail)).Select(i => i.id).ToList();
+            }
+            else if (type.Equals("sms")) //短訊
+            {
+                List<string> tmIds = tmidInfos.Where(i => !string.IsNullOrWhiteSpace(i.mobile)).Select(i => i.id).ToList();
+            }
 
             return Ok(new { state = RespondCode.Ok });
         }
+        //取得學區所屬學校教師
+        private async Task<List<string>> GetTmidByAreaId(List<string> areaIds)
+        {
+            var cosmosClientIes = _azureCosmos.GetCosmosClient();
+            //取得學校ID列表
+            List<string> schIds = new List<string>();
+            string sqlSch = $"SELECT c.id FROM c WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(areaIds)}, c.areaId)";
+            ///實體校
+            await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
+            {
+                string schId = item.GetProperty("id").ToString();
+                schIds.Add(schId);
+            }
+            List<string> teacherCodes = schIds.Select(s => $"Teacher-{s}").ToList();
+            //取得學校教師ID列表
+            List<string> tmids = new List<string>();
+            string sqlTch = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
+            await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
+            {
+                string tmid = item.GetProperty("id").ToString();
+                tmids.Add(tmid);
+            }
+            return tmids;
+        }
+        //取得地理資訊所屬TMID
+        private async Task<List<string>> GetTmidByGeo(List<Geo> geos)
+        {
+            var cosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
+            string sqlWhere = string.Empty;
+            List<string> sqlWhereOrList = new List<string>();
+            foreach (Geo geo in geos)
+            {
+                string sqlWhereRow = String.Empty;
+                if (!string.IsNullOrWhiteSpace(geo.countryId))
+                {
+                    if (!string.IsNullOrWhiteSpace(sqlWhereRow)) sqlWhereRow += " AND ";
+                    sqlWhereRow += $"c.country = '{geo.countryId}'";
+                }
+                if (!string.IsNullOrWhiteSpace(geo.provinceId))
+                {
+                    if (!string.IsNullOrWhiteSpace(sqlWhereRow)) sqlWhereRow += " AND ";
+                    sqlWhereRow += $"c.province = '{geo.provinceId}'";
+                }
+                if (!string.IsNullOrWhiteSpace(geo.cityId))
+                {
+                    if (!string.IsNullOrWhiteSpace(sqlWhereRow)) sqlWhereRow += " AND ";
+                    sqlWhereRow += $"c.city = '{geo.cityId}'";
+                }
+                if (!string.IsNullOrWhiteSpace(sqlWhereRow))
+                {
+                    sqlWhereOrList.Add(sqlWhereRow);
+                }
+            }
+            if (sqlWhereOrList.Count > 0)
+            {
+                foreach (string sqlWhereOrRow in sqlWhereOrList)
+                {
+                    if (!string.IsNullOrWhiteSpace(sqlWhere)) sqlWhere += " OR ";
+                    sqlWhere += $"({sqlWhereOrRow})";
+                }
+            }
+            List<string> tmids = new List<string>();
+            if (!string.IsNullOrWhiteSpace(sqlWhere))
+            {
+                string sqlEx = $"SELECT DISTINCT c.id FROM c WHERE {sqlWhere}";
+                await foreach (var item in cosmosClientCsv2
+                .GetContainer("Core", "ID2")
+                .GetItemQueryIteratorSql<JsonElement>(sqlEx, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("base-ex") }))
+                {
+                    string id = item.GetProperty("id").ToString();
+                    tmids.Add(id);
+                }
+            }
+            return tmids;
+        }
+        //寄送通知核心程式(途中)
+        private void sendMessageByApi()
+        {
+            //_coreAPIHttpService.PushNotify(idNameCodes, $"{bizcode}_school", Constant.NotifyType_IES5_Task, new Dictionary<string, object> { { "tmdname", name }, { "schoolName", schname }, { "schoolId", $"{school}" }, { "tmdid", userid } }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
+        }
+
+        //取得學校機構所屬學校教師
+        private async Task<List<string>> GetTmidByUnitId(List<string> units)
+        {
+            if (units.Count.Equals(0)) return new List<string>();
+
+            var cosmosClientIes = _azureCosmos.GetCosmosClient();
+            var coreCosmosClientCn = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead");
+            //取得IES5學校ID
+            string iesSql = "SELECT c.id, c.type FROM c WHERE 1=1 ";
+            string iesWhere = string.Empty;
+            //IES學校ID取得、學校類型取得
+            List<string> iesSchIds = new List<string>();
+            Dictionary<string, string> schTypeDic = new Dictionary<string, string>(); //IES5學校ID與學校類型對照表
+            List<string> teacherCodes = new List<string>(); //用來取得老師資料的分區鍵
+            string sqlSch = $"{iesSql}{iesWhere}";
+            await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlSch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
+            {
+                string schId = item.GetProperty("id").ToString();
+                int schType = item.GetProperty("type").GetInt32();
+                iesSchIds.Add(schId);
+                if (!schTypeDic.ContainsKey(schId)) schTypeDic.Add(schId, schType.ToString());
+            }
+            //取得Core學校ID及類型
+            string coreSql = "SELECT c.shortCode, c.unitType FROM c";
+            string coreWhere = $" WHERE ARRAY_CONTAINS({JsonSerializer.Serialize(iesSchIds)}, c.shortCode) ";
+            string coreWhereUnittype = string.Empty;
+            if (units.Contains("1"))
+            {
+                if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
+                coreWhereUnittype += $" ARRAY_CONTAINS(['1','8'], c.unitType) ";
+            }
+            if (units.Contains("2"))
+            {
+                if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
+                coreWhereUnittype += $" c.unitType = '2' ";
+            }
+            if (units.Contains("3"))
+            {
+                if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
+                coreWhereUnittype += $" c.unitType = '3' ";
+            }
+            if (units.Contains("4"))
+            {
+                if (!string.IsNullOrWhiteSpace(coreWhereUnittype)) coreWhereUnittype += " OR ";
+                coreWhereUnittype += $" ARRAY_CONTAINS(['4','5','6','7','9'], c.unitType) ";
+            }
+            if (!string.IsNullOrWhiteSpace(coreWhereUnittype))
+            {
+                coreWhere += $" AND ({coreWhereUnittype})";
+            }
+            Dictionary<string, string> coreSchUnitTypeDic = new Dictionary<string, string>(); //Core學校ID與學校類型對照表
+            string sqlCore = $"{coreSql}{coreWhere}";
+            await foreach (var item in coreCosmosClientCn.GetContainer("Core", "School").GetItemQueryIteratorSql<JsonElement>(queryText: sqlCore, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"base") }))
+            {
+                string shortCode = item.GetProperty("shortCode").ToString();
+                string unitType = item.GetProperty("unitType").ToString();
+                if (!teacherCodes.Contains($"Teacher-{shortCode}")) teacherCodes.Add($"Teacher-{shortCode}");
+                if (!coreSchUnitTypeDic.ContainsKey(shortCode)) coreSchUnitTypeDic.Add(shortCode, unitType);
+            }
+            //學校類型轉換
+            Dictionary<string, string> finalSchUnitTypeDic = new Dictionary<string, string>();
+            foreach (KeyValuePair<string, string> item in coreSchUnitTypeDic)
+            {
+                string schId = item.Key;
+                string coreUnitType = (coreSchUnitTypeDic.ContainsKey(schId)) ? coreSchUnitTypeDic[schId] : string.Empty;
+                string iesUnitType = (schTypeDic.ContainsKey(schId)) ? schTypeDic[schId] : string.Empty;
+                string unitTypeF = string.Empty; //機構類型(最終判斷)
+                if (!string.IsNullOrWhiteSpace(coreUnitType))
+                {
+                    switch (coreUnitType)
+                    {
+                        case "1": //基礎教育機構
+                        case "8": //學前教育
+                            unitTypeF = "1"; // => 基礎教育機構(K-小學)
+                            break;
+                        case "2": //中等教育機構
+                            unitTypeF = "2"; // => 中等教育機構(國中、高中/職)
+                            break;
+
+                        case "3": //高等教育機構
+                            unitTypeF = "3"; // => 高等教育機構(大學、研究所)
+                            break;
+                        case "4": //政府單位機構
+                        case "5": //NGO機構
+                        case "6": //企業機構
+                        case "7": //其他
+                        case "9": //特殊教育
+                            unitTypeF = "4"; // => 其他
+                            break;
+                    }
+                }
+                else if (!string.IsNullOrWhiteSpace(iesUnitType))
+                {
+                    switch (iesUnitType)  //1 普教,2 高职教
+                    {
+                        case "1": //國教(K-12)
+                            unitTypeF = "1"; // => 基礎教育機構(K-小學)
+                            break;
+                        case "2": //大專院校
+                            unitTypeF = "3"; // => 高等教育機構(大學、研究所)
+                            break;
+                        default: //未設定
+                            unitTypeF = "4"; // => 其他
+                            break;
+                    }
+                }
+                finalSchUnitTypeDic.Add(schId, unitTypeF);
+            }
+            //由類型篩選學校ID [應該不需要,CoreV2的SQL已篩選過]
+            //List<string> schIdsForSql = new List<string>();
+            //foreach (KeyValuePair<string, string> item in finalSchUnitTypeDic)
+            //{
+            //    string schId = item.Key;
+            //    if (units.Contains(item.Value))
+            //    {
+            //        schIdsForSql.Add(schId);
+            //    }
+            //}
+            //取得學校所屬老師TMID
+            //teacherCodes = schIdsForSql.Select(s => $"Teacher-{s}").ToList();
+            teacherCodes = finalSchUnitTypeDic.Select(s => $"Teacher-{s.Key}").ToList();
+            List<string> tmids = new List<string>();
+            string sqlTch = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
+            await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
+            {
+                string tmid = item.GetProperty("id").ToString();
+                tmids.Add(tmid);
+            }
+            return tmids;
+
+        }
+        //取得學校所屬學校教師
+        private async Task<List<string>> GetTmidBySchoolId(List<string> schIds)
+        {
+            var cosmosClientIes = _azureCosmos.GetCosmosClient();
+            //取得學校ID列表
+            List<string> teacherCodes = schIds.Select(s => $"Teacher-{s}").ToList();
+            //取得學校教師ID列表
+            List<string> tmids = new List<string>();
+            string sqlTch = $"SELECT DISTINCT c.id FROM c WHERE c.pk = 'Teacher' AND c.status = 'join' AND ARRAY_CONTAINS({JsonSerializer.Serialize(teacherCodes)}, c.code)";
+            await foreach (var item in cosmosClientIes.GetContainer(Constant.TEAMModelOS, Constant.School).GetItemQueryIteratorSql<JsonElement>(queryText: sqlTch, requestOptions: null))
+            {
+                string tmid = item.GetProperty("id").ToString();
+                tmids.Add(tmid);
+            }
+            return tmids;
+        }
+
+        /// <summary>
+        /// 寄發端外通知
+        /// </summary>
+        /// <param name="tmIds"></param>
+        /// <param name="title"></param>
+        /// <param name="body"></param>
+        /// <param name="eventId"></param>
+        /// <param name="eventName"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        private HttpResponseMessage CallPushNotifyApi(List<string> tmIds, string title, string body, string eventId = "", string eventName = "", object data = null)
+        {
+            NotifyData notify = new NotifyData();
+            notify.hubName = "hita5";
+            notify.sender = "IES"; //先填IES 若之後有需求要標BI 再修正
+#if DEBUG
+            List<string> filterTmid = new List<string>() { "1522758684", "1595321354" };
+            tmIds = tmIds.Intersect(filterTmid).ToList();
+#endif
+            notify.tags = tmIds.Select(s => $"{s}_BI").ToList();
+            notify.title = title;
+            notify.body = body;
+            notify.eventId = eventId;
+            notify.eventName = eventName;
+            notify.data = (data != null) ? data.ToJsonString() : string.Empty;
+            var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+            var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+            var url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
+            var client = _httpClient;
+            string sendSite = (_option.Location.Contains("China")) ? "China" : "Global";
+            var token = CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, sendSite).Result;
+            if (client.DefaultRequestHeaders.Contains("Authorization"))
+            {
+                client.DefaultRequestHeaders.Remove("Authorization");
+            }
+            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
+            HttpResponseMessage responseMessage = client.PostAsJsonAsync($"{url}/service/PushNotify", notify).Result;
+            return responseMessage;
+        }
 
         /// <summary>
         /// 取得TMID資訊 接收參數class
@@ -1124,6 +1467,17 @@ namespace TEAMModelBI.Controllers.BICommon
             public string id { get; set; }
             public string name { get; set; }
         }
+        private record IdInfo : IdName
+        {
+            public string mail { get; set; }
+            public string mobile { get; set; }
+        }
+        private record NotifyAction
+        {
+            public string type { get; set; } = "click";
+            public string label { get; set; }
+            public string url { get; set; }
+        }
         //地區要去除的特殊字
         public List<string> comeRemoveStr = new List<string>() { "省", "市", "区", "州", "县", "旗", "盟", "自治", "地區", "區", "縣" };
     }

+ 76 - 0
TEAMModelBI/Controllers/BITest/TestController.cs

@@ -59,6 +59,13 @@ using TEAMModelOS.SDK.Models.Cosmos.OpenEntity;
 using TEAMModelOS.SDK.Models.Cosmos.BI.BICommon;
 using Microsoft.Azure.Cosmos.Table;
 using Microsoft.AspNetCore.Authorization;
+using Microsoft.Azure.Amqp.Framing;
+using TEAMModelOS.SDK.Models.Dtos;
+using Microsoft.Extensions.Logging;
+using WebSocketSharp;
+using System.Security.Policy;
+using Azure.Messaging.ServiceBus.Administration;
+using static TEAMModelOS.SDK.CoreAPIHttpService;
 
 namespace TEAMModelBI.Controllers.BITest
 {
@@ -1926,6 +1933,75 @@ namespace TEAMModelBI.Controllers.BITest
             return Ok(new { state = 200 });
         }
 
+        /// <summary>
+        /// BI呼叫端外通知API測試
+        /// </summary>
+        /// <param name="jsonElement"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("push-notify-test")]
+        public async Task<IActionResult> PushNotifyTest(JsonElement jsonElement)
+        {
+            //var data = new { value = new{ } };
+            //var data = new
+            //{
+            //    value = new
+            //    {
+            //        tmdid = "1595321354",
+            //        tmdname = "罗老师"
+            //    }
+            //};
+            var data = new
+            {
+                value = new
+                {
+                    tmdid = "1595321354",
+                    tmdname = "罗老师"
+                },
+                action = new[] {
+                    new {
+                        type = "click",
+                        label = "請按我1",
+                        url = "https://www.youtube.com/watch?v=HGWFKKDBLsI"
+                    },
+                    new {
+                        type = "click",
+                        label = "請按我2",
+                        url = "https://www.youtube.com/watch?v=xe5E6PjR9sg"
+                    }
+                }
+                
+            };
+            var notifyData = new
+            {
+                hubName = "hita5",
+                sender = "IES",
+                tags = new[] { "1595321354_BI", "1522758684_BI" },
+                title = "端外通知發送測試7",
+                body = "2024流行語有哪些,你知道嗎?每個時代都有專屬的流行語,除了去(2023)年流行的瑪卡巴卡、超派之外,今年流行的M3、已購買小孩愛吃都相當特別,不認真解釋可能還不知道是什麼意思。一起掌握2024年網路上最夯的流行語!",
+                eventId = "test-368329735249465344",
+                eventName = "端外通知發送測試",
+                data = data.ToJsonString()
+            };
+            var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+            var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+            var url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
+            var client = _httpClient.CreateClient();
+            string sendSite = "Global";
+            var token = CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, sendSite).Result;
+            if (client.DefaultRequestHeaders.Contains("Authorization"))
+            {
+                client.DefaultRequestHeaders.Remove("Authorization");
+                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
+            }
+            else
+            {
+                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
+            }
+            HttpResponseMessage responseMessage = client.PostAsJsonAsync($"{url}/service/PushNotify", notifyData).Result;
+            return Ok(new { state = 200, responseMessage });
+        }
+
         public class linqTest
         {
             public string id { get; set; }