代码拉取完成,页面将自动刷新
const log4js = require('log4js');
const crypto = require('crypto');
const fs = require('fs');
const nodeUtil = require('util');
const EventEmitter = require('events');
const config = require('./config');
// 日志
log4js.configure(config.log4js_conf);
const accessLogger = log4js.getLogger('access');
const lightLogger = log4js.getLogger('light');
accessLogger.setLevel(config.log_level.access);
lightLogger.setLevel(config.log_level.light);
const errMsg = {
method_not_null: 'method不能为空',
request_url_not_null: 'request_url不能为空',
back_url_not_null: 'back_url不能为空',
method_not_found: '指定的 method 不存在',
request_file_fail: '生成请求文件异常',
request_send_fail: '处理request文件时发送请求异常',
file_broken: '文件被损坏',
out_of_sync_count: '强制触发,超过最大同步请求数!',
request_timeout: '请求超时!',
unknow_error: '未定义的异常'
};
const logger = lightLogger;
const REQUEST = 'request';
const RESPONSE = 'response';
const unlink = nodeUtil.promisify(fs.unlink); //删除文件
const readdir = nodeUtil.promisify(fs.readdir); //读取目录内容
const writeFile = nodeUtil.promisify(fs.writeFile); //写文件
const stat = nodeUtil.promisify(fs.stat);
// 文件是否存在
const exists = (path) => {
return new Promise ((suc, fail) => {
fs.access(path, fs.constants.R_OK | fs.constants.W_OK, (err) => {
suc(!err)
})
})
}
const syncEvent = new EventEmitter(); //同步请求事件对象,用于发起同步请求时保存通知事件
const SYNC_EVENT_FIX = 'notice-'; //事件名前缀,事件名的格式为 ${notice}-${sequece}
const tmpDir = config.tmp_path; //临时文件保存目录
if (! fs.existsSync(tmpDir)){
logger.debug('临时目录不存在,创建 tmp 目录:' + tmpDir);
fs.mkdirSync(tmpDir);
}
let delQueue = []; //删除失败的文件缓存队列
//let writeError; //保存写入时出现的异常,用于判断是否可以写入
let requesteBuf = []; // 请求文件保存前的缓存
let responseBuf = []; //响应文件保存前的缓存
let sequence = 1; //序列
/**
* 获取一个序列号
*/
function getSequence(){
return sequence ++;
}
/**
* 新的请求数据
*/
const pushRequest = (data) => {
let sequence = getSequence();
requesteBuf.push({
sequence, data
});
return sequence;
};
/**
* 新的响应数据
*/
const pushResponse = (sequence, data) => {
responseBuf.push({
sequence, data
});
};
/**
* 检验文件是否完成
* @param file 文件路径
*/
const removeFile = async (file)=>{
logger.info(`删除文件:${file}`);
try{
await unlink(file);
}catch(e){
logger.error(`文件:${file},删除失败:`, e);
//request与response目录也会出现删除失败的情况,把删除失败的文件放入到一个队列中,可以在定时中定时清理
delQueue.push(file);
}
}
/**
* 清理 删除失败的文件缓存队列
*/
const cleanQueue = async ()=>{
if (!delQueue.length){
return;
}
let queue = [];
logger.info(`删除失败的文件 ${delQueue.length} 个,重新尝试删除`);
for(file of delQueue){
try{
await unlink(file);
}catch(e){
queue.push(file);
}
}
delQueue = queue;
logger.warn(`无法删除的文件 ${queue.length} 个`);
}
function hexMd5 (str) {
let md5 = crypto.createHash('md5');
md5.update(str, 'utf-8');
return md5.digest('hex');
}
/**
* 清除目录中的所有文件
*/
const emptyDir = async (path)=>{
if (await exists(path)){
let files = await readdir(path);
let count = files.length;
let fail = 0;
let delCount = 0;
let clearTimestrap = new Date().getTime() - config.clean_cycle; //此时间之前的文件删除
logger.info(`删除修改时间 ${new Date(clearTimestrap).format('yyyy-MM-dd HH:mm:ss')} 之前的文件`)
for(file of files){
let fix = file.split('.').pop().toLowerCase();
let filepath = `${path}/${file}`;
if (fix === REQUEST || fix === RESPONSE){
//此处可判断文件的创建时间,比如只删除10分钟前创建的文件,这样可避免文件被损坏后找,由于源文件被删除导致数据丢失的情况。
let fileStat = await stat(filepath);
if (fileStat.ctimeMs < clearTimestrap){ //使用修改时间比较
delCount ++;
//删除文件
try{
logger.debug(`删除已传输的文件:${filepath}`);
await unlink(filepath);
}catch(e){
logger.error(`文件:${filepath},删除失败:`, e);
fail ++;
}
}
}
}
logger.info(`清理目录 ${path} ,一共 ${count} 个文件,需删除 ${delCount},删除失败 ${fail}`)
}
}
//生成请求文件
const createRequestFile = async (data)=>{
let jsonString = JSON.stringify(data);
let timestamp = new Date().format('yyyyMMddHHmmss');
let filename = `${timestamp}-${hexMd5(jsonString)}.${REQUEST}`;
let tmpPath = `${tmpDir}/${filename}`;
let targetPath = `${config.request_path}/${filename}`;
logger.info(`生成请求文件:${filename}`);
//将文件先写入到临时目录,再move到request目录,这样可避免写入文件过大时,文件还未写入完成就被光闸读取到的情况。
await writeFile(tmpPath, jsonString); //先把文件写入到临时目录
//然后把文件从临时目录移动到 request 目录
fs.rename(tmpPath, targetPath, (e) =>{
e && logger.error('转移 request 文件异常:', e);
});
};
//生成响应文件
const createResponseFile = async (data)=>{
let jsonString = JSON.stringify(data);
let timestamp = new Date().format('yyyyMMddHHmmss');
let filename = `${timestamp}-${hexMd5(jsonString)}.${RESPONSE}`;
let tmpPath = `${tmpDir}/${filename}`;
//由于响应文件需要经光闸传输到另一端,所以也应该写入到 request 目录
let targetPath = `${config.request_path}/${filename}`;
logger.info(`生成请求文件:${filename}`);
//将文件先写入到临时目录,再move到request目录,这样可避免写入文件过大时,文件还未写入完成就被光闸读取到的情况。
await writeFile(tmpPath, jsonString); //先把文件写入到临时目录
//然后把文件从临时目录移动到 request 目录
fs.rename(tmpPath, targetPath, (e) =>{
e && logger.error('转移 request 文件异常:', e);
});
};
//获取一个异常header
const getErrMsg = (key) =>{
if (errMsg[key]){
return {
code: key,
message: errMsg[key]
}
}else{
return {
code: 'unknow_error',
message: errMsg.unknow_error
}
}
}
//定时保存请求与响应文件
setInterval(async () => {
if (requesteBuf.length){
logger.debug(`开始保存请求缓存:${requesteBuf.length} 条`)
try{
let tmp = requesteBuf;
requesteBuf = [];
await createRequestFile(tmp);
}catch(e){
logger.error('保存请求缓存失败:', e);
}
}
if (responseBuf.length){
logger.debug(`开始保存响应缓存:${responseBuf.length} 条`)
try{
let tmp = responseBuf;
responseBuf = [];
await createResponseFile(tmp);
}catch(e){
logger.error('保存响应缓存失败:', e);
}
}
}, config.save_cycle);
//定时判断同步事件数量,防止内存泄露
setInterval(() => {
let list = syncEvent.eventNames();
logger.debug(`现有通知事件:${list ? list.length : 0} 个。`);
if (list && list.length > config.max_sync_event_count){
logger.info('通知事件过多,开始清理');
for (let i=0,len=list.length - config.max_sync_event_count; i<len; i++){
syncEvent.emit(list[i], {
header: getErrMsg('out_of_sync_count'),
sequence: list[i].split('-').pop()
});
}
}
}, config.clear_sync_event_cycle);
module.exports = {
REQUEST, RESPONSE, logger, removeFile, hexMd5, emptyDir, cleanQueue, pushRequest, pushResponse, createRequestFile, createResponseFile, getErrMsg, exists,
sucResMsg: {
code: 'success',
message: '成功'
},
/**
* 绑定通知事件
* @param fun 触发事件,包含一个参数 (json) => {}, json为响应结果
*/
bindNotice: (seq, fun) => {
syncEvent.once(SYNC_EVENT_FIX + seq, fun);
logger.debug(`绑定同步请求通知事件:${seq}`);
},
//触发通知事件
emitNotice: (seq, json) => {
logger.debug(`触发同步请求通知事件:${seq}`)
let b = syncEvent.emit(SYNC_EVENT_FIX + seq, json);
b || logger.warn(`同事通知事件 seq=${seq} 触发失败,json=`, json);
}
}
Date.prototype.format = function(formatStr)
{
var str = formatStr;
var Week = ['日', '一', '二', '三', '四', '五', '六'];
str = str.replace(/yyyy|YYYY/, this.getFullYear());
str = str.replace(/yy|YY/, (this.getYear() % 100) > 9 ? (this.getYear() % 100).toString() : '0' + (this.getYear() % 100));
var month = this.getMonth() + 1;
str = str.replace(/MM/, month > 9 ? month.toString() : '0' + month);
str = str.replace(/M/g, month);
str = str.replace(/w|W/g, Week[this.getDay()]);
str = str.replace(/dd|DD/, this.getDate() > 9 ? this.getDate().toString() : '0' + this.getDate());
str = str.replace(/d|D/g, this.getDate());
str = str.replace(/hh|HH/, this.getHours() > 9 ? this.getHours().toString() : '0' + this.getHours());
str = str.replace(/h|H/g, this.getHours());
str = str.replace(/mm/, this.getMinutes() > 9 ? this.getMinutes().toString() : '0' + this.getMinutes());
str = str.replace(/m/g, this.getMinutes());
str = str.replace(/ss|SS/, this.getSeconds() > 9 ? this.getSeconds().toString() : '0' + this.getSeconds());
str = str.replace(/s|S/g, this.getSeconds());
return str;
};
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。