@@ -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.Position.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.Position.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.Position.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.Position.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,62 @@ | |||||
using Confluent.Kafka; | |||||
using TelpoPush.Position.Worker.Common; | |||||
using TelpoPush.Position.Worker.Service.Mq; | |||||
namespace TelpoPush.Position.Worker.Handlers | |||||
{ | |||||
public class KafkaSubscribe | |||||
{ | |||||
private readonly ILogger<KafkaSubscribe> _logger; | |||||
private readonly IHostEnvironment _env; | |||||
private readonly IKafkaService _kafkaService; | |||||
private readonly PositionProcess _positionProcess; | |||||
public KafkaSubscribe( | |||||
ILogger<KafkaSubscribe> logger, IHostEnvironment env, | |||||
IKafkaService kafkaService, | |||||
PositionProcess positionProcess) | |||||
{ | |||||
_logger = logger; | |||||
_env = env; | |||||
_kafkaService = kafkaService; | |||||
_positionProcess = positionProcess; | |||||
} | |||||
public async Task SubscribeAsync() | |||||
{ | |||||
#if DEBUG | |||||
_logger.LogInformation("11312"); | |||||
var temp = new Headers(); | |||||
string topic = "topic.push.position"; | |||||
//temp.Add(new Header("DataType", new byte[] { 0, 0, 0, 0 })); | |||||
//temp.Add(new Header("AlarmType", new byte[] { 2, 0, 0, 0 })); | |||||
//string psych = "{\"messageId\":\"1790941606816612864\",\"topic\":\"topic.push.third\",\"time\":\"2024-05-16 11:05:27\",\"data\":{\"imei\":\"861281060093147\",\"atteryLowId\":\"861281060093147664577f9\",\"info\":\"设备电量低于15%\"}}"; | |||||
//await _positionProcess.SendPosition(psych, topic, temp); | |||||
//// 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 topic, string message, Headers headers) | |||||
{ | |||||
await _positionProcess.SendPosition(message, topic, headers); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,346 @@ | |||||
using Confluent.Kafka; | |||||
using Microsoft.Extensions.Options; | |||||
using Newtonsoft.Json; | |||||
using TelpoPush.Position.Worker.Common; | |||||
using TelpoPush.Position.Worker.Models.Config; | |||||
using TelpoPush.Position.Worker.Models.Enum; | |||||
using TelpoPush.Position.Worker.Models.MqTemplates; | |||||
using TelpoPush.Position.Worker.Models.PushTemplates; | |||||
using TelpoPush.Position.Worker.Service.Cache; | |||||
using TelpoPush.Position.Worker.Service.Mq; | |||||
namespace TelpoPush.Position.Worker.Handlers | |||||
{ | |||||
public class PositionProcess | |||||
{ | |||||
private readonly static object _syncLocker = new object(); | |||||
private readonly IHostEnvironment _env; | |||||
private readonly ILogger<PositionProcess> _logger; | |||||
private readonly HttpHelperAsync _httpHelper; | |||||
private readonly RedisUtil _redis; | |||||
private readonly MqProcessMessage _serviceMqProcess; | |||||
private readonly PositionConfig _positionConfig; | |||||
public PositionProcess( | |||||
IHostEnvironment env, | |||||
ILogger<PositionProcess> logger, | |||||
HttpHelperAsync httpHelper, | |||||
RedisUtil redis, | |||||
MqProcessMessage serviceMqProcess, | |||||
IOptions<PositionConfig> positionConfig | |||||
) | |||||
{ | |||||
_env = env; | |||||
_logger = logger; | |||||
_httpHelper = httpHelper; | |||||
_redis = redis; | |||||
_positionConfig = positionConfig.Value; | |||||
_serviceMqProcess = serviceMqProcess; | |||||
} | |||||
public async Task SendPosition(string? message, string topic, Headers headers) | |||||
{ | |||||
#region 数据初始验证 | |||||
bool isHandle = true; | |||||
BaseModel model = null; | |||||
string imei = ""; | |||||
if (!string.IsNullOrEmpty(message)) | |||||
{ | |||||
model = JsonConvert.DeserializeObject<BaseModel>(message); | |||||
if (model != null) | |||||
{ | |||||
var Jo = JsonConvert.DeserializeObject<Dictionary<string, object>>(model.data.ToString()); | |||||
if (Jo.ContainsKey("imei")) | |||||
imei = Jo["imei"].ToString(); | |||||
if (Jo.ContainsKey("Imei")) | |||||
imei = Jo["Imei"].ToString(); | |||||
else if (Jo.ContainsKey("nickname")) | |||||
imei = Jo["nickname"].ToString(); | |||||
if (string.IsNullOrEmpty(imei)) | |||||
{ | |||||
_logger.LogInformation($"[数据信息不完整] imei信息不存在:{message}"); | |||||
isHandle = false; | |||||
} | |||||
else | |||||
await _redis.GetGpsDevice(imei); | |||||
} | |||||
else | |||||
{ | |||||
_logger.LogInformation($"[数据信息不完整] 数据解析异常:{message}"); | |||||
isHandle = false; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
_logger.LogInformation($"[数据信息不完整] message数据异常:{message}"); | |||||
isHandle = false; | |||||
} | |||||
#endregion | |||||
if (isHandle) | |||||
{ | |||||
lock (_syncLocker) | |||||
{ | |||||
//Headers 解析 | |||||
HeadersDto headersDto = new HeadersDto(); | |||||
try | |||||
{ | |||||
foreach (var item in headers) | |||||
{ | |||||
if (item.Key == KafkaHeader.DataType) | |||||
headersDto.DataType = BitConverter.ToInt32(item.GetValueBytes(), 0); | |||||
else if (item.Key == KafkaHeader.AlarmType) | |||||
headersDto.AlarmType = BitConverter.ToInt32(item.GetValueBytes(), 0); | |||||
else if (item.Key == KafkaHeader.OperType) | |||||
headersDto.OperType = BitConverter.ToInt32(item.GetValueBytes(), 0); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
_logger.LogError($"当前工作线程Headers异常,{ex.Message}|{ex.Source}|{ex.StackTrace}"); | |||||
} | |||||
try | |||||
{ | |||||
#region 注释 | |||||
//string dataType = headersDto.DataType != null ? "_" + headersDto.DataType : ""; | |||||
//string alarmType = headersDto.AlarmType != null ? "_" + headersDto.AlarmType : ""; | |||||
//string operType = headersDto.OperType != null ? "_" + headersDto.OperType : ""; | |||||
//string key = dataType + alarmType + operType; | |||||
//var dataStatus = _redis.IsDateStatus(model, imei, key).Result; | |||||
//过滤 | |||||
//if (headersDto.DataType == (int)MqDataType.TemperatureInfo | |||||
// || headersDto.DataType == (int)MqDataType.Temperature1Info | |||||
// || headersDto.DataType == (int)MqDataType.BindDevice | |||||
// || headersDto.DataType == (int)MqDataType.PositionInfo | |||||
// || headersDto.DataType == (int)MqDataType.HeartRateInfo | |||||
// || headersDto.DataType == (int)MqDataType.HeartRate1Info | |||||
// || headersDto.DataType == (int)MqDataType.Spo2Info | |||||
// || headersDto.DataType == (int)MqDataType.Spo21Info | |||||
// || headersDto.DataType == (int)MqDataType.BloodPressInfo | |||||
// || headersDto.DataType == (int)MqDataType.BloodPress1Info | |||||
// || headersDto.DataType == (int)MqDataType.SportResult | |||||
// || headersDto.DataType == (int)MqDataType.BloodSugar | |||||
// ) | |||||
// dataStatus.isPush = true; | |||||
//dataStatus.isPush = true; | |||||
//if (dataStatus.isPush) | |||||
//{ | |||||
//switch (topic) | |||||
//{ | |||||
// case "topic.push.third": | |||||
// switch (headersDto.DataType) | |||||
// { | |||||
// case (int)MqDataType.AlarmInfo: //报警消息 | |||||
// break; | |||||
// case (int)MqDataType.TemperatureInfo: //体温消息 | |||||
// break; | |||||
// case (int)MqDataType.PositionInfo: //定位消息 | |||||
// // DataServicePusPosition(model, imei); | |||||
// break; | |||||
// case (int)MqDataType.StepInfo: //步数消息 | |||||
// break; | |||||
// case (int)MqDataType.BatteryLevelInfo: //电量消息 | |||||
// break; | |||||
// case (int)MqDataType.DeviceCallLog: //设备通话记录 | |||||
// break; | |||||
// case (int)MqDataType.DeviceSmsLog: //设备短信记录 | |||||
// break; | |||||
// case (int)MqDataType.DeviceConfigInfo: //设备配置信息 | |||||
// break; | |||||
// case (int)MqDataType.Status: //设备状态(offline,online) | |||||
// break; | |||||
// case (int)MqDataType.Active: //设备激活状态 | |||||
// break; | |||||
// case (int)MqDataType.reply: //指令回调 | |||||
// break; | |||||
// case (int)MqDataType.Weather: //天气查询 | |||||
// break; | |||||
// case (int)MqDataType.ReadMsg: //短消息阅读 | |||||
// break; | |||||
// case (int)MqDataType.StudyAINotifyStatusUpload: //学习能力状态 | |||||
// break; | |||||
// case (int)MqDataType.HeartRateInfo: //心率 | |||||
// break; | |||||
// case (int)MqDataType.HeartRate1Info: //周期性心率 | |||||
// break; | |||||
// case (int)MqDataType.Spo2Info: //血氧 | |||||
// break; | |||||
// case (int)MqDataType.Spo21Info: //周期性血氧 | |||||
// break; | |||||
// case (int)MqDataType.Temperature1Info: //周期性报体温数据 | |||||
// break; | |||||
// case (int)MqDataType.DrownReportInfo: //防溺水告警 | |||||
// break; | |||||
// case (int)MqDataType.WearStatusInfo: //手表未佩戴告警 | |||||
// break; | |||||
// case (int)MqDataType.BloodPressInfo: //血压 | |||||
// break; | |||||
// case (int)MqDataType.BloodPress1Info: //周期性血压 | |||||
// break; | |||||
// case (int)MqDataType.PsychInfo: //心理监测 | |||||
// break; | |||||
// case (int)MqDataType.AiCallResult: //AI呼叫结果回调 | |||||
// case (int)MqDataType.CrossBorder: //越界上报(围栏进出告警) | |||||
// break; | |||||
// case (int)MqDataType.SportResult: //运动数据上报 | |||||
// break; | |||||
// case (int)MqDataType.BindDevice: //绑定业务 | |||||
// break; | |||||
// case (int)MqDataType.BloodSugar: //血糖业务 | |||||
// break; | |||||
// default: | |||||
// break; | |||||
// } | |||||
// break; | |||||
// default: | |||||
// break; | |||||
//} | |||||
//} | |||||
//else | |||||
// _logger.LogInformation($"数据未处理(历史数据):{JsonConvert.SerializeObject(dataStatus)}"); | |||||
#endregion | |||||
switch (topic) | |||||
{ | |||||
case "topic.push.position": | |||||
switch (headersDto.DataType) | |||||
{ | |||||
case (int)MqDataType.PositionInfo: //定位消息 | |||||
DataServicePusPosition(model, imei); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
_logger.LogError($"当前工作线程异常: {ex.Message}|{ex.Source}|{ex.StackTrace}"); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//位置 | |||||
public async Task DataServicePusPosition(BaseModel model, string imei) | |||||
{ | |||||
var device = await _redis.GetGpsDevice(imei); | |||||
if (device != null) | |||||
{ | |||||
await _redis.SetPersonInfoHash(imei); //更行设备用户详情缓存 | |||||
var position = JsonConvert.DeserializeObject<MqPositionTemplate>(model.data.ToString()); | |||||
if ((int)position.locationType != 2) // 限制条件:locationType=2 LBS定位不推送 | |||||
{ | |||||
Dictionary<string, int> dicHeader = new Dictionary<string, int>(); | |||||
dicHeader.Add(MqHeader.DataType, (int)MqDataType.PositionInfo); | |||||
#region 定位-围栏推送服务 | |||||
var fenceObj = new | |||||
{ | |||||
messageId = model.messageId, | |||||
topic = "topic.push.position", | |||||
time = model.time, | |||||
data = new | |||||
{ | |||||
DeviceId = device.deviceId, | |||||
imei = position.imei, | |||||
wifiInfo = position.wifiMacs, | |||||
address = position.address, | |||||
baiduLatitude = position.baiduLatitude, | |||||
baiduLongitude = position.baiduLongitude, | |||||
gaodeLatitude = position.gaodeLatitude, | |||||
gaodeLongitude = position.gaodeLongitude, | |||||
originalLatitude = position.originalLatitude, | |||||
originalLongitude = position.originalLongitude, | |||||
locationType = position.locationType, | |||||
LastUpdate = model.time, | |||||
UtcDate = DateTime.Parse(model.time).AddHours(-8).ToString("yyyy-MM-dd HH:mm:ss"), | |||||
Radius = position.radius, | |||||
} | |||||
}; | |||||
await _serviceMqProcess.ProcessFenceServer(imei, fenceObj, dicHeader, "定位-围栏"); | |||||
#endregion | |||||
#region 定位-JAVA数据推送服务 | |||||
var settingInfos = await _redis.GetManufactorPushSettingHash(imei, _positionConfig.RzlManufactorId, (int)MqDataType.PositionInfo); | |||||
settingInfos = null; | |||||
if (settingInfos != null) | |||||
{ | |||||
Dictionary<string, object> dic = new Dictionary<string, object>(); | |||||
dic.Add("imei", imei); | |||||
dic.Add("locationType", (int)position.locationType); | |||||
dic.Add("altitude", position.radius); | |||||
dic.Add("gaodeLongitude", position.gaodeLongitude); | |||||
dic.Add("gaodeLatitude", position.gaodeLatitude); | |||||
dic.Add("originalLongitude", position.originalLongitude); | |||||
dic.Add("originalLatitude", position.originalLatitude); | |||||
dic.Add("baiduLongitude", position.baiduLongitude); | |||||
dic.Add("baiduLatitude", position.baiduLatitude); | |||||
dic.Add("address", position.address); | |||||
if (!string.IsNullOrEmpty(position.wifiMacs)) | |||||
{ | |||||
position.wifiMacs = position.wifiMacs.Replace(",|", "|").Trim(','); | |||||
dic.Add("wifiMacs", position.wifiMacs); | |||||
} | |||||
dic.Add("dataTime", model.time); | |||||
MultipartFormDataContent mfdc = Utils.GetMultipartFormDataContent(dic, _positionConfig.RzlManufactorId, ref dic); | |||||
var result = await _httpHelper.PostFormAsync(settingInfos.pushUrl, mfdc); | |||||
_logger.LogInformation($"[定位-RZL数据-替换JAVA推送服务<{imei}>] url:{settingInfos.pushUrl},参数:{JsonConvert.SerializeObject(dic)},结果:{result}"); | |||||
} | |||||
else | |||||
{ | |||||
if (!string.IsNullOrEmpty(position.wifiMacs)) | |||||
position.wifiMacs = position.wifiMacs.Replace(",|", "|").Trim(','); | |||||
PushPositionTemplate positionInfo = new PushPositionTemplate //上报存储位置信息 | |||||
{ | |||||
MessageId = model.messageId, | |||||
Imei = imei, | |||||
Altitude = position.radius, | |||||
BaiduLatitude = position.baiduLatitude, | |||||
BaiduLongitude = position.baiduLongitude, | |||||
GaodeLatitude = position.gaodeLatitude, | |||||
GaodeLongitude = position.gaodeLongitude, | |||||
LocationType = (int)position.locationType, | |||||
OriginalLatitude = position.originalLatitude, | |||||
OriginalLongitude = position.originalLongitude, | |||||
Address = position.address, | |||||
wifiMacs = position.wifiMacs, | |||||
Time = model.time | |||||
}; | |||||
await _serviceMqProcess.ProcessDataPushServer(imei, positionInfo, dicHeader, "定位"); | |||||
} | |||||
#endregion | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.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,35 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.CacheTemplates | |||||
{ | |||||
public class ManufactorPushSettingInfoModel | |||||
{ | |||||
public string id { get; set; } | |||||
public string manufactorId { get; set; } | |||||
public string manufactorName { get; set; } | |||||
public string imei { get; set; } | |||||
public List<PushSettingsItem> pushSettings { get; set; } | |||||
public int settingType { get; set; } | |||||
} | |||||
public class PushSettingsItem | |||||
{ | |||||
public string dataName { get; set; } | |||||
public int dataType { get; set; } | |||||
public List<SettingInfosItem> settingInfos { get; set; } | |||||
} | |||||
public class SettingInfosItem | |||||
{ | |||||
public bool pushFlag { get; set; } | |||||
public string pushStartTime { get; set; } | |||||
public string pushEndTime { get; set; } | |||||
public int pushType { get; set; } | |||||
public string pushUrl { get; set; } | |||||
public string remark { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.CacheTemplates | |||||
{ | |||||
public class PersonInfoModel | |||||
{ | |||||
public PersonModel person { get; set; } | |||||
public string time { get; set; } | |||||
} | |||||
public class PersonModel | |||||
{ | |||||
public string personId { get; set; } | |||||
public string serialno { get; set; } | |||||
public string personName { get; set; } | |||||
public string deviceId { get; set; } | |||||
public string nickName { get; set; } | |||||
public bool gender { get; set; } | |||||
public int height { get; set; } | |||||
public int weight { get; set; } | |||||
public string bornDate { get; set; } | |||||
public string school { get; set; } | |||||
public string grade { get; set; } | |||||
public string className { get; set; } | |||||
public string imagePath { get; set; } | |||||
public string imagePathSmall { get; set; } | |||||
public int age { get; set; } | |||||
public string createTime { get; set; } | |||||
public string remarks { get; set; } | |||||
public int ishypertension { get; set; } | |||||
public string emotion { get; set; } | |||||
public int profession { get; set; } | |||||
public int regularity { get; set; } | |||||
public int chronicDisease { get; set; } | |||||
public string apiUid { get; set; } | |||||
public string apiRemark { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.Config | |||||
{ | |||||
public class PositionConfig | |||||
{ | |||||
public string CoreServiceApiUrl { get; set; } | |||||
public string GpsWebApiUrl { get; set; } | |||||
public string RzlManufactorId { get; set; } | |||||
public string RzlPushTemperatureUrl { get; set; } | |||||
public string RzlPushTranspondUrl { get; set; } | |||||
public string RzlVoiceCallback { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,94 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.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,32 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.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 string KafkaSaslUsername { get; set; } | |||||
//public string KafkaSaslPassword { get; set; } | |||||
//public string KafkaSslCaLocation { get; set; } | |||||
/// <summary> | |||||
/// 默认缓存时间 | |||||
/// </summary> | |||||
public int CacheDurationSeconds { get; set; } | |||||
public int CacheDurationSeconds10 { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,16 @@ | |||||
using Newtonsoft.Json; | |||||
namespace TelpoPush.Position.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.Position.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.Position.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.Position.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,17 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.MqTemplates | |||||
{ | |||||
public class BaseModel | |||||
{ | |||||
public string messageId { get; set; } | |||||
public string topic { get; set; } | |||||
public string time { get; set; } | |||||
public object data { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,29 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.MqTemplates | |||||
{ | |||||
public class MqPositionTemplate | |||||
{ | |||||
public string imei { get; set; } | |||||
public int locationType { get; set; } | |||||
public string address { get; set; } | |||||
public int altitude { 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 postcode { get; set; } | |||||
public string hashParam { get; set; } | |||||
public int radius { get; set; } | |||||
public string province { get; set; } | |||||
public string city { get; set; } | |||||
public string district { get; set; } | |||||
public string wifiMacs { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.PushTemplates | |||||
{ | |||||
public class PushFencePositionTemplate | |||||
{ | |||||
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; } | |||||
} | |||||
} |
@@ -0,0 +1,73 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace TelpoPush.Position.Worker.Models.PushTemplates | |||||
{ | |||||
public class PushPositionTemplate | |||||
{ | |||||
/// <summary> | |||||
/// 服务跟踪Id | |||||
/// </summary> | |||||
public string MessageId { get; set; } | |||||
/// <summary> | |||||
/// 海拔 | |||||
/// </summary> | |||||
public double Altitude { get; set; } | |||||
/// <summary> | |||||
/// 定位类型 | |||||
/// </summary> | |||||
public int LocationType { get; set; } | |||||
/// <summary> | |||||
/// 设备IMEI | |||||
/// </summary> | |||||
public string Imei { get; set; } | |||||
/// <summary> | |||||
/// 时间 | |||||
/// yyyy-MM-dd HH:mm:ss | |||||
/// </summary> | |||||
public string Time { get; set; } | |||||
/// <summary> | |||||
/// 原始纬度 | |||||
/// </summary> | |||||
public decimal OriginalLatitude { get; set; } | |||||
/// <summary> | |||||
/// 原始经度 | |||||
/// </summary> | |||||
public decimal OriginalLongitude { get; set; } | |||||
/// <summary> | |||||
/// 百度地图纬度 | |||||
/// </summary> | |||||
public decimal BaiduLatitude { get; set; } | |||||
/// <summary> | |||||
/// 百度地图经度 | |||||
/// </summary> | |||||
public decimal BaiduLongitude { get; set; } | |||||
/// <summary> | |||||
/// 高德地图纬度 | |||||
/// </summary> | |||||
public decimal GaodeLatitude { get; set; } | |||||
/// <summary> | |||||
/// 高德地图经度 | |||||
/// </summary> | |||||
public decimal GaodeLongitude { get; set; } | |||||
/// <summary> | |||||
/// 地址 | |||||
/// </summary> | |||||
public string Address { get; set; } | |||||
public string wifiMacs { get; set; } | |||||
} | |||||
} |
@@ -1,9 +1,13 @@ | |||||
using Dapper; | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Newtonsoft.Json.Serialization; | using Newtonsoft.Json.Serialization; | ||||
using Serilog; | using Serilog; | ||||
using TelpoDataService.Util.Clients; | using TelpoDataService.Util.Clients; | ||||
using TelpoPush.Position.Worker; | using TelpoPush.Position.Worker; | ||||
using TelpoPush.Position.Worker.Common; | |||||
using TelpoPush.Position.Worker.Handlers; | |||||
using TelpoPush.Position.Worker.Service.Cache; | |||||
using TelpoPush.Position.Worker.Service.Mq; | |||||
#region ÈÕÖ¾ | #region ÈÕÖ¾ | ||||
@@ -76,14 +80,14 @@ try | |||||
builder.Services.AddSerilog(); | builder.Services.AddSerilog(); | ||||
builder.Services.AddHttpClient(); | 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<PositionProcess>(); | |||||
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<PositionProcess>(); | |||||
builder.Services.AddHostedService<Worker>(); | builder.Services.AddHostedService<Worker>(); | ||||
var host = builder.Build(); | var host = builder.Build(); | ||||
host.Run(); | host.Run(); | ||||
@@ -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.Position.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,208 @@ | |||||
using Microsoft.Extensions.Options; | |||||
using TelpoPush.Position.Worker.Models.CacheTemplates; | |||||
using TelpoPush.Position.Worker.Models.Config; | |||||
namespace TelpoPush.Position.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_PERSON = "TELPO#GPSDEVICE_PERSON_HASH"; | |||||
private const string CACHE_HASH_KEY_TELPO_MANUFACTOR_CONFIG = "TELPO#MANUFACTOR_CONFG_HASH"; | |||||
private const string CACHE_HASH_KEY_TELPO_GPSDEVICE_PUSHSITTTIGS = "TELPO#GPSDEVICE_PUSH_SITTINGS_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) | |||||
{ | |||||
RedisHelper.HSetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE, imei, deviceInfo); | |||||
MemoryCacheUtil.Set(keyCache, deviceInfo, _configService.CacheDurationSeconds10); | |||||
return deviceInfo; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
MemoryCacheUtil.Set(keyCache, obj, _configService.CacheDurationSeconds10); | |||||
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) | |||||
{ | |||||
RedisHelper.HSetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE, imei, deviceInfo); | |||||
MemoryCacheUtil.Set(keyCache, deviceInfo, _configService.CacheDurationSeconds10); | |||||
return deviceInfo; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public async Task<PersonInfoModel> SetPersonInfoHash(string imei) | |||||
{ | |||||
if (string.IsNullOrWhiteSpace(imei)) return null; | |||||
string keyCache = $"{imei}_PersonInfoHash"; | |||||
var personInfo = _sqlMapper.PersonInfo(imei); | |||||
PersonInfoModel model = new PersonInfoModel() | |||||
{ | |||||
person = personInfo, | |||||
time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") | |||||
}; | |||||
if (personInfo != null) | |||||
{ | |||||
await RedisHelper.HSetAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE_PERSON, imei, model); | |||||
MemoryCacheUtil.Set(keyCache, model, 60);//1分钟 | |||||
} | |||||
else | |||||
{ | |||||
await RedisHelper.HDelAsync(CACHE_HASH_KEY_TELPO_GPSDEVICE_PERSON, imei); | |||||
MemoryCacheUtil.Remove(keyCache); | |||||
} | |||||
return model; | |||||
} | |||||
public async Task<string> GetHealthyDeviceKey(string imei) | |||||
{ | |||||
if (string.IsNullOrWhiteSpace(imei)) return null; | |||||
string HealthyDeviceKey = $"Telpol:HealthyDeviceKey:{imei}"; | |||||
string keyCache = $"{imei}_HealthyDeviceKey"; | |||||
try | |||||
{ | |||||
var objCache = MemoryCacheUtil.Get<string>(keyCache); | |||||
if (objCache == null) | |||||
{ | |||||
string deviceKey = await RedisHelper.HGetAsync<string>(HealthyDeviceKey, "data"); | |||||
if (!string.IsNullOrEmpty(deviceKey)) | |||||
{ | |||||
MemoryCacheUtil.Set(keyCache, deviceKey, _configService.CacheDurationSeconds10); | |||||
return deviceKey; | |||||
} | |||||
} | |||||
return objCache; | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
_logger.LogError($"GetHealthyDeviceKey,key={imei},缓存异常,重新获取数据,{ex.Message}|{ex.Source}|{ex.StackTrace}"); | |||||
string deviceKey = await RedisHelper.HGetAsync<string>(HealthyDeviceKey, "data"); | |||||
if (!string.IsNullOrEmpty(deviceKey)) | |||||
{ | |||||
MemoryCacheUtil.Set(keyCache, deviceKey, _configService.CacheDurationSeconds10); | |||||
return deviceKey; | |||||
} | |||||
else | |||||
return ""; | |||||
} | |||||
} | |||||
public async Task<SettingInfosItem> GetManufactorPushSettingHash(string imei, string manufactorId, int dataType) | |||||
{ | |||||
if (string.IsNullOrWhiteSpace(manufactorId)) return null; | |||||
string keyCache = $"{manufactorId}_{dataType}_ManufactorPushSettingHash"; | |||||
SettingInfosItem settingInfos = null; | |||||
try | |||||
{ | |||||
var objCache = MemoryCacheUtil.Get<SettingInfosItem>(keyCache); | |||||
if (objCache == null) | |||||
{ | |||||
var obj = await RedisHelper.HGetAsync<ManufactorPushSettingInfoModel>(CACHE_HASH_KEY_TELPO_GPSDEVICE_PUSHSITTTIGS, manufactorId); | |||||
if (obj != null) | |||||
{ | |||||
if (obj.pushSettings.Any()) | |||||
{ | |||||
DateTime dt = DateTime.Now; | |||||
var settingsItem = obj.pushSettings.FirstOrDefault<PushSettingsItem>(x => x.dataType == dataType); | |||||
if (settingsItem != null) | |||||
{ | |||||
if (settingsItem.settingInfos.Any()) | |||||
{ | |||||
foreach (var item in settingsItem.settingInfos) | |||||
{ | |||||
DateTime startTime = DateTime.Parse($"{dt.Year}-{dt.Month}-{dt.Day} {item.pushStartTime}"); | |||||
DateTime endTime = DateTime.Parse($"{dt.Year}-{dt.Month}-{dt.Day} {item.pushEndTime}"); | |||||
if (item.pushFlag && (dt > startTime && dt < endTime)) | |||||
{ | |||||
settingInfos = item; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if (settingInfos != null) | |||||
MemoryCacheUtil.Set(keyCache, settingInfos, _configService.CacheDurationSeconds10); | |||||
else | |||||
MemoryCacheUtil.Remove(keyCache); | |||||
} | |||||
} | |||||
else | |||||
settingInfos = objCache; | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
_logger.LogError($"GetManufactorPushSettingHash(imei={imei},dataType={dataType}),key={manufactorId},缓存异常,重新获取数据,{ex.Message}|{ex.Source}|{ex.StackTrace}"); | |||||
var obj = await RedisHelper.HGetAsync<ManufactorPushSettingInfoModel>(CACHE_HASH_KEY_TELPO_GPSDEVICE_PUSHSITTTIGS, manufactorId); | |||||
if (obj != null) | |||||
{ | |||||
if (obj.pushSettings.Any()) | |||||
{ | |||||
DateTime dt = DateTime.Now; | |||||
var settingsItem = obj.pushSettings.FirstOrDefault<PushSettingsItem>(x => x.dataType == dataType); | |||||
if (settingsItem != null) | |||||
{ | |||||
if (settingsItem.settingInfos.Any()) | |||||
{ | |||||
foreach (var item in settingsItem.settingInfos) | |||||
{ | |||||
DateTime startTime = DateTime.Parse($"{dt.Year}-{dt.Month}-{dt.Day} {item.pushStartTime}"); | |||||
DateTime endTime = DateTime.Parse($"{dt.Year}-{dt.Month}-{dt.Day} {item.pushEndTime}"); | |||||
if (item.pushFlag && (dt > startTime && dt < endTime)) | |||||
{ | |||||
settingInfos = item; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if (settingInfos != null) | |||||
MemoryCacheUtil.Set(keyCache, settingInfos, _configService.CacheDurationSeconds10); | |||||
else | |||||
MemoryCacheUtil.Remove(keyCache); | |||||
} | |||||
} | |||||
return settingInfos; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
using Dapper; | |||||
using MySql.Data.MySqlClient; | |||||
using System.Data; | |||||
using TelpoPush.Position.Worker.Models.CacheTemplates; | |||||
namespace TelpoPush.Position.Worker.Service.Cache | |||||
{ | |||||
public class SqlMapper | |||||
{ | |||||
private readonly IConfiguration _config; | |||||
private static string gps_conn = ""; | |||||
private static string telcommon_conn = ""; | |||||
private static string healthy_conn = ""; | |||||
public SqlMapper(IConfiguration config) | |||||
{ | |||||
_config = config; | |||||
gps_conn = _config["ConnectionStrings:DB_Connection_String"].ToString(); | |||||
telcommon_conn = _config["ConnectionStrings:Telpo_common_ConnString"].ToString(); | |||||
healthy_conn = _config["ConnectionStrings:Telpo_Healthy_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 PersonModel PersonInfo(string imei) | |||||
{ | |||||
using (IDbConnection connection = new MySqlConnection(gps_conn)) | |||||
{ | |||||
var sql = @"SELECT person_id personId, serialno, person_name personName, device_id deviceId, nick_name nickName, gender, height, weight, born_date bornDate, school, grade, class_name className, image_path imagePath,image_path_small imagePathSmall, age, create_time createTime, remarks, ishypertension, emotion,profession,regularity,chronic_disease chronicDisease,api_uid apiUid,api_remark apiRemark FROM gps_person WHERE serialno=@imei"; | |||||
return connection.QueryFirstOrDefault<PersonModel>(sql, new { imei }); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
using Confluent.Kafka; | |||||
namespace TelpoPush.Position.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.Position.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,135 @@ | |||||
using Confluent.Kafka; | |||||
using Microsoft.Extensions.Options; | |||||
using Newtonsoft.Json; | |||||
using TelpoPush.Position.Worker.Models.Config; | |||||
namespace TelpoPush.Position.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, | |||||
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) => | |||||
{ | |||||
Console.WriteLine($"Error: {e.Reason}"); | |||||
logger.LogError($"Error: {e.Reason}"); | |||||
}) | |||||
.SetStatisticsHandler((_, json) => | |||||
{ | |||||
Console.WriteLine($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} > 消息监听中.."); | |||||
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); | |||||
Console.WriteLine($" - 回收了 kafka 的分区: {partitionsStr}"); | |||||
logger.LogInformation($" - 回收了 kafka 分区: {partitionsStr}"); | |||||
}) | |||||
.Build()) | |||||
{ | |||||
consumer.Subscribe(topics); | |||||
try | |||||
{ | |||||
while (true) | |||||
{ | |||||
try | |||||
{ | |||||
var consumeResult = consumer.Consume(cancellationToken); | |||||
int DataType = -1, AlarmType = -1, OperType = -1; | |||||
foreach (var item in consumeResult?.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 '{consumeResult.Topic}', message '{consumeResult.Message?.Value}' , headers '{JsonConvert.SerializeObject(Headers)}', at: '{consumeResult?.TopicPartitionOffset}'."); | |||||
if (consumeResult.IsPartitionEOF) | |||||
{ | |||||
logger.LogInformation($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} 已经到底了:{consumeResult.Topic}, partition {consumeResult.Partition}, offset {consumeResult.Offset}."); | |||||
Console.WriteLine($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} 已经到底了:{consumeResult.Topic}, partition {consumeResult.Partition}, offset {consumeResult.Offset}."); | |||||
continue; | |||||
} | |||||
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); | |||||
} | |||||
} | |||||
} | |||||
catch (ConsumeException e) | |||||
{ | |||||
logger.LogError($"Consume error: {e.Error.Reason}"); | |||||
Console.WriteLine($"Consume error: {e.Error.Reason}"); | |||||
} | |||||
} | |||||
} | |||||
catch (OperationCanceledException) | |||||
{ | |||||
logger.LogError("Closing consumer."); | |||||
Console.WriteLine("Closing consumer."); | |||||
consumer.Close(); | |||||
} | |||||
} | |||||
await Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,77 @@ | |||||
using Confluent.Kafka; | |||||
using Microsoft.Extensions.Options; | |||||
using Newtonsoft.Json; | |||||
using TelpoPush.Position.Worker.Models.Config; | |||||
namespace TelpoPush.Position.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,70 @@ | |||||
using Newtonsoft.Json; | |||||
using TelpoPush.Position.Worker.Models.Enum; | |||||
using TelpoPush.Position.Worker.Models.MqTemplates; | |||||
namespace TelpoPush.Position.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($"【成功】Third推送(topic.property):IMEI<{imei}>,pushData:{JsonConvert.SerializeObject(model)}"); | |||||
} | |||||
public async Task ProcessDataPushServer(string imei, object model, Dictionary<string, int> headers, string tag) | |||||
{ | |||||
List<TopicModel> ls = new List<TopicModel>(); | |||||
ls.Add(new TopicModel() | |||||
{ | |||||
Topic = "topic.push", | |||||
Headers = _messageProducer.CreateHeader(headers) | |||||
}); | |||||
await _messageProducer.ProduceAsync(ls, model); | |||||
_logger.LogInformation($"【{tag}-成功】Third推送(topic.push):IMEI<{imei}>,Header<{JsonConvert.SerializeObject(headers)}>,pushData:{JsonConvert.SerializeObject(model)}"); | |||||
} | |||||
public async Task ProcessFenceServer(string imei, object model, Dictionary<string, int> headers, string tag) | |||||
{ | |||||
List<TopicModel> ls = new List<TopicModel>(); | |||||
ls.Add(new TopicModel() | |||||
{ | |||||
Topic = "topic.push.fence", | |||||
Headers = _messageProducer.CreateHeader(headers) | |||||
}); | |||||
await _messageProducer.ProduceAsync(ls, model); | |||||
_logger.LogInformation($"【{tag}-成功】Third推送(topic.push.fence):IMEI<{imei}>,Header<{JsonConvert.SerializeObject(headers)}>,pushData:{JsonConvert.SerializeObject(model)}"); | |||||
} | |||||
public async Task ProcessThirdhServer(string imei, object model, Dictionary<string, int> headers, string tag) | |||||
{ | |||||
List<TopicModel> ls = new List<TopicModel>(); | |||||
ls.Add(new TopicModel() | |||||
{ | |||||
Topic = "topic.push.third", | |||||
Headers = _messageProducer.CreateHeader(headers) | |||||
}); | |||||
await _messageProducer.ProduceAsync(ls, model); | |||||
_logger.LogInformation($"【{tag}-成功】Third推送(topic.push.third):IMEI<{imei}>,Header<{JsonConvert.SerializeObject(headers)}>,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.Position.Worker.Service.Mq | |||||
{ | |||||
public class TopicModel | |||||
{ | |||||
public string Topic { get; set; } | |||||
public Headers Headers { get; set; } | |||||
} | |||||
} |
@@ -23,4 +23,8 @@ | |||||
<PackageReference Include="MySql.Data" Version="8.4.0" /> | <PackageReference Include="MySql.Data" Version="8.4.0" /> | ||||
<PackageReference Include="TelpoDataService.Util" Version="1.6.9.28-beta1" /> | <PackageReference Include="TelpoDataService.Util" Version="1.6.9.28-beta1" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<Folder Include="Service\Biz\" /> | |||||
</ItemGroup> | |||||
</Project> | </Project> |
@@ -1,22 +1,23 @@ | |||||
using TelpoPush.Position.Worker.Handlers; | |||||
namespace TelpoPush.Position.Worker | namespace TelpoPush.Position.Worker | ||||
{ | { | ||||
public class Worker : BackgroundService | public class Worker : BackgroundService | ||||
{ | { | ||||
private readonly ILogger<Worker> _logger; | private readonly ILogger<Worker> _logger; | ||||
KafkaSubscribe _kafkaSubscribe; | |||||
public Worker(ILogger<Worker> logger) | |||||
public Worker(ILogger<Worker> logger, KafkaSubscribe kafkaSubscribe) | |||||
{ | { | ||||
_logger = logger; | _logger = logger; | ||||
_kafkaSubscribe = kafkaSubscribe; | |||||
} | } | ||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
{ | { | ||||
while (!stoppingToken.IsCancellationRequested) | while (!stoppingToken.IsCancellationRequested) | ||||
{ | { | ||||
if (_logger.IsEnabled(LogLevel.Information)) | |||||
{ | |||||
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); | |||||
} | |||||
await _kafkaSubscribe.SubscribeAsync(); | |||||
await Task.Delay(1000, stoppingToken); | await Task.Delay(1000, stoppingToken); | ||||
} | } | ||||
} | } | ||||