ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ASP.NET Core 6 对接海康综合安防平台实现HLS视频流集成

ASP.NET Core 6 对接海康综合安防平台实现HLS视频流集成 简介面向.NET开发者的ASP.NET Core 6 Web API示例工程演示如何使用Entity Framework Core构建符合RESTful风格的接口服务。对于需要快速搭建API脚手架或理解对象关系映射的读者项目从入口配置、依赖注入、数据库上下文、实体模型、数据迁移到控制器路由均提供了清晰完整的示例代码覆盖了常见的增删改查操作可直接作为基础模板复用。资源压缩包仅有13KB包含16个文件核心为9个C#源文件配合3个JSON配置文件和项目工程文件结构简洁、层次分明方便逐文件研读。目前已有388人学习下载适合.NET初学者入门也适合有经验者快速对照检查自己的项目布局。通过该示例读者可以重点掌握Entity Framework Core的数据模型映射方式、RESTful控制器编写方法、依赖注入与配置管理技巧同时还能结合作者的配套博文理解从项目初始化到数据库操作落地的完整思路从而在实际开发中减少重复踩坑快速搭建出高质量的后端服务。1. 项目背景与整体思路年前帮一个做园区安防的朋友搭了一套视频集成平台核心诉求很简单把海康综合安防管理平台iSecure Center里的监控画面以 HLS 流的形式嵌入到他们自己的 Web 管理系统里。后端选型商量了一圈最后定了 ASP.NET Core 6 Web API。这篇文章就把整套实现过程整理出来包含基础框架搭建、接口设计以及最关键的海康平台 HLS 流获取对接方案给需要在 .NET 生态里做安防集成的同学提供一个可以直接参考的样例。先说结论ASP.NET Core 6 做这种集成类 API 非常合适。理由有三个一是它本身跨平台Linux 上部署省授权费二是内置的 HttpClientFactory 对付海康这种需要频繁带 token 调用的 API 很方便三是性能上限高一个几百路摄像头的园区用 .NET 6 扛并发完全不是问题。整个项目的设计思路大致分三块基础层JWT 身份认证 统一响应格式 全局异常处理业务层摄像头信息管理、HLS 流地址获取、播放地址转换对接层海康 OpenAPI 网关封装、token 管理、HLS 流请求与缓存。这三层拆开之后不管后面是要换设备厂商还是加新的业务模块都只需要在对应层做改动不会牵一发动全身。2. 环境准备与项目初始化2.1 开发环境与依赖包我用的是 Visual Studio 2022装了 .NET 6 SDK。如果你用 Rider 或者 VS Code操作也差不多命令行能跑dotnet --version看到 6.x 就行。除了框架自带的包还需要引入以下几个 NuGet 包dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 6.0.16 dotnet add package Swashbuckle.AspNetCore --version 6.5.0 dotnet add package Newtonsoft.Json --version 13.0.3简单说明下每个包的作用JwtBearer实现基于 JWT 的认证客户端拿到 token 后访问受保护的接口Swashbuckle自动生成 Swagger 文档对接调试时直接浏览器里试接口不用额外装 POSTMANNewtonsoft.Json海康的 OpenAPI 返回体里有些字段命名比较随意用它做反序列化时控制更灵活。2.2 创建项目与目录结构用命令行创建项目干净利落dotnet new webapi -n HikVision.WebApi -f net6.0 cd HikVision.WebApi然后按下面的结构整理目录HikVision.WebApi/ ├── Controllers/ │ ├── AuthController.cs │ ├── CameraController.cs │ └── StreamController.cs ├── Models/ │ ├── ApiResponse.cs │ ├── CameraInfo.cs │ └── HikVisionDtos.cs ├── Services/ │ ├── HikVisionService.cs │ └── TokenService.cs ├── Middlewares/ │ └── ExceptionHandlingMiddleware.cs └── appsettings.json提示Controller 只做参数接收和结果返回业务逻辑尽量放到 Services 里这样后面对接别的平台或者写单元测试都会轻松很多。3. 基础框架搭建认证、统一响应、异常处理不管业务是什么一个 Web API 首先得把门面做好。我这里说的门面不是 UI而是三个基础能力接口要鉴权、返回格式要统一、报错信息要可控。3.1 JWT 身份认证配置在appsettings.json里加上 JWT 相关配置{ JwtSettings: { Issuer: HikVision.WebApi, Audience: HikVision.Client, SecretKey: your-256-bit-secret-key-please-change, ExpireMinutes: 120 } }然后在Program.cs里注册认证服务var jwtSettings builder.Configuration.GetSection(JwtSettings); var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings[SecretKey])); builder.Services.AddAuthentication(options { options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer jwtSettings[Issuer], ValidAudience jwtSettings[Audience], IssuerSigningKey key }; });这里有个小细节SecretKey必须超过 256 位32 字节否则 HS256 算法跑不起来。我一开始用了个短 key运行时报错折腾了好一会儿才意识到是这个原因。3.2 统一响应格式前端对接最烦的就是十个接口十个返回格式。所以我这边统一用一个ApiResponseT包装public class ApiResponseT { public int Code { get; set; } public string Message { get; set; } public T Data { get; set; } public static ApiResponseT Success(T data, string message ok) new ApiResponseT { Code 0, Message message, Data data }; public static ApiResponseT Fail(string message, int code -1) new ApiResponseT { Code code, Message message, Data default }; }所有 Controller 的动作方法都返回这个类型前端拿到code 0就说明业务成功否则看message做提示。这个习惯后来在对接 HLS 流地址的时候帮了大忙因为海康接口偶尔会返回设备不在线能力集不支持这类业务错误统一包装后错误信息能直接透传给前端展示不用再二次翻译。3.3 全局异常处理中间件光有统一返回还不够代码里总有预料之外的异常。全局异常中间件的思路是捕获所有未处理异常记录日志然后返回一个标准化的 500 响应。实现如下public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILoggerExceptionHandlingMiddleware _logger; public ExceptionHandlingMiddleware(RequestDelegate next, ILoggerExceptionHandlingMiddleware logger) { _next next; _logger logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, Unhandled exception); context.Response.StatusCode 500; context.Response.ContentType application/json; var response ApiResponseobject.Fail(服务器内部错误); await context.Response.WriteAsJsonAsync(response); } } }注册到管道里app.UseMiddlewareExceptionHandlingMiddleware();注意生产环境不要直接把异常堆栈抛给客户端既暴露内部结构也不安全。真正排查问题靠服务端日志就够了。4. 海康综合安防管理平台 HLS 流对接这部分是项目的重头戏也是网上问的人最多的地方。海康的 iSecure Center综合安防管理平台提供了完整的 OpenAPI 网关我们可以用它来获取监控点的 HLS 播放地址。4.1 海康 OpenAPI 对接前置条件在做任何代码之前你需要先向海康的实施工程师要到三样东西平台地址形如http://192.168.1.100:8443注意有端口AppKeyAppSecret这三样是调用 OpenAPI 的凭证。AppKey 和 AppSecret 通常在平台的系统管理 - 合作方管理里创建你可以理解为海康给你发的专属钥匙后续每次请求都要用它来签名。4.2 签名算法实现海康 OpenAPI 的鉴权方式是 HMAC-SHA256 签名。需要拼接以下内容X-Ca-KeyAppKeyX-Ca-Timestamp毫秒级时间戳请求方法GET/POST 请求路径 请求体签名串格式如下stringToSign method \n accept \n contentType \n path \n body其中accept一般是application/jsoncontentType也是application/json。用 AppSecret 作为密钥做 HMAC-SHA256 计算结果 Base64 编码后放到X-Ca-Signature头里。我封装了一个签名帮助类public static class HikVisionSigner { public static string Sign(string appSecret, string method, string path, string body) { var contentType application/json; var accept application/json; var stringToSign ${method}\n{accept}\n{contentType}\n{path}\n{body}; using var hmac new HMACSHA256(Encoding.UTF8.GetBytes(appSecret)); var hash hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)); return Convert.ToBase64String(hash); } }实操心得这里最容易踩的坑是path必须只包含路由部分不能带域名和 query string。比如完整地址是http://192.168.1.100:8443/artemis/api/video/v2/cameras/previewURLs签名时path只写/artemis/api/video/v2/cameras/previewURLs。我刚开始把整个 URL 放进去签名一直校验失败排查了大半天。4.3 获取 HLS 流地址的完整流程海康 OpenAPI 获取 HLS 流的接口以新版 artemis 网关为例路径是/artemis/api/video/v2/cameras/previewURLs。请求参数{ cameraCode: 摄像头唯一编码, streamType: 1, protocol: hls, transMode: 1 }参数含义cameraCode监控点编码在平台资源树里能看到streamType0 主码流1 子码流。需要高清看主码流多路同时预览时建议用子码流protocol固定hlstransMode传输模式1 表示 TCP。完整的调用代码public async Taskstring GetHlsPreviewUrl(string cameraCode, int streamType 1) { var timestamp DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); var path /artemis/api/video/v2/cameras/previewURLs; var bodyObj new { cameraCode, streamType, protocol hls, transMode 1 }; var body JsonConvert.SerializeObject(bodyObj); var sign HikVisionSigner.Sign(_appSecret, POST, path, body); var request new HttpRequestMessage(HttpMethod.Post, _baseUrl path) { Content new StringContent(body, Encoding.UTF8, application/json) }; request.Headers.Add(X-Ca-Key, _appKey); request.Headers.Add(X-Ca-Timestamp, timestamp); request.Headers.Add(X-Ca-Signature, sign); var response await _httpClient.SendAsync(request); var content await response.Content.ReadAsStringAsync(); var result JsonConvert.DeserializeObjectHikVisionResponse(content); if (result.Code ! 0) { throw new Exception($海康接口返回错误: {result.Msg}); } return result.Data.Url; }返回结果里data.url就是 HLS 的播放地址形如http://192.168.1.100:8443/artemis/live?tokenxxx...前端拿这个地址直接丢给video.js或者hls.js就能播。4.4 HLS 地址过期与缓存策略海康这个预览 URL 有时效性一般是几分钟到十几分钟不等这意味着你不能每次前端要地址都实时调海康也不能缓存太久。我这边采用的策略是用 ConcurrentDictionary 做内存缓存key 是cameraCode _ streamTypevalue 是完整的 URL 和过期时间过期时间设置为 10 分钟因为我们实测海康返回的 URL 大约 30 分钟有效留足余量前端播放失败时调一个刷新接口强制清除缓存再拿新地址。缓存代码private static readonly ConcurrentDictionarystring, (string Url, DateTime ExpireAt) _urlCache new(); public async Taskstring GetHlsPreviewUrlWithCache(string cameraCode, int streamType 1) { var key ${cameraCode}_{streamType}; if (_urlCache.TryGetValue(key, out var cached) cached.ExpireAt DateTime.Now) { return cached.Url; } var url await GetHlsPreviewUrl(cameraCode, streamType); _urlCache[key] (url, DateTime.Now.AddMinutes(10)); return url; }4.5 大华等其他平台对接的兼容思考文章标题虽然是海康但很多项目里是大华、宇视混着用的。好在这类平台的 HLS 取流逻辑高度相似都是AppKey/AppSecret 签名 - 获取 token - 调用 previewURL 接口拿地址只是签名规则和接口路径不同。所以代码里我把IHikVisionService抽成接口后续要是接大华新写一个DaHuaService注入进去就行Controller 层完全不用动。5. 摄像头管理与业务接口实现拿到 HLS 流地址只是第一步一个管理平台上总不能把摄像头编号写死在前端吧。所以还需要一组摄像头信息的 CRUD 接口。为了简化我用内存数据库你可以根据自己的项目换 EF Core 或 Dapper 接 MySQL。5.1 摄像头信息模型public class CameraInfo { public string CameraCode { get; set; } public string CameraName { get; set; } public string GroupName { get; set; } public int Channel { get; set; } public bool Enabled { get; set; } public DateTime CreatedAt { get; set; } }这个模型刻意做得比较轻实际项目里可以根据资源树、区域、权限等因素扩展字段。我一般会加一个DevicePlatform字段用来区分是海康还是大华这样在StreamController里就能根据这个字段做路由选择。5.2 摄像头信息管理接口CameraController里提供常规的增删改查这里只展示查询接口和启用/停用接口[HttpGet(list)] public async TaskApiResponseListCameraInfo GetCameras([FromQuery] string groupName ) { var query _cameras.AsQueryable(); if (!string.IsNullOrEmpty(groupName)) { query query.Where(x x.GroupName.Contains(groupName)); } return ApiResponseListCameraInfo.Success(query.ToList()); } [HttpPost({cameraCode}/status)] public async TaskApiResponsebool SetCameraStatus(string cameraCode, [FromBody] bool enabled) { var camera _cameras.FirstOrDefault(x x.CameraCode cameraCode); if (camera null) return ApiResponsebool.Fail(摄像头不存在); camera.Enabled enabled; return ApiResponsebool.Success(true, enabled ? 已启用 : 已停用); }5.3 获取播放流的组合接口业务系统前端通常关心的是给我这个摄像头的播放地址它不关心你是海康还是大华也不关心你是 HLS 还是 RTMP。所以我在StreamController里做了一个聚合接口[HttpGet(preview/{cameraCode})] public async TaskApiResponseobject GetPreview(string cameraCode, int streamType 1) { var camera _cameraService.GetByCode(cameraCode); if (camera null) return ApiResponseobject.Fail(摄像头不存在); if (!camera.Enabled) return ApiResponseobject.Fail(摄像头已停用); var url await _hikVisionService.GetHlsPreviewUrlWithCache(cameraCode, streamType); return ApiResponseobject.Success(new { cameraCode camera.CameraCode, cameraName camera.CameraName, streamType, hlsUrl url, expireAt DateTime.Now.AddMinutes(10) }); }这里返回给前端的信息里我刻意包含了expireAt。前端可以根据这个时间提前 1 分钟去请求新的播放地址避免播放画面突然中断。6. 常见问题与现场排查实录对接过程中遇到的问题十个里有七个出在海康的鉴权或者网络环境上。我把几个典型的坑整理出来做成速查表方便大家对照排查。问题现象可能原因排查方法解决方案签名校验失败时间戳与服务器时间偏差过大比对海康服务器时间与当前时间同步服务器时间或用海康返回的时间生成签名返回 code20001参数错误body 内容与签名不一致确认签名使用的 body 和实际发送的 body 完全相同统一用序列化后的字符串签名和发送请求超时网络策略拦截telnet 平台 IP 端口是否通在防火墙中放行对应端口拿到 URL 打不开URL 有效期过期检查生成时间和当前时间差实现缓存刷新机制偶发 401网关并发限流查看平台日志是否有限流记录客户端做好流量控制增加重试机制6.1 签名失败的典型场景我遇到最隐蔽的一个坑是用 Postman 调海康接口时Body 里写了格式化后的 JSON有换行有空格签名也用的这个格式化字符串能通。但是到了 C# 代码里用 Newtonsoft 序列化出来的 JSON 和 Postman 里那个不完全一样比如空格处理导致签名串不一致。解决办法把要签名的 body 字符串固定住签名和发送请求严格使用同一个字符串变量不要这边序列化一次那边又重新组装一次。6.2 时间戳偏差问题海康的网关要求请求时间与服务器时间偏差在 5 分钟以内否则拒绝。有次客户现场的海康服务器时间慢了两分钟我这边代码用DateTimeOffset.UtcNow生成的毫秒时间戳是标准的 UTC 时间两边一对比就差了 8 小时时区问题——不准确说是差了时区偏移加上服务器慢的时间直接签名失败。最稳妥的做法先从海康的一个公开接口比如/artemis/api/basic/v1/auth/systems拿到它的服务器时间然后计算本地与它的偏移量后续所有请求都加上这个偏移量。6.3 HLS 播放地址无法播放这类问题通常不是接口本身的问题而是播放链路的问题。我总结了一个快速定位链先用 VLC 播放器直接打开拿到的 HLS 地址确认能播VLC 能播但浏览器不行基本就是跨域或者 HTTPS 混合内容问题。海康返回的 HLS 地址是http://如果你的 Web 系统是 HTTPS浏览器会直接拦截这时候要么让海康网关也走 HTTPS要么在页面里做个代理转发多路同时播放卡顿大概率是子码流没用上检查streamType参数。我在实际项目中前端用的是hls.js直接支持 HLS 协议。在 HTTPS 页面里放了 HTTP 的流地址被浏览器拦截过一次后来用 Nginx 做了个/live/反向代理把流地址的协议和域名统一到 HTTPS 下问题才解决。7. 部署与性能优化经验7.1 Linux 部署注意点.NET 6 API 部署到 LinuxCentOS 7 或 Ubuntu 20.04 都行很简单发布命令dotnet publish -c Release -r linux-x64 --self-contained true--self-contained true的意思是发布产物里携带 .NET 运行时服务器不需要另外安装 .NET 环境。缺点是包体积大一些但对于客户现场的服务器能少装一个依赖就少一点麻烦。部署后我用systemd注册成服务开机自启[Unit] DescriptionHikVision WebApi Afternetwork.target [Service] WorkingDirectory/opt/hikapi ExecStart/opt/hikapi/HikVision.WebApi Restartalways RestartSec10 [Install] WantedBymulti-user.target7.2 并发与性能参数调优海康综合安防平台有个特点预览 URL 接口的并发能力有限有网关流控所以我们的 API 不能无脑把请求透传过去。除了前面说的缓存机制外我还在HttpClient里做了配置services.AddHttpClientIHikVisionService, HikVisionService(client { client.Timeout TimeSpan.FromSeconds(10); }) .AddPolicyHandler(GetRetryPolicy()); static IAsyncPolicyHttpResponseMessage GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(r !r.IsSuccessStatusCode) .WaitAndRetryAsync(2, retryAttempt TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); }这里用了一个小技巧指数退避重试最多重试 2 次。第一次失败等 2 秒第二次失败等 4 秒。注意这个重试只适用于非幂等性不敏感的 GET 类请求POST 类型要慎重。7.3 日志与监控日志框架用的Serilog配了文件输出和控制台输出。每次调用海康接口时我会把请求参数、耗时、返回码记录在案_logger.LogInformation(请求海康HLS地址: cameraCode{CameraCode}, streamType{StreamType}, 耗时{Elapsed}ms, cameraCode, streamType, sw.ElapsedMilliseconds);这个日志在对接和排查问题时非常有用。有次客户反馈明天早上 8 点画面总是不出来最后看日志发现每天早上 7:50 左右有一条超时记录再逆推发现是海康平台在 7:30 执行了定时任务占用了大量资源导致预览接口响应变慢。如果没有日志这种问题很难定位。8. 写在最后项目实施中的几点体会做这个项目的最大感受是技术本身的难度其实不大真正的难点在于对接方的黑盒程度。海康的 OpenAPI 文档算是业界比较完善的了签名原理清晰接口定义明确但真到现场还是会遇到文档没写到的情况比如某些老版本平台的 HLS 地址不支持直接外网访问、某些型号的摄像头必须要先调一次启动预览才能拿到流地址等。另外在代码结构上我把所有和海康相关的签名、加密、请求细节都封装在 Service 层里Controller 层只认业务对象。当时看着只是多花了半天时间做抽象后来客户说要接大华平台改造工作量从预计的一周直接降到了两天。再分享一个设计上的小建议对接类接口的参数从数据库或者配置中心读取而不是硬编码在代码里。AppKey、AppSecret、平台地址这些东西一旦换了服务器或者项目迁移直接改配置文件就能生效。我习惯把它们放在appsettings.json的自定义节点里配合环境变量做覆盖这样开发和生产的配置互不影响。这个项目后续如果要扩展方向很明确一是把摄像头在线状态监测加上海康有对应的状态查询接口二是做一个流地址统一网关把 HLS、RTMP、WebRTC 几种协议统一转换成适合浏览器播放的格式三是加上录像回放 URL 的获取接口。核心架构不变往这个框架里添新接口就行了。本文还有配套的精品资源点击获取
返回列表