/** * @description 日志上报 */ export default class LogReport { URL_API = { 'http:': 'http://www.androidscloud.com:8002', 'https:': 'https://www.androidscloud.com:8003', } URL_SWITCH = '/api/public/v5/log/card/getLinkLogReportSwitch'; // 日志开关查询接口地址 URL_ADDRESS = '/api/public/v5/log/card/reportCardLinkLog'; // 日志上报地址 $Request = null; // 用于发送 HTTP 请求的对象 version = ''; // 版本号 可通过$Request版本获取 // 上报参数 paramsJson = { 'timeConsuming': '', // 进入云机耗时字段 毫秒单位 'clientVersion': '', // 客户端版本号 // 通过$Request版本获取 'clientType': '', // 客户端类型 // 目前判断出wx或h5 'phoneModel': '', // 手机型号 // 无法获取 'phoneSystemVersion': '', // 手机系统版本号 // 无法获取 'phoneNetwork': '', // 手机网络类型 'videoType': '', // 视频类型 'imageQuality': '', // 推流质量 [高清 | 流畅] 'userCardId': '', 'cardInfoId': '', // 无法获取 'resourceId': '', // 资源ID 'transferServerIp': '', // 中转服务器IP 'linkStartTime': '', // 链接开始时间 格式 yyyy-MM-dd HH:mm:ss 'linkEndTime': '', // 链接结束时间 格式 yyyy-MM-dd HH:mm:ss // 'linkTime': '', // 链接时间 格式 yyyy-MM-dd HH:mm:ss 'linkScene': 1, // 链接场景 'linkWay': 0, // 链接方式(0:其它原因 1:中转链接、2:打洞链接、3:安卓卡网络状态差、4:接口返回链接信息缺失) 'plugFowStatus': '', // 推流状态 int: 1 成功 2:失败 'logContent': '' // 日志内容 }; reportSwitchStatus = false; // 上报日志开关状态 logs = []; // 用于存储日志的数组 // 客户端类型 枚举值 CLIENT_TYPE = Object.freeze({ 'android': 1, 'ios': 2, 'pc': 3, 'miniprogram': 5, // wx小程序 在 uniapp获取类型为miniprogram 'h5': 7, }); // 码率 枚举值 BITRATE = Object.freeze({ 1800: '标清', 2200: '标清', 2800: '标清', 6000: '高清', 1243000: '超清', }); // 链接场景 枚举值 LINK_SCENE = Object.freeze({ '直连': 1, '重连': 2, '通道断开重连': 3, '信令连接失败重连': 4, '鉴权失败重连': 5 }); // 视频类型 枚举值 VIDEO_TYPE = Object.freeze({ 'h265': 1, 'h264': 2, }); // 接口响应码 枚举值 对应 linkWay字段状态 RESPONSE_CODE = Object.freeze({ 5200: 3, // RBD资源挂载中 5220: 3, // 云手机正在一键修复中 5203: 3, // 入使用排队9.9,年卡 5204: 3, // 9.9年卡连接异常,重新进入排队 5228: 3, // '卡的网络状态为差' 5229: 4, // '接口返回链接信息缺失' }); maxLogs = 1; // 存储最大日志数量 timer = null; // 定时器 timerTime = 6000; // // 日志上报间隔时间 timeStartTime = 0; // 链接开始时间 /** * 构造函数,初始化 LogReport 类的实例 * @param {Object} $Request - 用于发送 HTTP 请求的对象 * @param {Object} opt - 可选的配置对象 */ constructor(opt) { /** // 扩展API是否准备好,如果没有则监听“plusready"事件 // if (window.plus) { // this.plusReady() // } else { // document.addEventListener('plusready', this.plusReady, false) // } document.addEventListener('plusready', ()=> { console.log('plusReady') console.log(plus) console.log(plus.device.model) console.log(plus.networkinfo.getCurrentType()) // plus.device.model // plus.networkinfo.getCurrentType() // plus.device.networkinfo.getCurrentType() }, false); */ // 初始化 $Request 属性 this.$Request = opt.request; this.version = this.$Request.defaults.headers.versionname; this.init(); } // 初始化 async init() { const now = new Date(); // 开始连接的时间戳 this.timeStartTime = now.getTime(); // 设置本次日志上报时间 this.paramsJson.linkStartTime = this.formatDate(now); // 调用 checkSwitch 方法,检查后端上报日志开关是否打开 await this.checkSwitch(); this.netWorkChange(); // 创建定时器 // await this.createTimer(); } // 获取浏览器网络类型 isDestroy 是否移除监听 netWorkChange(isDestroy = false) { if(!isDestroy && 'connection' in navigator) { const connection = navigator.connection; // 初始化时获取一次 this.paramsJson.phoneNetwork = connection.effectiveType; // 监听网络类型变化 const updateResourceLoad = () => { this.paramsJson.phoneNetwork = connection.effectiveType; } // 监听网络类型变化 connection.addEventListener('change', updateResourceLoad); if(isDestroy) { // 移除事件监听 connection.removeEventListener('change', updateResourceLoad); } } } /** * 检查后端上报日志开关是否打开 */ async checkSwitch() { try{ const res = await this.getLinkLogReportSwitch(); if(res.status === 0 && res.success){ let { cardLinkRodeSwitch } = res.data; this.reportSwitchStatus = cardLinkRodeSwitch; }else{ console.error('检查日志上报开关失败') } }catch(e){ console.error(e) } } /** * 查询webRTC日志上报开关状态 * 无参 * @return {Request} * { * "status": 0, * "msg": "", * "data": { * "cardLinkRodeSwitch":true, // 是否上报记录 * "cardLinkLogFileSwitch":true // 是否收集错误日志 * } * } */ getLinkLogReportSwitch() { return this.$Request.get(this.URL_API[location.protocol] + this.URL_SWITCH); } // 采集日志, 等待日志收集到一定数量或一定时间后再上报 collectLog(log) { try { // 日志开关关闭,直接返回 if(!this.reportSwitchStatus) {return;} // 组装本次日志上报参数 let logData = this.combinativeParam(); logData.logContent = log; this.logs.push(logData); // 超过最大日志数量,上报日志 if(this.logs.length >= this.maxLogs && this.reportSwitchStatus){ this.report(); // 重置定时器 // this.createTimer(); } } catch (error) { console.error('collectLog内部错误'); console.log(error); console.dir(error); console.log('log', log); } } // 设置日志上报参数 setParams(obj) { try { // 合并参数 this.paramsJson = { ...this.paramsJson, ...obj } } catch (error) { console.error(error); } } // 组装日志上报固定参数 combinativeParam() { try { let params = { ...this.paramsJson, }; params.clientVersion = this.version; // 客户端类型 枚举值赋值 params.clientType = this.enumAssignment(this.CLIENT_TYPE, this.paramsJson.clientType); params.imageQuality = this.enumAssignment(this.BITRATE, this.paramsJson.imageQuality); params.linkScene = this.enumAssignment(this.LINK_SCENE, this.paramsJson.linkScene); params.videoType = this.enumAssignment(this.VIDEO_TYPE, this.paramsJson.videoType); return params; } catch (error) { console.error(error); } } // 日志记录上报 字符串日志上报 report() { this.logs.forEach(() => { this.$Request.post(this.URL_API[location.protocol] + this.URL_ADDRESS, { ...this.logs.shift() }) .then(res => { console.log('日志上报成功', res); }); }) } // 生成or重置定时器 async createTimer() { await this.clearTimer(); if(this.reportSwitchStatus){ this.timer = setInterval(() => { if(this.logs.length > 0){ this.report(); } }, this.timerTime); } } // 清空定时器 async clearTimer() { this.timer && clearInterval(this.timer); return true; } // 检查是否为枚举值 isCheckEnum(enumObj, velue) { return Object.values(enumObj).includes(velue); } // 使用枚举值赋值 enumAssignment(enumObj, velue) { try { let str = ''; if(velue.toString() !== '') { // 判断是否已为枚举值 str = this.isCheckEnum(enumObj, velue) ? velue : enumObj?.[velue]; } return str; } catch (error) { console.error('enumAssignment内部错误'); console.log(error); console.dir(error); console.log('enumObj', enumObj); console.log('velue', velue); } } // 关闭销毁 async destroy() { // 清空日志 this.logs = []; // 关闭日志上报开关 this.reportSwitchStatus = false; // 清空定时器 await this.clearTimer(); // 移除事件监听 this.netWorkChange(true); } /** * 格式化日期 * @param {Date} date - 要格式化的日期对象 * @param {string} format - 格式化字符串,使用以下占位符: * - 'Y' 表示年份(4 位数) * - 'm' 表示月份(2 位数) * - 'd' 表示日期(2 位数) * - 'H' 表示小时(24 小时制) * - 'h' 表示小时(12 小时制) * - 'i' 表示分钟 * - 's' 表示秒数 * - 'u' 表示毫秒 * - 'a' 表示上午/下午标识(小写) * - 'A' 表示上午/下午标识(大写) * @returns {string} 格式化后的日期字符串 */ formatDate(date, format='Y-m-d H:i:s.u') { // 判断输入是否为 Date 对象 if (!(date instanceof Date)) { throw new TypeError('The first parameter must be a Date object.'); } // 定义时间单位的获取方法 const formatObj = { 'Y': date.getFullYear(), // 年份 'm': date.getMonth() + 1, // 月份(+1 是因为 getMonth() 返回的月份是从 0 开始的) 'd': date.getDate(), // 日期 'H': date.getHours(), // 小时(24 小时制) 'h': date.getHours() % 12 || 12, // 小时(12 小时制) 'i': date.getMinutes(), // 分钟 's': date.getSeconds(), // 秒数 'u': date.getMilliseconds(), // 毫秒 'a': date.getHours() >= 12 ? 'pm' : 'am', // 上午/下午标识 'A': date.getHours() >= 12 ? 'PM' : 'AM' // 上午/下午标识(大写) }; // 替换格式字符串中的占位符 return format.replace(/Y|m|d|H|h|i|s|u|a|A/g, (match) => { return formatObj[match]; }); } }