From 06939b5133885153446a60b2e99188bb3415ee02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=9B=B7?= <284428564@QQ.com> Date: Fri, 7 Jun 2024 09:43:53 +0800 Subject: [PATCH] first commit --- .dockerignore | 30 + .gitignore | 340 +++++++++++ .../Common/HttpHelperAsync.cs | 460 +++++++++++++++ .../LimitedConcurrencyLevelTaskScheduler.cs | 137 +++++ TelpoPush.Fence.Worker/Common/TimeHelper.cs | 33 ++ TelpoPush.Fence.Worker/Common/Utils.cs | 40 ++ TelpoPush.Fence.Worker/Dockerfile | 30 + .../Handlers/FenceProcess.cs | 537 ++++++++++++++++++ .../Handlers/KafkaSubscribe.cs | 59 ++ .../Models/CacheTemplates/DeviceFenceModel.cs | 73 +++ .../Models/CacheTemplates/DeviceInfoModel.cs | 19 + .../Models/Config/FenceConfig.cs | 15 + .../Models/Config/RedisConfig.cs | 94 +++ .../Models/Config/ServiceConfig.cs | 25 + .../Models/Enum/AlarmType.cs | 34 ++ .../Models/Enum/GeofenceType.cs | 18 + .../Models/Enum/HeadersDto.cs | 16 + .../Models/Enum/LocationType.cs | 19 + .../Models/Enum/MqDataTopic.cs | 19 + .../Models/Enum/MqDataType.cs | 156 +++++ .../Models/Enum/MqHeader.cs | 26 + .../Algorithms/CirclePolygonAlgorithm.cs | 94 +++ .../Fence/Algorithms/RayCastingAlgorithm.cs | 36 ++ .../Models/Fence/ChinaPolygon.cs | 81 +++ TelpoPush.Fence.Worker/Models/Fence/Circle.cs | 18 + .../Models/Fence/GeoConvert.cs | 199 +++++++ .../Models/Fence/GeoUtils.cs | 178 ++++++ .../Models/Fence/GeofenceHelper.cs | 144 +++++ .../Models/Fence/GpsFencePoint.cs | 31 + TelpoPush.Fence.Worker/Models/Fence/Point.cs | 23 + .../Models/MqTemplates/BaseModel.cs | 42 ++ .../Models/PushTemplates/PushWxTemplate.cs | 18 + .../PushTemplates/PushXiaoAnPushTemplate.cs | 45 ++ TelpoPush.Fence.Worker/Program.cs | 103 ++++ .../Properties/launchSettings.json | 15 + .../Service/Cache/MemoryCacheUtil.cs | 82 +++ .../Service/Cache/RedisUtil.cs | 191 +++++++ .../Service/Cache/SqlMapper.cs | 65 +++ .../Service/Mq/IKafkaService.cs | 9 + .../Service/Mq/KafkaHeader.cs | 15 + .../Service/Mq/KafkaService.cs | 157 +++++ .../Service/Mq/MessageProducer.cs | 77 +++ .../Service/Mq/MqProcessMessage.cs | 81 +++ .../Service/Mq/TopicModel.cs | 15 + .../TelpoPush.Fence.Worker.csproj | 31 + TelpoPush.Fence.Worker/Worker.cs | 25 + .../appsettings.Development.json | 33 ++ TelpoPush.Fence.Worker/appsettings.json | 70 +++ .../appsettings.production.json | 29 + TelpoPush.Fence.Worker/appsettings.test.json | 29 + TelpoPushFence.sln | 25 + nuget.config | 7 + setup_production.sh | 18 + setup_test.sh | 17 + telpo_push_fence_run.sh | 27 + 55 files changed, 4210 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 TelpoPush.Fence.Worker/Common/HttpHelperAsync.cs create mode 100644 TelpoPush.Fence.Worker/Common/LimitedConcurrencyLevelTaskScheduler.cs create mode 100644 TelpoPush.Fence.Worker/Common/TimeHelper.cs create mode 100644 TelpoPush.Fence.Worker/Common/Utils.cs create mode 100644 TelpoPush.Fence.Worker/Dockerfile create mode 100644 TelpoPush.Fence.Worker/Handlers/FenceProcess.cs create mode 100644 TelpoPush.Fence.Worker/Handlers/KafkaSubscribe.cs create mode 100644 TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceFenceModel.cs create mode 100644 TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceInfoModel.cs create mode 100644 TelpoPush.Fence.Worker/Models/Config/FenceConfig.cs create mode 100644 TelpoPush.Fence.Worker/Models/Config/RedisConfig.cs create mode 100644 TelpoPush.Fence.Worker/Models/Config/ServiceConfig.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/AlarmType.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/GeofenceType.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/HeadersDto.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/LocationType.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/MqDataTopic.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/MqDataType.cs create mode 100644 TelpoPush.Fence.Worker/Models/Enum/MqHeader.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/Algorithms/CirclePolygonAlgorithm.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/Algorithms/RayCastingAlgorithm.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/ChinaPolygon.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/Circle.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/GeoConvert.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/GeoUtils.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/GeofenceHelper.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/GpsFencePoint.cs create mode 100644 TelpoPush.Fence.Worker/Models/Fence/Point.cs create mode 100644 TelpoPush.Fence.Worker/Models/MqTemplates/BaseModel.cs create mode 100644 TelpoPush.Fence.Worker/Models/PushTemplates/PushWxTemplate.cs create mode 100644 TelpoPush.Fence.Worker/Models/PushTemplates/PushXiaoAnPushTemplate.cs create mode 100644 TelpoPush.Fence.Worker/Program.cs create mode 100644 TelpoPush.Fence.Worker/Properties/launchSettings.json create mode 100644 TelpoPush.Fence.Worker/Service/Cache/MemoryCacheUtil.cs create mode 100644 TelpoPush.Fence.Worker/Service/Cache/RedisUtil.cs create mode 100644 TelpoPush.Fence.Worker/Service/Cache/SqlMapper.cs create mode 100644 TelpoPush.Fence.Worker/Service/Mq/IKafkaService.cs create mode 100644 TelpoPush.Fence.Worker/Service/Mq/KafkaHeader.cs create mode 100644 TelpoPush.Fence.Worker/Service/Mq/KafkaService.cs create mode 100644 TelpoPush.Fence.Worker/Service/Mq/MessageProducer.cs create mode 100644 TelpoPush.Fence.Worker/Service/Mq/MqProcessMessage.cs create mode 100644 TelpoPush.Fence.Worker/Service/Mq/TopicModel.cs create mode 100644 TelpoPush.Fence.Worker/TelpoPush.Fence.Worker.csproj create mode 100644 TelpoPush.Fence.Worker/Worker.cs create mode 100644 TelpoPush.Fence.Worker/appsettings.Development.json create mode 100644 TelpoPush.Fence.Worker/appsettings.json create mode 100644 TelpoPush.Fence.Worker/appsettings.production.json create mode 100644 TelpoPush.Fence.Worker/appsettings.test.json create mode 100644 TelpoPushFence.sln create mode 100644 nuget.config create mode 100644 setup_production.sh create mode 100644 setup_test.sh create mode 100644 telpo_push_fence_run.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce6fdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,340 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/Common/HttpHelperAsync.cs b/TelpoPush.Fence.Worker/Common/HttpHelperAsync.cs new file mode 100644 index 0000000..9032a67 --- /dev/null +++ b/TelpoPush.Fence.Worker/Common/HttpHelperAsync.cs @@ -0,0 +1,460 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Common +{ + /// + /// HTTP帮助类 + /// + public class HttpHelperAsync + { + private IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + public HttpHelperAsync(IHttpClientFactory httpClientFactory, ILogger logger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + } + + + #region 异步 + /// + /// 发起POST异步请求表单 + /// + /// 请求地址 + /// POST提交的内容 + /// POST内容的媒体类型,如:application/xml、application/json + /// HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回string + public async Task PostFormAsync(string url, MultipartFormDataContent content, + Dictionary headers = null, + int timeOut = 5) + { + try + { + var hostName = GetHostName(url); + using (HttpClient client = _httpClientFactory.CreateClient(hostName)) + { + client.Timeout = TimeSpan.FromSeconds(timeOut); + if (headers?.Count > 0) + { + foreach (string key in headers.Keys) + { + client.DefaultRequestHeaders.Add(key, headers[key]); + } + } + content.Headers.Add("ContentType", "multipart/form-data");//声明头部 + using (HttpResponseMessage response = await client.PostAsync(url, content)) + { + return JsonConvert.SerializeObject(new { response.IsSuccessStatusCode, response.StatusCode }); + //if (response.IsSuccessStatusCode) + //{ + // string responseString = await response.Content.ReadAsStringAsync(); + // return responseString; + //} + //else + //{ + // return string.Empty; + //} + } + } + } + catch (Exception ex) + { + return $"推送完成:请求响应超过{timeOut}秒,异常 {ex.Message}"; + } + } + + + + /// + /// 发起GET异步请求 + /// + /// 返回类型 + /// 请求地址 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回string + public async Task GetAsync(string url, Dictionary headers = null, int timeOut = 30) + { + var hostName = GetHostName(url); + using (HttpClient client = _httpClientFactory.CreateClient(hostName)) + { + client.Timeout = TimeSpan.FromSeconds(timeOut); + if (headers?.Count > 0) + { + foreach (string key in headers.Keys) + { + client.DefaultRequestHeaders.Add(key, headers[key]); + } + } + using (HttpResponseMessage response = await client.GetAsync(url)) + { + if (response.IsSuccessStatusCode) + { + string responseString = await response.Content.ReadAsStringAsync(); + return responseString; + } + else + { + return string.Empty; + } + } + } + } + + + /// + /// 发起POST异步请求 + /// + /// 请求地址 + /// POST提交的内容 + /// POST内容的媒体类型,如:application/xml、application/json + /// HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回string + public async Task PostAsync(string url, string body, + Dictionary headers = null, + int timeOut = 30, + string bodyMediaType = "application/json", + string responseContentType = "application/json;charset=utf-8") + { + + //content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + //var clientHandler = new HttpClientHandler + //{ + // ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true + //}; + //using (var client = new HttpClient(clientHandler)) + //{ + // client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + try + { + var hostName = GetHostName(url); + using (HttpClient client = _httpClientFactory.CreateClient(hostName)) + { + client.Timeout = TimeSpan.FromSeconds(timeOut); + if (headers?.Count > 0) + { + foreach (string key in headers.Keys) + { + client.DefaultRequestHeaders.Add(key, headers[key]); + } + } + StringContent content = new StringContent(body, System.Text.Encoding.UTF8, mediaType: bodyMediaType); + if (!string.IsNullOrWhiteSpace(responseContentType)) + { + content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(responseContentType); + } + using (HttpResponseMessage response = await client.PostAsync(url, content)) + { + if (response.IsSuccessStatusCode) + { + string responseString = await response.Content.ReadAsStringAsync(); + return responseString; + } + else + return $"请求异常:{response.IsSuccessStatusCode},{response.StatusCode}"; + } + } + } + catch(Exception ex) + { + return $"请求异常:{ex.Message}"; + } + } + + /// + /// 发起POST异步请求 + /// + /// 请求地址 + /// POST提交的内容 + /// POST内容的媒体类型,如:application/xml、application/json + /// HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回string + public async Task PutAsync(string url, string body, + string bodyMediaType = null, + string responseContentType = null, + Dictionary headers = null, + int timeOut = 30) + { + var hostName = GetHostName(url); + using (HttpClient client = _httpClientFactory.CreateClient(hostName)) + { + client.Timeout = TimeSpan.FromSeconds(timeOut); + if (headers?.Count > 0) + { + foreach (string key in headers.Keys) + { + client.DefaultRequestHeaders.Add(key, headers[key]); + } + } + StringContent content = new StringContent(body, System.Text.Encoding.UTF8, mediaType: bodyMediaType); + if (!string.IsNullOrWhiteSpace(responseContentType)) + { + content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(responseContentType); + } + using (HttpResponseMessage response = await client.PutAsync(url, content)) + { + if (response.IsSuccessStatusCode) + { + string responseString = await response.Content.ReadAsStringAsync(); + return responseString; + } + else + { + return string.Empty; + } + } + } + } + + /// + /// 发起GET异步请求 + /// + /// 返回类型 + /// 请求地址 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回string + public async Task DeleteAsync(string url, Dictionary headers = null, int timeOut = 30) + { + var hostName = GetHostName(url); + using (HttpClient client = _httpClientFactory.CreateClient(hostName)) + { + client.Timeout = TimeSpan.FromSeconds(timeOut); + if (headers?.Count > 0) + { + foreach (string key in headers.Keys) + { + client.DefaultRequestHeaders.Add(key, headers[key]); + } + } + using (HttpResponseMessage response = await client.DeleteAsync(url)) + { + if (response.IsSuccessStatusCode) + { + string responseString = await response.Content.ReadAsStringAsync(); + return responseString; + } + else + { + return string.Empty; + } + } + } + } + + /// + /// 发起GET异步请求 + /// + /// 返回类型 + /// 请求地址 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回T + public async Task GetAsync(string url, Dictionary headers = null, int timeOut = 30) where T : new() + { + string responseString = await GetAsync(url, headers, timeOut); + if (!string.IsNullOrWhiteSpace(responseString)) + { + return JsonConvert.DeserializeObject(responseString); + } + else + { + return default(T); + } + } + + + /// + /// 发起POST异步请求 + /// + /// 返回类型 + /// 请求地址 + /// POST提交的内容 + /// POST内容的媒体类型,如:application/xml、application/json + /// HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回T + public async Task PostAsync(string url, string body, + Dictionary headers = null, + int timeOut = 30, + string bodyMediaType = "application/json", + string responseContentType = "application/json;charset=utf-8" + ) where T : new() + { + string responseString = await PostAsync(url, body, headers, timeOut, bodyMediaType, responseContentType); + if (!string.IsNullOrWhiteSpace(responseString)) + { + return JsonConvert.DeserializeObject(responseString); + } + else + { + return default(T); + } + } + + /// + /// 发起PUT异步请求 + /// + /// 返回类型 + /// 请求地址 + /// POST提交的内容 + /// POST内容的媒体类型,如:application/xml、application/json + /// HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回T + public async Task PutAsync(string url, string body, + string bodyMediaType = null, + string responseContentType = null, + Dictionary headers = null, + int timeOut = 30) where T : new() + { + string responseString = await PutAsync(url, body, bodyMediaType, responseContentType, headers, timeOut); + if (!string.IsNullOrWhiteSpace(responseString)) + { + return JsonConvert.DeserializeObject(responseString); + } + else + { + return default(T); + } + } + + /// + /// 发起DELETE异步请求 + /// + /// 返回类型 + /// 请求地址 + /// 请求头信息 + /// 请求超时时间,单位秒 + /// 返回T + public async Task DeleteAsync(string url, Dictionary headers = null, int timeOut = 30) where T : new() + { + string responseString = await DeleteAsync(url, headers, timeOut); + if (!string.IsNullOrWhiteSpace(responseString)) + { + return JsonConvert.DeserializeObject(responseString); + } + else + { + return default(T); + } + } + #region 私有函数 + + /// + /// 获取请求的主机名 + /// + /// + /// + private static string GetHostName(string url) + { + if (!string.IsNullOrWhiteSpace(url)) + { + return url.Replace("https://", "").Replace("http://", "").Split('/')[0]; + } + else + { + return "AnyHost"; + } + } + + #endregion + + + #endregion + + #region 同步 + + /// + /// 发起GET同步请求 + /// + /// + /// + /// + /// + /// + + + public string HttpGet(string url, Dictionary headers = null, string contentType = "application/json;charset=utf-8") + { + if (string.IsNullOrEmpty(url)) return ""; + try + { + using (HttpClient client = new HttpClient()) + { + if (contentType != null) + client.DefaultRequestHeaders.Add("ContentType", contentType); + if (headers != null) + { + foreach (var header in headers) + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + HttpResponseMessage response = client.GetAsync(url).Result; + return response.Content.ReadAsStringAsync().Result; + } + } + catch (Exception ex) + { + _logger.LogDebug($"HttpGet/URL:{url},headers:{JsonConvert.SerializeObject(headers)},异常:{ex.Message}|{ex.Source}|{ex.StackTrace}"); + } + return ""; + } + /// + /// 发起POST同步请求 + /// + /// + /// + /// application/xml、application/json、application/text、application/x-www-form-urlencoded + /// 填充消息头 + /// + public string HttpPost(string url, string postData = null, Dictionary headers = null, string contentType = "application/json") + { + if (string.IsNullOrEmpty(url)) return ""; + try + { + postData = postData ?? ""; + using (HttpClient client = new HttpClient()) + { + if (headers != null) + { + foreach (var header in headers) + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + using (HttpContent httpContent = new StringContent(postData, Encoding.UTF8)) + { + if (contentType != null) + httpContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); + + HttpResponseMessage response = client.PostAsync(url, httpContent).Result; + return response.Content.ReadAsStringAsync().Result; + } + } + } + catch (Exception ex){ + _logger.LogDebug($"HttpPost/URL:{url},postStr:{postData},headers:{JsonConvert.SerializeObject(headers)},异常:{ex.Message}|{ex.Source}|{ex.StackTrace}"); + } + return ""; + } + + + #endregion + } +} \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/Common/LimitedConcurrencyLevelTaskScheduler.cs b/TelpoPush.Fence.Worker/Common/LimitedConcurrencyLevelTaskScheduler.cs new file mode 100644 index 0000000..2a1b1e7 --- /dev/null +++ b/TelpoPush.Fence.Worker/Common/LimitedConcurrencyLevelTaskScheduler.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Common +{ + /// + /// Provides a task scheduler that ensures a maximum concurrency level while + /// running on top of the ThreadPool. + /// + public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler + { + /// Whether the current thread is processing work items. + [ThreadStatic] + private static bool _currentThreadIsProcessingItems; + /// The list of tasks to be executed. + private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) + /// The maximum concurrency level allowed by this scheduler. + private readonly int _maxDegreeOfParallelism; + /// Whether the scheduler is currently processing work items. + private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) + + /// + /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the + /// specified degree of parallelism. + /// + /// The maximum degree of parallelism provided by this scheduler. + public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) + { + if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); + _maxDegreeOfParallelism = maxDegreeOfParallelism; + } + + /// Queues a task to the scheduler. + /// The task to be queued. + protected sealed override void QueueTask(Task task) + { + // Add the task to the list of tasks to be processed. If there aren't enough + // delegates currently queued or running to process tasks, schedule another. + lock (_tasks) + { + _tasks.AddLast(task); + if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) + { + ++_delegatesQueuedOrRunning; + NotifyThreadPoolOfPendingWork(); + } + } + } + + /// + /// Informs the ThreadPool that there's work to be executed for this scheduler. + /// + private void NotifyThreadPoolOfPendingWork() + { + ThreadPool.UnsafeQueueUserWorkItem(_ => + { + // Note that the current thread is now processing work items. + // This is necessary to enable inlining of tasks into this thread. + _currentThreadIsProcessingItems = true; + try + { + // Process all available items in the queue. + while (true) + { + Task item; + lock (_tasks) + { + // When there are no more items to be processed, + // note that we're done processing, and get out. + if (_tasks.Count == 0) + { + --_delegatesQueuedOrRunning; + break; + } + + // Get the next item from the queue + item = _tasks.First.Value; + _tasks.RemoveFirst(); + } + + // Execute the task we pulled out of the queue + base.TryExecuteTask(item); + } + } + // We're done processing items on the current thread + finally { _currentThreadIsProcessingItems = false; } + }, null); + } + + /// Attempts to execute the specified task on the current thread. + /// The task to be executed. + /// + /// Whether the task could be executed on the current thread. + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If this thread isn't already processing a task, we don't support inlining + if (!_currentThreadIsProcessingItems) return false; + + // If the task was previously queued, remove it from the queue + if (taskWasPreviouslyQueued) TryDequeue(task); + + // Try to run the task. + return base.TryExecuteTask(task); + } + + /// Attempts to remove a previously scheduled task from the scheduler. + /// The task to be removed. + /// Whether the task could be found and removed. + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) return _tasks.Remove(task); + } + + /// Gets the maximum concurrency level supported by this scheduler. + public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } + + /// Gets an enumerable of the tasks currently scheduled on this scheduler. + /// An enumerable of the tasks currently scheduled. + protected sealed override IEnumerable GetScheduledTasks() + { + bool lockTaken = false; + try + { + Monitor.TryEnter(_tasks, ref lockTaken); + if (lockTaken) return _tasks.ToArray(); + else throw new NotSupportedException(); + } + finally + { + if (lockTaken) Monitor.Exit(_tasks); + } + } + } +} diff --git a/TelpoPush.Fence.Worker/Common/TimeHelper.cs b/TelpoPush.Fence.Worker/Common/TimeHelper.cs new file mode 100644 index 0000000..a83c798 --- /dev/null +++ b/TelpoPush.Fence.Worker/Common/TimeHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Common +{ + public class TimeHelper + { + /// + /// 时间戳转成时间类型 + /// + /// + /// + public static DateTime ConvertToLocalDateTime(string timeStamp) + { + DateTime dtStart = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Utc, TimeZoneInfo.Local); + if (timeStamp.Length == 13) + { + long lTime = long.Parse(timeStamp + "0000"); + TimeSpan toNow = new TimeSpan(lTime); + return dtStart.Add(toNow); + } + return dtStart.AddSeconds(long.Parse(timeStamp)); + } + + public static string ToDateTimeStr(DateTime dt) + { + return dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + } +} diff --git a/TelpoPush.Fence.Worker/Common/Utils.cs b/TelpoPush.Fence.Worker/Common/Utils.cs new file mode 100644 index 0000000..9c3a599 --- /dev/null +++ b/TelpoPush.Fence.Worker/Common/Utils.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Security.Cryptography; + +namespace TelpoPush.Fence.Worker.Common +{ + public class Utils + { + public static MultipartFormDataContent GetMultipartFormDataContent(Dictionary dic, string appId, ref Dictionary outDic) + { + MultipartFormDataContent mfdc = new MultipartFormDataContent(); + StringBuilder sb = new StringBuilder(); + if (dic != null && dic.Count > 0) + { + var dicOrderBy = dic.OrderBy(z => z.Key); + foreach (KeyValuePair kv in dicOrderBy) + { + sb.Append($"{kv.Key}={kv.Value.ToString()}&"); + mfdc.Add(new StringContent(kv.Value.ToString()), kv.Key);//参数, 内容在前,参数名称在后 + } + } + string signStr = $"{sb.ToString().Trim('&')}{appId}"; + byte[] bytes = Encoding.UTF8.GetBytes(signStr); + byte[] hash = SHA256.Create().ComputeHash(bytes); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < hash.Length; i++) + { + builder.Append(hash[i].ToString("X2")); + } + string sign = builder.ToString().ToLower(); + dic.Add("sign", sign); + mfdc.Add(new StringContent(sign), "sign");//参数, 内容在前,参数名称在后 + outDic = dic; + return mfdc; + } + } +} diff --git a/TelpoPush.Fence.Worker/Dockerfile b/TelpoPush.Fence.Worker/Dockerfile new file mode 100644 index 0000000..2e58cd3 --- /dev/null +++ b/TelpoPush.Fence.Worker/Dockerfile @@ -0,0 +1,30 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build +WORKDIR /src +COPY ["TelpoPush.Fence.Worker/TelpoPush.Fence.Worker.csproj", "TelpoPush.Fence.Worker/"] + +COPY ["nuget.config","."] +RUN dotnet nuget remove source nuget.org +RUN dotnet nuget add source https://repo.huaweicloud.com/repository/nuget/v3/index.json -n huaweicloud_nuget + +RUN dotnet restore "TelpoPush.Fence.Worker/TelpoPush.Fence.Worker.csproj" +COPY . . +WORKDIR "/src/TelpoPush.Fence.Worker" + +FROM build AS publish +RUN dotnet publish "TelpoPush.Fence.Worker.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +#COPY pem /app/pem +ENV environment=Development +RUN apk add --no-cache tzdata +ENV TimeZone=Asia/Shanghai +ENV LANG C.UTF-8 +ENV ASPNETCORE_ENVIRONMENT_LOG=Development +RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone +ENTRYPOINT dotnet TelpoPush.Fence.Worker.dll --environment=$environment \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/Handlers/FenceProcess.cs b/TelpoPush.Fence.Worker/Handlers/FenceProcess.cs new file mode 100644 index 0000000..6730d9a --- /dev/null +++ b/TelpoPush.Fence.Worker/Handlers/FenceProcess.cs @@ -0,0 +1,537 @@ +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System.Text; +using TelpoDataService.Util.Clients; +using TelpoDataService.Util; +using TelpoDataService.Util.Entities.GpsCard; +using TelpoDataService.Util.Entities.GpsLocationHistory; +using TelpoDataService.Util.Models; +using TelpoDataService.Util.QueryObjects; +using TelpoPush.Fence.Worker.Common; +using TelpoPush.Fence.Worker.Models.CacheTemplates; +using TelpoPush.Fence.Worker.Models.Config; +using TelpoPush.Fence.Worker.Models.Enum; +using TelpoPush.Fence.Worker.Models.Fence; +using TelpoPush.Fence.Worker.Models.MqTemplates; +using TelpoPush.Fence.Worker.Models.PushTemplates; +using TelpoPush.Fence.Worker.Service.Cache; +using TelpoPush.Fence.Worker.Service.Mq; + +namespace TelpoPush.Fence.Worker.Handlers +{ + public class FenceProcess + { + private readonly static object _GeofenceStatusLock = new object(); + private readonly static object _GeofenceDataLock = new object(); + private readonly GpsCardAccessorClient _fenceStatusApiClient; + private readonly GpsLocationHistoryAccessorClient _alarmApiClient; + private readonly IHostEnvironment _env; + private readonly ILogger _logger; + private readonly HttpHelperAsync _httpHelper; + private readonly RedisUtil _redis; + private readonly MqProcessMessage _serviceMqProcess; + private readonly FenceConfig _positionConfig; + + public FenceProcess( + IHostEnvironment env, + ILogger logger, + HttpHelperAsync httpHelper, + RedisUtil redis, + GpsCardAccessorClient fenceStatusApiClient, + GpsLocationHistoryAccessorClient alarmApiClient, + MqProcessMessage serviceMqProcess, + IOptions positionConfig + ) + { + _env = env; + _logger = logger; + _httpHelper = httpHelper; + _redis = redis; + _positionConfig = positionConfig.Value; + _serviceMqProcess = serviceMqProcess; + _fenceStatusApiClient = fenceStatusApiClient; + _alarmApiClient = alarmApiClient; + + } + + public async Task SendFence(string message) + { + + _redis.ClearGpsDeviceFence(); + + BaseModel model = JsonConvert.DeserializeObject(message); + var loc = model?.data; + _logger.LogInformation($"设备{loc.imei},获取kafka数据并解析<{JsonConvert.SerializeObject(model)}>"); + if ((int)loc.locationType == 2) // 限制条件:locationType=2 LBS定位不推送 + { + _logger.LogInformation($"设备{loc.imei},定位类型为LBS:不做处理"); + return; + } + DateTime time = DateTime.Parse(model.time); //=LastUpdate + DateTime UtcDate = DateTime.Parse(loc.UtcDate); + bool isPush = await _redis.GetIsDateStatus(new DeviceTime + { + imei = loc.imei, + dt = time + }); + + isPush = true; + if (!isPush) + _logger.LogInformation($"数据未处理(历史数据):{loc.imei},{model.time}"); + + + var fenceObj = await _redis.GetGpsDeviceFence(loc.imei); + if (fenceObj == null) + _logger.LogInformation($"设备<{loc.imei}>还未设置电子围栏!"); + + int radius = loc.Radius; + GpsFencePoint location = new GpsFencePoint() + { + Longitude = Convert.ToDouble(loc.gaodeLongitude), + Latitude = Convert.ToDouble(loc.gaodeLatitude) + }; + var lpt = new GpsPoint(loc.originalLatitude, loc.originalLongitude); + bool isInside = false; //{false=外面,true=里面} + bool isInside_old = false; //{false=外面,true=里面} + try + { + foreach (var fence in fenceObj.fenceList) + { + if (!Convert.ToBoolean(fence.isActive)) //围栏开关 0是关 1是开 + { + _logger.LogInformation($"围栏<{fence.geofenceId}:{fence.geofenceName}>未生效!"); + continue; + } + //当前gaode坐标为中心点,精度(Radius)为半径画圆 + double _lat, _lng; + GeoConvert.G2Gps((double)loc.gaodeLatitude, (double)loc.gaodeLongitude, out _lat, out _lng); + var _cpt = new GpsPoint(Convert.ToDecimal(_lat), Convert.ToDecimal(_lng)); + var _circle = new Circle(_cpt, loc.Radius); + switch (fence.fenceType) // 1 为圆形,2 多边形 + { + case 1: // 圆形 + { + foreach (var item in fence.pointList) + { + //圆形围栏坐标+半径 + double lat, lng; + GeoConvert.G2Gps((double)item.latitude, (double)item.longitude, out lat, out lng); + var cpt = new GpsPoint(Convert.ToDecimal(lat), Convert.ToDecimal(lng)); + var circle = new Circle(cpt, item.radius); + //是否在围栏范围内 + isInside = GeoUtils.IsPointInCircle(lpt, circle); + isInside_old = isInside; + if (!isInside) + { + isInside_old = isInside; + //圆和圆是否有交点 + isInside = GeoUtils.IsCircle2Circle(_circle, circle); + } + } + } + break; + case 2: //多边形 + List pointList = new List(); + foreach (var item in fence.pointList) + { + pointList.Add(new GpsFencePoint + { + Longitude = (double)item.longitude, + Latitude = (double)item.latitude + }); + } + isInside = GeofenceHelper.IsPolygonContainsPoint(pointList, new GpsFencePoint { Longitude = (double)loc.gaodeLongitude, Latitude = (double)loc.gaodeLatitude }); + isInside_old = isInside; + if (!isInside) + { + //圆和多边形围栏否有交点 + var degree = GeofenceHelper.CalcDisgreeFromRadius(radius); + isInside = CirclePolygonAlgorithm.IsIntersect(location, degree, pointList); + isInside = GeofenceHelper.IsPolygonIntersectCircle(pointList, location, radius); + } + break; + default: + break; + } + + //围栏进处理逻辑---------------------- + // [上次(历史缓存)状态] + //lastStatus.isInside => false:在里面、false:在外面; + //[当前状态] + //sInside => false:外面、true:里面 + //============================================= + //进入围栏:isInside = true + // status = true + //离开围栏:isInside = false + // status = false + //进入围栏:isInside = true + // status(没有历史记录) + + + var lastStatus = await GetLastGeofenceStatus(loc.imei, fence.geofenceId, fence.geofenceName); + if (lastStatus != null) + { + if (isInside == lastStatus.isInside) + await DataSaveAsync(model, fence, isInside, isInside_old, lastStatus.isInside, true).ConfigureAwait(false); + } + else + { + if (isInside) + await DataSaveAsync(model, fence, isInside, true, true, false).ConfigureAwait(false); + } + } + } + catch (Exception ex) + { + _logger.LogError($"设备<{loc.imei}>进出围栏报警处理逻辑[异常] {ex.Message}\n{ex.StackTrace}\n{ex.HResult}"); + } + } + + + /// + /// 进出记录处理 + /// + /// 当前定位数据 + /// 当前围栏信息 + /// 当前围栏状态[面到面]:(sInside => false:外面、true:里面) + /// 当前围栏状态[点到面]:(isInside_old => false:外面、true:里面) + /// 最近(缓存)围栏状态[数据\Redis]:(false:在里面、false:在外面) + /// 是否第一次触发围栏状态(进入) + /// + public async Task DataSaveAsync(BaseModel model, DeviceFenceList fence, bool isInside, bool isInside_old, bool status, bool isRecord) + { + lock (_GeofenceDataLock) + { + var loc = model.data; + string key = loc.imei + "_" + fence.geofenceId; + DateTime UtcDate = DateTime.Parse(loc.UtcDate); + bool? saveStatus = null; + bool isExt = false; // 是否保存警告数据,并推送进/出围栏报警 + int isReuslt = 0; //最终进出结果(3=进,4=出) + string deviceName = "";//设备昵称 + string alarmTypeTilte = ""; //进出描述 + string alarmTypeTilteLog = ""; //日志描述 + string fenceTypeStr = fence.fenceType == 1 ? "圆形围栏" : "多边形围栏"; //围栏类型描述 + _logger.LogInformation($"{fenceTypeStr}:{fence.geofenceName},Id:{fence.geofenceId},当前状态:({isInside}){(isInside ? "在里面" : "在外面")},上次状态:({status}){(status ? "在外面" : "在里面")}"); + //进出逻辑判断 + #region 进围栏报警 + if (isInside && status) // 当前在里面,上次记录为在外面,则触发进围栏报警 + { + isReuslt = Convert.ToInt32(AlarmType.Entry); + alarmTypeTilte = Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Entry); + saveStatus = false; + #region 保存警告数据,并推送 进围栏报警 + //进围栏是否报警条件 + if (fence.alarmType == Convert.ToUInt32(GeofenceType.Entry) || fence.alarmType == Convert.ToUInt32(GeofenceType.Both)) + isExt = true; + #endregion + } + #endregion + #region 出围栏报警 + else if (!isInside && !status) // 当前在外面,上次记录为在里面,则触发出围栏报警 + { + isReuslt = Convert.ToInt32(AlarmType.Exit); + alarmTypeTilte = Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Exit); + saveStatus = true; + #region 保存警告数据,并推送 出围栏报警 + //出围栏是否报警条件 + if (fence.alarmType == Convert.ToUInt32(GeofenceType.Exit) || fence.alarmType == Convert.ToUInt32(GeofenceType.Both)) + isExt = true; + #endregion + } + #endregion + //进出数据处理 + if (isReuslt > 0) + { + #region 保存状态,更新缓存 + //更新最后状态到数据库 + var dfstatus = new GpsGeofenceStatus + { + IsGeofenceStatus = saveStatus.Value, + Serialno = loc.imei, + GeofenceId = fence.geofenceId, + }; + if (!isRecord) + { + dfstatus.Id = Guid.NewGuid().ToString(); + alarmTypeTilteLog = "第一次" + alarmTypeTilte; + } + else + alarmTypeTilteLog = alarmTypeTilte; + var saveGeofenceStatus = SaveGeofenceStatus(dfstatus); + if (!saveGeofenceStatus.result) + _logger.LogError(saveGeofenceStatus.message); + else + //更新缓存 + _redis.SetFenceLastStatus(key, new LastStatusInfo() + { + imei = loc.imei, + fenceId = fence.geofenceId, + fenceInfo = fence.geofenceName, + typeId = isReuslt, + typeInfo = Enum.GetName(typeof(AlarmTypeChinese), isReuslt), + isInside = saveStatus.Value, + address = loc.address, + dt = model.time + }).Wait(); + #endregion + if (isExt) + { + #region 保存警告数据,并推送 进/出围栏报警 + string GeofenceResult = ""; + StringBuilder sb_old = new StringBuilder(); + StringBuilder sb = new StringBuilder(); + string url = "https://id.ssjlai.com/antpayweb/#/gaode-point-in-ring?"; + if (fence.fenceType == 1) + { + foreach (var item in fence.pointList) + { + sb.Append($"新算法【出围栏({isInside})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},{loc.Radius}&polygon={item.longitude},{item.latitude},{fence.geofenceId},{item.radius}"); + sb_old.Append($"旧算法【出围栏({isInside_old})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},0&polygon={item.longitude},{item.latitude},{fence.geofenceId},{item.radius}"); + } + } + else + { + string pointListStr = ""; + foreach (var item in fence.pointList) + { + pointListStr += $"{item.longitude}-{item.latitude},"; + } + sb.Append($"新算法【出围栏({isInside})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},{loc.Radius}&polygon={pointListStr.Trim(',')},{fence.geofenceId},0"); + sb_old.Append($"旧算法【出围栏({isInside_old})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},0&polygon={pointListStr.Trim(',')},{fence.geofenceId},0"); + } + GeofenceResult = sb.ToString() + ";" + sb_old.ToString(); + //设备昵称 + var deviceInfo = _redis.GetGpsDevice(loc.imei).Result; + if (deviceInfo != null) + deviceName = deviceInfo.deviceName; + //警告保存到数据库 + var alarm = new HisGpsAlarm + { + DeviceId = loc.DeviceId, + BaiduLat = loc.baiduLatitude, + BaiduLng = loc.baiduLongitude, + CreateTime = DateTime.Now, + DeviceUtcTime = UtcDate, + GeofenceId = fence.geofenceId, + Glat = loc.gaodeLatitude, + Glng = loc.gaodeLongitude, + MessageId = Guid.NewGuid().ToString("D"), + Olat = loc.originalLatitude, + Olng = loc.originalLongitude, + Serialno = loc.imei, + DeviceName = deviceName, + TypeId = isReuslt, + Address = loc.address, + Remarks = alarmTypeTilte + " " + fence.geofenceName, + GeofenceResult = GeofenceResult + }; + _alarmApiClient.Add(alarm); + + #region 微信推送(启用) + PushWxTemplate wxModel = new PushWxTemplate + { + deviceId = loc.DeviceId, + imei = loc.imei, + alarmTypeId = alarm.TypeId, + alarmDeviceName = alarm.DeviceName, + alarmRemarks = alarmTypeTilte + " " + fence.geofenceName, + address = loc.address, + }; + _serviceMqProcess.ProcessWxAlarm(wxModel, fence.geofenceId, model.time).Wait(); + #endregion + #region 第三方推送(停用) + // 进围栏报警 + PushXiaoAnPushTemplate statusModel = new PushXiaoAnPushTemplate + { + Address = fence.geofenceName, + DateTime = model.time, + NickName = loc.imei, + Title = alarmTypeTilte + " 报警", + TextContent = alarmTypeTilte + " " + fence.geofenceName, + }; + //PushStatusXiaoAn(loc.imei, statusModel); + //_serviceMqProcess.ProcessThirdAlarm(statusModel, MqAlarmType.EnterFenceAlarm, model.time); + // _serviceMqProcess.ProcessThirdAlarm(alarm, MqAlarmType.EnterFenceAlarm, model.time); + #endregion + #region 数据服务(java)部分推送 + if (fence.appType == 2) + { + var push = new + { + BaiduLatitude = loc.baiduLatitude, + BaiduLongitude = loc.baiduLongitude, + GaodeLatitude = loc.gaodeLatitude, + GaodeLongitude = loc.gaodeLongitude, + OriginalLatitude = loc.originalLatitude, + OriginalLongitude = loc.originalLongitude, + FenceId = fence.geofenceId, + FenceName = fence.geofenceName, + Address = loc.address, + Info = alarmTypeTilte + " " + fence.geofenceName, + }; + + var commonInfo = new + { + Data = push, + Imei = loc.imei, + Time = model.time, + AlarmType = isReuslt + }; + _serviceMqProcess.ProcessPushService(commonInfo, loc.imei).Wait(); + } + #endregion + _logger.LogError($"设备{loc.imei}{alarmTypeTilteLog},loc.UtcDate: {UtcDate.ToString("yyyy-MM-dd HH:mm:ss")},loc.LastUpdate: {model.time},DateTime.Now: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"); + #endregion + } + } + } + } + + //获取最近一次围栏进出状态 + public async Task GetLastGeofenceStatus(string imei, string fenceId, string fenceName) + { + LastStatusInfo model = null; + string key = imei + "_" + fenceId; + var lastStatus = await _redis.GetFenceLastStatus(key); + if (lastStatus != null) + return lastStatus; + else + { + // 数据服务调用 + var param = new GeneralParam + { + Filters = new List + { + new QueryFilterCondition + { + Key=nameof(GpsGeofenceStatus.GeofenceId), + Value=fenceId, + ValueType=QueryValueTypeEnum.String, + Operator=QueryOperatorEnum.Equal + } + } + }; + var gfs = _fenceStatusApiClient.GetFirst(param); + if (gfs != null) + { + model = new LastStatusInfo() + { + imei = gfs.Serialno, + fenceId = gfs.GeofenceId, + fenceInfo = fenceName, + typeId = gfs.IsGeofenceStatus ? Convert.ToInt32(AlarmType.Exit) : Convert.ToInt32(AlarmType.Entry), + typeInfo = gfs.IsGeofenceStatus ? Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Exit) : Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Entry), + isInside = gfs.IsGeofenceStatus, + address = "", + dt = gfs.CreateTime.Value.ToString("yyyy-MM-dd HH:mm:ss") + }; + await _redis.SetFenceLastStatus(key, model); + } + } + return model; + } + + //保存围栏状态 + public (bool result, string message) SaveGeofenceStatus(GpsGeofenceStatus gfs) + { + lock (_GeofenceStatusLock) + { + bool result = false; + string message = String.Empty; + try + { + var param = new GeneralParam + { + Filters = new List + { + new QueryFilterCondition + { + Key=nameof(GpsGeofenceStatus.GeofenceId), + Value=gfs.GeofenceId, + ValueType=QueryValueTypeEnum.String, + Operator=QueryOperatorEnum.Equal + } + } + }; + + List list = _fenceStatusApiClient.GetList(param).ToList(); + if (list.Count > 0) + { + if (list.Count == 1) + { + var gfStatus = list.FirstOrDefault(); + gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus; + gfStatus.CreateTime = DateTime.Now; + _fenceStatusApiClient.Update(gfStatus); + } + else + { + //只保留一条记录,多余的删除(历史遗留数据) + int loop = 0; + foreach (var item in list) + { + loop++; + if (loop == 1) + { + var gfStatus = list.FirstOrDefault(); + gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus; + gfStatus.CreateTime = DateTime.Now; + _fenceStatusApiClient.Update(gfStatus); + } + else + { + _fenceStatusApiClient.Delete(item); + } + } + } + } + else + { + gfs.CreateTime = DateTime.Now; + _fenceStatusApiClient.Add(gfs); + _logger.LogInformation($"增加围栏状态:<{JsonConvert.SerializeObject(gfs)}>"); + } + + #region 旧代码 + // GeofenceStatus gfStatus = client.GetFirst(param); + //if (gfStatus != null) + //{ + // gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus; + // gfStatus.CreateTime = DateTime.Now; + // client.Update(gfStatus); + + // //只保留一条记录,多余的删除(历史遗留数据) + // List list = client.GetList(param).ToList(); + // if (list.Count > 1) + // { + // foreach (var item in list) + // { + // if (gfStatus.Id != item.Id) + // { + // client.Delete(item); + // } + // } + // } + + // logger.LogInformation($"修改围栏状态:<{JsonConvert.SerializeObject(gfStatus)}>"); + //} + //else + //{ + // gfs.CreateTime = DateTime.Now; + // client.Add(gfs); + // logger.LogInformation($"增加围栏状态:<{JsonConvert.SerializeObject(gfs)}>"); + //} + #endregion + message = $"保存围栏状态发生成功!"; + result = true; + } + catch (Exception ex) + { + message = $"保存围栏状态发生异常:{ex.Message},{ex.StackTrace}!"; + } + return (result, message); + } + } + } +} diff --git a/TelpoPush.Fence.Worker/Handlers/KafkaSubscribe.cs b/TelpoPush.Fence.Worker/Handlers/KafkaSubscribe.cs new file mode 100644 index 0000000..d5edb27 --- /dev/null +++ b/TelpoPush.Fence.Worker/Handlers/KafkaSubscribe.cs @@ -0,0 +1,59 @@ +using Confluent.Kafka; +using TelpoPush.Fence.Worker.Common; +using TelpoPush.Fence.Worker.Service.Mq; + +namespace TelpoPush.Fence.Worker.Handlers +{ + public class KafkaSubscribe + { + private readonly ILogger _logger; + private readonly IHostEnvironment _env; + private readonly IKafkaService _kafkaService; + private readonly FenceProcess _feneProcess; + + + public KafkaSubscribe( + ILogger logger, IHostEnvironment env, + IKafkaService kafkaService, + FenceProcess feneProcess) + { + _logger = logger; + _env = env; + _kafkaService = kafkaService; + _feneProcess = feneProcess; + } + public async Task SubscribeAsync() + { +#if DEBUG + + _logger.LogInformation("11312"); + + + //string message = "{\"messageId\":\"1798637996003269632\",\"topic\":\"topic.push.position\",\"time\":\"2024-06-06 16:43:21\",\"data\":{\"deviceId\":\"f9f395b1-a6c3-4919-bf24-5ed963d79fad\",\"imei\":\"861281060086083\",\"wifiInfo\":\"88:86:03:df:a3:6c,-59,|88:86:03:e0:09:64,-63,|e8:84:c6:ef:cc:14,-68,|34:da:b7:69:d5:00,-68,|88:86:03:e0:09:4c,-68,|88:86:03:e0:0a:5c,-74,|88:86:03:df:b7:54,-83,|88:86:03:df:dc:18,-87,\",\"address\":\"通钦街道合作市第三小学\",\"baiduLatitude\":34.9966307394798,\"baiduLongitude\":102.918411172862,\"gaodeLatitude\":34.9908855355319,\"gaodeLongitude\":102.911835939226,\"originalLatitude\":34.9920144084504,\"originalLongitude\":102.910114012956,\"locationType\":3,\"lastUpdate\":\"2024-06-06 16:43:21\",\"utcDate\":\"2024-06-06 08:43:21\",\"radius\":50}}"; + //await _feneProcess.SendFence(message); + + //// await _kafkaService.SubscribeAsync(DoReceive, CancellationToken.None); + +#else + + LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(5); + TaskFactory factory = new TaskFactory(lcts); + try + { + await factory.StartNew(async () => + { + await _kafkaService.SubscribeAsync(DoReceive, CancellationToken.None); + }); + } + catch (Exception ex) + { + _logger.LogError($"Subscribe 处理Kafka数据发生异常 {ex.Message}|{ex.Source}|{ex.StackTrace}"); + } +#endif + } + async void DoReceive(string message) + { + await _feneProcess.SendFence(message); + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceFenceModel.cs b/TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceFenceModel.cs new file mode 100644 index 0000000..cb0c123 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceFenceModel.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.CacheTemplates +{ + public class DeviceFenceModel + { + public string imei { get; set; } + public string deviceId { get; set; } + public int count { get; set; } + public List fenceList { get; set; } + } + + + public class DeviceFenceList + { + public string geofenceId { get; set; } + public string geofenceName { get; set; } + public List WifiInfo { get; set; } + public int fenceType { get; set; } + public int alarmType { get; set; } + public string address { get; set; } + public List pointList { get; set; } + public bool isActive { get; set; } + public int appType { get; set; } + } + + public class DeviceFenceModels + { + public string imei { get; set; } + public string deviceId { get; set; } + public string geofenceId { get; set; } + public string geofenceName { get; set; } + public int fenceType { get; set; } + public int alarmType { get; set; } + public int radius { get; set; } + public decimal latitude { get; set; } + public decimal longitude { get; set; } + public string address { get; set; } + public bool isActive { get; set; } + public int appType { get; set; } + } + + public class pointList + { + public int index { get; set; } + public int radius { get; set; } + public decimal latitude { get; set; } + public decimal longitude { get; set; } + } + + + public class DeviceTime + { + public string imei { get; set; } + public DateTime dt { get; set; } + } + + public class LastStatusInfo + { + public string imei { get; set; } + public string fenceId { get; set; } + public string fenceInfo { get; set; } + public int typeId { get; set; } + public bool isInside { get; set; } + public string typeInfo { get; set; } + public string address { get; set; } + public string dt { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceInfoModel.cs b/TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceInfoModel.cs new file mode 100644 index 0000000..c90f1bd --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/CacheTemplates/DeviceInfoModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.CacheTemplates +{ + public class DeviceInfoModel + { + public string deviceId { get; set; } + public string imei { get; set; } + public string deviceName { get; set; } + public string orgId { get; set; } + public string apiUid { get; set; } + public string activeStatus { get; set; } + public string activeTime { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Config/FenceConfig.cs b/TelpoPush.Fence.Worker/Models/Config/FenceConfig.cs new file mode 100644 index 0000000..6b33098 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Config/FenceConfig.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Config +{ + public class FenceConfig + { + public string XiaoAnManufactorId { get; set; } + public string XiaoAnPushUrl { get; set; } + + } +} diff --git a/TelpoPush.Fence.Worker/Models/Config/RedisConfig.cs b/TelpoPush.Fence.Worker/Models/Config/RedisConfig.cs new file mode 100644 index 0000000..c38e1d7 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Config/RedisConfig.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Config +{ + /// + /// Redis配置模板类 + /// + public class RedisConfig + { + public string Server { get; set; } + /// + /// Redis server password + /// + public string Password { get; set; } + /// + /// Redis server database, default 0 + /// + public int? DefaultDatabase { get; set; } + /// + /// The asynchronous method automatically uses pipeline, and the 10W concurrent time is 450ms(welcome to feedback) + /// + public bool? AsyncPipeline { get; set; } + /// + /// Connection pool size, default 50 + /// + public int? Poolsize { get; set; } + /// + /// Idle time of elements in the connection pool(MS), suitable for connecting to remote redis server, default 20000 + /// + public int? IdleTimeout { get; set; } + /// + /// Connection timeout(MS), default 5000 + /// + public int? ConnectTimeout { get; set; } + /// + /// Send / receive timeout(MS), default 10000 + /// + public int? SyncTimeout { get; set; } + /// + /// Preheat connections, receive values such as preheat = 5 preheat 5 connections, default 5 + /// + public int? Preheat { get; set; } + /// + /// Follow system exit event to release automatically, default true + /// + public bool? AutoDispose { get; set; } + /// + /// Enable encrypted transmission, default false + /// + public bool? Ssl { get; set; } + /// + /// 是否尝试集群模式,阿里云、腾讯云集群需要设置此选项为 false, default true + /// + public bool? Testcluster { get; set; } + /// + /// Execution error, retry attempts, default 0 + /// + public int? Tryit { get; set; } + /// + /// Connection name, use client list command to view + /// + public string Name { get; set; } + /// + /// key前辍,所有方法都会附带此前辍,csredis.Set(prefix + "key", 111) + /// + public string Prefix { get; set; } + + public override string ToString() + { + if (string.IsNullOrWhiteSpace(Server)) throw new ArgumentNullException(nameof(Server)); + var sb = new StringBuilder(Server); + if (!string.IsNullOrWhiteSpace(Password)) sb.Append($",password={Password}"); + if (DefaultDatabase.HasValue) sb.Append($",defaultDatabase={DefaultDatabase}"); + if (AsyncPipeline.HasValue) sb.Append($",asyncPipeline={AsyncPipeline}"); + if (Poolsize.HasValue) sb.Append($",poolsize={Poolsize}"); + if (IdleTimeout.HasValue) sb.Append($",idleTimeout={IdleTimeout}"); + if (ConnectTimeout.HasValue) sb.Append($",connectTimeout={ConnectTimeout}"); + if (SyncTimeout.HasValue) sb.Append($",syncTimeout={0}"); + if (Preheat.HasValue) sb.Append($",preheat={Preheat}"); + if (AutoDispose.HasValue) sb.Append($",autoDispose={AutoDispose}"); + if (Ssl.HasValue) sb.Append($",ssl={Ssl}"); + if (Testcluster.HasValue) sb.Append($",testcluster={Testcluster}"); + if (Tryit.HasValue) sb.Append($",tryit={Tryit}"); + if (!string.IsNullOrWhiteSpace(Name)) sb.Append($",name={Name}"); + if (!string.IsNullOrWhiteSpace(Prefix)) sb.Append($",prefix={Prefix}"); + + return sb.ToString(); + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Config/ServiceConfig.cs b/TelpoPush.Fence.Worker/Models/Config/ServiceConfig.cs new file mode 100644 index 0000000..d923170 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Config/ServiceConfig.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Config +{ + public class ServiceConfig + { + /// + /// 数据服务Host Url + /// + public string TelpoDataUrl { get; set; } + + /// + /// Kafka服务地址 + /// + public string KafkaBootstrapServers { get; set; } + public List KafkaTopics { get; set; } + public string KafkaGroupId { get; set; } + public int MaxDegreeOfParallelism { get; set; } + public int CacheDurationSeconds { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/AlarmType.cs b/TelpoPush.Fence.Worker/Models/Enum/AlarmType.cs new file mode 100644 index 0000000..5dcd3e8 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/AlarmType.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Enum +{ + /// + /// 报警类型 + /// + public enum AlarmType + { + Online = 0, + Offline = 9, + SOS = 1,//求救 + LowBattery = 2,//电量 + Entry = 3, //进围栏 + Exit = 4, //出围栏 + PowerOff = 5//关机报警 + } + /// + /// + public enum AlarmTypeChinese + { + 上线 = 0, + 离线 = 9, + SOS求救 = 1,//求救 + 低电报警 = 2,//电量 + 进入围栏 = 3, //进围栏 + 离开围栏 = 4, //出围栏 + 关机报警 = 5//关机报警 + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/GeofenceType.cs b/TelpoPush.Fence.Worker/Models/Enum/GeofenceType.cs new file mode 100644 index 0000000..eab29a0 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/GeofenceType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Enum +{ + /// + /// 围栏类型 + /// + public enum GeofenceType + { + Entry = 1, + Both = 2, + Exit = 3 + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/HeadersDto.cs b/TelpoPush.Fence.Worker/Models/Enum/HeadersDto.cs new file mode 100644 index 0000000..b87ba8c --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/HeadersDto.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +namespace TelpoPush.Fence.Worker.Models.Enum +{ + /// + /// 消息数据头 + /// + public class HeadersDto + { + [JsonProperty(PropertyName = "DataType")] + public int? DataType { get; set; } + [JsonProperty(PropertyName = "AlarmType")] + public int? AlarmType { get; set; } + [JsonProperty(PropertyName = "OperType")] + public int? OperType { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/LocationType.cs b/TelpoPush.Fence.Worker/Models/Enum/LocationType.cs new file mode 100644 index 0000000..50b81fb --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/LocationType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Enum +{ + /// + /// 定位类型 + /// + public enum LocationType + { + GPS = 1, + LBS = 2, + WIFI = 3, + WifiPlus = 4 + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/MqDataTopic.cs b/TelpoPush.Fence.Worker/Models/Enum/MqDataTopic.cs new file mode 100644 index 0000000..d98f1ce --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/MqDataTopic.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Enum +{ + + public enum MqDataTopic : int + { + /// + /// 中高实时心率 + /// + ZkRealHRMonitorTopic = 1 + + + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/MqDataType.cs b/TelpoPush.Fence.Worker/Models/Enum/MqDataType.cs new file mode 100644 index 0000000..8309280 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/MqDataType.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Enum +{ + /// + /// 数据类型,标识发布到kafka的消息的数据类型 + /// + public enum MqDataType : int + { + /// + /// 报警消息 + /// + AlarmInfo = 0, + + /// + /// 温度数据信息 + /// + TemperatureInfo = 1, + + /// + /// 步数信息 + /// + StepInfo = 2, + + /// + /// 电量信息 + /// + BatteryLevelInfo = 3, + + /// + /// 设备配置信息 + /// + DeviceConfigInfo = 4, + + /// + /// 设备通话记录 + /// + DeviceCallLog = 5, + + /// + /// 设备短信记录 + /// + DeviceSmsLog = 6, + + /// + /// 位置信息 + /// + PositionInfo = 7, + + /// + /// 支付 + /// + PayInfo = 8, + + /// + /// 设备状态(offline,online) + /// + Status = 9, + + /// + /// 设备激活状态(激活1,未激活0) + /// + Active = 10, + + /// + /// 指令回调 + /// + reply = 11, + + /// + /// 天气查询 + /// + Weather = 12, + + /// + /// 短信阅读事件 + /// + ReadMsg = 13, + /// + /// 学习能力状态上报事件 + /// + StudyAINotifyStatusUpload = 14, + /// + /// 心率 + /// + HeartRateInfo = 15, + /// + /// 血氧 + /// + Spo2Info = 16, + /// + /// 周期性报体温数据。 + /// + Temperature1Info = 17, + /// + /// 周期心率。 + /// + HeartRate1Info = 18, + /// + /// 周期性血氧 + /// + Spo21Info = 19, + /// + /// 溺水状态 + /// + DrownReportInfo = 20, + /// + /// 手表佩戴状态 + /// + WearStatusInfo = 21, + /// + /// 血压 + /// + BloodPressInfo = 22, + /// + /// 周期性血压 + /// + BloodPress1Info = 23, + /// + /// 心理监测 + /// + PsychInfo = 24, + + /// + /// AI呼叫回调结果 + /// + AiCallResult = 25, + + /// + /// 越界上报(围栏进出告警) + /// + CrossBorder = 26, + + /// + /// 运动数据上报 + /// + SportResult = 27, + /// + /// 运动数据上报 + /// + BloodSugar = 28, + + /// + /// 中考实时心率数据上报 + /// + ZkRealHeartRate = 29, + /// + /// 绑定业务 + /// + BindDevice = 100 + } +} diff --git a/TelpoPush.Fence.Worker/Models/Enum/MqHeader.cs b/TelpoPush.Fence.Worker/Models/Enum/MqHeader.cs new file mode 100644 index 0000000..c24bdd0 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Enum/MqHeader.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.Enum +{ + public static class MqHeader + { + /// + /// DataType + /// + public const string DataType = "DataType"; + + /// + /// OperType + /// + public const string OperType = "OperType"; + + /// + /// AlarmType + /// + public const string AlarmTypes = "AlarmType"; + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/Algorithms/CirclePolygonAlgorithm.cs b/TelpoPush.Fence.Worker/Models/Fence/Algorithms/CirclePolygonAlgorithm.cs new file mode 100644 index 0000000..ddd6978 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/Algorithms/CirclePolygonAlgorithm.cs @@ -0,0 +1,94 @@ +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public static class CirclePolygonAlgorithm + { + /// + /// 判断圆和多边形是否相交 + /// + /// 圆心坐标 + /// 圆半径 + /// 多边形各个点坐标 + /// + public static bool IsIntersect(GpsFencePoint circle, double degree, IList polygon) + { + double powRadius = Math.Pow(degree, 2); + + //情况1:多边形任一顶点在圆内,则认为相交 + foreach (var pt in polygon) + { + if (Math.Pow(circle.Longitude - pt.Longitude, 2) + Math.Pow(circle.Latitude - pt.Latitude, 2) < powRadius) return true; + } + + //情况2:多边形顶点集的任一有效线段和圆相交(圆心到线段的距离小于半径) + var lines = EnumeratePolygonLines(polygon); + foreach (var seg in lines) + { + if (PointToLine(circle, seg.Item1, seg.Item2) < degree) return true; + } + + return false; + } + + private static IList> EnumeratePolygonLines(IList polygon) + { + var list = new List>(); + + for (int i = 0; i < polygon.Count - 1; i++) + { + list.Add(new Tuple(polygon[i], polygon[i + 1])); + } + list.Add(new Tuple(polygon[polygon.Count - 1], polygon[0])); + + return list; + } + + //计算两点之间的距离 + private static double LineSpace(GpsFencePoint ptStart, GpsFencePoint ptEnd) + { + if (ptStart == ptEnd) return 0; + return Math.Sqrt((ptEnd.Longitude - ptStart.Longitude) * (ptEnd.Longitude - ptStart.Longitude) + (ptEnd.Latitude - ptStart.Latitude) * (ptEnd.Latitude - ptStart.Latitude)); + } + + /// + /// 点到线段的距离 + /// + /// + /// 线段顶点1 + /// 线段顶点2 + /// + private static double PointToLine(GpsFencePoint pt, GpsFencePoint ptStart, GpsFencePoint ptEnd) + { + double distance = 0; + double a, b, c; + a = LineSpace(ptStart, ptEnd); //线段长度 + b = LineSpace(pt, ptStart); //点到线段顶点1的距离 + c = LineSpace(pt, ptEnd); //点到线段顶点2的距离 + if (b == 0 || c == 0) + { + distance = 0; + return distance; + } + if (a == 0) + { + distance = b; + return distance; + } + if (c * c >= a * a + b * b) + { + distance = b; + return distance; + } + if (b * b >= a * a + c * c) + { + distance = c; + return distance; + } + + double p = (a + b + c) / 2; //半周长 + double s = Math.Sqrt(p * (p - a) * (p - b) * (p - c)); // 海伦公式求面积 + distance = 2 * s / a;// 返回点到线的距离(利用三角形面积公式求高) + + return distance; + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/Algorithms/RayCastingAlgorithm.cs b/TelpoPush.Fence.Worker/Models/Fence/Algorithms/RayCastingAlgorithm.cs new file mode 100644 index 0000000..171561b --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/Algorithms/RayCastingAlgorithm.cs @@ -0,0 +1,36 @@ +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public static class RayCastingAlgorithm + { + public static bool IsWithin(GpsFencePoint pt, IList polygon, bool noneZeroMode) + { + int ptNum = polygon.Count(); + if (ptNum < 3) + { + return false; + } + int j = ptNum - 1; + bool oddNodes = false; + int zeroState = 0; + for (int k = 0; k < ptNum; k++) + { + var ptK = polygon[k]; + var ptJ = polygon[j]; + if (ptK.Latitude > pt.Latitude != ptJ.Latitude > pt.Latitude && pt.Longitude < (ptJ.Longitude - ptK.Longitude) * (pt.Latitude - ptK.Latitude) / (ptJ.Latitude - ptK.Latitude) + ptK.Longitude) + { + oddNodes = !oddNodes; + if (ptK.Latitude > ptJ.Latitude) + { + zeroState++; + } + else + { + zeroState--; + } + } + j = k; + } + return noneZeroMode ? zeroState != 0 : oddNodes; + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/ChinaPolygon.cs b/TelpoPush.Fence.Worker/Models/Fence/ChinaPolygon.cs new file mode 100644 index 0000000..1851ebd --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/ChinaPolygon.cs @@ -0,0 +1,81 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public class ChinaPolygon + { + private static readonly PolyGon China; + private const string ChinaStr = "{\"myPts\":[{\"X\":17.978733095556183,\"Y\":108.10546875},{\"X\":12.554563528593656,\"Y\":111.09374999999999},{\"X\":5.8783321096743268,\"Y\":110.390625},{\"X\":6.4026484059638964,\"Y\":115.13671875},{\"X\":11.178401873711785,\"Y\":117.59765625},{\"X\":15.284185114076445,\"Y\":118.828125},{\"X\":20.715015145512098,\"Y\":119.00390625},{\"X\":23.160563309048314,\"Y\":118.740234375},{\"X\":23.704894502324912,\"Y\":118.66333007812499},{\"X\":25.259600649162689,\"Y\":120.42663574218749},{\"X\":30.789036751261161,\"Y\":123.837890625},{\"X\":35.227672354935862,\"Y\":120.9814453125},{\"X\":37.142803443716836,\"Y\":123.53027343749999},{\"X\":39.639537564366684,\"Y\":124.1455078125},{\"X\":40.426042128264932,\"Y\":124.81018066406249},{\"X\":41.029643387166381,\"Y\":126.1285400390625},{\"X\":41.722130585125782,\"Y\":126.826171875},{\"X\":41.516803958101178,\"Y\":127.19970703125},{\"X\":41.335575973123895,\"Y\":127.76000976562501},{\"X\":41.53325414281322,\"Y\":128.441162109375},{\"X\":41.943148747326958,\"Y\":128.265380859375},{\"X\":41.975827261025728,\"Y\":129.0234375},{\"X\":42.366661663732742,\"Y\":129.781494140625},{\"X\":42.89206418807337,\"Y\":130.023193359375},{\"X\":42.455887641971657,\"Y\":130.4296875},{\"X\":42.89206418807337,\"Y\":131.2646484375},{\"X\":43.46886761482925,\"Y\":131.572265625},{\"X\":44.855868807357247,\"Y\":131.28662109375},{\"X\":45.120052841530544,\"Y\":131.9677734375},{\"X\":44.902577996288876,\"Y\":132.8466796875},{\"X\":45.073520606709707,\"Y\":133.30810546875},{\"X\":48.400032496106853,\"Y\":135.19775390625},{\"X\":48.560249791743288,\"Y\":134.89013671875},{\"X\":48.487486479884147,\"Y\":134.09912109375},{\"X\":47.872143968887308,\"Y\":131.46240234375},{\"X\":48.122101028190805,\"Y\":130.89111328125},{\"X\":48.603857608232524,\"Y\":130.9130859375},{\"X\":48.908059399650078,\"Y\":130.75927734375},{\"X\":49.023461463214126,\"Y\":130.58349609375},{\"X\":49.095452162534826,\"Y\":130.23193359375},{\"X\":49.653404588437894,\"Y\":129.5068359375},{\"X\":49.781264058178365,\"Y\":128.056640625},{\"X\":53.146770330850842,\"Y\":126.16699218749999},{\"X\":53.657661020298,\"Y\":123.79394531249999},{\"X\":53.514184520771131,\"Y\":120.89355468749999},{\"X\":52.776185688961711,\"Y\":119.81689453125},{\"X\":52.429222277955134,\"Y\":119.94873046875},{\"X\":52.482780222078226,\"Y\":120.52001953124999},{\"X\":49.710272582105695,\"Y\":117.79541015625001},{\"X\":50.233151832472245,\"Y\":116.16943359374999},{\"X\":48.034019158642863,\"Y\":115.26855468749999},{\"X\":47.502358951968574,\"Y\":115.8837890625},{\"X\":47.694974341862817,\"Y\":118.54248046874999},{\"X\":46.815098645992428,\"Y\":119.68505859375},{\"X\":46.98025235521883,\"Y\":118.63037109375},{\"X\":46.709735944071568,\"Y\":117.3779296875},{\"X\":46.468132992155539,\"Y\":116.5869140625},{\"X\":45.552525251340128,\"Y\":115.31249999999999},{\"X\":45.552525251340128,\"Y\":114.60937499999999},{\"X\":44.840290651397993,\"Y\":113.44482421875},{\"X\":45.243953422623242,\"Y\":111.99462890625},{\"X\":45.073520606709707,\"Y\":111.59912109375},{\"X\":44.308126684886126,\"Y\":111.07177734375},{\"X\":43.78695837311561,\"Y\":111.73095703125},{\"X\":42.8115217450979,\"Y\":109.9951171875},{\"X\":42.585444257384907,\"Y\":107.55615234375},{\"X\":41.787697005390633,\"Y\":104.9853515625},{\"X\":42.779275360241904,\"Y\":100.634765625},{\"X\":42.908160071960538,\"Y\":96.5478515625},{\"X\":44.370986962971728,\"Y\":95.51513671875},{\"X\":44.386691502152061,\"Y\":95.09765625},{\"X\":45.104546309768729,\"Y\":93.40576171875},{\"X\":45.305802599435779,\"Y\":91.20849609375},{\"X\":45.828799251921339,\"Y\":90.94482421875},{\"X\":46.815098645992428,\"Y\":91.16455078125},{\"X\":48.048709942886859,\"Y\":90.175781249999986},{\"X\":48.1367666796927,\"Y\":89.208984374999986},{\"X\":49.210420445650286,\"Y\":87.890625},{\"X\":49.23912083246698,\"Y\":86.7919921875},{\"X\":48.661942846070062,\"Y\":86.46240234375},{\"X\":48.472921272487824,\"Y\":85.62744140625},{\"X\":48.10743118848039,\"Y\":85.341796875},{\"X\":47.189712464484209,\"Y\":85.4296875},{\"X\":47.055154085503482,\"Y\":85.0341796875},{\"X\":47.428087261714253,\"Y\":82.96875},{\"X\":45.767522962149904,\"Y\":82.067871093749986},{\"X\":45.352145245851773,\"Y\":82.309570312499986},{\"X\":45.475540271585928,\"Y\":81.80419921875},{\"X\":44.980342380849727,\"Y\":79.73876953125},{\"X\":43.277205322120238,\"Y\":80.617675781249986},{\"X\":43.100982876188546,\"Y\":80.26611328125},{\"X\":42.924251753870685,\"Y\":80.31005859375},{\"X\":42.763145866894916,\"Y\":80.09033203125},{\"X\":42.163403424224008,\"Y\":80.04638671875},{\"X\":41.11246878918088,\"Y\":77.40966796875},{\"X\":41.1290213474951,\"Y\":76.79443359375},{\"X\":40.547200234410489,\"Y\":76.35498046875},{\"X\":40.480381429081717,\"Y\":75.893554687499986},{\"X\":40.780541431860307,\"Y\":75.5419921875},{\"X\":40.597270634420269,\"Y\":74.8388671875},{\"X\":40.128491056854081,\"Y\":73.8720703125},{\"X\":39.453161128073937,\"Y\":73.49853515625},{\"X\":38.496593518947556,\"Y\":73.6962890625},{\"X\":38.324420427006515,\"Y\":74.5751953125},{\"X\":37.370157184057533,\"Y\":74.8828125},{\"X\":37.247821201554281,\"Y\":74.267578125},{\"X\":35.514343134318182,\"Y\":76.44287109375},{\"X\":35.209721645221386,\"Y\":77.87109375},{\"X\":34.234512362369841,\"Y\":78.37646484375},{\"X\":32.639374873606691,\"Y\":79.12353515625},{\"X\":32.824211101613358,\"Y\":78.662109375},{\"X\":32.620870183181133,\"Y\":78.28857421875},{\"X\":31.184609135743251,\"Y\":78.59619140625},{\"X\":29.821582720575016,\"Y\":81.23291015625},{\"X\":30.12612436422458,\"Y\":81.73828125},{\"X\":28.091366281406945,\"Y\":85.2978515625},{\"X\":27.897349229684259,\"Y\":85.67138671875},{\"X\":27.741884632507087,\"Y\":88.57177734375},{\"X\":27.117812842321225,\"Y\":88.835449218749986},{\"X\":28.013801376380712,\"Y\":89.802246093749986},{\"X\":27.68352808378776,\"Y\":91.69189453125},{\"X\":27.761329874505233,\"Y\":92.74658203125},{\"X\":29.0945770775118,\"Y\":94.72412109375},{\"X\":29.171348850951507,\"Y\":96.04248046875},{\"X\":27.9361805667694,\"Y\":96.9873046875},{\"X\":28.052590823339859,\"Y\":97.97607421875},{\"X\":27.293689224852407,\"Y\":98.26171875},{\"X\":27.371767300523047,\"Y\":98.54736328125},{\"X\":25.839449402063185,\"Y\":98.28369140625},{\"X\":25.165173368663929,\"Y\":97.55859375},{\"X\":23.845649887659352,\"Y\":97.5146484375},{\"X\":23.845649887659352,\"Y\":98.54736328125},{\"X\":21.922663209325936,\"Y\":99.16259765625},{\"X\":21.207458730482653,\"Y\":100.8544921875},{\"X\":21.391704731036587,\"Y\":101.00830078125},{\"X\":21.043491216803556,\"Y\":101.7333984375},{\"X\":22.289096418723052,\"Y\":101.90917968749999},{\"X\":22.532853707527117,\"Y\":102.67822265625},{\"X\":22.816694126899844,\"Y\":105.77636718749999},{\"X\":22.61401087437029,\"Y\":106.50146484374999},{\"X\":21.6778482933475,\"Y\":106.85302734374999},{\"X\":21.371244370618321,\"Y\":108.10546875},{\"X\":20.694461597907797,\"Y\":108.43505859374999},{\"X\":17.978733095556183,\"Y\":108.10546875}]}"; + + static ChinaPolygon() + { + China = JsonConvert.DeserializeObject(ChinaStr); + } + + public static bool IsInChina(double lat, double lng) + { + return China.FindPoint(lat, lng); + } + /// + /// + /// + private class Point + { + public double X { get; set; } + public double Y { get; set; } + } + /// + /// + /// + private class PolyGon + { + public List myPts = new List(); + public PolyGon() + { + } + public PolyGon(List points) + { + foreach (Point p in points) + { + myPts.Add(p); + } + } + public void Add(Point p) + { + myPts.Add(p); + } + + private int Count() + { + return myPts.Count; + } + + + // The function will return true if the point x,y is inside the polygon, or + // false if it is not. If the point is exactly on the edge of the polygon, + // then the function may return true or false. + + public bool FindPoint(double X, double Y) + { + int sides = Count() - 1; + int j = sides - 1; + bool pointStatus = false; + for (int i = 0; i < sides; i++) + { + if (myPts[i].Y < Y && myPts[j].Y >= Y || myPts[j].Y < Y && myPts[i].Y >= Y) + { + if (myPts[i].X + (Y - myPts[i].Y) / (myPts[j].Y - myPts[i].Y) * (myPts[j].X - myPts[i].X) < X) + { + pointStatus = !pointStatus; + } + } + j = i; + } + return pointStatus; + } + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/Circle.cs b/TelpoPush.Fence.Worker/Models/Fence/Circle.cs new file mode 100644 index 0000000..f2592c8 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/Circle.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public class Circle + { + public GpsPoint Center { get; set; } + public decimal Radius { get; set; } + + public Circle(GpsPoint center, decimal radius) + { + Center = center; + Radius = radius; + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/GeoConvert.cs b/TelpoPush.Fence.Worker/Models/Fence/GeoConvert.cs new file mode 100644 index 0000000..2fa8dc2 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/GeoConvert.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public class GeoConvert + { + const double Pi = 3.14159265358979324; + const double A = 6378245.0; + const double Ee = 0.00669342162296594323; + const double XPi = 3.14159265358979324 * 3000.0 / 180.0; + // + /// + /// 地球坐标系 ===> 火星坐标系统 (gps === > google) + /// + /// + /// + /// + /// + public static void Transform(double wgLat, double wgLon, out double mgLat, out double mgLon) + { + if (OutOfChina(wgLat, wgLon)) + { + mgLat = wgLat; + mgLon = wgLon; + return; + } + var dLat = TransformLat(wgLon - 105.0, wgLat - 35.0); + var dLon = TransformLon(wgLon - 105.0, wgLat - 35.0); + var radLat = wgLat / 180.0 * Pi; + var magic = Math.Sin(radLat); + magic = 1 - Ee * magic * magic; + var sqrtMagic = Math.Sqrt(magic); + dLat = (dLat * 180.0) / ((A * (1 - Ee)) / (magic * sqrtMagic) * Pi); + dLon = (dLon * 180.0) / (A / sqrtMagic * Math.Cos(radLat) * Pi); + mgLat = wgLat + dLat; + mgLon = wgLon + dLon; + } + + ///// + ///// 算法纠偏,无缓存 + ///// 0:高德纬度 + ///// 1:高德经度 + ///// 2:百度纬度 + ///// 3:百度经度 + ///// + ///// + ///// + ///// + //public static decimal[] ConvertGoogleBaiduLatLng(decimal lat, decimal lng) + //{ + // //中国经度范围:73°33′E至135°05′E 纬度范围:3°51′N至53°33′N + // var LatLng = new decimal[] { lat, lng, lat, lng }; + // if (lat > 4 && lat < 54 && lng > 73 && lng < 136) + // { + // try + // { + // double gLat = 0, gLng = 0, bLat = 0, bLng = 0; + // Transform(SafeType.SafeDouble(lat), SafeType.SafeDouble(lng), out gLat, out gLng); + // G2B(gLat, gLng, out bLat, out bLng); + // LatLng[0] = Convert.ToDecimal(gLat); + // LatLng[1] = Convert.ToDecimal(gLng); + // LatLng[2] = Convert.ToDecimal(bLat); + // LatLng[3] = Convert.ToDecimal(bLng); + // } + // catch (Exception ex) + // { + // LogHelper.WriteLog($"ConvertGoogleBaiduLatLng[异常] {ex.Message}"); + // } + // } + // return LatLng; + //} + + + /// + /// google to gps + /// + /// + /// + /// + /// + public static void G2Gps(double lat, double lng, out double olat, out double olng) + { + if (OutOfChina(lat, lng)) + { + olat = lat; + olng = lng; + return; + } + + var dLat = TransformLat(lng - 105.0, lat - 35.0); + var dLng = TransformLon(lng - 105.0, lat - 35.0); + var radLat = lat / 180.0 * Math.PI; + var magic = Math.Sin(radLat); + magic = 1 - Ee * magic * magic; + var sqrtMagic = Math.Sqrt(magic); + dLat = (dLat * 180.0) / ((A * (1 - Ee)) / (magic * sqrtMagic) * Math.PI); + dLng = (dLng * 180.0) / (A / sqrtMagic * Math.Cos(radLat) * Math.PI); + olat = lat - dLat; + olng = lng - dLng; + } + + /// + /// 判断是否在中国 + /// + /// + /// + /// + static bool OutOfChina(double lat, double lon) + { + return !ChinaPolygon.IsInChina(lat, lon); + } + + static double TransformLat(double x, double y) + { + var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.Sqrt(Math.Abs(x)); + ret += (20.0 * Math.Sin(6.0 * x * Pi) + 20.0 * Math.Sin(2.0 * x * Pi)) * 2.0 / 3.0; + ret += (20.0 * Math.Sin(y * Pi) + 40.0 * Math.Sin(y / 3.0 * Pi)) * 2.0 / 3.0; + ret += (160.0 * Math.Sin(y / 12.0 * Pi) + 320 * Math.Sin(y * Pi / 30.0)) * 2.0 / 3.0; + return ret; + } + + static double TransformLon(double x, double y) + { + var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.Sqrt(Math.Abs(x)); + ret += (20.0 * Math.Sin(6.0 * x * Pi) + 20.0 * Math.Sin(2.0 * x * Pi)) * 2.0 / 3.0; + ret += (20.0 * Math.Sin(x * Pi) + 40.0 * Math.Sin(x / 3.0 * Pi)) * 2.0 / 3.0; + ret += (150.0 * Math.Sin(x / 12.0 * Pi) + 300.0 * Math.Sin(x / 30.0 * Pi)) * 2.0 / 3.0; + return ret; + } + + /// + /// Google to Baidu + /// + /// + /// + /// + /// + public static void G2B(double glat, double glng, out double blat, out double blng) + { + var x = glng; + var y = glat; + var z = Math.Sqrt(x * x + y * y) + 0.00002 * Math.Sin(y * XPi); + var theta = Math.Atan2(y, x) + 0.000003 * Math.Cos(x * XPi); + blng = z * Math.Cos(theta) + 0.0065; + blat = z * Math.Sin(theta) + 0.006; + } + + /// + /// Baidu to Google + /// + /// + /// + /// + /// + public static void B2G(double blat, double blng, out double glat, out double glng) + { + var x = blng - 0.0065; + var y = blat - 0.006; + var z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * XPi); + var theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * XPi); + glng = z * Math.Cos(theta); + glat = z * Math.Sin(theta); + } + /// + /// 百度转原始经纬度 + /// + /// + /// + /// + /// + public static void B2Gps(double blat, double blng, out double olat, out double olng) + { + if (OutOfChina(blat, blng)) + { + olat = blat; + olng = blng; + return; + } + double glat, glng; + B2G(blat, blng, out glat, out glng); + G2Gps(glat, glng, out olat, out olng); + } + /// + /// 原始转百度 + /// + /// + /// + /// + /// + public static void Gps2Bd(double olat, double olng, out double blat, out double blng) + { + double glat, glng; + Transform(olat, olng, out glat, out glng); + G2B(glat, glng, out blat, out blng); + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/GeoUtils.cs b/TelpoPush.Fence.Worker/Models/Fence/GeoUtils.cs new file mode 100644 index 0000000..a6680ca --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/GeoUtils.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Text; + + +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public class Bounds + { + public GpsPoint SW { get; set; } + public GpsPoint NE { get; set; } + + public Bounds(GpsPoint sw, GpsPoint ne) + { + SW = sw; + NE = ne; + } + + public GpsPoint GetCenter() + { + return new GpsPoint((SW.Lng + NE.Lng) / 2, (SW.Lat + NE.Lat) / 2); + } + } + public class GeoUtils + { + private const decimal A = 6370996.81m; + + private const double EARTH_RADIUS = 6378137;//赤道半径(单位m) + + /// + /// 点是否在矩形区域中 + /// + /// + /// + /// + public static bool IsPointInRect(GpsPoint pt, Bounds rect) + { + var e = rect.SW; + var h = rect.NE; + return (pt.Lng >= e.Lng && pt.Lng <= h.Lng && pt.Lat >= e.Lat && pt.Lat <= h.Lat); + } + /// + /// 点是否在圆形区域中 + /// + /// + /// + /// + public static bool IsPointInCircle(GpsPoint pt, Circle circle) + { + var i = circle.Center; + var g = circle.Radius; + var f = GetDistance(pt, i); + return f <= g; + } + + /// + /// 圆和圆是否有交点 + /// + /// + /// + /// + public static bool IsCircle2Circle(Circle _circle, Circle circle) + { + var _r = _circle.Radius; + var r = circle.Radius; + var f = GetDistance(_circle.Center, circle.Center); + //判断两圆是否相交 + if (f > _r + r) + { + return false; + } + return true; + } + + /// + /// 获取形状的矩形范围 + /// + /// + /// + public static Bounds GetBound(IList pts) + { + if (pts.Count == 0) + return new Bounds(new GpsPoint(0, 0), new GpsPoint(0, 0)); ; + decimal minLat = pts[0].Lat, minLng = pts[0].Lng, maxLat = pts[0].Lat, maxLng = pts[0].Lng; + foreach (var pt in pts) + { + minLat = Math.Min(minLat, pt.Lat); + minLng = Math.Min(minLng, pt.Lng); + maxLat = Math.Max(maxLat, pt.Lat); + maxLng = Math.Max(maxLng, pt.Lng); + } + + return new Bounds(new GpsPoint(minLng, minLat), new GpsPoint(maxLng, maxLat)); + } + + + + /// + /// 经纬度测距算法 + /// + /// + /// + /// + /// + /// 单位 米 + public static double GetDistance2(double lon1, double lat1, double lon2, double lat2) + { + double radLat1 = rad(lat1); + double radLat2 = rad(lat2); + double a = radLat1 - radLat2; + double b = rad(lon1) - rad(lon2); + double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin(b / 2), 2))); + s = s * EARTH_RADIUS; + return s; + } + + /// + /// 转化为弧度(rad) + /// + /// 经度或纬度 + /// 弧度 + private static double rad(double d) + { + return d * Math.PI / 180.0; + } + + + /// + /// 计算两点的距离 单位:米 + /// + /// + /// + /// + public static decimal GetDistance(GpsPoint j, GpsPoint h) + { + j.Lng = ValidLng(j.Lng, -180, 180); + j.Lat = ValidLat(j.Lat, -74, 74); + h.Lng = ValidLng(h.Lng, -180, 180); + h.Lat = ValidLat(h.Lat, -74, 74); + decimal f, e, i, g; + f = DegreeToRad(j.Lng); + i = DegreeToRad(j.Lat); + e = DegreeToRad(h.Lng); + g = DegreeToRad(h.Lat); + return Convert.ToDecimal((double)A * Math.Acos((Math.Sin((double)i) * Math.Sin((double)g) + Math.Cos((double)i) * Math.Cos((double)g) * Math.Cos((double)e - (double)f)))); + } + + private static decimal DegreeToRad(decimal d) + { + return Convert.ToDecimal(Math.PI * (double)d / 180); + } + + private static decimal RadToDegree(decimal d) + { + return Convert.ToDecimal((180 * (double)d) / Math.PI); + } + + private static decimal ValidLng(decimal lng, decimal min, decimal max) + { + while (lng > max) + { + lng -= max - min; + } + while (lng < min) + { + lng += max - min; + } + return lng; + } + + private static decimal ValidLat(decimal lat, decimal min, decimal max) + { + lat = Math.Max(lat, min); + lat = Math.Min(lat, max); + return lat; + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/GeofenceHelper.cs b/TelpoPush.Fence.Worker/Models/Fence/GeofenceHelper.cs new file mode 100644 index 0000000..6510f1e --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/GeofenceHelper.cs @@ -0,0 +1,144 @@ +using System.Drawing; + +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public static class GeofenceHelper + { + + public static double CalcDisgreeFromRadius(int radius) + { + double metersPerSecond = 30.0; + double seconds = radius / metersPerSecond; + return seconds / 3600; + } + + /// + /// 计算点是否在多边形内 + /// + /// 多边形点集合 + /// 要判断的点 + /// 是否在多边形内 + public static bool IsPolygonContainsPoint(IList polygon, GpsFencePoint pt) + { + return RayCastingAlgorithm.IsWithin(pt, polygon, true); + } + + /// + /// 判断圆是否和多边形相交 + /// + /// 多边形点集合 + /// 要判断的圆的圆心点 + /// 圆半径(单位米) + /// + public static bool IsPolygonIntersectCircle(IList polygon, GpsFencePoint ptCenter,int radius) + { + var degree = CalcDisgreeFromRadius(radius); + return CirclePolygonAlgorithm.IsIntersect(ptCenter, degree, polygon); + } + + + + //---yl---新增-- 2022.6.13---- + /// + /// 判断圆和多边形是否相交 + /// + /// 圆心坐标 + /// 圆半径 + /// 多边形各个点坐标 + /// + public static bool IsCircle2Polygon(Point circle, double radius, IList polygon) + { + bool res = false; + + double powRadius = Math.Pow(radius, 2); + + //情况1:多边形任一顶点在圆内,则认为相交 + foreach (var pt in polygon) + { + if (Math.Pow(circle.X - pt.X, 2) + Math.Pow(circle.Y - pt.Y, 2) < powRadius) + { + res = true; + return res; + //return true; + } + } + + //情况2:多边形顶点集的任一有效线段和圆相交(圆心到线段的距离小于半径) + var lines = EnumeratePolygonLines(polygon); + foreach (var seg in lines) + { + if (PointToLine(circle, seg.Item1, seg.Item2) < radius) + { + res = true; + return res; + //return true; + } + //return true; + } + + return res;// false; + } + + private static IList> EnumeratePolygonLines(IList polygon) + { + var list = new List>(); + + for (int i = 0; i < polygon.Count - 1; i++) + { + list.Add(new Tuple(polygon[i], polygon[i + 1])); + } + list.Add(new Tuple(polygon[polygon.Count - 1], polygon[0])); + + return list; + } + + //计算两点之间的距离 + private static double LineSpace(Point ptStarts, Point ptEnds) + { + if (ptStarts == ptEnds) return 0; + return Math.Sqrt((ptEnds.X - ptStarts.X) * (ptEnds.X - ptStarts.X) + (ptEnds.Y - ptStarts.Y) * (ptEnds.Y - ptStarts.Y)); + } + + /// + /// 点到线段的距离 + /// + /// + /// 线段顶点1 + /// 线段顶点2 + /// + private static double PointToLine(Point pt, Point ptStart, Point ptEnd) + { + double distance = 0; + double a, b, c; + a = LineSpace(ptStart, ptEnd); //线段长度 + b = LineSpace(pt, ptStart); //点到线段顶点1的距离 + c = LineSpace(pt, ptEnd); //点到线段顶点2的距离 + if (b == 0 || c == 0) + { + distance = 0; + return distance; + } + if (a == 0) + { + distance = b; + return distance; + } + if (c * c >= a * a + b * b) + { + distance = b; + return distance; + } + if (b * b >= a * a + c * c) + { + distance = c; + return distance; + } + + double p = (a + b + c) / 2; //半周长 + double s = Math.Sqrt(p * (p - a) * (p - b) * (p - c)); // 海伦公式求面积 + distance = 2 * s / a;// 返回点到线的距离(利用三角形面积公式求高) + + return distance; + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/GpsFencePoint.cs b/TelpoPush.Fence.Worker/Models/Fence/GpsFencePoint.cs new file mode 100644 index 0000000..b174c37 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/GpsFencePoint.cs @@ -0,0 +1,31 @@ +namespace TelpoPush.Fence.Worker.Models.Fence +{ + /// + /// gps点坐标 + /// + public class GpsFencePoint + { + public GpsFencePoint() { } + + /// + /// + /// + /// 经度 + /// 纬度 + public GpsFencePoint(double lng,double lat) + { + Longitude = lng; + Latitude = lat; + } + + /// + /// 经度 + /// + public double Longitude { get; set; } + + /// + /// 纬度 + /// + public double Latitude { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Models/Fence/Point.cs b/TelpoPush.Fence.Worker/Models/Fence/Point.cs new file mode 100644 index 0000000..30cd7b6 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/Fence/Point.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TelpoPush.Fence.Worker.Models.Fence +{ + public class GpsPoint + { + public decimal Lat { get; set; } + public decimal Lng { get; set; } + + public GpsPoint(decimal lat, decimal lng) + { + Lat = lat; + Lng = lng; + } + + public bool Equals(GpsPoint obj) + { + return Math.Abs(obj.Lat - Lat) < 0.000000001m && Math.Abs(obj.Lng - Lng) < 0.000000001m; + } + } +} diff --git a/TelpoPush.Fence.Worker/Models/MqTemplates/BaseModel.cs b/TelpoPush.Fence.Worker/Models/MqTemplates/BaseModel.cs new file mode 100644 index 0000000..59f2670 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/MqTemplates/BaseModel.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.MqTemplates +{ + public class BaseModel + { + + public string messageId { get; set; } + public string topic { get; set; } + public string time { get; set; } + public data data { get; set; } + } + + public class data + { + public string DeviceId { get; set; } + public int Radius { get; set; } + public string imei { get; set; } + public string wifiInfo { get; set; } + public int locationType { get; set; } + public string address { get; set; } + public decimal baiduLatitude { get; set; } + public decimal baiduLongitude { get; set; } + public decimal gaodeLatitude { get; set; } + public decimal gaodeLongitude { get; set; } + public decimal originalLatitude { get; set; } + public decimal originalLongitude { get; set; } + public string LastUpdate { get; set; } + public string UtcDate { get; set; } + } + + public class wifiInfo + { + public string mmac { get; set; } + public string macs { get; set; } + } + +} diff --git a/TelpoPush.Fence.Worker/Models/PushTemplates/PushWxTemplate.cs b/TelpoPush.Fence.Worker/Models/PushTemplates/PushWxTemplate.cs new file mode 100644 index 0000000..39d232a --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/PushTemplates/PushWxTemplate.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.PushTemplates +{ + public class PushWxTemplate + { + public string deviceId { get; set; } + public string imei { get; set; } + public int alarmTypeId { get; set; } + public string alarmDeviceName { get; set; } + public string alarmRemarks { get; set; } + public string address { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Models/PushTemplates/PushXiaoAnPushTemplate.cs b/TelpoPush.Fence.Worker/Models/PushTemplates/PushXiaoAnPushTemplate.cs new file mode 100644 index 0000000..7e47375 --- /dev/null +++ b/TelpoPush.Fence.Worker/Models/PushTemplates/PushXiaoAnPushTemplate.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Models.PushTemplates +{ + /// + /// 进出围栏http推送(校安) + /// + public class PushXiaoAnPushTemplate + { + /// + /// 推送title + /// + [JsonProperty("title")] + public string Title { get; set; } + + /// + /// 地址 + /// + [JsonProperty("address")] + public string Address { get; set; } + + /// + /// 推送内容 + /// + [JsonProperty("textcontent")] + public string TextContent { get; set; } + + /// + /// 设备imei号 + /// + [JsonProperty("nickname")] + public string NickName { get; set; } + + /// + /// 时间 + /// + [JsonProperty("datetime")] + public string DateTime { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/Program.cs b/TelpoPush.Fence.Worker/Program.cs new file mode 100644 index 0000000..49ed587 --- /dev/null +++ b/TelpoPush.Fence.Worker/Program.cs @@ -0,0 +1,103 @@ + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Serilog; +using TelpoDataService.Util.Clients; +using TelpoPush.Fence.Worker; +using TelpoPush.Fence.Worker.Common; +using TelpoPush.Fence.Worker.Handlers; +using TelpoPush.Fence.Worker.Models.Config; +using TelpoPush.Fence.Worker.Service.Cache; +using TelpoPush.Fence.Worker.Service.Mq; + +try +{ + #region ־ + //using Serilog.Events; + //using Serilog; + //using Microsoft.AspNetCore.Builder; + + //Log.Logger = new LoggerConfiguration() + //#if DEBUG + // .MinimumLevel.Debug() + //#else + // .MinimumLevel.Information() + //#endif + // .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + // .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + // .Enrich.FromLogContext() + // //.Filter.ByExcluding(c => c.Properties.Any(p => p.Value.ToString().Contains("Microsoft")))// + // .WriteTo.Async(c => c.File("/var/telpo_pushthird_ssl2/logs/infos/info.log", + // restrictedToMinimumLevel: LogEventLevel.Information, + // rollingInterval: RollingInterval.Day,//() + // //fileSizeLimitBytes: 20971520, //õļСΪ3M Ĭ1G + // rollOnFileSizeLimit: true, //ļС󴴽µ + // retainedFileCountLimit: 7,//Ĭ31˼ֻ31־ļ" + // outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}" + // ) + // ) + // .WriteTo.Async(c => c.File("/var/telpo_pushthird_ssl2/logs/errors/errors.log", + // restrictedToMinimumLevel: LogEventLevel.Error, + // rollingInterval: RollingInterval.Day, + // rollOnFileSizeLimit: true, + // retainedFileCountLimit: 7, + // outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff }[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}" + // ) + // ) + // .WriteTo.Async(c => c.Console()) + // .CreateLogger(); + + //ѡļappsetting.json + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .Build(); + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .Enrich.WithThreadId() + .CreateLogger(); + #endregion + + Log.Information("Starting up"); + var builder = Host.CreateApplicationBuilder(args); + + var config = builder.Configuration; + builder.Services.Configure(config.GetSection("FenceConfig")); + builder.Services.Configure(config.GetSection("ServiceConfig")); + builder.Services.Configure(config.GetSection("Redis")); + + JsonSerializerSettings setting = new JsonSerializerSettings(); + JsonConvert.DefaultSettings = () => + { + setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + setting.ContractResolver = new CamelCasePropertyNamesContractResolver(); + return setting; + }; + builder.Services.AddTelpoDataServices(opt => + { + opt.TelpoDataUrl = config["ServiceConfig:TelpoDataUrl"]; + }); + + builder.Services.AddSerilog(); + builder.Services.AddHttpClient(); + builder.Services.AddTransient(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddHostedService(); + var host = builder.Build(); + host.Run(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Application start-up failed"); +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/Properties/launchSettings.json b/TelpoPush.Fence.Worker/Properties/launchSettings.json new file mode 100644 index 0000000..5279752 --- /dev/null +++ b/TelpoPush.Fence.Worker/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "TelpoPush.Fence.Worker": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/Service/Cache/MemoryCacheUtil.cs b/TelpoPush.Fence.Worker/Service/Cache/MemoryCacheUtil.cs new file mode 100644 index 0000000..e6fc0c9 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Cache/MemoryCacheUtil.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; + +namespace TelpoPush.Fence.Worker.Service.Cache +{ + internal class MemoryCacheUtil + { + static MemoryCache cache = new MemoryCache(new MemoryCacheOptions()); + /// + /// 创建缓存项的文件 + /// + /// 缓存Key + /// object对象 + public static void Set(string key, object value) + { + if (key != null) + { + cache.Set(key, value); + } + } + /// + /// 创建缓存项过期 + /// + /// 缓存Key + /// object对象 + /// 过期时间(秒) + public static void Set(string key, object value, int expires) + { + if (key != null) + { + cache.Set(key, value, new MemoryCacheEntryOptions() + //设置缓存时间,如果被访问重置缓存时间。设置相对过期时间x秒 + .SetSlidingExpiration(TimeSpan.FromSeconds(expires))); + } + } + + /// + /// 获取缓存对象 + /// + /// 缓存Key + /// object对象 + public static object Get(string key) + { + object val = null; + if (key != null && cache.TryGetValue(key, out val)) + { + + return val; + } + else + { + return default(object); + } + } + + /// + /// 获取缓存对象 + /// + /// T对象 + /// 缓存Key + /// + public static T Get(string key) + { + object obj = Get(key); + return obj == null ? default(T) : (T)obj; + } + + + /// + /// 移除缓存项的文件 + /// + /// 缓存Key + public static void Remove(string key) + { + cache.Remove(key); + } + } + } diff --git a/TelpoPush.Fence.Worker/Service/Cache/RedisUtil.cs b/TelpoPush.Fence.Worker/Service/Cache/RedisUtil.cs new file mode 100644 index 0000000..3daab55 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Cache/RedisUtil.cs @@ -0,0 +1,191 @@ +using Microsoft.Extensions.Options; +using TelpoPush.Fence.Worker.Models.CacheTemplates; +using TelpoPush.Fence.Worker.Models.Config; + +namespace TelpoPush.Fence.Worker.Service.Cache +{ + public class RedisUtil + { + private const string CACHE_HASH_KEY_TELPO_GPSDEVICE = "TELPO#GPSDEVICE_HASH"; + private const string CACHE_HASH_KEY_TELPO_GPSDEVICE_FENCE = "TELPO#GPSDEVICE_FENCE_HASH"; + private const string CACHE_HASH_KEY_FENCE_LAST_TIME = "TELPO_FENCE#GPSDEVICE_LAST_TIME_HASH"; + private const string CACHE_HASH_KEY_FENCE_LAST_STATUS = "TELPO_FENCE#GPSDEVICE_LAST_STATUS_HASH"; + private readonly ILogger _logger; + private readonly ServiceConfig _configService; + private readonly SqlMapper _sqlMapper; + public RedisUtil(ILogger logger,IOptions optConfigRedis, IOptions configService, SqlMapper sqlMapper) + { + _configService = configService.Value; + _logger = logger; + optConfigRedis.Value.Prefix = ""; + optConfigRedis.Value.DefaultDatabase = 7; + var csredis = new CSRedis.CSRedisClient(optConfigRedis.Value.ToString()); + RedisHelper.Initialization(csredis); + _sqlMapper = sqlMapper; + } + + public async Task GetGpsDevice(string imei) + { + if (string.IsNullOrWhiteSpace(imei)) return null; + string keyCache = $"{imei}_GpsDevice"; + try + { + var objCache = MemoryCacheUtil.Get(keyCache); + if (objCache == null) + { + var obj = await RedisHelper.HGetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE, imei); + if (obj == null) + { + var deviceInfo = _sqlMapper.DeviceInfo(imei); + if (deviceInfo != null) + { + await RedisHelper.HSetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE, imei, deviceInfo); + MemoryCacheUtil.Set(keyCache, deviceInfo, _configService.CacheDurationSeconds); + return deviceInfo; + } + } + else + { + MemoryCacheUtil.Set(keyCache, obj, _configService.CacheDurationSeconds); + return obj; + } + } + return objCache; + } + catch (Exception ex) + { + _logger.LogError($"GetGpsDevice,key={imei},缓存异常,重新获取数据,{ex.Message}|{ex.Source}|{ex.StackTrace}"); + var deviceInfo = _sqlMapper.DeviceInfo(imei); + if (deviceInfo != null) + { + await RedisHelper.HSetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE, imei, deviceInfo); + MemoryCacheUtil.Set(keyCache, deviceInfo, _configService.CacheDurationSeconds); + return deviceInfo; + } + } + return null; + } + + public bool ClearGpsDeviceFence() { + + var fence = _sqlMapper.DeviceFenceAll(); + var status = RedisHelper.HGetAll(CACHE_HASH_KEY_FENCE_LAST_STATUS); + + var list = status.Where(s => !fence.Where(p => s.Key == p.imei + "_" + p.geofenceId).Any()).ToList(); + + foreach (var item in list) + { + RedisHelper.HDel(CACHE_HASH_KEY_FENCE_LAST_STATUS, item.Key); + } + //int[] oldArray = { 1, 2, 3, 4, 5 }; + //int[] newArray = { 2, 4, 5, 7, 8, 9 }; + //var jiaoJi = oldArray.Intersect(newArray).ToList();//2,4,5 + //var oldChaJi = oldArray.Except(newArray).ToList();//1,3 + //var newChaJi = newArray.Except(oldArray).ToList();//7,8,9 + //var bingJi = oldArray.Union(newArray).ToList();//1,2,3,4,5,7,8,9 + + return true; + } + + + public async Task GetGpsDeviceFence(string imei) + { + if (string.IsNullOrWhiteSpace(imei)) return null; + var obj = await RedisHelper.HGetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE_FENCE, imei); + if (obj == null) + { + var device = GetGpsDevice(imei).Result; + if (device == null) return null; + DeviceFenceModel model = new DeviceFenceModel(); + model.imei = device.imei; + model.deviceId = device.deviceId; + List fenceList = new List(); + var list = _sqlMapper.DeviceFencelist(imei); + if (list.Count > 0) + { + foreach (var fence in list) + { + DeviceFenceList val = new DeviceFenceList(); + val.geofenceId = fence.geofenceId; + val.geofenceName = fence.geofenceName; + val.WifiInfo = _sqlMapper.DeviceFenceWifi(imei, fence.geofenceId); + val.fenceType = fence.fenceType; + val.isActive = fence.isActive; + val.appType = fence.appType; + val.address = fence.address; + val.alarmType = fence.alarmType; + List pointList = new List(); + if (fence.fenceType == 1) + { + pointList.Add(new pointList() + { + index = 0, + radius = fence.radius, + latitude = fence.latitude, + longitude = fence.longitude + }); + } + else + { + var pointLists = _sqlMapper.DeviceFencePointlist(fence.geofenceId); + foreach (var point in pointLists) + { + pointList.Add(new pointList() + { + index = point.index, + radius = 0, + latitude = point.latitude, + longitude = point.longitude + }); + } + } + val.pointList = pointList; + fenceList.Add(val); + } + model.count = fenceList.Count; + model.fenceList = fenceList; + await RedisHelper.HSetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE_FENCE, imei, model); + return model; + } + else + return null; + } + return obj; + } + public async Task GetIsDateStatus(DeviceTime model) + { + bool result = true; + try + { + string file = model.imei; + if (string.IsNullOrWhiteSpace(model.imei)) return false; + var obj = await RedisHelper.HGetAsync(CACHE_HASH_KEY_FENCE_LAST_TIME, file); + if (obj == null) + await RedisHelper.HSetAsync(CACHE_HASH_KEY_FENCE_LAST_TIME, file, model); + else + { + if (model.dt > obj.dt) + await RedisHelper.HSetAsync(CACHE_HASH_KEY_FENCE_LAST_TIME, file, model); + else + result = false; + } + } + catch (Exception ex) + { + string exs = ex.Message; + } + return result; + } + public async Task SetFenceLastStatus(string fenceKey, LastStatusInfo info) + { + await RedisHelper.HSetAsync(CACHE_HASH_KEY_FENCE_LAST_STATUS, fenceKey, info); + } + public async Task GetFenceLastStatus(string fenceKey) + { + if (string.IsNullOrWhiteSpace(fenceKey)) return null; + var status = await RedisHelper.HGetAsync(CACHE_HASH_KEY_FENCE_LAST_STATUS, fenceKey); + return status; + } + + } +} diff --git a/TelpoPush.Fence.Worker/Service/Cache/SqlMapper.cs b/TelpoPush.Fence.Worker/Service/Cache/SqlMapper.cs new file mode 100644 index 0000000..75ca081 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Cache/SqlMapper.cs @@ -0,0 +1,65 @@ +using Dapper; +using MySql.Data.MySqlClient; +using System.Data; +using TelpoPush.Fence.Worker.Models.CacheTemplates; + +namespace TelpoPush.Fence.Worker.Service.Cache +{ + public class SqlMapper + { + private readonly IConfiguration _config; + private static string gps_conn = ""; + private static string telcommon_conn = ""; + public SqlMapper(IConfiguration config) + { + _config = config; + gps_conn = _config["ConnectionStrings:DB_Connection_String"].ToString(); + telcommon_conn = _config["ConnectionStrings:Telpo_common_ConnString"].ToString(); + } + + public DeviceInfoModel DeviceInfo(string imei) + { + using (IDbConnection connection = new MySqlConnection(gps_conn)) + { + var sql = @"SELECT d.device_id deviceId, p.nick_name deviceName,p.api_uid apiUid,d.serialno imei, d.org_uid orgId, d.active_status activeStatus,active_time activeTime FROM gps_device as d LEFT JOIN `gps_person` as p on p.device_id=d.device_id WHERE d.serialno=@imei"; + return connection.QueryFirstOrDefault(sql, new { imei }); + } + } + + public List DeviceFenceWifi(string imei, string geofence_id) + { + using (IDbConnection connection = new MySqlConnection(gps_conn)) + { + var sql = @"SELECT wifi_info FROM gps_geofence_wifi WHERE imei=@imei AND geofence_id=@geofence_id"; + return connection.Query(sql, new { imei, geofence_id }).ToList(); + } + } + + public List DeviceFenceAll() + { + using (IDbConnection connection = new MySqlConnection(gps_conn)) + { + var sql = @"SELECT geofence_id geofenceId,geofence_name geofenceName, device_id deviceId, serialno AS imei, radius, center_lat latitude, center_lng longitude, fence_type fenceType, is_active isActive, app_type appType, alarm_type alarmType, address FROM gps_geofence"; + return connection.Query(sql ).ToList(); + } + } + + public List DeviceFencelist(string imei) + { + using (IDbConnection connection = new MySqlConnection(gps_conn)) + { + var sql = @"SELECT geofence_id geofenceId,geofence_name geofenceName, device_id deviceId, serialno AS imei, radius, center_lat latitude, center_lng longitude, fence_type fenceType, is_active isActive, app_type appType, alarm_type alarmType, address FROM gps_geofence where serialno=@imei"; + return connection.Query(sql, new { imei }).ToList(); + } + } + + public List DeviceFencePointlist(string geofence_id) + { + using (IDbConnection connection = new MySqlConnection(gps_conn)) + { + var sql = @"SELECT geofence_id geofenceId, radius, longitude, latitude, vertex_index AS `index` FROM gps_geofence_details where geofence_id=@geofence_id ORDER BY `index`"; + return connection.Query(sql, new { geofence_id }).ToList(); + } + } + } +} diff --git a/TelpoPush.Fence.Worker/Service/Mq/IKafkaService.cs b/TelpoPush.Fence.Worker/Service/Mq/IKafkaService.cs new file mode 100644 index 0000000..f2923e5 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Mq/IKafkaService.cs @@ -0,0 +1,9 @@ +using Confluent.Kafka; + +namespace TelpoPush.Fence.Worker.Service.Mq +{ + public interface IKafkaService + { + Task SubscribeAsync(Action messageFunc, CancellationToken cancellationToken); + } +} diff --git a/TelpoPush.Fence.Worker/Service/Mq/KafkaHeader.cs b/TelpoPush.Fence.Worker/Service/Mq/KafkaHeader.cs new file mode 100644 index 0000000..eb47843 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Mq/KafkaHeader.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Service.Mq +{ + public class KafkaHeader + { + public static readonly string DataType = "DataType"; + public static readonly string AlarmType = "AlarmType"; + public static readonly string OperType = "OperType"; + } +} diff --git a/TelpoPush.Fence.Worker/Service/Mq/KafkaService.cs b/TelpoPush.Fence.Worker/Service/Mq/KafkaService.cs new file mode 100644 index 0000000..5aaf001 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Mq/KafkaService.cs @@ -0,0 +1,157 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using TelpoPush.Fence.Worker.Models.Config; + +namespace TelpoPush.Fence.Worker.Service.Mq +{ + public class KafkaService : IKafkaService + { + private readonly ConsumerConfig _consumerConfig; + private readonly IHostEnvironment env; + private readonly ILogger logger; + private readonly ServiceConfig _configService; + public KafkaService(ILogger _logger, IHostEnvironment _env, IOptions optConfigService) + { + _configService = optConfigService.Value; + env = _env; + logger = _logger; + + _consumerConfig = new ConsumerConfig + { + BootstrapServers = _configService.KafkaBootstrapServers, + GroupId = _configService.KafkaGroupId, + EnableAutoCommit = false, // 禁止AutoCommit + Acks = Acks.Leader, // 假设只需要Leader响应即可 + AutoOffsetReset = AutoOffsetReset.Earliest,// 从最早的开始消费起 + CancellationDelayMaxMs = 1//set CancellationDelayMaxMs + }; + + + //_consumerConfig = new ConsumerConfig + //{ + // BootstrapServers = _configService.KafkaBootstrapServers, + // GroupId = _configService.KafkaGroupId, + // EnableAutoCommit = false, + // StatisticsIntervalMs = 5000, + // SessionTimeoutMs = 6000, + // AutoOffsetReset = AutoOffsetReset.Earliest, + // EnablePartitionEof = true, + // CancellationDelayMaxMs=1 + //}; + } + public async Task SubscribeAsync(Action messageFunc, CancellationToken cancellationToken) + { + List topics = _configService.KafkaTopics; + using (var consumer = new ConsumerBuilder(_consumerConfig) + .SetErrorHandler((_, e) => + { + logger.LogError($"Error: {e.Reason}"); + }) + .SetStatisticsHandler((_, json) => + { + logger.LogInformation($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} > 消息监听中.."); + }) + .SetPartitionsAssignedHandler((c, partitions) => + { + string partitionsStr = string.Join(", ", partitions); + logger.LogInformation($" - 分配的 kafka 分区: {partitionsStr}"); + }) + .SetPartitionsRevokedHandler((c, partitions) => + { + string partitionsStr = string.Join(", ", partitions); + logger.LogInformation($" - 回收了 kafka 分区: {partitionsStr}"); + }) + .Build()) + { + + consumer.Subscribe(topics); + try + { + while (true) + { + try + { + var consumeResult = consumer.Consume(cancellationToken); + string topic = consumeResult.Topic; + string messageResult = consumeResult.Message.Value; + Headers headers = consumeResult.Message.Headers; + bool isPartitionEOF = consumeResult.IsPartitionEOF; + var partition = consumeResult.Partition; + + int DataType = -1, AlarmType = -1, OperType = -1; + foreach (var item in headers) + { + if (item.Key == KafkaHeader.DataType) + DataType = BitConverter.ToInt32(item.GetValueBytes(), 0); + else if (item.Key == KafkaHeader.AlarmType) + AlarmType = BitConverter.ToInt32(item.GetValueBytes(), 0); + else if (item.Key == KafkaHeader.OperType) + OperType = BitConverter.ToInt32(item.GetValueBytes(), 0); + } + var Headers = new { DataType, AlarmType, OperType }; + logger.LogInformation($"Consumed topic '{topic}' , message '{messageResult}' , headers '{JsonConvert.SerializeObject(Headers)}', at '{consumeResult?.TopicPartitionOffset}'."); + if (isPartitionEOF) + { + logger.LogInformation($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} 已经到底了:{topic}, partition {partition}, offset {consumeResult?.Offset}."); + continue; + } + if (!string.IsNullOrEmpty(messageResult)) + { + messageFunc(topic, messageResult, headers); + try + { + consumer.Commit(consumeResult); + } + catch (KafkaException e) + { + logger.LogError($" - {e.Message}."); + } + } + #region 注释 + //string messageResult = null; + //Headers headers = null; + //try + //{ + // messageResult = consumeResult.Message.Value; + // headers = consumeResult.Message.Headers; + //} + //catch (Exception ex) + //{ + // var errorMessage = $" - {DateTime.Now:yyyy-MM-dd HH:mm:ss}【Exception 消息反序列化失败,Value:{consumeResult.Message.Value}】 :{ex.StackTrace?.ToString()}"; + // Console.WriteLine(errorMessage); + // logger.LogError(errorMessage); + // messageResult = null; + //} + //if (!string.IsNullOrEmpty(messageResult)/* && consumeResult.Offset % commitPeriod == 0*/) + //{ + // string topic = consumeResult.Topic; + // messageFunc(topic, messageResult, headers); + + // //try + // //{ + // // consumer.Commit(consumeResult); + // //} + // //catch (KafkaException e) + // //{ + // // Console.WriteLine(e.Message); + // //} + //} + #endregion + } + catch (ConsumeException e) + { + logger.LogError($"Consume error: {e.Error.Reason}"); + } + } + } + catch (OperationCanceledException) + { + logger.LogError("Closing consumer."); + consumer.Close(); + } + } + await Task.CompletedTask; + } + } +} diff --git a/TelpoPush.Fence.Worker/Service/Mq/MessageProducer.cs b/TelpoPush.Fence.Worker/Service/Mq/MessageProducer.cs new file mode 100644 index 0000000..08a22c3 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Mq/MessageProducer.cs @@ -0,0 +1,77 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using TelpoPush.Fence.Worker.Models.Config; + +namespace TelpoPush.Fence.Worker.Service.Mq +{ + /// + /// 消息生产者 + /// + public class MessageProducer + { + private readonly ILogger _logger; + private readonly ServiceConfig _configService; + private readonly IProducer _producer; + + public MessageProducer(ILogger logger, IOptions optConfigService) + { + _logger = logger; + _configService = optConfigService.Value; + + var config = new ProducerConfig + { + BootstrapServers = _configService.KafkaBootstrapServers, + EnableIdempotence = true, + Acks = Acks.All, + //LingerMs=5000, + //BatchNumMessages =1000, + //BatchSize=32768, + //CompressionType= CompressionType.Lz4, + MessageSendMaxRetries = 3 + }; + _producer = new ProducerBuilder(config).Build(); + } + + public Headers CreateHeader(Dictionary pair = null) + { + if (pair == null) + { + return null; + } + else + { + Headers headers = new Headers(); + + foreach (var item in pair) + { + headers.Add(item.Key, BitConverter.GetBytes(item.Value)); + } + return headers; + } + } + + public async Task ProduceAsync(List topic, object message) + { + try + { + foreach (var item in topic) + { + // producer = new ProducerBuilder(config).Build(); + await _producer.ProduceAsync(item.Topic, new Message + { + Headers = item.Headers, + Value = JsonConvert.SerializeObject(message) + }); + } + } + catch (ProduceException e) + { + _logger.LogError($"推送到kafka失败,topic: {topic},\n message:{JsonConvert.SerializeObject(message)}: \n{e.Error.Reason}"); + } + } + + + + } +} diff --git a/TelpoPush.Fence.Worker/Service/Mq/MqProcessMessage.cs b/TelpoPush.Fence.Worker/Service/Mq/MqProcessMessage.cs new file mode 100644 index 0000000..1fbaaa5 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Mq/MqProcessMessage.cs @@ -0,0 +1,81 @@ +using Confluent.Kafka; +using Newtonsoft.Json; +using System.Reflection.PortableExecutable; +using TelpoPush.Fence.Worker.Models.Enum; +using TelpoPush.Fence.Worker.Models.MqTemplates; +using TelpoPush.Fence.Worker.Models.PushTemplates; + +namespace TelpoPush.Fence.Worker.Service.Mq +{ + public class MqProcessMessage + { + private readonly ILogger _logger; + private readonly MessageProducer _messageProducer; + + public MqProcessMessage(ILogger logger, MessageProducer producer) + { + _logger = logger; + _messageProducer = producer; + } + + public async Task ProcessProperty(string imei, BaseModel model, HeadersDto headers) + { + List ls = new List(); + + ls.Add(new TopicModel() + { + Topic = "topic.push.property", + Headers = _messageProducer.CreateHeader(new Dictionary + { + {MqHeader.DataType,headers.DataType.Value } + }) + }); + await _messageProducer.ProduceAsync(ls, model); + _logger.LogInformation($"【成功】围栏告警推送(topic.property):IMEI<{imei}>,pushData:{JsonConvert.SerializeObject(model)}"); + } + + + //推送服务 + public async Task ProcessPushService(object model,string imei) + { + List ls = new List(); + ls.Add(new TopicModel() + { + Topic = "topic.push", + Headers = _messageProducer.CreateHeader(new Dictionary + { + {MqHeader.DataType,(int)MqDataType.AlarmInfo } + }) + }); + await _messageProducer.ProduceAsync(ls, model); + + _logger.LogInformation($"【成功】围栏告警推送(topic.push):IMEI<{imei}>,pushData:{JsonConvert.SerializeObject(model)}"); + } + + + + public async Task ProcessWxAlarm(PushWxTemplate model, string fenceId, string timeString) + { + + List ls = new List(); + ls.Add(new TopicModel() + { + Topic = "topic.push.wx", + Headers = _messageProducer.CreateHeader(new Dictionary + { + {MqHeader.DataType,(int)MqDataType.AlarmInfo }, + }) + }); + var objData = new + { + messageId = string.Format("{0:yyyyMMddHHmmssffff}", DateTime.Now), + topic = string.Join(",", ls.Select(e => e.Topic)), + time = timeString, + data = model + }; + await _messageProducer.ProduceAsync(ls, objData); + _logger.LogInformation($"【成功】围栏告警推送(topic.push.wx):IMEI<{model.imei}>,pushData:{JsonConvert.SerializeObject(model)}"); + } + + } +} diff --git a/TelpoPush.Fence.Worker/Service/Mq/TopicModel.cs b/TelpoPush.Fence.Worker/Service/Mq/TopicModel.cs new file mode 100644 index 0000000..18464a1 --- /dev/null +++ b/TelpoPush.Fence.Worker/Service/Mq/TopicModel.cs @@ -0,0 +1,15 @@ +using Confluent.Kafka; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelpoPush.Fence.Worker.Service.Mq +{ + public class TopicModel + { + public string Topic { get; set; } + public Headers Headers { get; set; } + } +} diff --git a/TelpoPush.Fence.Worker/TelpoPush.Fence.Worker.csproj b/TelpoPush.Fence.Worker/TelpoPush.Fence.Worker.csproj new file mode 100644 index 0000000..ee9ed38 --- /dev/null +++ b/TelpoPush.Fence.Worker/TelpoPush.Fence.Worker.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + + enable + dotnet-TelpoPush.Fence.Worker-3429e355-573c-49e4-9ea5-28d6c4563829 + Linux + disable + + + + + + + + + + + + + + + + + + + + + + diff --git a/TelpoPush.Fence.Worker/Worker.cs b/TelpoPush.Fence.Worker/Worker.cs new file mode 100644 index 0000000..0307282 --- /dev/null +++ b/TelpoPush.Fence.Worker/Worker.cs @@ -0,0 +1,25 @@ +using TelpoPush.Fence.Worker.Handlers; + +namespace TelpoPush.Fence.Worker +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + KafkaSubscribe _kafkaSubscribe; + + public Worker(ILogger logger, KafkaSubscribe kafkaSubscribe) + { + _logger = logger; + _kafkaSubscribe = kafkaSubscribe; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + await _kafkaSubscribe.SubscribeAsync(); + await Task.Delay(1000, stoppingToken); + } + } + } +} diff --git a/TelpoPush.Fence.Worker/appsettings.Development.json b/TelpoPush.Fence.Worker/appsettings.Development.json new file mode 100644 index 0000000..3ec3356 --- /dev/null +++ b/TelpoPush.Fence.Worker/appsettings.Development.json @@ -0,0 +1,33 @@ +{ + "ConnectionStrings": { + //"DB_Connection_String": "server=139.224.254.18;port=3305;database=gps_card;uid=root;pwd=telpo#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none", + //"Telpo_common_ConnString": "server=139.224.254.18;port=3305;database=telpo_common;uid=root;pwd=telpo#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none" + + "DB_Connection_String": "server=rm-uf6j529mu0v6g0btpco.mysql.rds.aliyuncs.com;port=3305;database=gps_card;uid=root;pwd=telpo#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none", + "Telpo_common_ConnString": "server=rm-uf6j529mu0v6g0btpco.mysql.rds.aliyuncs.com;port=3305;database=telpo_common;uid=linwl;pwd=linwl#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none" + }, + "FenceConfig": { + "XiaoAnManufactorId": "ad5434be-f5f2-4dee-a68d-3b2e7ac0ed7c", + "XiaoAnPushUrl": "http://182.92.204.237:5003/api/warn/send" + }, + "ServiceConfig": { + "TelpoDataUrl": "http://id.ssjlai.com/data", + "KafkaBootstrapServers": "47.116.67.214:9092", // "172.19.42.53:9092", + "KafkaTopics": [ "topic.push.fence" ], + "KafkaGroupId": "telpo.fence", + "MaxDegreeOfParallelism": "3", + "CacheDurationSeconds": 1200 //20分钟 + }, + "Redis": { + //"Server": "47.116.142.20:8090", + "Server": "139.224.254.18:8090", + "Password": "telpo#1234", + "DefaultDatabase": 7, + "Poolsize": 150, + "Preheat": 50, + "Prefix": "_Fence_", + "ConnectTimeout": 5000, + "IdleTimeout": 20000 + }, + "Environment": "dev" +} \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/appsettings.json b/TelpoPush.Fence.Worker/appsettings.json new file mode 100644 index 0000000..6b19d41 --- /dev/null +++ b/TelpoPush.Fence.Worker/appsettings.json @@ -0,0 +1,70 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System.Net.Http.HttpClient": "Warning" + } + }, + "Serilog": { + "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Async", "Serilog.Sinks.Console", "Serilog.Sinks.Exceptionless" ], + "MinimumLevel": { + "Default": "Verbose", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System.Net.Http.HttpClient": "Warning" + } + }, + "WriteTo:Information": { + "Name": "Async", + "Args": { + "Configure": [ + { + "Name": "File", + "Args": { + "RestrictedToMinimumLevel": "Information", + "RollingInterval": "Day", + "RollOnFileSizeLimit": "true", + "OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss }[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}", + "Path": "/var/telpo_pushfence/logs/infos/info.log", + "RetainedFileCountLimit": 7 // "--设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件", "等于null时永远保留文件": null + // "FileSizeLimitBytes": 20971520, //设置单个文件大小为3M 默认1G + // "RollOnFileSizeLimit": true //超过文件大小后创建新的 + + } + } + ] + } + }, + "WriteTo:Error": { + "Name": "Async", + "Args": { + "Configure": [ + { + "Name": "File", + "Args": { + "RestrictedToMinimumLevel": "Error", + "RollingInterval": "Day", + "RollOnFileSizeLimit": "true", + "OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss }[{Level:u3}] [Thread-{ThreadId}][{SourceContext:l}] {Message:lj}{NewLine}{Exception}", + "Path": "/var/telpo_pushfence/logs/errors/error.log", + "RetainedFileCountLimit": 7 // "--设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件", "等于null时永远保留文件": null + // "FileSizeLimitBytes": 20971520, //设置单个文件大小为3M 默认1G + // "RollOnFileSizeLimit": true //超过文件大小后创建新的 + } + } + ] + } + }, + "WriteTo:Console": { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Verbose", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss }[{Level:u3}] [Thread-{ThreadId}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}", + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console" + } + } + } +} \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/appsettings.production.json b/TelpoPush.Fence.Worker/appsettings.production.json new file mode 100644 index 0000000..1e81925 --- /dev/null +++ b/TelpoPush.Fence.Worker/appsettings.production.json @@ -0,0 +1,29 @@ +{ + "ConnectionStrings": { + "DB_Connection_String": "server=rm-uf6j529mu0v6g0btp.mysql.rds.aliyuncs.com;port=3305;database=gps_card;uid=root;pwd=telpo#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none", + "Telpo_common_ConnString": "server=rm-uf6j529mu0v6g0btp.mysql.rds.aliyuncs.com;port=3305;database=telpo_common;uid=linwl;pwd=linwl#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none" + }, + "FenceConfig": { + "XiaoAnManufactorId": "ad5434be-f5f2-4dee-a68d-3b2e7ac0ed7c", + "XiaoAnPushUrl": "http://182.92.204.237:5003/api/warn/send" + }, + "ServiceConfig": { + "TelpoDataUrl": "https://ai.ssjlai.com/data", + "KafkaBootstrapServers": "172.19.42.40:9092,172.19.42.41:9092,172.19.42.48:9092", + "KafkaTopics": [ "topic.push.fence" ], + "KafkaGroupId": "telpo.fence", + "MaxDegreeOfParallelism": "3", + "CacheDurationSeconds": 1200 //20分钟 + }, + "Redis": { + "Server": "172.19.42.40:8090", + "Password": "telpo#1234", + "DefaultDatabase": 7, + "Poolsize": 150, + "Preheat": 50, + "Prefix": "_Fence_", + "ConnectTimeout": 5000, + "IdleTimeout": 20000 + }, + "Environment": "production" +} \ No newline at end of file diff --git a/TelpoPush.Fence.Worker/appsettings.test.json b/TelpoPush.Fence.Worker/appsettings.test.json new file mode 100644 index 0000000..b075c60 --- /dev/null +++ b/TelpoPush.Fence.Worker/appsettings.test.json @@ -0,0 +1,29 @@ +{ + "ConnectionStrings": { + "DB_Connection_String": "server=172.19.42.40;port=3305;database=gps_card;uid=root;pwd=telpo#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none", + "Telpo_common_ConnString": "server=172.19.42.40;port=3305;database=telpo_common;uid=root;pwd=telpo#1234;CharSet=utf8;MinimumPoolSize=10;MaximumPoolSize=1000;SslMode=none" + }, + "FenceConfig": { + "XiaoAnManufactorId": "ad5434be-f5f2-4dee-a68d-3b2e7ac0ed7c", + "XiaoAnPushUrl": "http://182.92.204.237:5003/api/warn/send" + }, + "ServiceConfig": { + "TelpoDataUrl": "http://id.ssjlai.com/data", + "KafkaBootstrapServers": "172.19.42.53:9092", // "172.19.42.53:9092", + "KafkaTopics": [ "topic.push.fence" ], + "KafkaGroupId": "telpo.fence", + "MaxDegreeOfParallelism": "3", + "CacheDurationSeconds": 1200 //20分钟 + }, + "Redis": { + "Server": "172.19.42.44:8090", + "Password": "telpo#1234", + "DefaultDatabase": 7, + "Poolsize": 150, + "Preheat": 50, + "Prefix": "_Fence_", + "ConnectTimeout": 5000, + "IdleTimeout": 20000 + }, + "Environment": "test" +} \ No newline at end of file diff --git a/TelpoPushFence.sln b/TelpoPushFence.sln new file mode 100644 index 0000000..7d15b49 --- /dev/null +++ b/TelpoPushFence.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34902.65 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelpoPush.Fence.Worker", "TelpoPush.Fence.Worker\TelpoPush.Fence.Worker.csproj", "{6D80B967-98F7-43DC-8AB6-BEB2F23B35F1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6D80B967-98F7-43DC-8AB6-BEB2F23B35F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D80B967-98F7-43DC-8AB6-BEB2F23B35F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D80B967-98F7-43DC-8AB6-BEB2F23B35F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D80B967-98F7-43DC-8AB6-BEB2F23B35F1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8CCD230E-0371-4487-AAE2-9174BEA2C719} + EndGlobalSection +EndGlobal diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..365f5c7 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/setup_production.sh b/setup_production.sh new file mode 100644 index 0000000..0827f77 --- /dev/null +++ b/setup_production.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +image_version=$version +# 删除镜像 +docker rmi -f $( + docker images | grep registry.cn-shanghai.aliyuncs.com/gps_card/telpo_push_fence | awk '{print $3}' +) +# 构建telpo/mrp:$image_version镜像 +docker build -f ./TelpoPush.Work.Fence/Dockerfile . -t telpo/telpo_push_fence:$image_version +#TODO:推送镜像到阿里仓库 +echo '=================开始推送镜像=======================' +docker login --username=rzl_wangjx@1111649216405698 --password=telpo.123 registry.cn-shanghai.aliyuncs.com +docker tag telpo/telpo_push_fence:$image_version registry.cn-shanghai.aliyuncs.com/gps_card/telpo_push_fence:$image_version +docker push registry.cn-shanghai.aliyuncs.com/gps_card/telpo_push_fence:$image_version +echo '=================推送镜像完成=======================' +#删除产生的None镜像 +docker rmi -f $(docker images | grep none | awk '{print $3}') +# 查看镜像列表 +docker images diff --git a/setup_test.sh b/setup_test.sh new file mode 100644 index 0000000..606245a --- /dev/null +++ b/setup_test.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +image_version=$version +# 删除镜像 +docker rmi -f $( + docker images | grep 139.224.254.18:5000/telpo_push_fence | awk '{print $3}' +) +# 构建telpo/mrp:$image_version镜像 +docker build -f ./TelpoPush.Work.Fence/Dockerfile . -t telpo/telpo_push_fence:$image_version +#TODO:推送镜像到私有仓库 +echo '=================开始推送镜像=======================' +docker tag telpo/telpo_push_fence:$image_version 139.224.254.18:5000/telpo_push_fence:$image_version +docker push 139.224.254.18:5000/telpo_push_fence:$image_version +echo '=================推送镜像完成=======================' +#删除产生的None镜像 +docker rmi -f $(docker images | grep none | awk '{print $3}') +# 查看镜像列表 +docker images \ No newline at end of file diff --git a/telpo_push_fence_run.sh b/telpo_push_fence_run.sh new file mode 100644 index 0000000..f184e28 --- /dev/null +++ b/telpo_push_fence_run.sh @@ -0,0 +1,27 @@ +#!/bin/bash +environment=$1 +version=$2 +echo "环境变量为${environment},版本为$version!" +if [[ ${environment} == 'production' ]]; then + echo "开始远程构建容器" + docker stop telpo_push_fence || true + docker rm telpo_push_fence || true + docker rmi -f $(docker images | grep registry.cn-shanghai.aliyuncs.com/gps_card/telpo_push_fence | awk '{print $3}') + docker login --username=rzl_wangjx@1111649216405698 --password=telpo.123 registry.cn-shanghai.aliyuncs.com + docker pull registry.cn-shanghai.aliyuncs.com/gps_card/telpo_push_fence:$version + docker run -d --network=host -e environment=production -v /home/data/telpo_push_fence/log:/var/telpo_pushfence/logs --restart=always --name telpo_push_fence registry.cn-shanghai.aliyuncs.com/gps_card/telpo_push_fence:$version; + #删除产生的None镜像 + docker rmi -f $(docker images | grep none | awk '{print $3}') + docker ps -a + +elif [[ ${environment} == 'test' ]]; then + echo "开始在测试环境远程构建容器" + docker stop telpo_push_fence || true + docker rm telpo_push_fence || true + docker rmi -f $(docker images | grep 139.224.254.18:5000/telpo_push_fence | awk '{print $3}') + docker pull 139.224.254.18:5000/telpo_push_fence:$version + docker run -d --network=host -e environment=${environment} -v /home/data/telpo_push_fence/log:/var/telpo_pushfence/logs --restart=always --name telpo_push_fence 139.224.254.18:5000/telpo_push_fence:$version; + #删除产生的None镜像 + docker rmi -f $(docker images | grep none | awk '{print $3}') + docker ps -a +fi \ No newline at end of file