@@ -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/** |
@@ -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 |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// HTTP帮助类 | |||
/// </summary> | |||
public class HttpHelperAsync | |||
{ | |||
private IHttpClientFactory _httpClientFactory; | |||
private readonly ILogger<HttpHelperAsync> _logger; | |||
public HttpHelperAsync(IHttpClientFactory httpClientFactory, ILogger<HttpHelperAsync> logger) | |||
{ | |||
_httpClientFactory = httpClientFactory; | |||
_logger = logger; | |||
} | |||
#region 异步 | |||
/// <summary> | |||
/// 发起POST异步请求表单 | |||
/// </summary> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="body">POST提交的内容</param> | |||
/// <param name="bodyMediaType">POST内容的媒体类型,如:application/xml、application/json</param> | |||
/// <param name="responseContentType">HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回string</returns> | |||
public async Task<string> PostFormAsync(string url, MultipartFormDataContent content, | |||
Dictionary<string, string> 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}"; | |||
} | |||
} | |||
/// <summary> | |||
/// 发起GET异步请求 | |||
/// </summary> | |||
/// <typeparam name="T">返回类型</typeparam> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回string</returns> | |||
public async Task<string> GetAsync(string url, Dictionary<string, string> 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; | |||
} | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 发起POST异步请求 | |||
/// </summary> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="body">POST提交的内容</param> | |||
/// <param name="bodyMediaType">POST内容的媒体类型,如:application/xml、application/json</param> | |||
/// <param name="responseContentType">HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回string</returns> | |||
public async Task<string> PostAsync(string url, string body, | |||
Dictionary<string, string> 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}"; | |||
} | |||
} | |||
/// <summary> | |||
/// 发起POST异步请求 | |||
/// </summary> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="body">POST提交的内容</param> | |||
/// <param name="bodyMediaType">POST内容的媒体类型,如:application/xml、application/json</param> | |||
/// <param name="responseContentType">HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回string</returns> | |||
public async Task<string> PutAsync(string url, string body, | |||
string bodyMediaType = null, | |||
string responseContentType = null, | |||
Dictionary<string, string> 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; | |||
} | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 发起GET异步请求 | |||
/// </summary> | |||
/// <typeparam name="T">返回类型</typeparam> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回string</returns> | |||
public async Task<string> DeleteAsync(string url, Dictionary<string, string> 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; | |||
} | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// 发起GET异步请求 | |||
/// </summary> | |||
/// <typeparam name="T">返回类型</typeparam> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回T</returns> | |||
public async Task<T> GetAsync<T>(string url, Dictionary<string, string> headers = null, int timeOut = 30) where T : new() | |||
{ | |||
string responseString = await GetAsync(url, headers, timeOut); | |||
if (!string.IsNullOrWhiteSpace(responseString)) | |||
{ | |||
return JsonConvert.DeserializeObject<T>(responseString); | |||
} | |||
else | |||
{ | |||
return default(T); | |||
} | |||
} | |||
/// <summary> | |||
/// 发起POST异步请求 | |||
/// </summary> | |||
/// <typeparam name="T">返回类型</typeparam> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="body">POST提交的内容</param> | |||
/// <param name="bodyMediaType">POST内容的媒体类型,如:application/xml、application/json</param> | |||
/// <param name="responseContentType">HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回T</returns> | |||
public async Task<T> PostAsync<T>(string url, string body, | |||
Dictionary<string, string> 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<T>(responseString); | |||
} | |||
else | |||
{ | |||
return default(T); | |||
} | |||
} | |||
/// <summary> | |||
/// 发起PUT异步请求 | |||
/// </summary> | |||
/// <typeparam name="T">返回类型</typeparam> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="body">POST提交的内容</param> | |||
/// <param name="bodyMediaType">POST内容的媒体类型,如:application/xml、application/json</param> | |||
/// <param name="responseContentType">HTTP响应上的content-type内容头的值,如:application/xml、application/json、application/text、application/x-www-form-urlencoded等</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回T</returns> | |||
public async Task<T> PutAsync<T>(string url, string body, | |||
string bodyMediaType = null, | |||
string responseContentType = null, | |||
Dictionary<string, string> 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<T>(responseString); | |||
} | |||
else | |||
{ | |||
return default(T); | |||
} | |||
} | |||
/// <summary> | |||
/// 发起DELETE异步请求 | |||
/// </summary> | |||
/// <typeparam name="T">返回类型</typeparam> | |||
/// <param name="url">请求地址</param> | |||
/// <param name="headers">请求头信息</param> | |||
/// <param name="timeOut">请求超时时间,单位秒</param> | |||
/// <returns>返回T</returns> | |||
public async Task<T> DeleteAsync<T>(string url, Dictionary<string, string> headers = null, int timeOut = 30) where T : new() | |||
{ | |||
string responseString = await DeleteAsync(url, headers, timeOut); | |||
if (!string.IsNullOrWhiteSpace(responseString)) | |||
{ | |||
return JsonConvert.DeserializeObject<T>(responseString); | |||
} | |||
else | |||
{ | |||
return default(T); | |||
} | |||
} | |||
#region 私有函数 | |||
/// <summary> | |||
/// 获取请求的主机名 | |||
/// </summary> | |||
/// <param name="url"></param> | |||
/// <returns></returns> | |||
private static string GetHostName(string url) | |||
{ | |||
if (!string.IsNullOrWhiteSpace(url)) | |||
{ | |||
return url.Replace("https://", "").Replace("http://", "").Split('/')[0]; | |||
} | |||
else | |||
{ | |||
return "AnyHost"; | |||
} | |||
} | |||
#endregion | |||
#endregion | |||
#region 同步 | |||
/// <summary> | |||
/// 发起GET同步请求 | |||
/// </summary> | |||
/// <param name="url"></param> | |||
/// <param name="headers"></param> | |||
/// <param name="contentType"></param> | |||
/// <returns></returns> | |||
/// | |||
public string HttpGet(string url, Dictionary<string, string> 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 ""; | |||
} | |||
/// <summary> | |||
/// 发起POST同步请求 | |||
/// </summary> | |||
/// <param name="url"></param> | |||
/// <param name="postData"></param> | |||
/// <param name="contentType">application/xml、application/json、application/text、application/x-www-form-urlencoded</param> | |||
/// <param name="headers">填充消息头</param> | |||
/// <returns></returns> | |||
public string HttpPost(string url, string postData = null, Dictionary<string, string> 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 | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// Provides a task scheduler that ensures a maximum concurrency level while | |||
/// running on top of the ThreadPool. | |||
/// </summary> | |||
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler | |||
{ | |||
/// <summary>Whether the current thread is processing work items.</summary> | |||
[ThreadStatic] | |||
private static bool _currentThreadIsProcessingItems; | |||
/// <summary>The list of tasks to be executed.</summary> | |||
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks) | |||
/// <summary>The maximum concurrency level allowed by this scheduler.</summary> | |||
private readonly int _maxDegreeOfParallelism; | |||
/// <summary>Whether the scheduler is currently processing work items.</summary> | |||
private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) | |||
/// <summary> | |||
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the | |||
/// specified degree of parallelism. | |||
/// </summary> | |||
/// <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this scheduler.</param> | |||
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) | |||
{ | |||
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); | |||
_maxDegreeOfParallelism = maxDegreeOfParallelism; | |||
} | |||
/// <summary>Queues a task to the scheduler.</summary> | |||
/// <param name="task">The task to be queued.</param> | |||
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(); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// Informs the ThreadPool that there's work to be executed for this scheduler. | |||
/// </summary> | |||
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); | |||
} | |||
/// <summary>Attempts to execute the specified task on the current thread.</summary> | |||
/// <param name="task">The task to be executed.</param> | |||
/// <param name="taskWasPreviouslyQueued"></param> | |||
/// <returns>Whether the task could be executed on the current thread.</returns> | |||
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); | |||
} | |||
/// <summary>Attempts to remove a previously scheduled task from the scheduler.</summary> | |||
/// <param name="task">The task to be removed.</param> | |||
/// <returns>Whether the task could be found and removed.</returns> | |||
protected sealed override bool TryDequeue(Task task) | |||
{ | |||
lock (_tasks) return _tasks.Remove(task); | |||
} | |||
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary> | |||
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } | |||
/// <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary> | |||
/// <returns>An enumerable of the tasks currently scheduled.</returns> | |||
protected sealed override IEnumerable<Task> 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); | |||
} | |||
} | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 时间戳转成时间类型 | |||
/// </summary> | |||
/// <param name="timeStamp"></param> | |||
/// <returns></returns> | |||
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"); | |||
} | |||
} | |||
} |
@@ -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<string, object> dic, string appId, ref Dictionary<string, object> outDic) | |||
{ | |||
MultipartFormDataContent mfdc = new MultipartFormDataContent(); | |||
StringBuilder sb = new StringBuilder(); | |||
if (dic != null && dic.Count > 0) | |||
{ | |||
var dicOrderBy = dic.OrderBy(z => z.Key); | |||
foreach (KeyValuePair<string, object> 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; | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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<GpsGeofenceStatus> _fenceStatusApiClient; | |||
private readonly GpsLocationHistoryAccessorClient<HisGpsAlarm> _alarmApiClient; | |||
private readonly IHostEnvironment _env; | |||
private readonly ILogger<FenceProcess> _logger; | |||
private readonly HttpHelperAsync _httpHelper; | |||
private readonly RedisUtil _redis; | |||
private readonly MqProcessMessage _serviceMqProcess; | |||
private readonly FenceConfig _positionConfig; | |||
public FenceProcess( | |||
IHostEnvironment env, | |||
ILogger<FenceProcess> logger, | |||
HttpHelperAsync httpHelper, | |||
RedisUtil redis, | |||
GpsCardAccessorClient<GpsGeofenceStatus> fenceStatusApiClient, | |||
GpsLocationHistoryAccessorClient<HisGpsAlarm> alarmApiClient, | |||
MqProcessMessage serviceMqProcess, | |||
IOptions<FenceConfig> 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<BaseModel>(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:<locationType={(int)loc.locationType}>不做处理"); | |||
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<GpsFencePoint> pointList = new List<GpsFencePoint>(); | |||
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}"); | |||
} | |||
} | |||
/// <summary> | |||
/// 进出记录处理 | |||
/// </summary> | |||
/// <param name="model">当前定位数据</param> | |||
/// <param name="fence">当前围栏信息</param> | |||
/// <param name="isInside">当前围栏状态[面到面]:(sInside => false:外面、true:里面)</param> | |||
/// <param name="isInside_old">当前围栏状态[点到面]:(isInside_old => false:外面、true:里面)</param> | |||
/// <param name="status">最近(缓存)围栏状态[数据\Redis]:(false:在里面、false:在外面)</param> | |||
/// <param name="isRecord">是否第一次触发围栏状态(进入)</param> | |||
/// <returns></returns> | |||
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<LastStatusInfo> 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<QueryFilterCondition> | |||
{ | |||
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<QueryFilterCondition> | |||
{ | |||
new QueryFilterCondition | |||
{ | |||
Key=nameof(GpsGeofenceStatus.GeofenceId), | |||
Value=gfs.GeofenceId, | |||
ValueType=QueryValueTypeEnum.String, | |||
Operator=QueryOperatorEnum.Equal | |||
} | |||
} | |||
}; | |||
List<GpsGeofenceStatus> 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<GeofenceStatus> 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); | |||
} | |||
} | |||
} | |||
} |
@@ -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<KafkaSubscribe> _logger; | |||
private readonly IHostEnvironment _env; | |||
private readonly IKafkaService _kafkaService; | |||
private readonly FenceProcess _feneProcess; | |||
public KafkaSubscribe( | |||
ILogger<KafkaSubscribe> 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); | |||
} | |||
} | |||
} |
@@ -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<DeviceFenceList> fenceList { get; set; } | |||
} | |||
public class DeviceFenceList | |||
{ | |||
public string geofenceId { get; set; } | |||
public string geofenceName { get; set; } | |||
public List<string> WifiInfo { get; set; } | |||
public int fenceType { get; set; } | |||
public int alarmType { get; set; } | |||
public string address { get; set; } | |||
public List<pointList> 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; } | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// Redis配置模板类 | |||
/// </summary> | |||
public class RedisConfig | |||
{ | |||
public string Server { get; set; } | |||
/// <summary> | |||
/// Redis server password | |||
/// </summary> | |||
public string Password { get; set; } | |||
/// <summary> | |||
/// Redis server database, default 0 | |||
/// </summary> | |||
public int? DefaultDatabase { get; set; } | |||
/// <summary> | |||
/// The asynchronous method automatically uses pipeline, and the 10W concurrent time is 450ms(welcome to feedback) | |||
/// </summary> | |||
public bool? AsyncPipeline { get; set; } | |||
/// <summary> | |||
/// Connection pool size, default 50 | |||
/// </summary> | |||
public int? Poolsize { get; set; } | |||
/// <summary> | |||
/// Idle time of elements in the connection pool(MS), suitable for connecting to remote redis server, default 20000 | |||
/// </summary> | |||
public int? IdleTimeout { get; set; } | |||
/// <summary> | |||
/// Connection timeout(MS), default 5000 | |||
/// </summary> | |||
public int? ConnectTimeout { get; set; } | |||
/// <summary> | |||
/// Send / receive timeout(MS), default 10000 | |||
/// </summary> | |||
public int? SyncTimeout { get; set; } | |||
/// <summary> | |||
/// Preheat connections, receive values such as preheat = 5 preheat 5 connections, default 5 | |||
/// </summary> | |||
public int? Preheat { get; set; } | |||
/// <summary> | |||
/// Follow system exit event to release automatically, default true | |||
/// </summary> | |||
public bool? AutoDispose { get; set; } | |||
/// <summary> | |||
/// Enable encrypted transmission, default false | |||
/// </summary> | |||
public bool? Ssl { get; set; } | |||
/// <summary> | |||
/// 是否尝试集群模式,阿里云、腾讯云集群需要设置此选项为 false, default true | |||
/// </summary> | |||
public bool? Testcluster { get; set; } | |||
/// <summary> | |||
/// Execution error, retry attempts, default 0 | |||
/// </summary> | |||
public int? Tryit { get; set; } | |||
/// <summary> | |||
/// Connection name, use client list command to view | |||
/// </summary> | |||
public string Name { get; set; } | |||
/// <summary> | |||
/// key前辍,所有方法都会附带此前辍,csredis.Set(prefix + "key", 111) | |||
/// </summary> | |||
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(); | |||
} | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 数据服务Host Url | |||
/// </summary> | |||
public string TelpoDataUrl { get; set; } | |||
/// <summary> | |||
/// Kafka服务地址 | |||
/// </summary> | |||
public string KafkaBootstrapServers { get; set; } | |||
public List<string> KafkaTopics { get; set; } | |||
public string KafkaGroupId { get; set; } | |||
public int MaxDegreeOfParallelism { get; set; } | |||
public int CacheDurationSeconds { get; set; } | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 报警类型 | |||
/// </summary> | |||
public enum AlarmType | |||
{ | |||
Online = 0, | |||
Offline = 9, | |||
SOS = 1,//求救 | |||
LowBattery = 2,//电量 | |||
Entry = 3, //进围栏 | |||
Exit = 4, //出围栏 | |||
PowerOff = 5//关机报警 | |||
} | |||
/// <summary> | |||
/// </summary> | |||
public enum AlarmTypeChinese | |||
{ | |||
上线 = 0, | |||
离线 = 9, | |||
SOS求救 = 1,//求救 | |||
低电报警 = 2,//电量 | |||
进入围栏 = 3, //进围栏 | |||
离开围栏 = 4, //出围栏 | |||
关机报警 = 5//关机报警 | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 围栏类型 | |||
/// </summary> | |||
public enum GeofenceType | |||
{ | |||
Entry = 1, | |||
Both = 2, | |||
Exit = 3 | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
using Newtonsoft.Json; | |||
namespace TelpoPush.Fence.Worker.Models.Enum | |||
{ | |||
/// <summary> | |||
/// 消息数据头 | |||
/// </summary> | |||
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; } | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 定位类型 | |||
/// </summary> | |||
public enum LocationType | |||
{ | |||
GPS = 1, | |||
LBS = 2, | |||
WIFI = 3, | |||
WifiPlus = 4 | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 中高实时心率 | |||
/// </summary> | |||
ZkRealHRMonitorTopic = 1 | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 数据类型,标识发布到kafka的消息的数据类型 | |||
/// </summary> | |||
public enum MqDataType : int | |||
{ | |||
/// <summary> | |||
/// 报警消息 | |||
/// </summary> | |||
AlarmInfo = 0, | |||
/// <summary> | |||
/// 温度数据信息 | |||
/// </summary> | |||
TemperatureInfo = 1, | |||
/// <summary> | |||
/// 步数信息 | |||
/// </summary> | |||
StepInfo = 2, | |||
/// <summary> | |||
/// 电量信息 | |||
/// </summary> | |||
BatteryLevelInfo = 3, | |||
/// <summary> | |||
/// 设备配置信息 | |||
/// </summary> | |||
DeviceConfigInfo = 4, | |||
/// <summary> | |||
/// 设备通话记录 | |||
/// </summary> | |||
DeviceCallLog = 5, | |||
/// <summary> | |||
/// 设备短信记录 | |||
/// </summary> | |||
DeviceSmsLog = 6, | |||
/// <summary> | |||
/// 位置信息 | |||
/// </summary> | |||
PositionInfo = 7, | |||
/// <summary> | |||
/// 支付 | |||
/// </summary> | |||
PayInfo = 8, | |||
/// <summary> | |||
/// 设备状态(offline,online) | |||
/// </summary> | |||
Status = 9, | |||
/// <summary> | |||
/// 设备激活状态(激活1,未激活0) | |||
/// </summary> | |||
Active = 10, | |||
/// <summary> | |||
/// 指令回调 | |||
/// </summary> | |||
reply = 11, | |||
/// <summary> | |||
/// 天气查询 | |||
/// </summary> | |||
Weather = 12, | |||
/// <summary> | |||
/// 短信阅读事件 | |||
/// </summary> | |||
ReadMsg = 13, | |||
/// <summary> | |||
/// 学习能力状态上报事件 | |||
/// </summary> | |||
StudyAINotifyStatusUpload = 14, | |||
/// <summary> | |||
/// 心率 | |||
/// </summary> | |||
HeartRateInfo = 15, | |||
/// <summary> | |||
/// 血氧 | |||
/// </summary> | |||
Spo2Info = 16, | |||
/// <summary> | |||
/// 周期性报体温数据。 | |||
/// </summary> | |||
Temperature1Info = 17, | |||
/// <summary> | |||
/// 周期心率。 | |||
/// </summary> | |||
HeartRate1Info = 18, | |||
/// <summary> | |||
/// 周期性血氧 | |||
/// </summary> | |||
Spo21Info = 19, | |||
/// <summary> | |||
/// 溺水状态 | |||
/// </summary> | |||
DrownReportInfo = 20, | |||
/// <summary> | |||
/// 手表佩戴状态 | |||
/// </summary> | |||
WearStatusInfo = 21, | |||
/// <summary> | |||
/// 血压 | |||
/// </summary> | |||
BloodPressInfo = 22, | |||
/// <summary> | |||
/// 周期性血压 | |||
/// </summary> | |||
BloodPress1Info = 23, | |||
/// <summary> | |||
/// 心理监测 | |||
/// </summary> | |||
PsychInfo = 24, | |||
/// <summary> | |||
/// AI呼叫回调结果 | |||
/// </summary> | |||
AiCallResult = 25, | |||
/// <summary> | |||
/// 越界上报(围栏进出告警) | |||
/// </summary> | |||
CrossBorder = 26, | |||
/// <summary> | |||
/// 运动数据上报 | |||
/// </summary> | |||
SportResult = 27, | |||
/// <summary> | |||
/// 运动数据上报 | |||
/// </summary> | |||
BloodSugar = 28, | |||
/// <summary> | |||
/// 中考实时心率数据上报 | |||
/// </summary> | |||
ZkRealHeartRate = 29, | |||
/// <summary> | |||
/// 绑定业务 | |||
/// </summary> | |||
BindDevice = 100 | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// DataType | |||
/// </summary> | |||
public const string DataType = "DataType"; | |||
/// <summary> | |||
/// OperType | |||
/// </summary> | |||
public const string OperType = "OperType"; | |||
/// <summary> | |||
/// AlarmType | |||
/// </summary> | |||
public const string AlarmTypes = "AlarmType"; | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
namespace TelpoPush.Fence.Worker.Models.Fence | |||
{ | |||
public static class CirclePolygonAlgorithm | |||
{ | |||
/// <summary> | |||
/// 判断圆和多边形是否相交 | |||
/// </summary> | |||
/// <param name="circle">圆心坐标</param> | |||
/// <param name="radius">圆半径</param> | |||
/// <param name="polygon">多边形各个点坐标</param> | |||
/// <returns></returns> | |||
public static bool IsIntersect(GpsFencePoint circle, double degree, IList<GpsFencePoint> 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<Tuple<GpsFencePoint, GpsFencePoint>> EnumeratePolygonLines(IList<GpsFencePoint> polygon) | |||
{ | |||
var list = new List<Tuple<GpsFencePoint, GpsFencePoint>>(); | |||
for (int i = 0; i < polygon.Count - 1; i++) | |||
{ | |||
list.Add(new Tuple<GpsFencePoint, GpsFencePoint>(polygon[i], polygon[i + 1])); | |||
} | |||
list.Add(new Tuple<GpsFencePoint, GpsFencePoint>(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)); | |||
} | |||
/// <summary> | |||
/// 点到线段的距离 | |||
/// </summary> | |||
/// <param name="pt"></param> | |||
/// <param name="ptStart">线段顶点1</param> | |||
/// <param name="ptEnd">线段顶点2</param> | |||
/// <returns></returns> | |||
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; | |||
} | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
namespace TelpoPush.Fence.Worker.Models.Fence | |||
{ | |||
public static class RayCastingAlgorithm | |||
{ | |||
public static bool IsWithin(GpsFencePoint pt, IList<GpsFencePoint> 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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
// | |||
/// <summary> | |||
/// 地球坐标系 ===> 火星坐标系统 (gps === > google) | |||
/// </summary> | |||
/// <param name="wgLat"></param> | |||
/// <param name="wgLon"></param> | |||
/// <param name="mgLat"></param> | |||
/// <param name="mgLon"></param> | |||
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; | |||
} | |||
///// <summary> | |||
///// 算法纠偏,无缓存 | |||
///// 0:高德纬度 | |||
///// 1:高德经度 | |||
///// 2:百度纬度 | |||
///// 3:百度经度 | |||
///// </summary> | |||
///// <param name="lat"></param> | |||
///// <param name="lng"></param> | |||
///// <returns></returns> | |||
//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; | |||
//} | |||
/// <summary> | |||
/// google to gps | |||
/// </summary> | |||
/// <param name="lat"></param> | |||
/// <param name="lng"></param> | |||
/// <param name="olat"></param> | |||
/// <param name="olng"></param> | |||
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; | |||
} | |||
/// <summary> | |||
/// 判断是否在中国 | |||
/// </summary> | |||
/// <param name="lat"></param> | |||
/// <param name="lon"></param> | |||
/// <returns></returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// Google to Baidu | |||
/// </summary> | |||
/// <param name="glat"></param> | |||
/// <param name="glng"></param> | |||
/// <param name="blat"></param> | |||
/// <param name="blng"></param> | |||
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; | |||
} | |||
/// <summary> | |||
/// Baidu to Google | |||
/// </summary> | |||
/// <param name="blat"></param> | |||
/// <param name="blng"></param> | |||
/// <param name="glat"></param> | |||
/// <param name="glng"></param> | |||
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); | |||
} | |||
/// <summary> | |||
/// 百度转原始经纬度 | |||
/// </summary> | |||
/// <param name="blat"></param> | |||
/// <param name="blng"></param> | |||
/// <param name="olat"></param> | |||
/// <param name="olng"></param> | |||
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); | |||
} | |||
/// <summary> | |||
/// 原始转百度 | |||
/// </summary> | |||
/// <param name="olat"></param> | |||
/// <param name="olng"></param> | |||
/// <param name="blat"></param> | |||
/// <param name="blng"></param> | |||
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); | |||
} | |||
} | |||
} |
@@ -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) | |||
/// <summary> | |||
/// 点是否在矩形区域中 | |||
/// </summary> | |||
/// <param name="pt"></param> | |||
/// <param name="rect"></param> | |||
/// <returns></returns> | |||
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); | |||
} | |||
/// <summary> | |||
/// 点是否在圆形区域中 | |||
/// </summary> | |||
/// <param name="pt"></param> | |||
/// <param name="circle"></param> | |||
/// <returns></returns> | |||
public static bool IsPointInCircle(GpsPoint pt, Circle circle) | |||
{ | |||
var i = circle.Center; | |||
var g = circle.Radius; | |||
var f = GetDistance(pt, i); | |||
return f <= g; | |||
} | |||
/// <summary> | |||
/// 圆和圆是否有交点 | |||
/// </summary> | |||
/// <param name="pt"></param> | |||
/// <param name="circle"></param> | |||
/// <returns></returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// 获取形状的矩形范围 | |||
/// </summary> | |||
/// <param name="pts"></param> | |||
/// <returns></returns> | |||
public static Bounds GetBound(IList<GpsPoint> 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)); | |||
} | |||
/// <summary> | |||
/// 经纬度测距算法 | |||
/// </summary> | |||
/// <param name="lon1"></param> | |||
/// <param name="lat1"></param> | |||
/// <param name="lon2"></param> | |||
/// <param name="lat2"></param> | |||
/// <returns>单位 米</returns> | |||
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; | |||
} | |||
/// <summary> | |||
/// 转化为弧度(rad) | |||
/// </summary> | |||
/// <param name="d">经度或纬度</param> | |||
/// <returns>弧度</returns> | |||
private static double rad(double d) | |||
{ | |||
return d * Math.PI / 180.0; | |||
} | |||
/// <summary> | |||
/// 计算两点的距离 单位:米 | |||
/// </summary> | |||
/// <param name="j"></param> | |||
/// <param name="h"></param> | |||
/// <returns></returns> | |||
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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
/// <summary> | |||
/// 计算点是否在多边形内 | |||
/// </summary> | |||
/// <param name="polygon">多边形点集合</param> | |||
/// <param name="pt">要判断的点</param> | |||
/// <returns>是否在多边形内</returns> | |||
public static bool IsPolygonContainsPoint(IList<GpsFencePoint> polygon, GpsFencePoint pt) | |||
{ | |||
return RayCastingAlgorithm.IsWithin(pt, polygon, true); | |||
} | |||
/// <summary> | |||
/// 判断圆是否和多边形相交 | |||
/// </summary> | |||
/// <param name="polygon">多边形点集合</param> | |||
/// <param name="ptCenter">要判断的圆的圆心点</param> | |||
/// <param name="radius">圆半径(单位米)</param> | |||
/// <returns></returns> | |||
public static bool IsPolygonIntersectCircle(IList<GpsFencePoint> polygon, GpsFencePoint ptCenter,int radius) | |||
{ | |||
var degree = CalcDisgreeFromRadius(radius); | |||
return CirclePolygonAlgorithm.IsIntersect(ptCenter, degree, polygon); | |||
} | |||
//---yl---新增-- 2022.6.13---- | |||
/// <summary> | |||
/// 判断圆和多边形是否相交 | |||
/// </summary> | |||
/// <param name="circle">圆心坐标</param> | |||
/// <param name="radius">圆半径</param> | |||
/// <param name="polygon">多边形各个点坐标</param> | |||
/// <returns></returns> | |||
public static bool IsCircle2Polygon(Point circle, double radius, IList<Point> 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<Tuple<Point, Point>> EnumeratePolygonLines(IList<Point> polygon) | |||
{ | |||
var list = new List<Tuple<Point, Point>>(); | |||
for (int i = 0; i < polygon.Count - 1; i++) | |||
{ | |||
list.Add(new Tuple<Point, Point>(polygon[i], polygon[i + 1])); | |||
} | |||
list.Add(new Tuple<Point, Point>(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)); | |||
} | |||
/// <summary> | |||
/// 点到线段的距离 | |||
/// </summary> | |||
/// <param name="pt"></param> | |||
/// <param name="ptStart">线段顶点1</param> | |||
/// <param name="ptEnd">线段顶点2</param> | |||
/// <returns></returns> | |||
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; | |||
} | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
namespace TelpoPush.Fence.Worker.Models.Fence | |||
{ | |||
/// <summary> | |||
/// gps点坐标 | |||
/// </summary> | |||
public class GpsFencePoint | |||
{ | |||
public GpsFencePoint() { } | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="lng">经度</param> | |||
/// <param name="lat">纬度</param> | |||
public GpsFencePoint(double lng,double lat) | |||
{ | |||
Longitude = lng; | |||
Latitude = lat; | |||
} | |||
/// <summary> | |||
/// 经度 | |||
/// </summary> | |||
public double Longitude { get; set; } | |||
/// <summary> | |||
/// 纬度 | |||
/// </summary> | |||
public double Latitude { get; set; } | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 进出围栏http推送(校安) | |||
/// </summary> | |||
public class PushXiaoAnPushTemplate | |||
{ | |||
/// <summary> | |||
/// 推送title | |||
/// </summary> | |||
[JsonProperty("title")] | |||
public string Title { get; set; } | |||
/// <summary> | |||
/// 地址 | |||
/// </summary> | |||
[JsonProperty("address")] | |||
public string Address { get; set; } | |||
/// <summary> | |||
/// 推送内容 | |||
/// </summary> | |||
[JsonProperty("textcontent")] | |||
public string TextContent { get; set; } | |||
/// <summary> | |||
/// 设备imei号 | |||
/// </summary> | |||
[JsonProperty("nickname")] | |||
public string NickName { get; set; } | |||
/// <summary> | |||
/// 时间 | |||
/// </summary> | |||
[JsonProperty("datetime")] | |||
public string DateTime { get; set; } | |||
} | |||
} |
@@ -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<FenceConfig>(config.GetSection("FenceConfig")); | |||
builder.Services.Configure<ServiceConfig>(config.GetSection("ServiceConfig")); | |||
builder.Services.Configure<RedisConfig>(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<HttpHelperAsync>(); | |||
builder.Services.AddSingleton<SqlMapper>(); | |||
builder.Services.AddSingleton<RedisUtil>(); | |||
builder.Services.AddSingleton<IKafkaService, KafkaService>(); | |||
builder.Services.AddSingleton<KafkaSubscribe>(); | |||
builder.Services.AddSingleton<MessageProducer>(); | |||
builder.Services.AddSingleton<MqProcessMessage>(); | |||
builder.Services.AddSingleton<FenceProcess>(); | |||
builder.Services.AddHostedService<Worker>(); | |||
var host = builder.Build(); | |||
host.Run(); | |||
} | |||
catch (Exception ex) | |||
{ | |||
Log.Fatal(ex, "Application start-up failed"); | |||
} | |||
finally | |||
{ | |||
Log.CloseAndFlush(); | |||
} |
@@ -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" | |||
} |
@@ -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()); | |||
/// <summary> | |||
/// 创建缓存项的文件 | |||
/// </summary> | |||
/// <param name="key">缓存Key</param> | |||
/// <param name="obj">object对象</param> | |||
public static void Set(string key, object value) | |||
{ | |||
if (key != null) | |||
{ | |||
cache.Set(key, value); | |||
} | |||
} | |||
/// <summary> | |||
/// 创建缓存项过期 | |||
/// </summary> | |||
/// <param name="key">缓存Key</param> | |||
/// <param name="obj">object对象</param> | |||
/// <param name="expires">过期时间(秒)</param> | |||
public static void Set(string key, object value, int expires) | |||
{ | |||
if (key != null) | |||
{ | |||
cache.Set(key, value, new MemoryCacheEntryOptions() | |||
//设置缓存时间,如果被访问重置缓存时间。设置相对过期时间x秒 | |||
.SetSlidingExpiration(TimeSpan.FromSeconds(expires))); | |||
} | |||
} | |||
/// <summary> | |||
/// 获取缓存对象 | |||
/// </summary> | |||
/// <param name="key">缓存Key</param> | |||
/// <returns>object对象</returns> | |||
public static object Get(string key) | |||
{ | |||
object val = null; | |||
if (key != null && cache.TryGetValue(key, out val)) | |||
{ | |||
return val; | |||
} | |||
else | |||
{ | |||
return default(object); | |||
} | |||
} | |||
/// <summary> | |||
/// 获取缓存对象 | |||
/// </summary> | |||
/// <typeparam name="T">T对象</typeparam> | |||
/// <param name="key">缓存Key</param> | |||
/// <returns></returns> | |||
public static T Get<T>(string key) | |||
{ | |||
object obj = Get(key); | |||
return obj == null ? default(T) : (T)obj; | |||
} | |||
/// <summary> | |||
/// 移除缓存项的文件 | |||
/// </summary> | |||
/// <param name="key">缓存Key</param> | |||
public static void Remove(string key) | |||
{ | |||
cache.Remove(key); | |||
} | |||
} | |||
} |
@@ -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<RedisUtil> _logger; | |||
private readonly ServiceConfig _configService; | |||
private readonly SqlMapper _sqlMapper; | |||
public RedisUtil(ILogger<RedisUtil> logger,IOptions<RedisConfig> optConfigRedis, IOptions<ServiceConfig> 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<DeviceInfoModel> GetGpsDevice(string imei) | |||
{ | |||
if (string.IsNullOrWhiteSpace(imei)) return null; | |||
string keyCache = $"{imei}_GpsDevice"; | |||
try | |||
{ | |||
var objCache = MemoryCacheUtil.Get<DeviceInfoModel>(keyCache); | |||
if (objCache == null) | |||
{ | |||
var obj = await RedisHelper.HGetAsync<DeviceInfoModel>(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<LastStatusInfo>(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<DeviceFenceModel> GetGpsDeviceFence(string imei) | |||
{ | |||
if (string.IsNullOrWhiteSpace(imei)) return null; | |||
var obj = await RedisHelper.HGetAsync<DeviceFenceModel>(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<DeviceFenceList> fenceList = new List<DeviceFenceList>(); | |||
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> pointList = new List<pointList>(); | |||
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<bool> GetIsDateStatus(DeviceTime model) | |||
{ | |||
bool result = true; | |||
try | |||
{ | |||
string file = model.imei; | |||
if (string.IsNullOrWhiteSpace(model.imei)) return false; | |||
var obj = await RedisHelper.HGetAsync<DeviceTime>(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<LastStatusInfo> GetFenceLastStatus(string fenceKey) | |||
{ | |||
if (string.IsNullOrWhiteSpace(fenceKey)) return null; | |||
var status = await RedisHelper.HGetAsync<LastStatusInfo>(CACHE_HASH_KEY_FENCE_LAST_STATUS, fenceKey); | |||
return status; | |||
} | |||
} | |||
} |
@@ -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<DeviceInfoModel>(sql, new { imei }); | |||
} | |||
} | |||
public List<string> 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<string>(sql, new { imei, geofence_id }).ToList(); | |||
} | |||
} | |||
public List<DeviceFenceModels> 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<DeviceFenceModels>(sql ).ToList(); | |||
} | |||
} | |||
public List<DeviceFenceModels> 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<DeviceFenceModels>(sql, new { imei }).ToList(); | |||
} | |||
} | |||
public List<pointList> 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<pointList>(sql, new { geofence_id }).ToList(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
using Confluent.Kafka; | |||
namespace TelpoPush.Fence.Worker.Service.Mq | |||
{ | |||
public interface IKafkaService | |||
{ | |||
Task SubscribeAsync(Action<string, string, Headers> messageFunc, CancellationToken cancellationToken); | |||
} | |||
} |
@@ -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"; | |||
} | |||
} |
@@ -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<KafkaService> logger; | |||
private readonly ServiceConfig _configService; | |||
public KafkaService(ILogger<KafkaService> _logger, IHostEnvironment _env, IOptions<ServiceConfig> 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<string, string, Headers> messageFunc, CancellationToken cancellationToken) | |||
{ | |||
List<string> topics = _configService.KafkaTopics; | |||
using (var consumer = new ConsumerBuilder<Ignore, string>(_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; | |||
} | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 消息生产者 | |||
/// </summary> | |||
public class MessageProducer | |||
{ | |||
private readonly ILogger<MessageProducer> _logger; | |||
private readonly ServiceConfig _configService; | |||
private readonly IProducer<Null, string> _producer; | |||
public MessageProducer(ILogger<MessageProducer> logger, IOptions<ServiceConfig> 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<Null, string>(config).Build(); | |||
} | |||
public Headers CreateHeader(Dictionary<string, int> 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<TopicModel> topic, object message) | |||
{ | |||
try | |||
{ | |||
foreach (var item in topic) | |||
{ | |||
// producer = new ProducerBuilder<Null, string>(config).Build(); | |||
await _producer.ProduceAsync(item.Topic, new Message<Null, string> | |||
{ | |||
Headers = item.Headers, | |||
Value = JsonConvert.SerializeObject(message) | |||
}); | |||
} | |||
} | |||
catch (ProduceException<Null, string> e) | |||
{ | |||
_logger.LogError($"推送到kafka失败,topic: {topic},\n message:{JsonConvert.SerializeObject(message)}: \n{e.Error.Reason}"); | |||
} | |||
} | |||
} | |||
} |
@@ -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<MqProcessMessage> _logger; | |||
private readonly MessageProducer _messageProducer; | |||
public MqProcessMessage(ILogger<MqProcessMessage> logger, MessageProducer producer) | |||
{ | |||
_logger = logger; | |||
_messageProducer = producer; | |||
} | |||
public async Task ProcessProperty(string imei, BaseModel model, HeadersDto headers) | |||
{ | |||
List<TopicModel> ls = new List<TopicModel>(); | |||
ls.Add(new TopicModel() | |||
{ | |||
Topic = "topic.push.property", | |||
Headers = _messageProducer.CreateHeader(new Dictionary<string, int> | |||
{ | |||
{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<TopicModel> ls = new List<TopicModel>(); | |||
ls.Add(new TopicModel() | |||
{ | |||
Topic = "topic.push", | |||
Headers = _messageProducer.CreateHeader(new Dictionary<string, int> | |||
{ | |||
{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<TopicModel> ls = new List<TopicModel>(); | |||
ls.Add(new TopicModel() | |||
{ | |||
Topic = "topic.push.wx", | |||
Headers = _messageProducer.CreateHeader(new Dictionary<string, int> | |||
{ | |||
{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)}"); | |||
} | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Worker"> | |||
<PropertyGroup> | |||
<TargetFramework>net8.0</TargetFramework> | |||
<!--<Nullable>enable</Nullable>--> | |||
<ImplicitUsings>enable</ImplicitUsings> | |||
<UserSecretsId>dotnet-TelpoPush.Fence.Worker-3429e355-573c-49e4-9ea5-28d6c4563829</UserSecretsId> | |||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | |||
<Nullable>disable</Nullable> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Confluent.Kafka" Version="2.4.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> | |||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" /> | |||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> | |||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" /> | |||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> | |||
<PackageReference Include="Serilog.Sinks.Exceptionless" Version="4.0.0" /> | |||
<PackageReference Include="CSRedisCore" Version="3.8.803" /> | |||
<PackageReference Include="Dapper" Version="2.1.35" /> | |||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> | |||
<PackageReference Include="MySql.Data" Version="8.4.0" /> | |||
<PackageReference Include="TelpoDataService.Util" Version="1.6.9.28-beta1" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Folder Include="Service\Biz\" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,25 @@ | |||
using TelpoPush.Fence.Worker.Handlers; | |||
namespace TelpoPush.Fence.Worker | |||
{ | |||
public class Worker : BackgroundService | |||
{ | |||
private readonly ILogger<Worker> _logger; | |||
KafkaSubscribe _kafkaSubscribe; | |||
public Worker(ILogger<Worker> 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); | |||
} | |||
} | |||
} | |||
} |
@@ -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" | |||
} |
@@ -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" | |||
} | |||
} | |||
} | |||
} |
@@ -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" | |||
} |
@@ -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" | |||
} |
@@ -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 |
@@ -0,0 +1,7 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<configuration> | |||
<packageSources> | |||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> | |||
<add key="company" value="http://139.224.254.18:3344" /> | |||
</packageSources> | |||
</configuration> |
@@ -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 |
@@ -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 |
@@ -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 |