commit 06939b5133885153446a60b2e99188bb3415ee02
Author: 杨雷 <284428564@QQ.com>
Date: Fri Jun 7 09:43:53 2024 +0800
first commit
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