diff --git a/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/AppHost.cs b/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/AppHost.cs index d391d90..2b1b935 100644 --- a/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/AppHost.cs +++ b/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/AppHost.cs @@ -3,10 +3,13 @@ var builder = DistributedApplication.CreateBuilder(args); var apiService = builder.AddProject("apiservice") .WithHttpHealthCheck("/health"); +var videoapi = builder.AddProject("videoapi"); + builder.AddProject("webfrontend") + .WithReference(videoapi) .WithExternalHttpEndpoints() .WithHttpHealthCheck("/health") .WithReference(apiService) .WaitFor(apiService); -builder.Build().Run(); +builder.Build().Run(); \ No newline at end of file diff --git a/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/Manage.AppHost.AppHost.csproj b/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/Manage.AppHost.AppHost.csproj index 82a2a9c..140da3f 100644 --- a/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/Manage.AppHost.AppHost.csproj +++ b/WeiCloud.Fusion/AspireApp/Manage.AppHost.AppHost/Manage.AppHost.AppHost.csproj @@ -11,6 +11,7 @@ + diff --git a/WeiCloud.Fusion/VideoService/Video.API/Controllers/DaHua/VideoManageController.cs b/WeiCloud.Fusion/VideoService/Video.API/Controllers/DaHua/VideoManageController.cs index 86952bd..9e19019 100644 --- a/WeiCloud.Fusion/VideoService/Video.API/Controllers/DaHua/VideoManageController.cs +++ b/WeiCloud.Fusion/VideoService/Video.API/Controllers/DaHua/VideoManageController.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using Video.Application; +using Video.DomainService; namespace Video.API.Controllers.DaHua { @@ -6,6 +8,139 @@ namespace Video.API.Controllers.DaHua [ApiController] public class VideoManageController : ControllerBase { - + private readonly ILogger _logger; + private readonly IRootVideoPlaybackService _rootVideoPlaybackService; + private readonly IConfiguration _configuration; + + public VideoManageController(ILogger logger, IRootVideoPlaybackService rootVideoPlaybackService, IConfiguration configuration) + { + _logger = logger; + _rootVideoPlaybackService = rootVideoPlaybackService; + _configuration = configuration; + } + + #region 大华视频处理 + + /// + /// 大华视频的登录获取Token + /// + /// + /// + [HttpPost("token/dh")] + public async Task> GetDaHToken(LoginRequestDto dto) + { + return await _rootVideoPlaybackService.GetDaHToken(dto); + } + + /// + /// 大华视频回放 + /// + /// + /// + [HttpPost("playback/dh")] + public async Task> StartAndPlaybackDH(PlaybackReqDto dto) + { + return await _rootVideoPlaybackService.GetDaHRecordVideoUrl(dto); + } + + /// + /// 大华的录像回放地址(rtsp) + /// + /// + /// + [HttpPost("rtspplayback/dh")] + public async Task> RtspPlaybackByTime(PlaybackReqDto dto) + { + return await _rootVideoPlaybackService.RtspPlaybackByTime(dto); + } + + /// + /// 大华视频的实时流地址 + /// + /// + /// + [HttpPost("realtime/dh")] + public async Task> GetRealtimeUrl(StreamReqDto dto) + { + return await _rootVideoPlaybackService.GetRealtimeUrl(dto); + } + + /// + /// 大华视频的实时流地址(rtsp方式) + /// + /// + /// + [HttpPost("rtspstart/dh")] + public async Task> RtspStartVideoUrl(StreamReqDto dto) + { + return await _rootVideoPlaybackService.RtspStartVideoUrl(dto); + } + + /// + /// 大华设备通道 + /// + /// + /// + [HttpPost("channel/dh")] + public async Task> GetChannelCodes(ChannelPageReqDto dto) + { + return await _rootVideoPlaybackService.GetChannelCodes(dto); + } + + /// + /// 大华视频的下载 + /// + /// + /// + [HttpGet("download/dh")] + public async Task DownloadVideoAsync(PlayDownloadReqDto dto) + { + var remoteUrl = + $"https://{_configuration["DahHost"]}/evo-apigw/evo-httpnode/vod/cam/download.mp4" + + $"?vcuid={dto.Vcuid}" + + $"&subtype=1" + + $"&starttime={dto.Starttime:yyyy_MM_dd_HH_mm_ss}" + + $"&endtime={dto.Endtime:yyyy_MM_dd_HH_mm_ss}" + + $"&videoType={dto.VideoType}" + + $"&token={dto.Token}" + + $"&recordType={dto.RecordType}"; + + try + { + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (_, _, _, _) => true + }; + + using var http = new HttpClient(handler); + var resp = await http.GetAsync(remoteUrl, HttpCompletionOption.ResponseHeadersRead); + + resp.EnsureSuccessStatusCode(); + + var stream = await resp.Content.ReadAsStreamAsync(); + var contentType = "video/mp4"; + + return File(stream, contentType, "video.mp4"); + } + catch (Exception ex) + { + return StatusCode(500, new { success = false, message = "下载失败", error = ex.Message }); + } + } + + /// + /// 大华登出 + /// + /// + /// + /// + /// + [HttpGet("logout/dh")] + public async Task> Logout(string authorization, string? openId, int? userClient) + { + return await _rootVideoPlaybackService.Logout(authorization, openId, userClient); + } + + #endregion 大华视频处理 } -} +} \ No newline at end of file diff --git a/WeiCloud.Fusion/VideoService/Video.API/Video.API.csproj b/WeiCloud.Fusion/VideoService/Video.API/Video.API.csproj index 7eb2668..b5a59f4 100644 --- a/WeiCloud.Fusion/VideoService/Video.API/Video.API.csproj +++ b/WeiCloud.Fusion/VideoService/Video.API/Video.API.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + True @@ -22,6 +23,7 @@ + diff --git a/WeiCloud.Fusion/VideoService/Video.Application/ApiResult.cs b/WeiCloud.Fusion/VideoService/Video.Application/ApiResult.cs new file mode 100644 index 0000000..0a0312d --- /dev/null +++ b/WeiCloud.Fusion/VideoService/Video.Application/ApiResult.cs @@ -0,0 +1,51 @@ +namespace Video.Application +{ + public class ApiResult + { + public int Code { get; set; } + public string Msg { get; set; } + public string InnerMsg { get; set; } + public T Data { get; set; } + + public static ApiResult IsSuccess(T data, string msg = "请求成功") + { + return new ApiResult() { Code = 200, Data = data, Msg = msg }; + } + + /// + /// 服务端处理错误 + /// + /// + /// + public static ApiResult IsFail(string msg) + { + return new ApiResult { Code = 2005, Msg = msg }; + } + + /// + /// 服务端处理错误 + /// + /// + /// + public static ApiResult IsNoFound(string msg = null) + { + return new ApiResult { Code = 2005, Msg = msg ?? "数据不存在" }; + } + + /// + /// 客户端错误 + /// + /// + /// + public static ApiResult IsBadReq(string msg) + { + return new ApiResult { Code = 2006, Msg = msg }; + } + } + + public class ApiHubResult : ApiResult + { + public long? Projectid { get; set; } + public long? Constid { get; set; } + } +} \ No newline at end of file diff --git a/WeiCloud.Fusion/Video.Application/IdentityClientConfig.cs b/WeiCloud.Fusion/VideoService/Video.Application/IdentityClientConfig.cs similarity index 100% rename from WeiCloud.Fusion/Video.Application/IdentityClientConfig.cs rename to WeiCloud.Fusion/VideoService/Video.Application/IdentityClientConfig.cs diff --git a/WeiCloud.Fusion/Video.Application/RequestDto/DahuaVideoQueryDto.cs b/WeiCloud.Fusion/VideoService/Video.Application/RequestDto/DahuaVideoQueryDto.cs similarity index 100% rename from WeiCloud.Fusion/Video.Application/RequestDto/DahuaVideoQueryDto.cs rename to WeiCloud.Fusion/VideoService/Video.Application/RequestDto/DahuaVideoQueryDto.cs diff --git a/WeiCloud.Fusion/Video.Application/ResponeDto/DahuaVideoResDto.cs b/WeiCloud.Fusion/VideoService/Video.Application/ResponeDto/DahuaVideoResDto.cs similarity index 100% rename from WeiCloud.Fusion/Video.Application/ResponeDto/DahuaVideoResDto.cs rename to WeiCloud.Fusion/VideoService/Video.Application/ResponeDto/DahuaVideoResDto.cs diff --git a/WeiCloud.Fusion/Video.Application/Video.Application.csproj b/WeiCloud.Fusion/VideoService/Video.Application/Video.Application.csproj similarity index 100% rename from WeiCloud.Fusion/Video.Application/Video.Application.csproj rename to WeiCloud.Fusion/VideoService/Video.Application/Video.Application.csproj diff --git a/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/DahuaGeneralCtlService.cs b/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/DahuaGeneralCtlService.cs index 0e4df87..cf6b179 100644 --- a/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/DahuaGeneralCtlService.cs +++ b/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/DahuaGeneralCtlService.cs @@ -693,8 +693,8 @@ namespace Video.DomainService private static String EncryptByPublicKey(String context, String publicKey) { - RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); - ///rsa.FromXmlString(ConvertToXmlPublicJavaKey(publicKey)); + RSACryptoServiceProvider rsa = new(); + rsa.ImportParameters(FromXmlStringExtensions(ConvertToXmlPublicJavaKey(publicKey))); byte[] byteText = System.Text.Encoding.UTF8.GetBytes(context); byte[] byteEntry = rsa.Encrypt(byteText, false); diff --git a/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/IRootVideoPlaybackService.cs b/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/IRootVideoPlaybackService.cs new file mode 100644 index 0000000..a111901 --- /dev/null +++ b/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/IRootVideoPlaybackService.cs @@ -0,0 +1,58 @@ +using Video.Application; + +namespace Video.DomainService +{ + public interface IRootVideoPlaybackService + { + /// + /// 大华视频回放 + /// + /// + /// + Task> GetDaHRecordVideoUrl(PlaybackReqDto dto); + + /// + /// 大华的token获取 + /// + /// + /// + Task> GetDaHToken(LoginRequestDto dto); + + /// + /// 大华的实时视频 + /// + /// + /// + Task> GetRealtimeUrl(StreamReqDto dto); + + /// + /// rtsp实时预览接口方式 + /// + /// + /// + Task> RtspStartVideoUrl(StreamReqDto dto); + + /// + /// rtsp录像回放 + /// + /// + /// + Task> RtspPlaybackByTime(PlaybackReqDto dto); + + /// + /// 大华设备通道分页查询 + /// + /// + /// + Task> GetChannelCodes(ChannelPageReqDto dto); + + /// + /// 大华登出 + /// + /// + /// + /// + /// + Task> Logout(string authorization, string? openId, int? userClient); + } +} \ No newline at end of file diff --git a/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/RootVideoPlaybackService.cs b/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/RootVideoPlaybackService.cs new file mode 100644 index 0000000..dad17c1 --- /dev/null +++ b/WeiCloud.Fusion/VideoService/Video.DomainService/Dahvision/RootVideoPlaybackService.cs @@ -0,0 +1,199 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Video.Application; + +namespace Video.DomainService +{ + public class RootVideoPlayBackService : IRootVideoPlaybackService + { + private readonly ILogger _logger; + private readonly IConfiguration _cfg; + + private readonly IDahuaGeneralCtlService _dahuaGeneralCtlService; + + /// + /// 构造函数 + /// + /// + public RootVideoPlayBackService(ILogger logger, IConfiguration configuration, IDahuaGeneralCtlService dahuaGeneralCtlService) + { + _logger = logger; + _cfg = configuration; + + _dahuaGeneralCtlService = dahuaGeneralCtlService; + } + + #region 大华视频处理 + + /// + /// 大华回放接口 + /// + /// + /// + /// + public async Task> GetDaHRecordVideoUrl(PlaybackReqDto dto) + { + ApiResult result = new ApiResult() { Code = 200, Msg = "接口调用成功" }; + + var urlReult = await _dahuaGeneralCtlService.RecordVideoUrl(dto); + if (!urlReult.Success) + { + result.Code = 500; + result.Msg = urlReult.Msg; + _logger.LogWarning("大华录像回放接口调用失败:{Msg}", urlReult.Msg); + } + + result.Data = urlReult.Data; + + return result; + } + + /// + /// 大华的鉴权 + /// + /// + /// + public async Task> GetDaHToken(LoginRequestDto dto) + { + ApiResult result = new() { Code = 200, Msg = "接口调用成功" }; + //1. 获取公钥 + DaHApiResult publicKeyResult = await _dahuaGeneralCtlService.GetPublicKey(); + if (!publicKeyResult.Success) + { + result.Code = 500; + result.Msg = publicKeyResult.Msg; + _logger.LogWarning("获取大华公钥失败:{Msg}", publicKeyResult.Msg); + } + //2. 鉴权 + dto.PublicKey = publicKeyResult.Data.PublicKey; + + DaHApiResult loginResult = await _dahuaGeneralCtlService.GetToken(dto); + if (!loginResult.Success) + { + result.Code = 500; + result.Msg = loginResult.Msg; + _logger.LogWarning("大华鉴权失败:{Msg}", loginResult.Msg); + return result; + } + //大华的规则 + result.Data = loginResult.Data.AccessToken; + TokenEntry refreshed = new TokenEntry + { + AccessToken = result.Data, + RefreshToken = result.Data, + ExpireAt = DateTimeOffset.UtcNow.AddSeconds(120) + }; + TokenCache.TokenMap[dto.ClientId] = refreshed; + return result; + } + + /// + /// 大华实时 + /// + /// + /// + /// + public async Task> GetRealtimeUrl(StreamReqDto dto) + { + ApiResult result = new ApiResult() { Code = 200, Msg = "接口调用成功" }; + var urlReult = await _dahuaGeneralCtlService.RealtimeStreamUrl(dto); + if (!urlReult.Success) + { + result.Code = 500; + result.Msg = urlReult.Msg; + _logger.LogWarning("大华实时视频接口调用失败:{Msg}", urlReult.Msg); + } + result.Data = urlReult.Data; + return result; + } + + /// + /// 大华设备通道分页查询 + /// + /// + /// + public async Task> GetChannelCodes(ChannelPageReqDto dto) + { + ApiResult result = new ApiResult() { Code = 200, Msg = "接口调用成功" }; + var pageResult = await _dahuaGeneralCtlService.GetChannelPageList(dto); + if (!pageResult.Success) + { + result.Code = 500; + result.Msg = pageResult.Msg; + _logger.LogWarning("大华设备通道分页查询接口调用失败:{Msg}", pageResult.Msg); + } + result.Data = pageResult.Data; + return result; + } + + /// + /// 大华登出 + /// + /// + /// + /// + /// + /// + public async Task> Logout(string authorization, string? openId, int? userClient) + { + var result = new ApiResult(); + var logoutResult = await _dahuaGeneralCtlService.Logout(authorization, openId, userClient); + if (!logoutResult.Success) + { + result.Code = 500; + result.Msg = logoutResult.Msg; + _logger.LogWarning("大华登出接口调用失败:{Msg}", logoutResult.Msg); + } + else + { + result.Code = 200; + result.Msg = "登出成功"; + } + return result; + } + + /// + /// rtsp实时预览接口方式 + /// + /// + /// + /// + public async Task> RtspStartVideoUrl(StreamReqDto dto) + { + ApiResult result = new ApiResult() { Code = 200, Msg = "接口调用成功" }; + var urlReult = await _dahuaGeneralCtlService.RtspStartVideoUrl(dto); + if (!urlReult.Success) + { + result.Code = 500; + result.Msg = urlReult.Msg; + _logger.LogWarning("大华实时视频接口调用失败:{Msg}", urlReult.Msg); + } + result.Data = urlReult.Data; + return result; + } + + /// + /// rtsp录像回放 + /// + /// + /// + public async Task> RtspPlaybackByTime(PlaybackReqDto dto) + { + ApiResult result = new ApiResult() { Code = 200, Msg = "接口调用成功" }; + + var urlReult = await _dahuaGeneralCtlService.RtspPlaybackByTime(dto); + if (!urlReult.Success) + { + result.Code = 500; + result.Msg = urlReult.Msg; + _logger.LogWarning("大华录像回放接口调用失败:{Msg}", urlReult.Msg); + } + + result.Data = urlReult.Data; + + return result; + } + + #endregion 大华视频处理 + } +} \ No newline at end of file diff --git a/WeiCloud.Fusion/VideoService/Video.DomainService/Video.DomainService.csproj b/WeiCloud.Fusion/VideoService/Video.DomainService/Video.DomainService.csproj index 4d52fb7..18e7ac7 100644 --- a/WeiCloud.Fusion/VideoService/Video.DomainService/Video.DomainService.csproj +++ b/WeiCloud.Fusion/VideoService/Video.DomainService/Video.DomainService.csproj @@ -7,8 +7,8 @@ - + diff --git a/WeiCloud.Fusion/WeiCloud.Fusion.sln b/WeiCloud.Fusion/WeiCloud.Fusion.sln index d7d546f..381249b 100644 --- a/WeiCloud.Fusion/WeiCloud.Fusion.sln +++ b/WeiCloud.Fusion/WeiCloud.Fusion.sln @@ -39,7 +39,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Video.Store", "VideoService EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkingLotService.API", "ParkingLotService\ParkingLotService.API\ParkingLotService.API.csproj", "{D97C471C-3190-4F8E-A916-7A056A65EDCE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Video.Application", "Video.Application\Video.Application.csproj", "{77D967FB-79E5-42AB-A773-E129DBD16C97}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeiCloud.Core", "WeiCloud.Core\WeiCloud.Core.csproj", "{40B0D902-553C-C52F-71A2-56FB176FCCBD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Video.Application", "VideoService\Video.Application\Video.Application.csproj", "{9F2BD2C5-6496-419D-B87A-4F481E963C4D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -95,10 +97,14 @@ Global {D97C471C-3190-4F8E-A916-7A056A65EDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {D97C471C-3190-4F8E-A916-7A056A65EDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU {D97C471C-3190-4F8E-A916-7A056A65EDCE}.Release|Any CPU.Build.0 = Release|Any CPU - {77D967FB-79E5-42AB-A773-E129DBD16C97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77D967FB-79E5-42AB-A773-E129DBD16C97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77D967FB-79E5-42AB-A773-E129DBD16C97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77D967FB-79E5-42AB-A773-E129DBD16C97}.Release|Any CPU.Build.0 = Release|Any CPU + {40B0D902-553C-C52F-71A2-56FB176FCCBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40B0D902-553C-C52F-71A2-56FB176FCCBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40B0D902-553C-C52F-71A2-56FB176FCCBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40B0D902-553C-C52F-71A2-56FB176FCCBD}.Release|Any CPU.Build.0 = Release|Any CPU + {9F2BD2C5-6496-419D-B87A-4F481E963C4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F2BD2C5-6496-419D-B87A-4F481E963C4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F2BD2C5-6496-419D-B87A-4F481E963C4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F2BD2C5-6496-419D-B87A-4F481E963C4D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -116,7 +122,8 @@ Global {6CBD9E97-4FEF-4DA2-ADFB-21B4D9DB366F} = {19A25984-FFA8-49BE-A710-6F269A406C61} {058C0CAB-5956-4811-8340-86919DDB2845} = {19A25984-FFA8-49BE-A710-6F269A406C61} {D97C471C-3190-4F8E-A916-7A056A65EDCE} = {0A3134C8-219C-4674-B152-1FA6561E4217} - {77D967FB-79E5-42AB-A773-E129DBD16C97} = {19A25984-FFA8-49BE-A710-6F269A406C61} + {40B0D902-553C-C52F-71A2-56FB176FCCBD} = {44DAA396-C724-480A-A2BC-9A33D29E8FEA} + {9F2BD2C5-6496-419D-B87A-4F481E963C4D} = {19A25984-FFA8-49BE-A710-6F269A406C61} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {379A56DA-D3F0-4E7E-8FF7-DA8E20015BF3}