logReport.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /**
  2. * @description 日志上报
  3. */
  4. export default class LogReport {
  5. URL_API = {
  6. 'http:': 'http://www.androidscloud.com:8002',
  7. 'https:': 'https://www.androidscloud.com:8003',
  8. }
  9. URL_SWITCH = '/api/public/v5/log/card/getLinkLogReportSwitch'; // 日志开关查询接口地址
  10. URL_ADDRESS = '/api/public/v5/log/card/reportCardLinkLog'; // 日志上报地址
  11. $Request = null; // 用于发送 HTTP 请求的对象
  12. version = ''; // 版本号 可通过$Request版本获取
  13. // 上报参数
  14. paramsJson = {
  15. 'timeConsuming': '', // 进入云机耗时字段 毫秒单位
  16. 'clientVersion': '', // 客户端版本号 // 通过$Request版本获取
  17. 'clientType': '', // 客户端类型 // 目前判断出wx或h5
  18. 'phoneModel': '', // 手机型号 // 无法获取
  19. 'phoneSystemVersion': '', // 手机系统版本号 // 无法获取
  20. 'phoneNetwork': '', // 手机网络类型
  21. 'videoType': '', // 视频类型
  22. 'imageQuality': '', // 推流质量 [高清 | 流畅]
  23. 'userCardId': '',
  24. 'cardInfoId': '', // 无法获取
  25. 'resourceId': '', // 资源ID
  26. 'transferServerIp': '', // 中转服务器IP
  27. 'linkStartTime': '', // 链接开始时间 格式 yyyy-MM-dd HH:mm:ss
  28. 'linkEndTime': '', // 链接结束时间 格式 yyyy-MM-dd HH:mm:ss
  29. // 'linkTime': '', // 链接时间 格式 yyyy-MM-dd HH:mm:ss
  30. 'linkScene': 1, // 链接场景
  31. 'linkWay': 0, // 链接方式(0:其它原因 1:中转链接、2:打洞链接、3:安卓卡网络状态差、4:接口返回链接信息缺失)
  32. 'plugFowStatus': '', // 推流状态 int: 1 成功 2:失败
  33. 'logContent': '' // 日志内容
  34. };
  35. reportSwitchStatus = false; // 上报日志开关状态
  36. logs = []; // 用于存储日志的数组
  37. // 客户端类型 枚举值
  38. CLIENT_TYPE = Object.freeze({
  39. 'android': 1,
  40. 'ios': 2,
  41. 'pc': 3,
  42. 'miniprogram': 5, // wx小程序 在 uniapp获取类型为miniprogram
  43. 'h5': 7,
  44. });
  45. // 码率 枚举值
  46. BITRATE = Object.freeze({
  47. 1800: '标清',
  48. 2200: '标清',
  49. 2800: '标清',
  50. 6000: '高清',
  51. 1243000: '超清',
  52. });
  53. // 链接场景 枚举值
  54. LINK_SCENE = Object.freeze({
  55. '直连': 1,
  56. '重连': 2,
  57. '通道断开重连': 3,
  58. '信令连接失败重连': 4,
  59. '鉴权失败重连': 5
  60. });
  61. // 视频类型 枚举值
  62. VIDEO_TYPE = Object.freeze({
  63. 'h265': 1,
  64. 'h264': 2,
  65. });
  66. // 接口响应码 枚举值 对应 linkWay字段状态
  67. RESPONSE_CODE = Object.freeze({
  68. 5200: 3, // RBD资源挂载中
  69. 5220: 3, // 云手机正在一键修复中
  70. 5203: 3, // 入使用排队9.9,年卡
  71. 5204: 3, // 9.9年卡连接异常,重新进入排队
  72. 5228: 3, // '卡的网络状态为差'
  73. 5229: 4, // '接口返回链接信息缺失'
  74. });
  75. maxLogs = 1; // 存储最大日志数量
  76. timer = null; // 定时器
  77. timerTime = 6000; // // 日志上报间隔时间
  78. timeStartTime = 0; // 链接开始时间
  79. /**
  80. * 构造函数,初始化 LogReport 类的实例
  81. * @param {Object} $Request - 用于发送 HTTP 请求的对象
  82. * @param {Object} opt - 可选的配置对象
  83. */
  84. constructor(opt) {
  85. /**
  86. // 扩展API是否准备好,如果没有则监听“plusready"事件
  87. // if (window.plus) {
  88. // this.plusReady()
  89. // } else {
  90. // document.addEventListener('plusready', this.plusReady, false)
  91. // }
  92. document.addEventListener('plusready', ()=> {
  93. console.log('plusReady')
  94. console.log(plus)
  95. console.log(plus.device.model)
  96. console.log(plus.networkinfo.getCurrentType())
  97. // plus.device.model
  98. // plus.networkinfo.getCurrentType()
  99. // plus.device.networkinfo.getCurrentType()
  100. }, false);
  101. */
  102. // 初始化 $Request 属性
  103. this.$Request = opt.request;
  104. this.version = this.$Request.defaults.headers.versionname;
  105. this.init();
  106. }
  107. // 初始化
  108. async init() {
  109. const now = new Date();
  110. // 开始连接的时间戳
  111. this.timeStartTime = now.getTime();
  112. // 设置本次日志上报时间
  113. this.paramsJson.linkStartTime = this.formatDate(now);
  114. // 调用 checkSwitch 方法,检查后端上报日志开关是否打开
  115. await this.checkSwitch();
  116. this.netWorkChange();
  117. // 创建定时器
  118. // await this.createTimer();
  119. }
  120. // 获取浏览器网络类型 isDestroy 是否移除监听
  121. netWorkChange(isDestroy = false) {
  122. if(!isDestroy && 'connection' in navigator) {
  123. const connection = navigator.connection;
  124. // 初始化时获取一次
  125. this.paramsJson.phoneNetwork = connection.effectiveType;
  126. // 监听网络类型变化
  127. const updateResourceLoad = () => {
  128. this.paramsJson.phoneNetwork = connection.effectiveType;
  129. }
  130. // 监听网络类型变化
  131. connection.addEventListener('change', updateResourceLoad);
  132. if(isDestroy) {
  133. // 移除事件监听
  134. connection.removeEventListener('change', updateResourceLoad);
  135. }
  136. }
  137. }
  138. /**
  139. * 检查后端上报日志开关是否打开
  140. */
  141. async checkSwitch() {
  142. try{
  143. const res = await this.getLinkLogReportSwitch();
  144. if(res.status === 0 && res.success){
  145. let { cardLinkRodeSwitch } = res.data;
  146. this.reportSwitchStatus = cardLinkRodeSwitch;
  147. }else{
  148. console.error('检查日志上报开关失败')
  149. }
  150. }catch(e){
  151. console.error(e)
  152. }
  153. }
  154. /**
  155. * 查询webRTC日志上报开关状态
  156. * 无参
  157. * @return {Request}
  158. * {
  159. * "status": 0,
  160. * "msg": "",
  161. * "data": {
  162. * "cardLinkRodeSwitch":true, // 是否上报记录
  163. * "cardLinkLogFileSwitch":true // 是否收集错误日志
  164. * }
  165. * }
  166. */
  167. getLinkLogReportSwitch() {
  168. return this.$Request.get(this.URL_API[location.protocol] + this.URL_SWITCH);
  169. }
  170. // 采集日志, 等待日志收集到一定数量或一定时间后再上报
  171. collectLog(log) {
  172. try {
  173. // 日志开关关闭,直接返回
  174. if(!this.reportSwitchStatus) {return;}
  175. // 组装本次日志上报参数
  176. let logData = this.combinativeParam();
  177. logData.logContent = log;
  178. this.logs.push(logData);
  179. // 超过最大日志数量,上报日志
  180. if(this.logs.length >= this.maxLogs && this.reportSwitchStatus){
  181. this.report();
  182. // 重置定时器
  183. // this.createTimer();
  184. }
  185. } catch (error) {
  186. console.error('collectLog内部错误');
  187. console.log(error);
  188. console.dir(error);
  189. console.log('log', log);
  190. }
  191. }
  192. // 设置日志上报参数
  193. setParams(obj) {
  194. try {
  195. // 合并参数
  196. this.paramsJson = {
  197. ...this.paramsJson,
  198. ...obj
  199. }
  200. } catch (error) {
  201. console.error(error);
  202. }
  203. }
  204. // 组装日志上报固定参数
  205. combinativeParam() {
  206. try {
  207. let params = {
  208. ...this.paramsJson,
  209. };
  210. params.clientVersion = this.version;
  211. // 客户端类型 枚举值赋值
  212. params.clientType = this.enumAssignment(this.CLIENT_TYPE, this.paramsJson.clientType);
  213. params.imageQuality = this.enumAssignment(this.BITRATE, this.paramsJson.imageQuality);
  214. params.linkScene = this.enumAssignment(this.LINK_SCENE, this.paramsJson.linkScene);
  215. params.videoType = this.enumAssignment(this.VIDEO_TYPE, this.paramsJson.videoType);
  216. return params;
  217. } catch (error) {
  218. console.error(error);
  219. }
  220. }
  221. // 日志记录上报 字符串日志上报
  222. report() {
  223. this.logs.forEach(() => {
  224. this.$Request.post(this.URL_API[location.protocol] + this.URL_ADDRESS, { ...this.logs.shift() })
  225. .then(res => {
  226. console.log('日志上报成功', res);
  227. });
  228. })
  229. }
  230. // 生成or重置定时器
  231. async createTimer() {
  232. await this.clearTimer();
  233. if(this.reportSwitchStatus){
  234. this.timer = setInterval(() => {
  235. if(this.logs.length > 0){
  236. this.report();
  237. }
  238. }, this.timerTime);
  239. }
  240. }
  241. // 清空定时器
  242. async clearTimer() {
  243. this.timer && clearInterval(this.timer);
  244. return true;
  245. }
  246. // 检查是否为枚举值
  247. isCheckEnum(enumObj, velue) {
  248. return Object.values(enumObj).includes(velue);
  249. }
  250. // 使用枚举值赋值
  251. enumAssignment(enumObj, velue) {
  252. try {
  253. let str = '';
  254. if(velue.toString() !== '') {
  255. // 判断是否已为枚举值
  256. str = this.isCheckEnum(enumObj, velue) ? velue : enumObj?.[velue];
  257. }
  258. return str;
  259. } catch (error) {
  260. console.error('enumAssignment内部错误');
  261. console.log(error);
  262. console.dir(error);
  263. console.log('enumObj', enumObj);
  264. console.log('velue', velue);
  265. }
  266. }
  267. // 关闭销毁
  268. async destroy() {
  269. // 清空日志
  270. this.logs = [];
  271. // 关闭日志上报开关
  272. this.reportSwitchStatus = false;
  273. // 清空定时器
  274. await this.clearTimer();
  275. // 移除事件监听
  276. this.netWorkChange(true);
  277. }
  278. /**
  279. * 格式化日期
  280. * @param {Date} date - 要格式化的日期对象
  281. * @param {string} format - 格式化字符串,使用以下占位符:
  282. * - 'Y' 表示年份(4 位数)
  283. * - 'm' 表示月份(2 位数)
  284. * - 'd' 表示日期(2 位数)
  285. * - 'H' 表示小时(24 小时制)
  286. * - 'h' 表示小时(12 小时制)
  287. * - 'i' 表示分钟
  288. * - 's' 表示秒数
  289. * - 'u' 表示毫秒
  290. * - 'a' 表示上午/下午标识(小写)
  291. * - 'A' 表示上午/下午标识(大写)
  292. * @returns {string} 格式化后的日期字符串
  293. */
  294. formatDate(date, format='Y-m-d H:i:s.u') {
  295. // 判断输入是否为 Date 对象
  296. if (!(date instanceof Date)) {
  297. throw new TypeError('The first parameter must be a Date object.');
  298. }
  299. // 定义时间单位的获取方法
  300. const formatObj = {
  301. 'Y': date.getFullYear(), // 年份
  302. 'm': date.getMonth() + 1, // 月份(+1 是因为 getMonth() 返回的月份是从 0 开始的)
  303. 'd': date.getDate(), // 日期
  304. 'H': date.getHours(), // 小时(24 小时制)
  305. 'h': date.getHours() % 12 || 12, // 小时(12 小时制)
  306. 'i': date.getMinutes(), // 分钟
  307. 's': date.getSeconds(), // 秒数
  308. 'u': date.getMilliseconds(), // 毫秒
  309. 'a': date.getHours() >= 12 ? 'pm' : 'am', // 上午/下午标识
  310. 'A': date.getHours() >= 12 ? 'PM' : 'AM' // 上午/下午标识(大写)
  311. };
  312. // 替换格式字符串中的占位符
  313. return format.replace(/Y|m|d|H|h|i|s|u|a|A/g, (match) => {
  314. return formatObj[match];
  315. });
  316. }
  317. }