You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
15 KiB
362 lines
15 KiB
using Alarm.Application.RequestDto; |
|
using Alarm.Application.ResponeDto; |
|
using Common.Shared.Application.DaHua; |
|
using Common.Shared.DomainService.MqttClient; |
|
using Microsoft.Extensions.Configuration; |
|
using Microsoft.Extensions.Logging; |
|
using Org.BouncyCastle.Crypto.Parameters; |
|
using Org.BouncyCastle.Security; |
|
using System.Net.Http.Json; |
|
using System.Security.Cryptography; |
|
using System.Text.Json; |
|
using WeiCloud.Core.BaseModels; |
|
|
|
namespace Alarm.DomainService.DahAlarm |
|
{ |
|
public class DahuaGeneralCtlService : IDahuaGeneralCtlService |
|
{ |
|
private readonly ILogger<DahuaGeneralCtlService> _logger; |
|
private readonly IConfiguration _configuration; |
|
private readonly HttpClient _http; |
|
private readonly MQTTClient _mqttClient; |
|
private readonly IMqttClientService _mqttClientService; |
|
private string mqttHostIp; |
|
private int mqttHostPort; |
|
private int mqttTimeout; |
|
private string mqttUsername; |
|
private string mqttPassword; |
|
private string mqttClientId; |
|
private string topicName; |
|
|
|
/// <summary> |
|
/// |
|
/// </summary> |
|
/// <param name="logger"></param> |
|
/// <param name="configuration"></param> |
|
/// <param name="http"></param> |
|
/// <param name="mQTTClient"></param> |
|
public DahuaGeneralCtlService(ILogger<DahuaGeneralCtlService> logger, IConfiguration configuration, HttpClient http, MQTTClient mQTTClient, IMqttClientService mqttClientService) |
|
{ |
|
_logger = logger; |
|
_configuration = configuration; |
|
_http = http; |
|
_mqttClient = mQTTClient; |
|
mqttHostIp = _configuration["SubscribeMQTT:HostIP"]!;//IP地址 |
|
mqttHostPort = _configuration["SubscribeMQTT:HostPort"].ToInt();//端口号 |
|
mqttTimeout = _configuration["SubscribeMQTT:Timeout"].ToInt();//超时时间 |
|
mqttUsername = _configuration["SubscribeMQTT:UserName"]!;//用户名 |
|
mqttPassword = _configuration["SubscribeMQTT:Password"]!;//密码 |
|
mqttClientId = _configuration["SubscribeMQTT:ClientId"]!; |
|
topicName = _configuration["SubscribeMQTT:TopicName"]!; |
|
_mqttClientService = mqttClientService; |
|
} |
|
|
|
/// <summary> |
|
/// 获取公钥 |
|
/// </summary> |
|
/// <returns></returns> |
|
/// <exception cref="NotImplementedException"></exception> |
|
public async Task<DaHApiResult<PublicKeyDto>> GetPublicKey() |
|
{ |
|
DaHApiResult<PublicKeyDto> result = new() { Success = true, Code = "0", Data = new PublicKeyDto() { PublicKey = "" } }; |
|
try |
|
{ |
|
var url = $"https://{_configuration["DahuaAuth:Host"]}/evo-apigw/evo-oauth/1.0.0/oauth/public-key"; |
|
|
|
using var resp = await _http.GetAsync(url); |
|
resp.EnsureSuccessStatusCode(); |
|
var json = await resp.Content.ReadAsStringAsync(); |
|
var envelope = JsonSerializer.Deserialize<DaHApiResult<PublicKeyDto>>(json, new JsonSerializerOptions |
|
{ |
|
PropertyNameCaseInsensitive = true |
|
}); |
|
|
|
if (envelope?.Data?.PublicKey is null or { Length: 0 }) |
|
{ |
|
_logger.LogWarning("获取大华公钥失败,返回结果:{Result}", json); |
|
result.Success = false; |
|
result.Code = "1001"; |
|
result.Msg = "获取大华公钥失败"; |
|
|
|
return result; |
|
} |
|
|
|
result.Data.PublicKey = envelope.Data.PublicKey; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogWarning(ex, "大华平台获取公钥出错"); |
|
} |
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// 获取token |
|
/// </summary> |
|
/// <param name="dto"></param> |
|
/// <returns></returns> |
|
public async Task<DaHApiResult<LoginResDto>> GetToken(LoginRequestDto dto) |
|
{ |
|
DaHApiResult<LoginResDto> result = new DaHApiResult<LoginResDto>() { Success = true, Code = "0" }; |
|
if (dto is null) |
|
{ |
|
result.Success = false; |
|
|
|
result.Code = "1002"; |
|
result.Msg = "请求参数不能为空"; |
|
_logger.LogWarning("获取大华登录令牌失败,参数不能为空"); |
|
return result; |
|
} |
|
if (string.IsNullOrWhiteSpace(dto.Password)) |
|
{ |
|
result.Success = false; |
|
result.Code = "1003"; |
|
result.Msg = "密码不能为空"; |
|
_logger.LogWarning("获取大华登录令牌失败,密码不能为空"); |
|
return result; |
|
} |
|
try |
|
{ |
|
//1. 获取公钥 |
|
DaHApiResult<PublicKeyDto> publicKeyResult = await GetPublicKey(); |
|
if (!publicKeyResult.Success) |
|
{ |
|
result.Code = "500"; |
|
result.Msg = publicKeyResult.Msg; |
|
_logger.LogWarning("获取大华公钥失败:{Msg}", publicKeyResult.Msg); |
|
} |
|
//2. 鉴权 |
|
dto.PublicKey = publicKeyResult.Data.PublicKey; |
|
|
|
var url = $"https://{_configuration["DahuaAuth:Host"]}/evo-apigw/evo-oauth/1.0.0/oauth/extend/token"; |
|
//必须加密 |
|
dto.Password = EncryptByPublicKey(dto.Password, dto.PublicKey!); |
|
using var resp = await _http.PostAsJsonAsync(url, dto); |
|
resp.EnsureSuccessStatusCode(); |
|
|
|
var tokenInfo = await resp.Content.ReadFromJsonAsync<DaHApiResult<LoginResDto>>(); |
|
|
|
if (tokenInfo == null || !result.Success || result.Code != "0") |
|
{ |
|
result.Success = false; |
|
result.Code = "1004"; |
|
result.Msg = "获取大华登录令牌失败"; |
|
_logger.LogWarning("获取大华登录令牌失败,返回结果:{Result}", result); |
|
} |
|
//固定的拼接方式 |
|
result.Data.AccessToken = string.Concat(tokenInfo!.Data.TokenType, " ", tokenInfo.Data.AccessToken); |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogError(ex, "获取大华登录令牌出错"); |
|
result.Success = false; |
|
result.Code = "1004"; |
|
result.Msg = "获取大华登录令牌失败"; |
|
} |
|
return result; |
|
} |
|
|
|
/// <summary> |
|
/// 新增报警事件订阅 |
|
/// </summary> |
|
/// <param name="dto"></param> |
|
/// <returns></returns> |
|
/// <exception cref="NotImplementedException"></exception> |
|
public async Task<DaHApiResult<object>> AddSubscribeEvent(string accessToken) |
|
{ |
|
var result = new DaHApiResult<object> { Success = true, Code = "0", Data = new object() }; |
|
|
|
try |
|
{ |
|
var baseHost = _configuration["DahuaEvent:Host"]; |
|
if (string.IsNullOrWhiteSpace(baseHost)) |
|
{ |
|
return new DaHApiResult<object> |
|
{ |
|
Success = false, |
|
Code = "2001", |
|
Msg = "配置 DahuaEvent:Host 为空" |
|
}; |
|
} |
|
|
|
var url = $"https://{baseHost}/evo-apigw/evo-event/1.0.0/subscribe/mqinfo"; |
|
|
|
var req = new SubscribeReqDto |
|
{ |
|
Param = new SubscribeParam |
|
{ |
|
Monitors = new List<MonitorEntry> |
|
{ |
|
new() |
|
{ |
|
Monitor = _configuration["DahuaEvent:Callback"]!, |
|
Events = |
|
[ |
|
new() |
|
{ |
|
Category = "alarm", |
|
SubscribeAll = 1, |
|
DomainSubscribe = 2, |
|
|
|
EventType = 1 |
|
} |
|
] |
|
} |
|
}, |
|
Subsystem = new SubsystemInfo { SubsystemType = 0, Name = _configuration["DahuaEvent:SubsystemName"]!, Magic = _configuration["DahuaEvent:SubsystemMagic"]! } |
|
} |
|
}; |
|
|
|
// —— 发起请求 —— |
|
using var request = new HttpRequestMessage(HttpMethod.Post, url) |
|
{ |
|
Content = JsonContent.Create(req, options: new JsonSerializerOptions |
|
{ |
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, |
|
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase |
|
}) |
|
}; |
|
|
|
// 大华网关要求:Authorization 放完整值(Bearer xxx) |
|
request.Headers.TryAddWithoutValidation("Authorization", accessToken); |
|
request.Headers.Accept.ParseAdd("application/json"); |
|
|
|
using var resp = await _http.SendAsync(request); |
|
var body = await resp.Content.ReadFromJsonAsync<DaHApiResult<object>>(); |
|
|
|
if (!resp.IsSuccessStatusCode || body is null) |
|
{ |
|
return new DaHApiResult<object> |
|
{ |
|
Success = false, |
|
Code = "2002", |
|
Msg = $"HTTP {resp.StatusCode} 或响应为空" |
|
}; |
|
} |
|
|
|
if (!(body.Success && string.Equals(body.Code, "0", StringComparison.OrdinalIgnoreCase))) |
|
{ |
|
return new DaHApiResult<object> |
|
{ |
|
Success = false, |
|
Code = body.Code ?? "2003", |
|
Msg = body.Msg ?? "订阅失败" |
|
}; |
|
} |
|
|
|
result.Data = body; |
|
return result; |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogError(ex, "大华事件订阅异常"); |
|
return new DaHApiResult<object> |
|
{ |
|
Success = false, |
|
Code = "2005", |
|
Msg = "事件订阅异常" |
|
}; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 报警事件订阅回调处理 |
|
/// </summary> |
|
/// <param name="dto"></param> |
|
/// <returns></returns> |
|
/// <exception cref="NotImplementedException"></exception> |
|
public async Task<ApiResult<bool>> HandleAsync(EventEnvelopeDto dto) |
|
{ |
|
ApiResult<bool> result = new() { Code = 200, Msg = "接口调用成功", Data = true }; |
|
try |
|
{ |
|
if (dto is null) |
|
{ |
|
result.Code = 500; |
|
result.Msg = "请求参数不能为空"; |
|
result.Data = false; |
|
_logger.LogWarning("大华报警事件订阅回调处理失败,参数不能为空"); |
|
return result; |
|
} |
|
|
|
if (dto.Info is not null) |
|
{ |
|
//这是大华的残卫报警类型 |
|
if (dto.Info.AlarmType == 4321) |
|
{ |
|
//拼接物联平台标准的mqtt消息格式 |
|
var payload = "[{\"taglabel\":\"" + dto.Info.DeviceCode + " + \".alart.\" + " + dto.Info.DeviceName + "\",\"value\":\"" + dto.Info.AlarmStat + "\",\"time\":\"" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + "\"}]"; |
|
|
|
await _mqttClient.EnsureConnectedAsync(mqttHostIp, mqttHostPort, mqttUsername, mqttPassword, topicName, mqttClientId); |
|
|
|
await _mqttClientService.PublishAsync("/zrh/sun/alarm", payload); |
|
} |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
_logger.LogError(ex, "大华报警事件订阅回调处理异常"); |
|
result.Code = 500; |
|
result.Msg = "大华报警事件订阅回调处理异常"; |
|
result.Data = false; |
|
} |
|
return result; |
|
} |
|
|
|
#region RES加密 |
|
|
|
private static String EncryptByPublicKey(String context, String publicKey) |
|
{ |
|
RSACryptoServiceProvider rsa = new(); |
|
|
|
rsa.ImportParameters(FromXmlStringExtensions(ConvertToXmlPublicJavaKey(publicKey))); |
|
byte[] byteText = System.Text.Encoding.UTF8.GetBytes(context); |
|
byte[] byteEntry = rsa.Encrypt(byteText, false); |
|
return Convert.ToBase64String(byteEntry); |
|
} |
|
|
|
public static RSAParameters FromXmlStringExtensions(string xmlString) |
|
{ |
|
RSAParameters parameters = new RSAParameters(); |
|
|
|
System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument(); |
|
xmlDoc.LoadXml(xmlString); |
|
|
|
if (xmlDoc.DocumentElement!.Name.Equals("RSAKeyValue")) |
|
{ |
|
foreach (System.Xml.XmlNode node in xmlDoc.DocumentElement.ChildNodes) |
|
{ |
|
switch (node.Name) |
|
{ |
|
case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
throw new Exception("Invalid XML RSA key."); |
|
} |
|
|
|
return parameters; |
|
} |
|
|
|
public static string ConvertToXmlPublicJavaKey(string publicJavaKey) |
|
{ |
|
RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicJavaKey)); |
|
string xmlpublicKey = string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>", |
|
Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()), |
|
Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned())); |
|
Console.WriteLine(xmlpublicKey); |
|
return xmlpublicKey; |
|
} |
|
|
|
#endregion RES加密 |
|
} |
|
} |