浏览代码

[BI]取得IOT資料時追加IP分析地理位置
- 各地理位置統計年月日資料生成
- 前端統計頁面製作

jeff 3 月之前
父节点
当前提交
e63e383fdd

+ 2 - 0
TEAMModelBI/ClientApp/src/language/lang/zh-cn.js

@@ -690,6 +690,8 @@ const zh_cn = {
         parameterError: '参数错误',
     },
     product: {
+        tmid: '醍魔豆帐号',
+        geo: '地理位置',
         dataTarget: '取得对象',
         dataFilter: '数据筛选',
         filterType: '筛选类型',

+ 2 - 0
TEAMModelBI/ClientApp/src/language/lang/zh-tw.js

@@ -683,6 +683,8 @@ const zh_tw = {
         parameterError: '參數錯誤',
     },
     product: {
+        tmid: '醍魔豆帳號',
+        geo: '地理位置',
         dataTarget: '取得對象',
         dataFilter: '數據篩選',
         filterType: '篩選類型',

+ 48 - 15
TEAMModelBI/ClientApp/src/view/product/index.vue

@@ -9,8 +9,9 @@
             <div class="filtratebox-phase">
                 <span class="filtratebox-phase-title">{{$t(`product.dataTarget`)}}:</span>
                 <div class="filtratebox-phase-content">
-                    <div class="phase-item" :class="[clickNum.target==='school' ? 'filter-click':'' ]" @click="clickNum.target='school'">school</div>
-                    <div class="phase-item" :class="[clickNum.target==='tmid' ? 'filter-click':'' ]" @click="clickNum.target='tmid'">tmid</div>
+                    <div class="phase-item" :class="[clickNum.target==='school' ? 'filter-click':'' ]" @click="clickNum.target='school'">{{$t(`product.school`)}}</div>
+                    <div class="phase-item" :class="[clickNum.target==='tmid' ? 'filter-click':'' ]" @click="clickNum.target='tmid'">{{$t(`product.tmid`)}}</div>
+                    <div class="phase-item" :class="[clickNum.target==='geo' ? 'filter-click':'' ]" @click="clickNum.target='geo'">{{$t(`product.geo`)}}</div>
                 </div>
             </div>
             <!--产品类型-->
@@ -42,13 +43,10 @@
             <div class="select-result">
               <!--目标范围-->
               <div v-show="clickNum.filter === 0">
-                <div class="filtratebox-phase">
+                <div class="filtratebox-phase" v-show="clickNum.target==='school'">
                   <span class="filtratebox-phase-title subclass">{{productData.sourceName}}:</span>
                   <div class="filtratebox-phase-content">
                     <div class="phase-item" v-for="(items,index) in productData.source" :key="items.value" :class="[index ===clickNum.subject ? 'filter-click':'' ]" @click="(clickNum.subject=index,optionsValue='')">{{items.name}}</div>
-                    <!-- <div class="phase-item filter-click">456456</div>
-                <div class="phase-item">456456</div>
-                <div class="phase-item">456456</div> -->
                   </div>
                 </div>
                 <div class="filtratebox-phase">
@@ -60,7 +58,7 @@
                 <div class="filtratebox-phase">
                   <span class="filtratebox-phase-title subclass">{{$t(`product.accurateSelect`)}}:</span>
                   <div class="filtratebox-phase-content precise">
-                    <div v-if="(clickNum.subject === 0 || clickNum.subject === 2) && clickNum.filter===0" class="schoolclass">
+                    <div v-if="clickNum.target==='school' && (clickNum.subject === 0 || clickNum.subject === 2) && clickNum.filter===0" class="schoolclass">
                       <el-cascader v-model="optionsValue" :options="options" :props="props2" :collapse-tags=true :collapse-tags-tooltip=true filterable :filter-method="keywords" :placeholder="$t(`product.pleaseSelect`)">
                       </el-cascader>
                       <div class="addschoolbtn" @click="(adddialog=true,addvalue='',searchInit())">
@@ -875,15 +873,15 @@ function dataInit () {
     ElMessage.error(proxy.$t(`product.apiErrpr`) + ',' + proxy.$t(`product.basicDataError`))
   })
 }
-function serachToresult(startTime, endTime, product, schools, unit, target) {
+function serachToresult(startTime, endTime, product, schools, unit, target, geo=null) {
   // let data = { "dateFrom": "2023-04-12", "dateTo": "2023-04-19", "prod": "HiTeach", "schoolIds": ["tbslgb", "habook"], "dateUnit": "Day" }
   if (!startTime || !endTime) {
     ElMessage.info(proxy.$t(`product.timeRangeSelectError`))
     return
   }
   searchLoading.value = true;
-  let data = { "dateFrom": startTime, "dateTo": endTime, "prod": product, "schoolIds": schools, "dateUnit": unit, "target": target }
-  console.log(data, '内容')
+  let data = { "dateFrom": startTime, "dateTo": endTime, "prod": product, "schoolIds": schools, "dateUnit": unit, "target": target, "geo": geo }
+  console.log(data, 'serachToresult data内容')
   console.log(clickNum.value.time, '数字')
   postData.value = data
   proxy.$api.getUseproduct(data).then(async (res) => {
@@ -1065,7 +1063,7 @@ function timeChange (value) {
 }
 //整理数据内容
 async function searchData () {
-  console.log(optionsValue.value)
+  console.log(optionsValue.value) //國省市
   console.log(productData.value.timevalue)
   console.log(clickNum.value.filter, 'NUM')
   console.log(clickNum.value.target, 'target')
@@ -1077,6 +1075,7 @@ async function searchData () {
   let times = { start: productData.value.timevalue[0], end: productData.value.timevalue[1] }
   let yearValues=''
   let target = clickNum.value.target
+  let geo = null
   clickNum.value.time === 2 ? (yearValues=productData.value.timevalue.slice(0,4),times.start=productData.value.timevalue,times.end=yearValues+'-12-31'):'' 
   if (clickNum.value.filter === 0 && searchValue) { // 篩選類型 => 來源類型
     if (clickNum.value.subject === 0) {// 目标范围 => 學校     
@@ -1106,7 +1105,35 @@ async function searchData () {
       console.log(optionsValue.value, '城市关键值')
       console.log(typeof optionsValue.value, 'type')
       let state = ''
+      geo = { "countryId": null, "provinceId": null, "cityId": null }
       typeof optionsValue.value == 'string' ? state = 'province' : state = 'city'
+        if (state === 'province') {
+            if (siteValue === 'cn') {
+                geo.countryId = 'CN'
+                geo.provinceId = optionsData.find((obj) => obj.name === optionsValue.value).code;
+            }
+            else {
+                geo.countryId = optionsData.find((obj) => obj.name === optionsValue.value).code;
+            }
+        }
+        else if (state === 'city') {
+            if (siteValue === 'cn') {
+                geo.countryId = 'CN'
+                let provinceId = optionsData.find((obj) => obj.name === optionsValue.value[0]).code;
+                geo.provinceId = provinceId
+                let cityDic = optionsData.find((obj) => obj.code === provinceId).children;
+                let cityId = cityDic.find((obj) => obj.name === optionsValue.value[1]).code;
+                geo.cityId = cityId
+            }
+            else {
+                let countryId = optionsData.find((obj) => obj.name === optionsValue.value[0]).code;
+                geo.countryId = countryId;
+                geo.provinceId = null;
+                let cityDic = optionsData.find((obj) => obj.code === countryId).children;
+                let cityId = cityDic.find((obj) => obj.name === optionsValue.value[1]).code;
+                geo.cityId = cityId
+            }
+        }
       let resultData = await filterDistrict(state, optionsValue.value)
       schoolArr = resultData
       console.log(resultData, state, '省级查询及状态')
@@ -1123,17 +1150,23 @@ async function searchData () {
   console.log(schoolArr, dateUnits, times, '结果')
   !searchValue ? schoolArr = [] : ''
   schoolArr = [...new Set(schoolArr)]
-  serachToresult(times.start, times.end, "HiTeach", schoolArr, dateUnits, target)
+  serachToresult(times.start, times.end, "HiTeach", schoolArr, dateUnits, target, geo)
 }
 function serarchInit (value) {
   let filterKey = value
-  console.log(filterKey)
-  if (filterKey === 0) {
+  if (clickNum.value.target === 'geo') { //取得對象:地理位置
+      filterKey = 1;
+      clickNum.value.filter = 1;
+      if (clickNum.value.district === 2) { //地區選擇若為"學區" => 改選"城市"
+          clickNum.value.district = 1
+      }
+  }
+  if (filterKey === 0) { //篩選類型:來源類型
     // dataSource.value.composite=
     console.log(clickNum.value.subject, '0学区值0')
     options.value = dataSource.value.composite
     clickNum.value.subject === 2 ? options.value = optionsData : ''
-  } else if (filterKey === 1) {
+  } else if (filterKey === 1) { //篩選類型:地區城市
     options.value = optionsData
     console.log(clickNum.value.district, '1学区值1')
     clickNum.value.district === 2 ? options.value = dataSource.value.composite : ''

文件差异内容过多而无法显示
+ 99 - 2
TEAMModelBI/Controllers/BIProductAnalysis/ProductAnalysisController.cs


二进制
TEAMModelBI/Services/ipip.ipdb


+ 156 - 0
TEAMModelOS.SDK/Extension/GeoRegion.cs

@@ -0,0 +1,156 @@
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Drawing.Drawing2D;
+using System.IdentityModel.Tokens.Jwt;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+
+namespace TEAMModelOS.SDK.Extension
+{
+    public static class GeoRegion
+    {
+        //取得國省市區地理資料架構
+        public static regiondata GetRegionData()
+        {
+            regiondata region = new regiondata();
+            //國際
+            var regionTw = new List<regionrow>();
+            using (StreamReader r = new StreamReader("JsonFile/Region/region_gl.json"))
+            {
+                string json = r.ReadToEnd();
+                regionTw = JsonSerializer.Deserialize<List<regionrow>>(json);
+                foreach (regionrow itemcy in regionTw)
+                {
+                    //country
+                    string countryCode = itemcy.code;
+                    if (!region.country.ContainsKey(countryCode))
+                    {
+                        region.country.Add(countryCode, new regionbase() { code = countryCode, name = itemcy.name });
+                    }
+                    //province 無
+                    //city
+                    if (itemcy.children != null)
+                    {
+                        foreach (JsonElement itemcyChild in itemcy.children)
+                        {
+                            regionrow itemct = itemcyChild.ToObject<regionrow>();
+                            string provinceCode = "tw"; //台灣的省代碼用"tw"
+                            string cityCode = itemct.code;
+                            if (!region.city.ContainsKey(countryCode)) region.city.Add(countryCode, new Dictionary<string, Dictionary<string, regionbase>>());
+                            if (!region.city[countryCode].ContainsKey(provinceCode)) region.city[countryCode].Add(provinceCode, new Dictionary<string, regionbase>());
+                            if (!region.city[countryCode][provinceCode].ContainsKey(cityCode)) region.city[countryCode][provinceCode].Add(cityCode, new regionbase() { code = cityCode, name = itemct.name });
+                            //dist
+                            if (itemct.children != null)
+                            {
+                                foreach (JsonElement itemctChild in itemct.children)
+                                {
+                                    regionrow itemds = itemctChild.ToObject<regionrow>();
+                                    string distCode = itemds.code;
+                                    if (!region.dist.ContainsKey(countryCode)) region.dist.Add(countryCode, new Dictionary<string, Dictionary<string, Dictionary<string, regionbase>>>());
+                                    if (!region.dist[countryCode].ContainsKey(provinceCode)) region.dist[countryCode].Add(provinceCode, new Dictionary<string, Dictionary<string, regionbase>>());
+                                    if (!region.dist[countryCode][provinceCode].ContainsKey(cityCode)) region.dist[countryCode][provinceCode].Add(cityCode, new Dictionary<string, regionbase>());
+                                    if (!region.dist[countryCode][provinceCode][cityCode].ContainsKey(distCode)) region.dist[countryCode][provinceCode][cityCode].Add(distCode, new regionbase() { code = distCode, name = itemds.name });
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            //大陸
+            var regionCn = new List<regionrow>();
+            using (StreamReader r = new StreamReader("JsonFile/Region/region_cn.json"))
+            {
+                string json = r.ReadToEnd();
+                regionCn = JsonSerializer.Deserialize<List<regionrow>>(json);
+                //country
+                string countryCode = "CN";
+                string countryName = "中国";
+                if (!region.country.ContainsKey(countryCode))
+                {
+                    region.country.Add(countryCode, new regionbase() { code = countryCode, name = countryName });
+                }
+                //province
+                foreach (regionrow itempv in regionCn)
+                {
+                    string provinceCode = itempv.code.Replace("0000", "");
+                    if (!region.province.ContainsKey(countryCode)) region.province.Add(countryCode, new Dictionary<string, regionbase>());
+                    if (!region.province[countryCode].ContainsKey(provinceCode)) region.province[countryCode].Add(provinceCode, new regionbase() { code = provinceCode, name = itempv.name });
+                    //city
+                    if (itempv.children != null)
+                    {
+                        foreach (JsonElement itempvChild in itempv.children)
+                        {
+                            regionrow itemct = itempvChild.ToObject<regionrow>();
+                            string cityCode = itemct.code;
+                            if (!region.city.ContainsKey(countryCode)) region.city.Add(countryCode, new Dictionary<string, Dictionary<string, regionbase>>());
+                            if (!region.city[countryCode].ContainsKey(provinceCode)) region.city[countryCode].Add(provinceCode, new Dictionary<string, regionbase>());
+                            if (!region.city[countryCode][provinceCode].ContainsKey(cityCode)) region.city[countryCode][provinceCode].Add(cityCode, new regionbase() { code = cityCode, name = itemct.name });
+                            //dist
+                            if (itemct.children != null)
+                            {
+                                foreach (JsonElement itemctChild in itemct.children)
+                                {
+                                    regionrow itemds = itemctChild.ToObject<regionrow>();
+                                    string distCode = itemds.code;
+                                    if (!region.dist.ContainsKey(countryCode)) region.dist.Add(countryCode, new Dictionary<string, Dictionary<string, Dictionary<string, regionbase>>>());
+                                    if (!region.dist[countryCode].ContainsKey(provinceCode)) region.dist[countryCode].Add(provinceCode, new Dictionary<string, Dictionary<string, regionbase>>());
+                                    if (!region.dist[countryCode][provinceCode].ContainsKey(cityCode)) region.dist[countryCode][provinceCode].Add(cityCode, new Dictionary<string, regionbase>());
+                                    if (!region.dist[countryCode][provinceCode][cityCode].ContainsKey(distCode)) region.dist[countryCode][provinceCode][cityCode].Add(distCode, new regionbase() { code = distCode, name = itemds.name });
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            return region;
+        }
+
+        //取得EN版國省市區地理資料架構 ※EN版只取國
+        public static regiondata GetRegionDataEn()
+        {
+            regiondata region = new regiondata();
+            //國際
+            var regionEn = new List<regionrow>();
+            using (StreamReader r = new StreamReader("JsonFile/Region/region_en.json"))
+            {
+                string json = r.ReadToEnd();
+                regionEn = JsonSerializer.Deserialize<List<regionrow>>(json);
+                foreach (regionrow itemcy in regionEn)
+                {
+                    //country
+                    string countryCode = itemcy.code;
+                    if (!region.country.ContainsKey(countryCode))
+                    {
+                        region.country.Add(countryCode, new regionbase() { code = countryCode, name = itemcy.name });
+                    }
+                }
+            }
+            return region;
+        }
+
+        public class regiondata
+        {
+            public Dictionary<string, regionbase> country { get; set; } = new();
+            public Dictionary<string, Dictionary<string, regionbase>> province { get; set; } = new();
+            public Dictionary<string, Dictionary<string, Dictionary<string, regionbase>>> city { get; set; } = new();
+            public Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, regionbase>>>> dist { get; set; } = new();
+        }
+        public class regionbase
+        {
+            public string code { get; set; } //代碼
+            public string name { get; set; } //名稱
+        }
+        public class regionrow : regionbase
+        {
+            public List<object> children { get; set; }
+        }
+
+        //地區要去除的特殊字
+        public static List<string> comeRemoveStr = new List<string>() { "省", "市", "区", "自治州", "县", "旗", "盟", "回族", "藏族", "羌族", "哈尼族", "彝族", "壮族", "苗族", "自治", "特别行政", "地區", "區", "縣" };
+        //大陸直轄市的省ID
+        public static List<string> municipalityId = new List<string>() { "11", "12", "31", "50", "81", "82" };
+    }
+}

文件差异内容过多而无法显示
+ 15172 - 0
TEAMModelOS.SDK/JsonFile/Region/region.json


文件差异内容过多而无法显示
+ 13614 - 0
TEAMModelOS.SDK/JsonFile/Region/region_cn.json


文件差异内容过多而无法显示
+ 1880 - 0
TEAMModelOS.SDK/JsonFile/Region/region_en.json


文件差异内容过多而无法显示
+ 1896 - 0
TEAMModelOS.SDK/JsonFile/Region/region_gl.json


+ 34 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/GeoAnalysis.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Models.Cosmos;
+using static TEAMModelOS.SDK.Models.Cosmos.IotTeachingData;
+
+namespace TEAMModelOS.SDK.Models
+{
+    public class GeoAnalysis : ProdAnalysisCalItem
+    {
+        public string geoId { get; set; } //形式: $"{countryId}-{provinceId}-{cityId}-{distId}" [例]"TW--30-"、"CN-41-410100-"、"MY---"
+        public Geo geo { get; set; }
+    }
+    public class GeoAnalysisCosmos : GeoAnalysis
+    {
+        public GeoAnalysisCosmos()
+        {
+            pk = "GeoAnalysis";
+            code = "GeoAnalysis";
+        }
+        public string pk { get; set; }
+        public string code { get; set; }
+        public string id { get; set; }
+        public string dateUnit { get; set; } //統計日期單位 "year":年統計 "month":月統計 "day":日統計
+        public string date { get; set; } //统计日期 ※依据dateUnit变化 [例]"year":2023 "month":202302 "day":20230210
+        public long dateTime { get; set; } //timestamp UTC milisecond 比較時間用
+        public int year { get; set; } //统计日期:年
+        public int month { get; set; } //统计日期:月
+        public int day { get; set; } //统计日期:日
+        public long createDate { get; set; } //統計時間
+        public int? ttl { get; set; } = -1;
+    }
+}

+ 12 - 11
TEAMModelOS.SDK/Models/Service/BI/BIProdAnalysis.cs

@@ -1122,11 +1122,12 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                         {
                             bool addFlg = false;
                             GeoAnalysis geoAnalysisRow = GeoAnalysisList.Where(s => s.geoId.Equals(geoId) && s.toolType.Equals(toolType)).FirstOrDefault();
-                            //無此tmid數據=>創建
+                            //無此geoId數據=>創建
                             if (geoAnalysisRow == null)
                             {
                                 geoAnalysisRow = new GeoAnalysis();
                                 geoAnalysisRow.geoId = geoId;
+                                geoAnalysisRow.geo = geo;
                                 geoAnalysisRow.toolType = toolType;
                                 addFlg = true;
                             }
@@ -1317,14 +1318,14 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                         bool geoAnalysisDayExist = await redisClinet8.KeyExistsAsync(keyDay);
                         if (geoAnalysisDayExist && !string.IsNullOrWhiteSpace(dateStrD_y) && !string.IsNullOrWhiteSpace(dateStrD_m) && !string.IsNullOrWhiteSpace(dateStrD_d))
                         {
-                            HashEntry[] hsetDay = redisClinet8.HashGetAll(keyDay); //某日 ProdAnalysis:Day所有學校的統計項目
+                            HashEntry[] hsetDay = redisClinet8.HashGetAll(keyDay); //某日 ProdAnalysis:Day所有地理位置的統計項目
                             foreach (HashEntry hset in hsetDay)
                             {
                                 string keyGeoId = hset.Name;
                                 string valGeoDataJson = hset.Value;
                                 GeoAnalysis geoDataTodo = valGeoDataJson.ToObject<GeoAnalysis>();
                                 //Redis Month 資料生成
-                                if (GeoAnalysisListMonth.ContainsKey(geoAnalMonthKey)) //月Dic已有此key => TMID累加
+                                if (GeoAnalysisListMonth.ContainsKey(geoAnalMonthKey)) //月Dic已有此key => geoId累加
                                 {
                                     if (GeoAnalysisListMonth[$"{geoAnalMonthKey}"].ContainsKey($"{keyGeoId}"))
                                     {
@@ -1495,23 +1496,22 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                             GeoAnalysisCosmos cosmosGeoRow = yearCosmosGeoItem.Value;
                             await _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<GeoAnalysisCosmos>(cosmosGeoRow);
                             //每年所有學校數據總計CosmosDB記入
-                            cosmosAllTmidSumYearDic = GenAnalysisRowSumData(cosmosAllTmidSumYearDic, cosmosTmidRow, calPropList, "year");
+                            cosmosAllGeoSumYearDic = GenAnalysisRowSumData(cosmosAllGeoSumYearDic, cosmosGeoRow, calPropList, "year");
                         }
                         //每年所有學校數據總計CosmosDB記入
-                        foreach (KeyValuePair<string, TmidAnalysisCosmos> tmidItem in cosmosAllTmidSumYearDic)
+                        foreach (KeyValuePair<string, GeoAnalysisCosmos> geoItem in cosmosAllGeoSumYearDic)
                         {
-                            string toolType = tmidItem.Key;
-                            TmidAnalysisCosmos cosmosAllTmidSumYear = tmidItem.Value;
-                            cosmosAllTmidSumYear.createDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                            await _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<TmidAnalysisCosmos>(cosmosAllTmidSumYear);
+                            string toolType = geoItem.Key;
+                            GeoAnalysisCosmos cosmosAllGeoSumYear = geoItem.Value;
+                            cosmosAllGeoSumYear.createDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                            await _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<GeoAnalysisCosmos>(cosmosAllGeoSumYear);
                         }
-                        //await _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").UpsertItemAsync<TmidAnalysisCosmos>(TmidDataNow);
                     }
                 }
             }
             catch (Exception ex)
             {
-                _ = _dingDing.SendBotMsg($"BI,{Environment.GetEnvironmentVariable("Option:Location")},CreatTmidProdAnalData() 生成TMID年月日日IOT統計資料錯誤\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.研發C組);
+                _ = _dingDing.SendBotMsg($"BI,{Environment.GetEnvironmentVariable("Option:Location")},CreatGeoProdAnalData() 生成地理資訊年月日日IOT統計資料錯誤\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.研發C組);
             }
         }
 
@@ -1824,6 +1824,7 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                     }
                 }
                 cosmosAllSum.geoId = geoId;
+                cosmosAllSum.geo = null;
                 cosmosAllSum.toolType = cosmosRow.toolType;
                 cosmosAllSum.date = cosmosRow.date;
                 cosmosAllSum.id = $"{cosmosAllSum.toolType}-{cosmosAllSum.date}-{geoId}";