ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue项目接入海康WebControl插件:RSA加密与视频预览实战避坑指南

Vue项目接入海康WebControl插件:RSA加密与视频预览实战避坑指南 第一次在Vue项目里对接海康设备的视频预览我差点被WebControl插件劝退。需求一句话就说完了让用户在内网页面里直接看摄像头画面。可真动手才发现光是把插件装上、把RSA加密那一层打通就够折腾好几天。这篇文章我把从插件安装、密钥加密到Vue里挂预览窗口的完整链路都整理一遍重点放在RSA加密避坑上——这个环节网上资料少、报错又很反直觉值得单独拿出来讲。无论你是接手老项目还是打算在现有Vue系统里新增视频预览模块按这个流程走至少能省下大半天的排查时间。1. 为什么是WebControl视频预览方案的取舍1.1 一个绕不开的现实浏览器原生放不了RTSP海康摄像头的视频流走的是RTSP协议而浏览器压根没有原生支持RTSP的能力。你输入rtsp://192.168.1.64:554/Streaming/Channels/101到Chrome地址栏结果多半是浏览器提示不认识这个协议。所以做这类项目时第一件事不是写代码而是先确定“视频到底通过什么方式投到页面上”。目前常见的路子有这么几条要么用海康WebControl插件要么让后端加一个转流服务把RTSP转成HLS或WebRTC要么直接买商业的视频云网关。这里面WebControl插件是最“原教旨”的海康方案它由海康官方提供本质上是一个浏览器插件通过插件的能力去拉RTSP流并解码渲染。我们当时选它原因很现实项目只能跑内网不允许额外部署转流服务器。HLS转流虽然跨浏览器能力强但等于在链路上多了一个故障点维护成本也上去了。设备是海康的接入端也用海康自家的插件这是最直接的路径。1.2 WebControl能干什么不能干什么很多人以为WebControl只是用来播放视频其实它还提供云台控制、抓图、录像回放、语音对讲等一系列能力具体看你集成的SDK版本和平台后台开放的权限。但它在浏览器兼容性上的限制非常明显老版本的产品走ActiveX新一点的版本走NPAPI而Chrome从45版本之后就把NPAPI禁掉了Edge、Firefox也陆续跟进。说白了这个插件天然就不是给现代浏览器准备的。做个简单对比大家感受一下选型差异方案优点缺点适用场景WebControl插件延时低、控制能力强、官方维护浏览器兼容性差、必须装插件内网PC端、旧系统改造HLS/m3u8转流免插件、移动端友好延时高、需要额外转流服务外网访问、手机端预览WebRTC转流延时低、免插件部署复杂、并发有限制对实时性要求高的新项目视频云网关兼容性最好、功能全成本高、需要额外设备中大型项目、多品牌设备混合接入那什么时候只能选WebControl我遇到的情况是客户明确禁止在内网加任何第三方服务设备又是海康整套环境这时候用插件是阻力最小的方案。如果你也在类似约束下那接下来的内容对你会特别有用。1.3 和H5方案、流媒体网关相比插件到底输在哪插件最大的问题其实不是“技术落后”而是“使用门槛高”。用户访问页面之前得先下载安装exe安装后还得重启浏览器再配置受信任站点每一样放在C端产品里都是劝退操作。但在内部管理系统里用户群体固定、浏览器版本可控这个门槛反而可以被接受。另外一点容易被忽略插件的维护责任在自己手上。海康官方对WebControl的更新节奏越来越慢有些SDK包还停留在几年前遇到系统新装、浏览器强制升级插件可能就加载不出来了。所以选插件方案的同时你一定得想好“如果浏览器升级了怎么办”的退路。我们当时的做法是把推荐浏览器版本写进项目说明页面加载时做一次环境检测不满足条件就弹提示引导。2. 环境准备里的隐形门槛插件安装、浏览器版本与开发环境协议2.1 插件安装的正确姿势先把插件拿到手。海康WebControl插件的安装包一般可以在设备配套光盘、官网下载中心或者项目交付方提供的资料包里找到。安装时尽量右键“以管理员身份运行”有些电脑上如果开着杀毒软件可能会拦截插件的驱动注册建议安装前先把杀毒软件临时退出装完再开回来。安装完成后有一个很容易踩的坑必须彻底关闭浏览器再重新打开。只是刷新页面不会让插件生效因为插件注册动作发生在浏览器进程启动时。我第一次集成时就是安装完没重启浏览器结果页面里一直报“插件未安装”折腾了快一个小时才发现是这个原因。另外建议装完插件后先去访问海康提供的插件测试页或者SDK包里的demo页面在独立环境里先把插件是否正常跑通验证掉再进Vue项目排查。不然两边的报错混在一起很难定位是插件本身的问题还是代码集成的问题。2.2 开发环境为什么建议用localhost插件初始化正常工作的前提是页面协议和插件服务地址协议一致。举个例子当你的Vue页面跑在http://localhost:8080而后端平台接口跑在http://192.168.1.20:8000时浏览器会认为这是跨域的HTTP资源通常可以正常拉取但如果页面跑在https://localhost:8080后端还是HTTP接口那问题就来了——现代浏览器会直接拦截“混合内容”请求插件初始化时拿不到服务地址日志里会看到连接被拒绝或初始化失败。所以开发阶段我建议直接用HTTP不要开HTTPS。并且项目里配置devServer代理把后端接口统一代理到本地这样页面请求都是同源的能规避掉一大半跨域问题。vue.config.js里类似这样写devServer: { proxy: { /platform: { target: http://192.168.1.20:8000, changeOrigin: true } } }如果你是在内网调试后端地址是固定的IP代理配置写好之后基本不用再动。2.3 浏览器兼容的内核问题既然选了WebControl就别指望能在新版本Chrome里顺利跑起来。我们实际项目里用的是360浏览器的兼容模式也就是IE内核或者某些定制版Chromium内核浏览器它们在插件加载上比原版Chrome宽容一些。这里有一个经验项目组内部必须统一浏览器版本不然就会出现你这里调通了、同事那边黑屏的尴尬局面。最稳妥的做法是在登录页做一次环境检测用JS判断插件对象是否存在不存在就给出一个“下载安装包 使用说明 推荐浏览器版本”的引导页面。虽然体验上多了一步但对内网项目来说比让用户自己瞎折腾强太多。3. Vue工程接入WebControl依赖引入与初始化时机3.1 在index.html里按顺序引入jquery和webcontrol.js海康WebControl的JS-SDK不像npm包那样可以直接npm install它的官方文件就是一个webcontrol.js或类似命名的脚本需要手动引入。而且这个SDK底层依赖jQuery所以必须先引入jQuery再引入webcontrol.js顺序不能反否则控制台会报$ is not defined或者WebControl is not defined。我通常直接在public/index.html里加script src/libs/jquery.min.js/script script src/libs/webcontrol.js/script把这两个文件放在public/libs目录下打包时Vue CLI会自动把它们原样复制到dist根目录不会经过webpack处理省去很多路径和编译兼容问题。注意别把它们放到src/assets里再import那样会走一遍webpack的模块解析可能和插件脚本里的全局变量声明方式冲突。3.2 组件中初始化WebControl的时机初始化动作建议放在mounted之后并且要用一个nextTick或者短暂的延时确保DOM已经渲染完成。因为后面创建预览窗口时需要把窗口附加到一个具体的div元素上如果div还没渲染出来SDK会找不到挂载点。下面这段代码是我们项目中验证过能跑通的骨架简化了一些业务逻辑let wc null let windowId null async function initWebControl(divRef) { wc new WebControl({ rtspPort: 554, appKey: your_app_key, secret: your_secret, useLoader: load.js }) await wc.initPlugin() const sessionId await loginByRsa() await wc.login({ sessionId }) windowId await wc.createWindow({ width: 100%, height: 100% }) await wc.attachViewer({ windowId, viewer: divRef }) }注意不同SDK版本的接口参数可能会有细微差别我这边写的是多个项目里跑通过的常见写法如果你的SDK包里login、createWindow的入参不太一样以你们拿到的官方demo为准。核心的顺序逻辑是通用的初始化插件、登录平台、创建窗口、绑定容器、发起预览。3.3 初始化失败的表现与处理插件初始化失败时常见的有三种现象第一种是控制台直接报WebControl is not defined这说明webcontrol.js没加载成功或者加载顺序有问题。先看浏览器Network面板里这个脚本是否返回200再看控制台是否有jQuery报错。第二种是报类似initPlugin error或抛出异常多半是插件没装、装了没重启浏览器或者浏览器内核不支持NPAPI插件。这时候只能回到第一步去排查环境。第三种是初始化成功但后续登录报“用户未认证”之类的错误这个大概率是RSA加密链路出了问题也就是文章后面重点展开的部分。4. RSA加密链路后端加密、前端登录的坑全在这4.1 密钥从哪来appKey和secret的获取先搞清楚密钥体系。接入海康平台时会在平台后台创建一个应用拿到一对凭据appKey和secret。appKey相当于应用的用户名可以直接出现在前端但secret相当于密码一旦泄露任何人都能冒充你的应用去调用平台接口。很多教程为了省事直接教你把secret明文写在new WebControl()的配置里。开发调试的时候可以这么干但放到生产环境就是安全事故。因为前端代码是公开的只要用户打开开发者工具就能在源码里看到你的明文密钥。正确处理方式是把secret放在后端由后端生成加密串前端只拿加密结果。当然内网项目有时候迫于工期选择前端直接加密我也理解但至少做到“secret不硬编码在仓库里”而是通过环境变量注入打包时再替换这样即便前端代码被拿到也看不出真实的secret值。4.2 加密结构拆解secret、时间戳与公钥海康的RSA加密规则和普通网站的登录加密不太一样它不是简单地把密码用公钥加密就完事而是要求把secret和一个当前时间戳拼在一起再用平台提供的RSA公钥加密。时间戳的作用是防重放攻击平台收到请求后会解密校验里面的时间戳和当前时间是否匹配超时就会拒绝。我接触到的接入文档里常见的拼接格式类似下面这种secret_1612345678901前面是secret后面跟下划线和13位毫秒时间戳然后用平台公钥做RSA加密把加密后的密文和appKey、timestamp一起提交给后端。可能你会问为什么还要单独传一个timestamp因为平台服务端需要知道你是用哪个时间戳去加密的它解密后才能拿这个时间和当前时间对比。如果只传密文它也得解密之后才知道时间戳逻辑上没问题但校验前就必须先解密成功。所以一般明文传一份时间戳密文里再带一份双保险。4.3 jsencrypt分段加密的正确写法前端做RSA加密最常用的库是jsencrypt原因是API简单、用起来快。但它有一个明显的坑默认的encrypt方法不支持超过密钥长度限制的长文本。RSA加密本身能处理的明文长度受密钥位数限制1024位公钥最多加密117字节2048位公钥最多加密245字节。如果你的secret本身不长通常不会触发这个问题。但有些项目里secret加上时间戳、再加上盐值后长度很容易逼近或超过117字节这时候直接调用encrypt会返回false或加密结果不完整后端解密出来是乱码或空串。一个通用的分段加密函数长这样import JSEncrypt from jsencrypt export function encryptLong(publicKey, plainText, keyLength 1024) { const encryptor new JSEncrypt() encryptor.setPublicKey(publicKey) const maxLength Math.ceil(keyLength / 8) - 11 const inputLen plainText.length let offset 0 let result while (inputLen - offset 0) { const length Math.min(inputLen - offset, maxLength) result encryptor.encrypt(plainText.substr(offset, length)) offset maxLength } return result }注意分段加密之后的结果是一段拼接起来的base64字符串后端拿到后需要按同样的分段规则去解密。所以如果你后端用的语言写RSA解密的工具人不懂“前端分段加密”这个逻辑很可能解密出来只有第一段是完整内容后面都丢了。解决方法是和后端提前约定好密文是分段加密的结果解密时也要分段解。4.4 时间戳类型和填充方式两个最容易翻车的地方时间戳这个坑特别隐蔽。我们有一次调联调始终报“时间戳校验失败”查了很久最后发现是前端加密时用的是Date.now()返回13位毫秒时间戳但提交给后端接口时写的是Math.floor(Date.now() / 1000)返回10位秒级时间戳。加密用的和提交用的不是同一个值后端自然校验不通过。正确做法是保留一个变量加密和提交都用它const timestamp String(Date.now()) const plainText ${yourSecret}_${timestamp} const encryptedSecret encryptLong(publicKey, plainText) // 提交时传 timestamp给后端校验用 const params { appKey, timestamp, encryptedSecret }另一个坑是RSA填充方式。jsencrypt默认走的是RSA_PKCS1_PADDING但有些平台的后端用的是OAEPPadding或者RSA/ECB/OAEPWithSHA-256AndMGF1Padding加解密双方对不上结果就是后端解出来是乱码或者直接抛异常。如果你的项目遇到“加密成功但登录失败后端说解密异常”先别急着改前端代码去和后端确认一下他们用的填充算法。如果确认后端是OAEP用jsencrypt就搞不定了需要换成jsrsasign。示例import { KEYUTIL, KJUR, hextob64 } from jsrsasign function encryptByOAEP(publicKeyPem, plainText) { const key KEYUTIL.getKey(publicKeyPem) const enc KJUR.crypto.Cipher.encrypt(plainText, key, RSA/ECB/OAEPWithSHA-256AndMGF1Padding) return hextob64(enc) }说到底RSA加密不是一个前端单方面就能搞定的事它必须和后端、甚至和平台SDK协商一致。所有网上找的代码只能当参考最终要以你们项目实际的接口文档为准。4.5 登录成功后如何把session交给WebControlRSA加密的目的是为了换取一个合法的登录凭证。后端拿到前端提交的加密串并校验通过后会返回一个sessionId或登录token。注意这里有两种做法第一种前端把这个sessionId传给WebControl SDK的login方法由SDK去访问平台接口。这种情况比较简洁后端其实已经不是核心参与者而是帮你做了一次“代理加密”或“代理转发”。第二种后端自己去和平台交互登录成功后返回给前端一个已经授权的播放地址前端直接用WebControl播放该地址这种情况下SDK的login方法可以跳过。我们项目里用的是第一种因为这样路由跳转、权限校验都统一走后端前端逻辑最简单。这里还是要提醒一下无论哪种方式都不要自己在前端拼一堆加密逻辑后把明文secret发给后端那样RSA就白做了。5. 预览功能的完整流程从创建窗口到清理销毁5.1 获取预览地址登录成功后下一步就是拿到摄像头的预览地址。预览地址可以通过后端接口获取也可以在拿到设备信息后按规则拼接前者更稳妥因为地址里可能包含账号密码由后端拼接可以避免把凭据暴露到前端。一个典型的RTSP预览地址长这样rtsp://admin:password192.168.1.64:554/Streaming/Channels/101末尾的101含义是第一个1代表通道号01代表主码流如果是102就是子码流。主码流清晰度高、占用带宽大适合大屏展示子码流清晰度低但流畅适合网格多画面监控。前端可以做成一个“流畅/高清”切换按钮本质是切换不同的码流地址。在开发阶段注意别把预览地址打太多日志尤其别让日志把RTSP地址里的密码也打出来否则一旦日志被截图或者上传等于把设备凭据公开了。5.2 创建窗口和绑定容器拿到预览地址后创建视频窗口的套路是固定的先创建窗口、再把窗口挂载到页面div、最后启动预览。挂载的那个div必须预留明确的宽高否则插件渲染时会拿到0宽高画面自然显示不出来。多路预览也一样每一路都申请一个独立的windowId然后各自绑定不同的div最后分别调用startPreview。需要注意插件能同时创建的窗口数是有限制的具体看版本有的是16路有的是32路。超出限制时插件会报“超出最大连接数”这时候要么分批展示要么降级用HLS方案。5.3 黑屏、白屏和“窗口出来了但没画面”的逐个排查预览阶段遇到最多的就是窗口创建成功但画面黑屏。遇到这种情况我通常按这个顺序排查先看容器div的宽高。有时候div被v-if控制视频开始播放时div还没渲染出来有时候div是空的子元素把高度撑出来了但插件播放器渲染的是canvas或activex控件需要父容器有固定高度。最省事的办法是给视频容器一个绝对定位设置width: 100%; height: 100%并且保证它的父级也设置了高度。再看预览地址是否带认证信息。有些RTSP地址不带username/password或者携带的账号权限不足插件会一直尝试连接但拉不到流界面表现就是黑屏或转圈。可以用VLC播放器在本地先试一下这个地址确认能播再回前端排查。然后看码流格式。部分老摄像头不支持H.265但SDK默认请求主码流是H.265插件又没启用硬解码就会黑屏。这时换成子码流H.264试试如果子码流能出画面基本可以断定是解码能力的问题。最后检查浏览器兼容模式。如果页面跑在“极速模式”插件可能加载不出来切换成“兼容模式”再试。这个和新版本Chrome的处境类似插件不行就是不支持现代浏览器的安全策略没有前端代码层面的解决办法只能引导用户换环境。5.4 离开页面时的销毁顺序销毁顺序搞反是另一个高频坑。正确的销毁顺序是先停止预览再关闭窗口再登出最后销毁插件实例。如果直接调销毁方法而不停止预览插件进程可能残留Windows任务管理器里能看到一个叫WebControl的进程一直在跑再次进入页面时会初始化非常慢甚至直接失败。Vue3组合式API里可以这样写onBeforeUnmount(async () { try { if (windowId) { await wc.stopPreview({ windowId }) await wc.closeWindow({ windowId }) } await wc.logout() await wc.destroy() } catch (e) { console.warn(销毁失败插件可能已释放过) } })注意包一层try/catch因为有的SDK版本里重复调用销毁方法会直接抛异常但在我们项目里表现是无所谓可以忽略只要保证“stopPreview在closeWindow之前”就行。6. 生产部署与维护证书、白名单和长期运行6.1 HTTPS与HTTP的协议错位问题生产环境部署时最容易遇到的是页面HTTPS、插件服务HTTP的协议错位。浏览器会拦截HTTPS页面发起的HTTP请求导致插件初始化时连不上后端。解决思路有两种全部统一到HTTPS或者全部统一到HTTP。内网项目如果没有正规证书可以用自签名证书但浏览器会报警告很多项目为了省事直接允许HTTP访问这也是可以的前提是内网环境足够安全。如果必须走HTTPS最好让后端平台也配置上HTTPS保证前端页面、接口、插件服务三端协议一致。6.2 地址白名单与受信任站点为了让插件能正常加载和调用浏览器通常需要把平台地址加入“受信任站点”或者单独对插件添加白名单。不同内核浏览器的位置不一样IE浏览器是在“Internet选项—安全—受信任的站点”里添加360浏览器兼容模式需要在“安全设置”里调整ActiveX控件启用选项并且把站点加到自定义白名单。这个配置对普通用户来说很不友好可以在首次进入页面时写一个引导弹窗把配置步骤一页一页展示出来。虽然体验一般但确实能减少大量“为什么我这看不了”的工单。另外一个比较实用的技巧是将插件SDK的域名也加入“兼容性视图”设置避免浏览器用错误的文档模式渲染页面。6.3 长期运行的资源释放与降级方案浏览器长期开着监控页面内存占用会逐渐上涨尤其是32位浏览器进程更容易接近内存上限。给页面加一个“释放资源”按钮点击后只停止预览不退出登录用户重新打开某一路视频时再恢复预览能有效降低内存压力。另外在监听页面不可见的地方也可以做一个自动隐藏视频窗口的操作减少插件渲染开销。如果你已经预见到项目将来要迁移到H5方案我个人的建议是在业务层做一层“视频组件”的抽象把WebControl的调用包在组件内部对外只暴露start(),stop(),switchStream()这几个方法。这样以后切换到HLS或WebRTC方案时只需要重写这个组件内部上层业务代码不用动。最后再分享一个维护阶段的小经验把插件版本、SDK文件版本、推荐浏览器版本、平台服务版本这四个信息做成检查清单每次出问题先核对这四个版本是否匹配。我们项目里好几次线上故障最后定位出来的原因都特别简单——有人换了浏览器正式版或者某台机器重装了系统后用了旧版插件。版本对齐了至少能少走一半弯路。
返回列表