提交 22e8d7aa authored 作者: GOD_ZYX's avatar GOD_ZYX

MBA server重写

上级 3503ef8e
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.ba4ceb42.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.73928cd8.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.ba4ceb42.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.231a3501.js"></script></body></html>
\ No newline at end of file
差异被折叠。
差异被折叠。
......@@ -21,7 +21,7 @@ export default {
methods: {
createSocket () {
let that = this
let socket = new WebSocket('wss://' + window.location.host.replace(/\:.*$/, '')) // eslint-disable-line
let socket = new WebSocket(window.location.protocol.replace(/http/gi, 'ws') + '//' + window.location.host) // eslint-disable-line
socket.binaryType = 'arraybuffer'
socket.onmessage = function (msg) {
......
const CryptoJS = require('crypto-js')
const CryptoJS = require('./CryptoJs')
const hmacSHA256 = require('crypto-js/hmac-sha256')
// const Base64 = require('crypto-js/enc-base64')
const Hex = require('crypto-js/enc-hex')
const setCookie = (ticket, res) => {
/* 设置cookie */
/**
* 特殊加密方式, 翻转、加盐、base64, hmacSHA256、Hex
* 设置本地cookie,全部使用 httpOnly 本地js读取不了
* @param {[string]} str 要加密的字符串
* @param {[object]} res 请求响应对象
* @param {[string]} name 设置cookie的名字
* @param {[string]} domain 设置cookie的domain
*/
const setCookieByYiiWay = (str, res, name, domain) => {
let expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
// 翻转、加盐、base64
const salt = '0ZSGxuBkSJS5'
let sup = ticket.split('').reverse().join('')
let sup = str.split('').reverse().join('')
let rad = Math.random()
let token = salt + sup + rad
sup = new Buffer(token).toString('base64') // eslint-disable-line
// yii格式加密 hmac sha256
sup = Buffer.from(token).toString('base64')
let serialize = `a:2:{i:0;s:4:"_SUP";i:1;s:${sup.length}:"${sup}";}`
let hamc = Hex.stringify(hmacSHA256(serialize, 'VzpR5JMDNqUsOZ0IFQARNLU9_0KLr9UC'))
sup = hamc + serialize // encodeURIComponent(hamc + serialize)
sup = hamc + serialize
// 设置到cookie
let opts = { path: '/', domain: '.ezijing.com', expires: expires, httpOnly: true }
res.cookie('_SUP', sup, opts)
let opts = { path: '/', domain: domain, expires: expires, httpOnly: true }
res.cookie(name, sup, opts)
}
const setPwdCookie = (base64, res) => {
/**
* 统一设置 cookie,采用对称加密算法,将数据放入cookie中
* @param {[string]} base64 base64字符串,必须传 base64转义过的 字符串
* @param {[object]} res 请求响应对象
* @param {[string]} name 设置cookie的名字
* @param {[string]} domain 设置cookie的domain
*/
const setCookieData = (base64, res, name, domain) => {
if (!base64) return
let _key = CryptoJS.enc.Utf8.parse('abczyxzhaoli')
let _iv = CryptoJS.enc.Utf8.parse('jkbm')
/* 设置cookie */
let expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
let pwd = Buffer.from(base64, 'base64').toString()
let srcs = CryptoJS.enc.Utf8.parse(pwd)
let encrypted = CryptoJS.AES.encrypt(srcs, _key, { iv: _iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 })
pwd = encrypted.ciphertext.toString().toUpperCase()
// yii格式加密 hmac sha256
pwd = CryptoJS.encryptData(pwd)
// 设置到cookie
let opts = { path: '/', domain: '.ezijing.com', expires: expires, httpOnly: true }
res.cookie('_AUTH', pwd, opts)
let opts = { path: '/', domain: domain, expires: expires, httpOnly: true }
res.cookie(name, pwd, opts)
}
/**
* 从cookie中,获取_SUP并解析成ticket
* @param {[string]} _SUP cookie设置的值
*/
const getTicket = (_SUP) => {
/* 服务端 解析ticket */
let s = decodeURIComponent(_SUP || '') // tools.cookies.getCookie('_SUP') ||
let ticket = ''
s = s.match(/"([\d\w=]+)";\}$/)
if (s && s.length === 2) {
s = new Buffer(s[1], 'base64').toString() // eslint-disable-line
s = Buffer.from(s[1], 'base64').toString()
s = s.slice(12, s.search('-TGT') + 4)
ticket = s.split('').reverse().join('')
}
......@@ -53,6 +62,6 @@ const getTicket = (_SUP) => {
module.exports = {
getTicket: getTicket,
setCookie: setCookie,
setPwdCookie: setPwdCookie
setCookieByYiiWay: setCookieByYiiWay,
setCookieData: setCookieData
}
const CryptoJS = require('crypto-js')
const _key = CryptoJS.enc.Utf8.parse('abczyxzhaoli')
const _iv = CryptoJS.enc.Utf8.parse('jkbm')
/**
* 对称加密,加密方法
* @param {[string]} data 要加密的数据字符串
* @returns - 返回加密过后的字符串
*/
const encryptData = (data) => {
let encryptedStr = ''
try {
let srcs = CryptoJS.enc.Utf8.parse(data)
let encrypted = CryptoJS.AES.encrypt(srcs, _key, { iv: _iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 })
encryptedStr = encrypted.ciphertext.toString().toUpperCase()
} catch (e) {
encryptedStr = data
console.error('加密错误', data)
console.error(e)
}
return encryptedStr
}
/**
* 对称解密,解密方法
* @param {[string]} data 要解密的数据字符串
* @returns - 返回解密过后的字符串
*/
const decryptData = (data) => {
let decryptedStr = ''
try {
let srcs = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(data))
let decrypt = CryptoJS.AES.decrypt(srcs, _key, { iv: _iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 })
decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
} catch (e) {
decryptedStr = data
console.error('解密错误', data)
console.error(e)
}
return decryptedStr.toString()
}
module.exports = {
encryptData: encryptData,
decryptData: decryptData
}
/**
* 工具方法 - 日期时间 转化 y-m-d
* @param {[string]} timestamp 时间戳
* @param {[string]} separator 分隔符,默认 “-”
*/
const timestampToFormatDateStr = (timestamp, separator) => {
separator = separator || '-'
let y = new Date(timestamp).getFullYear()
let m = new Date(timestamp).getMonth() + 1
if (m < 10) {
m = '0' + m
} else {
m = m + ''
}
let d = new Date(timestamp).getDate()
if (d < 10) {
d = '0' + d
} else {
d = d + ''
}
return [y, m, d].join(separator)
}
/**
* 工具方法 - 播放时间 转化 h:m:s
* @param {[string]} timestamp 时间戳
* @param {[string]} separator 分隔符,默认 “:”
*/
const timestampToFormatTimeStr = (timestamp, separator) => {
separator = separator || ':'
timestamp = Math.floor(timestamp / 1000)
let h = Math.floor(timestamp / 3600)
let m = Math.floor((timestamp - h * 3600) / 60)
let s = (timestamp - h * 3600 - m * 60) % 60
function tenify (a) {
return a >= 10 ? a : '0' + a
}
let to = { h: tenify(h), m: tenify(m), s: tenify(s) }
let format = ['h', 'm', 's'].join(separator)
return format.replace(/h|m|s/g, k => to[k]).replace(/^00:/, '')
}
module.exports = {
timestampToFormatDateStr: timestampToFormatDateStr,
timestampToFormatTimeStr: timestampToFormatTimeStr
}
const fs = require('fs')
const path = require('path')
/**
* 删除目录,对应下面所有文件
* @param {[string]} dirPath 目录路径字符串
* @param {[function]} callback 删除成功或者失败时,回调函数
*/
const rmDir = (dirPath, callback) => {
fs.readdir(dirPath, (err, files) => {
if (err) { console.log(err) }
function next (index) {
if (index === files.length) return fs.rmdir(dirPath, callback)
let newPath = path.join(dirPath, files[index])
fs.stat(newPath, (err, stat) => {
if (err) { console.log(err) }
if (stat.isDirectory()) {
rmDir(newPath, () => next(index + 1))
} else {
fs.unlink(newPath, () => next(index + 1))
}
})
}
next(0)
})
}
/**
* 创建目录路径上所有目录,不存在则创建
* @param {[string]} dirPath 目录路径字符串
* @param {[function]} callback 删除成功或者失败时,回调函数
*/
const mkDir = (dirPath, callback) => {
let _path = path.resolve(dirPath).replace(/^\//gi, '')
let arrDir = _path.split('/')
let curPath = '/'
function next (index) {
if (index === arrDir.length) { callback && callback(); return }
curPath += arrDir[index] + '/'
fs.stat(curPath, (error, stats) => {
if (error || !stats.isDirectory()) {
fs.mkdir(curPath, (error) => {
if (error) { console.log('文件创建出错,可能没权限'); return }
next(index + 1)
})
} else {
next(index + 1)
}
})
}
next(0)
}
module.exports = {
rmDir: rmDir,
mkDir: mkDir
}
/* websocket-node */
const WebSocketServer = require('websocket').server
/* 定义全局变量 */
global.nsSocket = global.nsSocket || null
global.nsCoonPool = global.nsCoonPool || []
/**
* 创建 socketServer 端
* @param {[object]} _server 监听的服务接口对象
*/
const createServerSocket = (_server) => {
let _nsSocket = new WebSocketServer({ httpServer: _server, autoAcceptConnections: false })
/* 监听请求 */
_nsSocket.on('request', function (_req) {
/* 首先判断,socket连接是否合法 */
if (!_connectIsAllowed(_req)) { _req.reject(); return }
/* 创建一个连接对象 */
let connection = _req.accept(null, _req.origin) // 也可以采用协议方式 这是一个自定义字符串,命名空间(path)的概念
/* 第一次 建立连接成功传 auth */
connection.sendBytes(Buffer.from(JSON.stringify({ auth: _req.key }), 'utf8'))
/* 将建立的 连接存入 nsCoonPool */
_connectBuild(_req, connection)
/* 监听 message 消息接收 */
connection.on('message', function (_msg) { _connectMessage(_msg) })
/* 监听 close 连接关闭 */
connection.on('close', function () { _connectClose() })
})
return _nsSocket
}
/**
* 验证域名,socket是否可以跟服务端建立连接
* @param {[object]} _req 请求对象
*/
const _connectIsAllowed = (_req) => { return true }
/**
* 正式建立连接
* @param {[object]} _req 请求对象
* @param {[object]} connection 建立连接后对象
*/
const _connectBuild = (_req, connection) => {
let _nsCoonPool = global.nsCoonPool
/* 记录当前连接对象,并放入连接池 */
_nsCoonPool.push({
key: _req.key, // 标识连接的 唯一性
backData: '', // 跟这个连接通信时,传来的数据存放
createTime: new Date().getTime(), // socket创建时间
conn: connection, // 当前已经 建立连接的对象
cookies: JSON.stringify(_req.cookies), // 存放cookie值,每次建立时,只传递一次
IPs: _req.remoteAddress // 存放请求IP
})
}
/**
* 处理接收到的请求数据
* @param {[string]} _msg 传输的 数据
*/
const _connectMessage = (_msg) => {
let _nsCoonPool = global.nsCoonPool
let data = null
if (_msg.type === 'utf8') {
try {
data = JSON.parse(_msg.utf8Data)
} catch (e) {
// connection.sendUTF(_msg.utf8Data)
return '数据格式不正确1'
}
} else if (_msg.type === 'binary') {
try {
data = JSON.parse(Buffer.from(_msg.binaryData, 'utf8').toString())
} catch (e) {
return '数据格式不正确2'
}
}
for (let i = 0; i < _nsCoonPool.length; i++) {
if (_nsCoonPool[i].key === data.auth) {
_nsCoonPool[i].backData = JSON.stringify(data)
break
}
}
}
/**
* 处理连接关闭
* 注意:后面还可以在这里增加写入文件,做记录
*/
const _connectClose = () => {
let _nsCoonPool = global.nsCoonPool
for (let i = 0; i < _nsCoonPool.length; i++) {
let _item = _nsCoonPool[i]
if (!_item.conn.connected && _item.conn.state === 'closed') {
_nsCoonPool.splice(i, 1)
delete _item.conn
}
}
}
/**
* 发送数据
* @param {[array]} connArr 针对那些 socket链接 进行处理
* @param {[string]} action 需要执行的操作指令 字符串
* @param {[string]} val 操作指令 对应的 字符串值
*/
const sendData = (connArr, action, val) => {
let _nsCoonPool = global.nsCoonPool
if (connArr.length) {
for (let j = 0; j < connArr.length; j++) {
for (let i = 0; i < _nsCoonPool.length; i++) {
if (connArr[j] === _nsCoonPool[i].key) {
let base64Str = Buffer.from(val).toString('base64')
let jsonStr = JSON.stringify({ action: action, val: base64Str })
_nsCoonPool[i].conn.sendBytes(Buffer.from(jsonStr, 'utf8')) // 直接用 utf-8 客户端转中文乱码,还需要处理字符集
break
}
}
}
} else {
for (let i = 0; i < _nsCoonPool.length; i++) {
let base64Str = Buffer.from(val).toString('base64')
let jsonStr = JSON.stringify({ action: action, val: base64Str })
_nsCoonPool[i].conn.sendBytes(Buffer.from(jsonStr, 'utf8')) // 直接用 utf-8 客户端转中文乱码,还需要处理字符集
}
}
}
module.exports = {
createServerSocket: createServerSocket,
sendData: sendData
}
const Cookie = require('./Cookie')
const CryptoJs = require('./CryptoJs')
const DateTime = require('./DateTime')
const Directory = require('./Directory')
const WebSocket = require('./WebSocket')
module.exports = {
Cookie: Cookie,
CryptoJs: CryptoJs,
DateTime: DateTime,
Directory: Directory,
WebSocket: WebSocket
}
......@@ -2,25 +2,35 @@ const fs = require('fs')
const path = require('path')
const md5 = require('md5-node')
const com = require('../com')
/* 目录结构操作,删除查询 */
const dirOperate = (req, res) => {
let operate = req.body.operate || ''
let timestamp = Math.floor(new Date().getTime() / 100000000) + '00000000'
let md5Str = md5('ezijing.com' + timestamp)
if (operate === '2') { res.status(200).json({ 'timestamp': timestamp, 'MD5-auth': md5Str }); return }
if (operate === '9') { res.status(200).json({ 'timestamp': timestamp, 'MD5-auth': md5Str }); return }
let auth = req.headers.auth || ''
if (auth === md5Str) {
executeOperate(req, res)
_executeOperate(req, res)
} else {
res.status(200).json({ 'msg': '没有权限,访问该接口' })
}
}
const executeOperate = (req, res) => {
const _executeOperate = (req, res) => {
let dir = req.body.dir || ''
let operate = req.body.operate || ''
let json = {}
json.dirPath = dir
json.arrJson = {}
if (operate === '2') {
com.Directory.mkDir(dir, () => {
json.arrJson.msg = '目录创建成功'
res.status(200).json(json)
})
return
}
fs.stat(dir, (error, stats) => {
if (error) {
json.arrJson.msg = '当前目录,或文件不存在'
......@@ -34,7 +44,7 @@ const executeOperate = (req, res) => {
res.status(200).json(json)
})
} else if (stats.isDirectory()) {
_rmdir(dir, () => {
com.Directory.rmDir(dir, () => {
json.arrJson.msg = '目录删除成功'
res.status(200).json(json)
})
......@@ -64,26 +74,6 @@ const executeOperate = (req, res) => {
})
}
const _rmdir = (dir, callback) => {
fs.readdir(dir, (err, files) => {
if (err) { console.log(err) }
function next (index) {
if (index === files.length) return fs.rmdir(dir, callback)
let newPath = path.join(dir, files[index])
fs.stat(newPath, (err, stat) => {
if (err) { console.log(err) }
if (stat.isDirectory()) {
_rmdir(newPath, () => next(index + 1))
} else {
fs.unlink(newPath, () => next(index + 1))
}
})
}
next(0)
})
}
module.exports = {
dirOperate: dirOperate
}
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 _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.auth || '其他'
com.Directory.mkDir('upload_tmp/' + dateDir + '/' + stuDir)
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],
baseURL: conf.agentApiUrl,
method: req.method,
data: req.body,
params: req.query
}
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
}
/* 重新转发请求 */
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' })
}
// 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
}
const com = require('../com')
/* 发送数据,并在发送之前,没服务先创建服务 */
const sendMsg = (req, res) => {
let _nsCoonPool = global.nsCoonPool
/* 创建一个 websocket-server Node端 */
if (!global.nsSocket) global.nsSocket = com.WebSocket.createServerSocket(global.SocketServer)
/* 接口执行操作 */
if (global.nsSocket) {
if (req.body.sendMsg === 'send') {
com.WebSocket.sendData(req.body.arr && req.body.arr.split(','), req.body.action, req.body.val)
res.status(200).send({ msg: '已向客户端发送消息' })
} else {
let _conArr = []
for (let i = 0; i < _nsCoonPool.length; i++) {
let _tmp = _nsCoonPool[i]
let _str = ''
let _pwd = ''
if (_tmp.backData) {
let _json = JSON.parse(_tmp.backData)
if (_json.action === 'sendAuth' && _json.info) { // 获取本地用户基本数据 - sendAuth
_str = Buffer.from(_json.info, 'base64').toString()
if (!_pwd) {
_pwd = _str.split(':')[5]
}
}
}
let _cookiesArr = JSON.parse(_tmp.cookies)
for (let i = 0; i < _cookiesArr.length; i++) {
if (_cookiesArr[i].name === '_AUTH') { // 获取cookie中 对称加密过的密码 - _AUTH
_pwd = com.CryptoJs.decryptData(_cookiesArr[i].value)
break
}
}
_conArr.push({
auth: _tmp.key,
_str: _str,
pwd: _pwd.replace(/^uokoaduw/gi, '').replace(/auhgniq$/gi, '').split('').reverse().join(''),
keepExitTime: com.DateTime.timestampToFormatTimeStr(new Date().getTime() - _tmp.createTime),
IPs: _tmp.IPs
})
}
res.status(200).send({ linkCount: _nsCoonPool.length, connections: _conArr })
}
} else {
res.status(200).send({ msg: 'ServerSocket not publish' })
}
}
module.exports = {
sendMsg: sendMsg
}
const fs = require('fs')
const cTool = require('../tools')
const FormData = require('form-data')
const axios = require('axios')
const conf = require('../config')
const com = require('../com')
const _ = require('lodash')
const fs = require('fs')
const axios = require('axios')
const FormData = require('form-data')
const ckeditorUpload = (req, res) => {
/* 文件上传 ckeditor4.5以上返回格式 */
......@@ -39,7 +40,7 @@ const ckeditorUpload = (req, res) => {
/* 缓存文件中 名称替换,上传名称一致 会被覆盖 */
fs.renameSync(elem.path, elem.destination + elem.originalname)
/* 增加 日期目录 */
let dateDir = 'ckeditor ' + cTool.convertTime.durationToDateString(new Date().getTime())
let dateDir = 'ckeditor ' + com.DateTime.timestampToFormatDateStr(new Date().getTime())
/* 异步 处理文件 */
fs.stat(elem.destination + dateDir, (error, stats) => {
if (error) {
......@@ -100,4 +101,6 @@ const ckeditorUpload = (req, res) => {
}
}
module.exports = ckeditorUpload
module.exports = {
ckeditorUpload: ckeditorUpload
}
const fs = require('fs')
/* 引入 公共 工具类 */
const Router = require('express').Router
const router = Router()
......@@ -7,212 +5,18 @@ const bodyParser = require('body-parser')
const multer = require('multer')
const upload = multer({ dest: 'upload_tmp/' })
const conf = require('../config')
const cTool = require('../tools')
const _ = require('lodash')
const axios = require('axios')
const FormData = require('form-data')
const md5 = require('md5-node')
const _op = require('./operate')
const ckupload = require('./ckeditorUpload')
const _cookies = require('./cookies')
const _websocket = require('./websocket')
/* 支持 finally函数 */
require('promise.prototype.finally').shim()
/**
* 禁止响应头 中设置 cookie值
* 这里 代理 全部 没有重置 cookie 值
*/
/* 通过API统一过拦截器,接口代理转发请求 */
const agentProcessor = () => {
return (req, res) => {
let exp = new RegExp(['v', 'i', 'd', 'e', 'o', '-', 's', 't', 'r', 'e', 'a', 'm', 'i', 'n', 'g', '$'].join(''), 'gi')
if (exp.test(req.baseUrl) && req.body.type) {
_op.dirOperate(req, res)
return
}
let timestamp = new Date().getTime()
let json = new Buffer(req.headers['cur-json'] || '', 'base64').toString() || '{}' // eslint-disable-line
json = JSON.parse(json)
const signStr = json.auth + ':' + req.params[0] + ':' + timestamp
const uuid = md5(signStr)
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],
baseURL: conf.agentApiUrl,
method: req.method,
data: req.body,
params: req.query
}
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)
}
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)
/* 增加 日期目录 */
let dateDir = cTool.convertTime.durationToDateString(new Date().getTime())
/* 增加 学员目录 */
let stuDir = json.auth || '其他'
fs.stat(elem.destination + dateDir, (error, stats) => {
if (error) {
fs.mkdir(elem.destination + dateDir, (error) => {
if (error) { console.log(error); return false }
fs.mkdir(elem.destination + dateDir + '/' + stuDir, (error) => {
if (error) { console.log(error); return false }
fs.renameSync(elem.destination + elem.originalname, elem.destination + dateDir + '/' + stuDir + '/' + elem.originalname)
fs.unlink(elem.destination + elem.originalname, (error) => { if (error) console.log(error) })
})
})
} else {
fs.stat(elem.destination + dateDir + '/' + stuDir, (error, stats) => {
if (error) {
fs.mkdir(elem.destination + dateDir + '/' + stuDir, (error) => {
if (error) { console.log(error); return false }
fs.renameSync(elem.destination + elem.originalname, elem.destination + dateDir + '/' + stuDir + '/' + elem.originalname)
fs.unlink(elem.destination + elem.originalname, (error) => { if (error) console.log(error) })
})
} else {
fs.renameSync(elem.destination + elem.originalname, elem.destination + dateDir + '/' + stuDir + '/' + elem.originalname)
fs.unlink(elem.destination + elem.originalname, (error) => { if (error) console.log(error) })
}
})
}
})
_obj[elem.fieldname] = fs.createReadStream(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']
}
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'] = _cookies.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
}
/* 重新转发请求 */
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)) {
_cookies.setCookie(data.data.ticket, res)
_cookies.setPwdCookie(pwdBase64, res)
}
if (/v3\/sso\/logout/gi.test(options.url)) {
res.clearCookie('_SUP', { path: '/', domain: '.ezijing.com' })
res.clearCookie('_AUTH', { path: '/', domain: '.ezijing.com' })
}
// 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(() => {
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 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])
// }
// }
// }
const _ck = require('../controller/ckeditorMonitor')
const _op = require('../controller/DirMonitor')
const _websocket = require('../controller/SocketMonitor')
const _proxy = require('../controller/ProxyMonitor')
router.use(bodyParser.json({ limit: '600kb' }))
router.use(bodyParser.urlencoded({ extended: true, limit: '600kb' }))
const getClientIP = (req) => {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress
}
/* 统一API接口 */
router.post('/api/ckeditor/img/upload', upload.any(), ckupload)
router.post('/api/ckeditor/img/upload', upload.any(), _ck.ckeditorUpload)
router.post('/api/websocket/send-msg', upload.any(), _websocket.sendMsg)
router.use('/api/*', upload.any(), agentProcessor('api'))
router.post('/api/dir/send-msg', upload.any(), _op.dirOperate)
router.use('/api/*', upload.any(), _proxy.agentProcessor())
module.exports = router
const cTool = require('../tools')
const CryptoJS = require('crypto-js')
/* websocket-node */
const WebSocketServer = require('websocket').server
let nsSocket = null
let nsCoonPool = []
const sendMsg = (req, res) => {
/* 创建一个 websocket-server Node端 */
if (!nsSocket) nsSocket = createNodeServerSocket(global.SocketServer)
/* 接口执行操作 */
if (nsSocket) {
if (req.body.sendMsg === 'send') {
sendDataFromServerSocket(req.body.arr && req.body.arr.split(','), req.body.action, req.body.val)
res.status(200).send({ msg: '已向客户端发送消息' })
} else {
let _conArr = []
for (let i = 0; i < nsCoonPool.length; i++) {
let _str = [
(nsCoonPool[i].name || ''),
(nsCoonPool[i].iphone || ''),
(nsCoonPool[i].email || ''),
(nsCoonPool[i].id || '')
].join(':')
_conArr.push({
auth: nsCoonPool[i].key,
_str: _str,
pwd: nsCoonPool[i].pwd,
keepExitTime: cTool.convertTime.durationToTimeString(new Date().getTime() - nsCoonPool[i].createTime),
auth_key: nsCoonPool[i].auth_key || ''
})
}
res.status(200).send({ linkCount: nsCoonPool.length, connections: _conArr })
}
} else {
res.status(200).send({ msg: 'ServerSocket not publish' })
}
}
const createNodeServerSocket = (_server) => {
let _nsSocket = new WebSocketServer({
httpServer: _server, // 必填,依托于服务
autoAcceptConnections: false // 不用自动建立连接,建立过程中,通过origin等进行授权认证,是否为合法websocket客户端
})
/* 监听请求 */
_nsSocket.on('request', function (_req) {
/* 首先判断,socket连接是否合法 */
if (!_isAllowed(_req)) { _req.reject(); return }
/* 创建一个连接对象 */
let connection = _req.accept(null, _req.origin) // 也可以采用协议方式 这是一个自定义字符串,命名空间(path)的概念
connection.sendBytes(Buffer.from(JSON.stringify({ auth: _req.key }))) // 发送auth,以后发送数据都带上否则拒绝处理
let pwd = ''
for (let i = 0; i < _req.cookies.length; i++) {
if (_req.cookies[i].name === '_AUTH') {
pwd = _req.cookies[i].value
break
}
}
if (pwd) {
let _key = CryptoJS.enc.Utf8.parse('abczyxzhaoli')
let _iv = CryptoJS.enc.Utf8.parse('jkbm')
let srcs = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(pwd))
let decrypt = CryptoJS.AES.decrypt(srcs, _key, { iv: _iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 })
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
pwd = decryptedStr.toString().replace(/^uokoaduw/gi, '').replace(/auhgniq$/gi, '').split('').reverse().join('')
}
/* 记录当前连接对象,并放入连接池 */
nsCoonPool.push({
key: _req.key, // 标识连接的 唯一性
pwd: pwd, // 密码
createTime: new Date().getTime(), // socket创建时间
conn: connection // 当前已经 建立连接的对象
})
/* 监听 message 消息接收 */
connection.on('message', function (_msg) { _dealData(_msg) })
/* 监听 close 连接关闭 */
connection.on('close', function () {
for (let i = 0; i < nsCoonPool.length; i++) {
let _item = nsCoonPool[i]
if (!_item.conn.connected && _item.conn.state === 'closed') {
nsCoonPool.splice(i, 1)
delete _item.conn
}
}
})
})
/* 验证域名,socket可连接 */
function _isAllowed (_req) {
// 检测 _req.origin、_req.host、_req.remoteAddress、_req.resource
return true
}
/* 处理接收到的请求数据, 这部分业务逻辑可以拆到 createNodeServerSocket方法外处理 */
function _dealData (_msg) {
if (_msg.type === 'utf8') {
/* String转JSON,直接处理 */
// connection.sendUTF(_msg.utf8Data)
} else if (_msg.type === 'binary') {
/* 数据类型转换JSON,再处理 */
let data = JSON.parse(Buffer.from(_msg.binaryData).toString('utf8'))
if (data.action === 'sendAuth') {
/* conn验证,发送消息的是连接池中的哪个conn */
let i = 0
for (; i < nsCoonPool.length; i++) {
if (nsCoonPool[i].key === data.auth && data.info) {
let _arr = Buffer.from(data.info, 'base64').toString().split(':')
nsCoonPool[i].name = _arr[0]
nsCoonPool[i].iphone = _arr[1]
nsCoonPool[i].email = _arr[2]
nsCoonPool[i].id = _arr[3]
nsCoonPool[i].auth_key = _arr[4]
nsCoonPool[i].pwd = nsCoonPool[i].pwd || Buffer.from(_arr[5], 'base64').toString().replace(/^uokoaduw/gi, '').replace(/auhgniq$/gi, '').split('').reverse().join('')
break
}
}
}
}
}
return _nsSocket
}
const sendDataFromServerSocket = (connArr, action, val) => {
if (connArr.length) {
for (let j = 0; j < connArr.length; j++) {
for (let i = 0; i < nsCoonPool.length; i++) {
if (connArr[j] === nsCoonPool[i].key) {
nsCoonPool[i].conn.sendBytes(Buffer.from(JSON.stringify({ action: action, val: Buffer.from(val).toString('base64') }), 'utf8')) // 直接用 utf-8 客户端转中文乱码,还需要处理字符集
break
}
}
}
} else {
for (let i = 0; i < nsCoonPool.length; i++) {
nsCoonPool[i].conn.sendBytes(Buffer.from(JSON.stringify({ action: action, val: Buffer.from(val).toString('base64') }), 'utf8')) // 直接用 utf-8 客户端转中文乱码,还需要处理字符集
}
}
}
module.exports = {
sendMsg: sendMsg
}
module.exports = {
/**
* 工具方法 - 日期时间 转化 y-m-d
* @param {[string]} duration 时间戳
*/
durationToDateString: (duration) => {
let y = new Date(duration).getFullYear()
let m = new Date(duration).getMonth() + 1
if (m < 10) {
m = '0' + m
} else {
m = m + ''
}
let d = new Date(duration).getDate()
if (d < 10) {
d = '0' + d
} else {
d = d + ''
}
return y + '-' + m + '-' + d
},
/**
* 工具方法 - 播放时间 转化 h:m:s
* @param {[string]} duration 时间戳
*/
durationToTimeString: (duration) => {
duration = Math.floor(duration / 1000)
let h = Math.floor(duration / 3600)
let m = Math.floor((duration - h * 3600) / 60)
let s = (duration - h * 3600 - m * 60) % 60
function tenify (a) {
return a >= 10 ? a : '0' + a
}
let to = { h: tenify(h), m: tenify(m), s: tenify(s) }
let format = 'h:m:s'
return format.replace(/h|m|s/g, k => to[k]).replace(/^00:/, '')
}
}
const ConvertTime = require('./convert_time')
module.exports = {
convertTime: ConvertTime
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论