Commit c2a63c00 authored by fanxuehui's avatar fanxuehui

Merge branch 'opt' into 'master'

Opt

See merge request !1
parents 65764207 feb99f13
......@@ -3205,7 +3205,8 @@
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.15.tgz",
"integrity": "sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg="
"integrity": "sha1-tEf2ZwoEVbv+7dETku/zMOoJdUg=",
"dev": true
},
"loose-envify": {
"version": "1.4.0",
......
......@@ -23,7 +23,6 @@
"author": "Dec-F",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.15",
"md5": "^2.2.1",
"rrweb": "^0.7.18",
"whatwg-fetch": "^3.0.0"
......
import 'whatwg-fetch';
import utils from './utils';
const common = {
/**
* fetch请求封装
* @param {*} action 请求线上地址
* @param {*} params 请求入参
* @param {*} method 请求方式,默认get
* @param {*} options 其他参数
* options为对象格式,值:
* isLoading(是否激活请求加载动画)
* isJson(是否设置post请求头contentType为application/json)
* content 自定义请求参数
*/
fetch(action, params = {}, method = 'get', options = {}) {
const token = utils.getCookie('token');
const XCsrfToken = utils.getCookie('csrf_token');
let url = action;
let option = {
method,
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'token': token,
'X-Csrf-Token': XCsrfToken
}
};
if (options && options.content) {
option = Object.assign({}, option, options.content);
}
if (method === 'post') {
if (options && options.isJson) {
option.body = JSON.stringify(params);
} else {
option = Object.assign({}, option, {
headers: {
Accept: 'application/json,text/plain,*/*',
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
'token': token,
'X-Csrf-Token': XCsrfToken
}
});
option.body = utils.serialize(params);
}
}
if (method === 'get') {
url = Object.keys(params).length ? url + '?' + utils.serialize(params) : url;
}
return fetch(url, option)
.then(response => {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
let error = new Error(response.statusText);
error.response = response;
throw error;
}
});
}
}
export default common;
\ No newline at end of file
// 分片计数器
class Counter {
count = 0;
next() {
return ++this.count;
}
reset() {
this.count = 0;
}
}
export default Counter;
import * as rrweb from "rrweb";
import TracksWorker from "./tracks.worker";
import common from './comment';
import utils from './utils';
export default class Monitor {
rrwebHandler = null;
tracksWorker = new TracksWorker();
// 记录
constructor() {
this.tracksWorker.onmessage = ({ data }) => {
console.log('local', data);
window.localStorage.setItem("rrevents", JSON.stringify(data));
// common.fetch('https://manager.tuiatest.cn/homePage/data','get');
};
constructor(props) {
console.log('初始化');
this.tracksWorker.postMessage({
type: "init",
payload: {
system: props.system,
config: props.config
}
record() {
});
}
resetRrwebHandler() {
this.rrwebHandler && this.rrwebHandler();
console.log('开始录制')
}
/**
* 录制
* @memberof Monitor
*/
record() {
this.resetRrwebHandler();
console.log("开始录制");
this.rrwebHandler = rrweb.record({
emit: event => {
this.tracksWorker.postMessage({ type: "record", payload: event });
},
});
}
// 停止
/**
* 停止录制
* @memberof Monitor
*/
stop() {
if (!this.rrwebHandler) {
throw new Error("没有正在录制的实例");
}
console.log('停止录制')
console.log("停止录制");
this.rrwebHandler();
}
// 重置
/**
* 重置
* @param {*} event
*/
reset(event) {
this.rrwebHandler && this.rrwebHandler();
console.log('重置数据')
this.resetRrwebHandler();
console.log("重置数据");
this.tracksWorker.postMessage({ type: "reset", payload: event });
}
// 发送信息
/**
* 发送信息
* @param {*} action
* @memberof Monitor
*/
postMessage(action) {
this.tracksWorker.postMessage(action);
}
......
// 数据维护,上报
import Counter from "./counter";
import { dataWrapper, log } from './utils';
class Reporter {
system = ''; // 系统名
config = {
env: 'prod',
log: false
}; // 系统配置
environmentUrl = ''; // 环境
userIdentifier = ''; //用户标示
path = ''; // 当前路径
isUploading = false; // 是否在上传cdn
bus = []; // 上传分片数据
counter = new Counter(); // 计数器和recordKey一起重置
cache = [] // cdn数据缓存
constructor(recordKey) {
this.recordKey = recordKey;
}
// 设置系统名
setSystem(system) {
this.system = system;
}
// 设置配置内容
setConfig(config) {
this.config = config;
this.setEnvironment(config.env);
}
// 设置系统环境变量
setEnvironment(env) {
if (env === 'dev') {
this.environmentUrl = 'http://hunter.duibadev.com.cn';
} else if (env === 'prod') {
this.environmentUrl = 'http://hunter.dui88.com.cn';
}
}
// 设置用户标示
setUserIdentifier(userIdentifier) {
this.userIdentifier = userIdentifier;
}
// 设置当前访问路径
setPath(url) {
this.path = url
}
// 上传CDN
toCDN(payload) {
this.isUploading = true;
const blob = new Blob([JSON.stringify(payload)], {type : 'application/json'});
const formData = new FormData();
const trackId = this.counter.count;
formData.append('file', blob, `${this.recordKey + trackId}.json`);
try {
fetch(`${this.environmentUrl}/upload`, {
// fetch(`http://172.16.47.148:3000/upload`, {
method: 'POST',
credentials: 'include',
body: formData
}).then(res => {
return res.json();
}).then(res => {
// 如果cache中有数据,说明是多次提交并且数据缓存在了cache中,那么我们全量快照可能在cache[0]
const snapArr = this.cache && this.cache.length > 0 ? this.cache : this.bus;
const snapIndex = snapArr.findIndex((item, index) => {
if (item.track.type === 2) {
return index;
}
});
const extra = {
system: this.system,
userIdentifier: this.userIdentifier,
path: this.path,
recordKey: this.recordKey,
trackId: trackId,
isCDN: true
}
log(this.config.log, 'type=2定位', snapIndex);
log(this.config.log, 'cnd Url', res.data.url);
// 向cache或者bus中注入
if(this.cache && this.cache.length > 0) {
this.cache[0].splice(snapIndex, 1, dataWrapper(extra, res.data.url));
} else {
this.bus.splice(snapIndex, 1, dataWrapper(extra, res.data.url));
}
this.isUploading = false;
// 如果cache里面有数据需要上传的,那么先上传
if(this.cache && this.cache.length > 0) {
this.cache.map(item => {
this.report(item);
});
this.cache = [];
}
return res;
});
} catch (e) {
log(this.config.log, '上传失败,原因:', e.message);
}
}
toBus(data) {
const extra = {
system: this.system,
userIdentifier: this.userIdentifier,
path: this.path,
recordKey: this.recordKey,
trackId: this.counter.next(),
isCDN: false
}
this.bus.push(dataWrapper(extra, data));
}
// 上传
report(data) {
log(this.config.log, '上传数据', data);
const reportData = data;
this.bus = [];
if (this.isUploading) {
log(this.config.log, 'cdn数据正在上传,先将内容存到cache', this.cache);
this.cache.push(reportData);
return;
}
try {
fetch(`${this.environmentUrl}/behavior/record`, {
// fetch(`http://172.16.47.148:3000/behavior/record`, {
method: 'POST',
credentials: 'include',
'Content-Type': 'application/json;charset=utf-8',
body: JSON.stringify({tracks: data})
}).then(res => {
log(this.config.log, '上传返回的内容', res);
});
} catch (e) {
log(this.config.log, '上传失败,原因:', e.message);
}
}
// 重置数据(分条使用)
reset(recordKey) {
this.cache = [];
this.bus = [];
this.counter.reset();
this.recordKey = recordKey;
}
}
export default Reporter;
import md5 from 'md5';
import utils from './utils';
import _ from 'lodash';
// 原始数据数组
let events = [];
// 包装后的数据对象
let wrapData = [];
// 分条视频id
let recordKey = '';
// 全量快照是否已经返回
let isCdnReturn = false;
// 入口,格式化数据,模块管理
import md5 from "md5";
import Reporter from "./reporter";
import { log } from './utils';
const reporter = new Reporter("recordKey");
onmessage = ({ data: { type, payload } }) => {
switch (type) {
case "init":
reporter.setSystem(payload.system);
reporter.setConfig(payload.config);
log(reporter.config.log, 'init参数', payload);
break;
case "record":
// todo : 数据本地存储
console.log(wrapData);
wrapData.push(utils.dataWrapper({
recordKey,
isCdn: false
}, payload));
events.push(payload);
reporter.toBus(payload);
log(reporter.config.log, 'bus', reporter.bus);
// todo : 全量快照上传cdn
if(payload.type === 2) {
const cdnIndex = _.findIndex(events, { 'type': 2 });
setTimeout(() => {
wrapData.splice(cdnIndex, 1, utils.dataWrapper({
recordKey,
isCdn: true
}, payload));
isCdnReturn = true;
}, 1000);
log(reporter.config.log, 'track type', payload.type);
if (payload.type === 2) {
reporter.toCDN(payload);
}
// todo : 数据压缩
// todo : 根据事件类型优先级触发上传策略(click)
if(payload.data.source === 2 && isCdnReturn) {
console.log('点击上上传events', wrapData);
postMessage(wrapData);
events = [];
wrapData = [];
if (payload.data.source === 2 && !reporter.isUploading) {
postMessage({ type: "localData", payload: reporter.bus })
reporter.report(reporter.bus);
}
// todo : 数据超出100条上线,自动上传
if (wrapData.length > 100 && isCdnReturn) {
console.log('超出线上100条上传events', wrapData)
postMessage(wrapData);
events = [];
wrapData = [];
if (reporter.bus.length > 100 && !reporter.isUploading) {
postMessage({ type: "localData", payload: reporter.bus })
reporter.report(reporter.bus);
}
break;
case "reset":
// todo : 重置参数,重新生成recordKey
events = [];
wrapData = [];
recordKey = '';
isCdnReturn = false;
const { url, email } = payload;
recordKey = md5(url + email + Date.parse(new Date()));
console.log(url, '分片id', recordKey);
const { url, email, userInfo } = payload;
reporter.setUserIdentifier(userInfo);
reporter.setPath(url);
let recordKey = md5(url + email + Date.parse(new Date()));
reporter.reset(recordKey);
log(reporter.config.log, '分片id', reporter.recordKey);
break;
default:
console.log("unknow action");
log(reporter.config.log, 'unknow action', type);
}
};
const utils = {
// 包装event
dataWrapper(extraData, event) {
/**
* 包装event
* @param {*} extraData
* @param {*} event
*/
export const dataWrapper = (extraData, event) => {
return {
...extraData,
event
track: event
}
},
// 判断是否有值
isNothing(value) {
return value === '' || value === undefined || value === null || (typeof value === 'number' && (isNaN(value) || !isFinite(value)));
},
// 获取token
getCookie(name) {
const regexp = new RegExp('(^| )' + name + '=([^;]*)(;|$)');
const matches = regexp.exec(document.cookie);
return matches ? matches[2] : null;
},
// 拼接URL请求参数
serialize(obj) {
const str = [];
for (let p in obj) {
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + '=' + encodeURIComponent(this.isNothing(obj[p]) ? '' : obj[p]));
}
}
return str.join('&');
},
}
};
export default utils;
\ No newline at end of file
/**
* 是否打印日志
* @param {*} logMes
*/
export const log = (log, logMes, logContent) => {
if( log) {
return console.log(logMes, logContent);
} else {
return;
}
};
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment