背景:最近在做一个保险的项目 监管要求,需要对用户操作进行录屏保存。市面上有的录屏系统太大,收费的,也挺贵,大概20-30万左右,还不包括存储硬件资源,果断选择自己开发/
技术实现:使用rrweb + pako.js 压缩文件
技术架构: C端录制(vue+rrweb+pako.js) + java+磁盘存储 + 后端系统配置录制页面及展示视频
具体代码写起来也不麻烦,关键是如果封装,我使用的是rrweb结合 vue插件开发一套录屏系统
核心插件部分
vsr.js
import {checkRecording, setValue} from "../../api"; // 判断是否录屏后台配置那个页面需要录屏,保存数据接口
import router from '../../router' // 引入router
import Cookies from 'js-cookie'
import md5 from 'js-md5'; // md5加密
import {rrWebZip} from "../../utils"; // 压缩工具函数
class vsr {
constructor() {
// this.operation = null;
// this.url = ''
// this.timer = null
// this.needRecord = false
// this.events = [];
}
async vsrInit() {
let events = []; // 定义一个空数组,保存录屏数据
let _this = this
rrweb.record({
emit(event) {
events.push(event);
},
sampling: {
// 不录制鼠标移动事件
// mousemove: false,
// 不录制鼠标交互事件
// mouseInteraction: false,
// 设置滚动事件的触发频率
scroll: 200, // 每 200ms 最多触发一次
// 设置输入事件的录制时机
// input: 'last', // 连续输入时,只录制最终值
MouseUp: false,
MouseDown: false,
// Click: false,
ContextMenu: false,
// DblClick: false,
// Focus: false,
// Blur: false,
// TouchStart: false,
// TouchEnd: false,
},
inlineStylesheet: true,
slimDOMOptions: {
svg: true,
script: true,
comment: true,
headFavicon: true,
headWhitespace: true,
headMetaDescKeywords: true,
headMetaSocial: true,
headMetaRobots: true,
headMetaHttpEquiv: true,
headMetaAuthorship: true,
headMetaVerification: true,
},
packFn: rrweb.pack, // 压缩算法
// recordLog: true
// checkoutEveryNth: 200, // 每 200 个 event 重新制作快照
});
// save 函数用于将 events 发送至后端存入,并重置 events 数组
async function save() {
const timestamp = new Date().getTime()
let body = ''
if (events.length > 0) {
// console.log(JSON.stringify(events), 'JSON.stringify(events)')
// console.log(rrWebZip(JSON.stringify(events)), 'rrWebZip(JSON.stringify(events)')
body = timestamp + md5(timestamp + 'zbkl') + rrWebZip(JSON.stringify(events)) // 压缩传输
events = [] // 每次调保存接口,立即清空当前录屏数据,保证每次保存为最新数据
await setValue(body);
}
}
router.beforeEach(async (to, from, next) => { // 监听页面跳转,即调是否录屏接口
let url = _this.getDomain() + '/#' + to.path
const timestamp = new Date().getTime()
if(to.name === 'Insure' || to.name === 'PayWay' || to.name === 'Succeed' || to.name === 'PayError' || to.name === 'Payment' || to.name === 'Renewal') {
let result = await checkRecording({
"startTime": timestamp,
"url": url,
'agentID': to.query && to.query.agentID
})
if (result.result && result.result.needRecord) {
Cookies.set('zbklTrackMark', result.result && result.result.eventsId, 'Infinity')
save()
if(to.name === 'Succeed' || to.name === 'PayError' || to.name === 'Payment') {
setTimeout(() => {
save()
},2000)
}
this.timeSave = _this.throttling(save, 1000, true); //函数节流,保证每秒只调一次接口,以节省请求次数,减少带宽
if(!result.result.isEndPage) {
window.addEventListener('touchend', this.timeSave, false) // 移动端事件,点击事件结束即调板寸接口
}else {
window.removeEventListener('touchend', this.timeSave, false)
}
}else {
window.removeEventListener('touchend', this.timeSave, false)
}
}else {
events = []
window.removeEventListener('touchend', this.timeSave, false)
}
next()
})
}
throttling (fn, wait, immediate) {
let timer;
let context, args;
let run = () => {
timer=setTimeout(()=>{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
if(immediate){
fn.apply(context,args);
}
run();
}
}
}
getDomain() { // 获取当前页面域名加页面路由名称,不带参数
let url = window.location.href
return url.split('://')[1].split('/#/')[0]
}
}
export default vsr;
index.js
import Router from "../../router";
import vsr from "./vsr";
// const vsrKey = function (val) {
// !sessionStorage.getItem('vsrKey') && sessionStorage.setItem('vsrKey', val)
// }
// 设置一个时间,长时间停留在页面上,请求发送取消
// const vsrKeyTime = function () {
// let nowTime = new Date().getTime()
// !sessionStorage.getItem('vsrKeyTime') && sessionStorage.setItem('vsrKeyTime', nowTime)
// }
// vsrKeyTime()
const vsrOpt = function (opt = {
whiteList: [],
version: '1.0.0'
}) {
const VSR = new vsr(opt);
VSR.vsrInit();
}
const install = function(Vue) {
Vue.use(Router);
};
if (typeof window !== "undefined" && window.Vue) {
install(window.Vue);
}
export default {
install,
vsrOpt,
// vsrKey
};
代码
import VueScreenRecord from "./components/vue-screen-record";
if(finishTime() > 0) {
// VueScreenRecord.vsrOpt()
// Vue.use(VueScreenRecord);
}
直接引用即可实现,前端的录制。
剩下的就是后端保存了,然后需要B端配置需要录屏的页面,及回放了。
网友评论