'use strict' /** * Copyright (c) 2017~2023, OBCon Inc. * All rights reserved. */ /** * @file * @copyright 2017~2023, OBCon Inc. * @author gye hyun james kim [pnuskgh@gmail.com] */ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const moment = require('moment'); //--- format : YYYYMMDDHHmmss.SSS ZZ - 20191220172919.083 +0900 const UtilityPrev = require('./UtilityPrev.js'); class Utility { constructor() { this.initialize(); } get module() { return new UtilityPrev(); } get type() { return { //--- typeof : string (String), bigint (BigInt), number (Number), boolean (Boolean), object (Null), symbol (Symbol), function, undefined (Undefined) //--- string, number, bigint, boolean, function, undefined, symbol //--- object : null, object, array typeof: (param) => Object.prototype.toString.call(param).replace(/\[object /g, '').replace(/\]/g, '').toLowerCase(), isNull: (param) => ((typeof(param) === 'undefined') || (param == null)), isNone: (param) => typeof(param) === 'undefined', isString: (param) => typeof(param) == 'string', isNumber: (param) => typeof(param) == 'number', isArray: (param) => this.type.typeof(param) == 'array', Null: this.type_Null }; } type_Null(value) { return ((typeof(value) === 'undefined') || (value == null)) ? '':value; } get link() { return { join: this.link_join, fullUrl: (url) => this.link.join(config.http.baseUrl, url) } } link_join(...paths) { if (paths.length == 0) { return ''; } const links = [ paths[0] ]; let pathPrev = paths[0]; for (let idx = 1;idx < paths.length;idx++) { if ((paths[idx] != '') && (paths[idx] != null) && (typeof(paths[idx]) == 'string')) { if ((pathPrev.endsWith('/')) && (paths[idx].startsWith('/'))) { pathPrev = paths[idx].substring(1); links.push(pathPrev); } else if ((pathPrev.endsWith('/') == false) && (paths[idx].startsWith('/') == false)) { pathPrev = paths[idx]; links.push('/' + pathPrev); } else { pathPrev = paths[idx]; links.push(pathPrev); } } } return links.join(''); } async _sleep(miliseconds) { const promise = new Promise(function(resolve, reject) { setTimeout(function() { resolve(); }, miliseconds); }); await promise; } async _wait(funcCondition, miliseconds) { const times = Math.ceil(miliseconds / 100); //--- ceil. 올림, floor. 버림 for (let idx = 0; idx < times; idx++) { await this._sleep(100); if (funcCondition()) { break; } } } async fetchFile(url, filename) { try { const file = path.join(appl.root, 'files', filename); const folder = path.dirname(file); if (fs.existsSync(folder) == false) { fs.mkdirSync(folder); } if (fs.existsSync(file)) { fs.unlinkSync(file); } const response = await fetch(url); const streamPipeline = promisify(pipeline); //--- Callback 함수를 Promise 함수로 변환 한다. await streamPipeline(response.body, createWriteStream(file)); return true; } catch (err) { utils.obj.loggerError(err); return false; } } async unzip(filename) { try { const file = path.join(appl.root, 'files', filename); const folder = path.join(appl.root, 'files', filename.replace(/\.zip$/, '')); if (fs.existsSync(folder)) { fs.rmSync(folder, { recursive: true, force: true }); } fs.mkdirSync(folder); await decompress(file, folder); return true; } catch (err) { utils.obj.loggerError(err); return false; } } get string() { return { charLen: this._charLen, substring_byte: this._substring_byte }; } _charLen(str, idx) { return (128 < str.charCodeAt(idx) ? 2:1); } _substring_byte(str, start, end) { const size = end - start; let idxByte = 0; let idxFr = 0; let idxTo = 0; for (let idx = 0; idx < str.length; idx++) { if (idxByte <= start) { idxFr = idx; } if (idxByte <= end) { idxTo = idx; } idxByte = idxByte + ((128 < str.charCodeAt(idx)) ? 2 : 1); } // for (let idx = 0; idxTo < end; idx++) { // idxTo = idxTo + ((128 < str.charCodeAt(idx)) ? 2 : 1); // } return str.substring(idxFr, idxTo); } decode(val, conditions, defaultValue = null) { for (let idx = 0; idx < conditions.length / 2; idx++) { const condition = conditions[idx * 2]; const value = conditions[idx * 2 + 1] || defaultValue; if (utils.typeof(condition) == 'array') { if (condition.includes(val)) { return value; } } else { if (val == condition) { return value; } } } return defaultValue; } initialize() { this.getPath = this.module.getPath.bind(this); this.getCustomPath = this.module.getCustomPath.bind(this); this.existFile = this.module.existFile.bind(this); this.getClass = this.module.getClass.bind(this); this.getModule = this.module.getModule.bind(this); this.Null = this.type.Null.bind(this); this.typeof = this.type.typeof.bind(this); this.isNone = this.type.isNone.bind(this); this.isNull = this.type.isNull.bind(this); this.isString = this.type.isString.bind(this); this.isNumber = this.type.isNumber.bind(this); this.isArray = this.type.isArray.bind(this); this.sleep = this._sleep.bind(this); this.wait = this._wait.bind(this); this.getFullUrl = this.link.fullUrl.bind(this); //--- Utility class에는 없는 함수 (이름이 다름) this.getTemplateFullPath = getTemplateFullPath; this.getTemplateUrl = getTemplateUrl; this.getHash = getHash; this.stringFormat = stringFormat; this.stringLength = stringLength; this.integerFormat = integerFormat; this.floatFormat = floatFormat; this.floatFormat2 = floatFormat2; this.existField = existField; this.existIdxField = existIdxField; this.getObjectValue = getObjectValue; this.deepCopy = deepCopy; this.datetimeFormatter = datetimeFormatter; this.initFlagString = initFlagString; this.getFlagString = getFlagString; this.setFlagString = setFlagString; this.getFlagNumber = getFlagNumber; this.setFlagNumber = setFlagNumber; this.checkHacking = checkHacking; this.randomInteger = randomInteger; this.randomFloat = randomFloat; this.isNetworkError = isNetworkError; this.saveNotificationNormal = saveNotificationNormal; this.saveNotificationError = saveNotificationError; this.stringToHex = stringToHex; this.hexToString = hexToString; this.isWindows = isWindows; this.isLinux = isLinux; this.makeHelp = makeHelp; this.makeSetting = makeSetting; this.getClientIp = getClientIp; this.obj = this.module.getModule('include', 'obconObject.js'); } } const zzUtil = new Utility(); //--- 전체 URL을 반환 한다. function getFullUrl(url='') { // let rturl = config.http.baseUrl + config.http.path.substring(1); // return (url == '') ? config.http.baseUrl:zzUtil.link.join(config.http.baseUrl, url); return zzUtil.link.join(config.http.baseUrl, url); } function _getTemplateFile(siteId, ...args) { const filePath = path.join.apply(this, args); let files = []; if ((siteId != null) && (siteId != -1)) { files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, 'layout', `site_${siteId}`, filePath)); files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, 'layout', `site_${siteId}`,filePath)); files.push(zzUtil.getPath('themes', config.theme.name, 'layout', `site_${siteId}`,filePath)); files.push(zzUtil.getPath('themes', config.theme.defaultName, 'layout', `site_${siteId}`,filePath)); } files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, 'layout', filePath)); files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, 'layout', filePath)); files.push(zzUtil.getPath('themes', config.theme.name, 'layout', filePath)); files.push(zzUtil.getPath('themes', config.theme.defaultName, 'layout', filePath)); if ((siteId != null) && (siteId != -1)) { files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, `site_${siteId}`, filePath)); files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, `site_${siteId}`, filePath)); files.push(zzUtil.getPath('themes', config.theme.name, `site_${siteId}`, filePath)); files.push(zzUtil.getPath('themes', config.theme.defaultName, `site_${siteId}`, filePath)); } files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, filePath)); files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, filePath)); files.push(zzUtil.getPath('themes', config.theme.name, filePath)); files.push(zzUtil.getPath('themes', config.theme.defaultName, filePath)); let rtPath = zzUtil.getPath('themes', config.theme.defaultName, filePath); for (let idx = 0; idx < files.length; idx++) { if (fs.existsSync(files[idx])) { rtPath = files[idx]; break; } } rtPath = rtPath.replace(/\\/g, '/').replace(`${config.folder_root}/themes/`, ''); return rtPath; } function getTemplateFullPath(siteId, ...args) { args.unshift(siteId); return `${config.folder_root}/themes/${_getTemplateFile.apply(this, args)}` } function getTemplateUrl(siteId, ... args) { if ((args.length == 1) && (args[0].startsWith('http'))) { return args[0]; } // Many view modules push static assets with a leading "/" (e.g. "/js/moment.min.js"). // `_getTemplateFile()` treats leading "/" as an absolute path which breaks theme lookup, // causing 404s for scripts/styles and blank SPA pages. args = args.map((v) => (typeof v === 'string' && v.startsWith('/')) ? v.replace(/^\/+/, '') : v); args.unshift(siteId); if (config.http.useFullUrl) { return `${config.http.baseUrl}${config.http.path}/${_getTemplateFile.apply(this, args)}` } else { return `${config.http.path}/${_getTemplateFile.apply(this, args)}` } } function stringFormat(valStr, len, pad=' ') { let rtStr = valStr; while (rtStr.length < len) { rtStr = pad + rtStr; } return rtStr; } function stringLength(str) { let len = 0; for (let idx = 0; idx < str.length; idx++) { if (escape(str.charAt(idx)).length == 6) { len++; } len++; } return len; } function integerFormat(valInt, len, pad=' ', abs=true) { let rtStr = valInt.toString(); if (-1 < rtStr.indexOf('.')) { rtStr = rtStr.split('.')[0]; } while (rtStr.length < len) { rtStr = pad + rtStr; } if ((abs) && (len < rtStr.length)) { rtStr = rtStr.slice(rtStr.length - len, rtStr.length); } return rtStr; } //--- To-Do : 향후 개선 한다. //--- abs : true. 소수점 아래와 소수점 위의 길이를 지킨다. function floatFormat(valFloat, lenFirst=5, lenSecond=2, padFirst=' ', padSecond='0', abs=true) { let rtStr = valFloat.toString(); let tmpFirst = null; let tmpSecond = null; if (-1 < rtStr.indexOf('.')) { let items = rtStr.split('.'); tmpFirst = items[0]; tmpSecond = items[1]; if (abs) { if (lenSecond < tmpSecond.length) { tmpSecond = tmpSecond.slice(0, lenSecond); } } } else { tmpFirst = rtStr; tmpSecond = ''; } while (tmpSecond.length < lenSecond) { tmpSecond = tmpSecond + padSecond; } rtStr = tmpFirst + '.' + tmpSecond; while (rtStr.length < (lenFirst + 1 + lenSecond)) { rtStr = padFirst + rtStr; } if ((abs) && ((lenFirst + 1 + lenSecond) < rtStr.length)) { rtStr = rtStr.slice(rtStr.length - (lenFirst + 1 + lenSecond), rtStr.length); } return rtStr; } //--- 소수점 아래 자리만 확정 한다. function floatFormat2(valFloat, lenSecond=2, padSecond='0') { let rtStr = valFloat.toString(); let tmpFirst = null; let tmpSecond = null; if (-1 < rtStr.indexOf('.')) { let items = rtStr.split('.'); tmpFirst = items[0]; tmpSecond = items[1]; if (lenSecond < tmpSecond.length) { tmpSecond = tmpSecond.slice(0, lenSecond); } } else { tmpFirst = rtStr; tmpSecond = ''; } while (tmpSecond.length < lenSecond) { tmpSecond = tmpSecond + padSecond; } rtStr = tmpFirst + '.' + tmpSecond; return rtStr; } function getHash(plainText) { let hash = crypto.createHash('md5'); hash.update(plainText); return hash.digest('hex').toLowerCase(); } function existField(paramObject, paramField) { if ((paramObject == null) || (typeof(paramObject) == 'undefined')) { return false; } return typeof(paramObject[paramField]) != 'undefined'; // return Object.getOwnPropertyNames(paramObject).includes(paramField); // return -1 < Object.getOwnPropertyNames(paramObject).indexOf(paramField); } function existIdxField(paramObject, paramField) { if ((paramObject == null) || (typeof(paramObject) == 'undefined')) { return false; } return typeof(paramObject[`idx_${paramField}`]) != 'undefined'; // return Object.getOwnPropertyNames(paramObject).includes(paramField); // return -1 < Object.getOwnPropertyNames(paramObject).indexOf(paramField); } function getObjectValue(paramObject, paramField, paramDefault) { return existField(paramObject, paramField) ? paramObject[paramField]:paramDefault; } function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); } function randomInteger(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function randomFloat(min, max) { return Math.random() * (max - min) + min; } //--- pos : 1(첫번째 문자), 2(두번째 문자), ..., 32 let maxPos = 32; function initFlagString(source) { let tmpStr = source + '0000000000000000000000000000000000000000'; return tmpStr.substring(0, maxPos); } function getFlagString(source, pos) { let tmpStr = initFlagString(source); tmpStr = tmpStr.substring(pos - 1, pos); return tmpStr; } function setFlagString(source, pos, set) { let tmpStr = initFlagString(source); tmpStr = tmpStr.substring(0, pos - 1) + set + tmpStr.substring(pos, maxPos); return tmpStr; } //--- pos : 1(일자리), 2(십자리), ..., 32 function getFlagNumber(source, pos) { let tmpStr = '0000000000000000' + Math.abs(source); tmpStr = tmpStr.substring(tmpStr.length - 16); tmpStr = tmpStr.substring(16 - pos, 16 - pos + 1); return parseInt(tmpStr); } function setFlagNumber(source, pos, set) { let tmpStr = '0000000000000000' + Math.abs(source); tmpStr = tmpStr.substring(tmpStr.length - 16); tmpStr = tmpStr.substring(0, 16 - pos) + set + tmpStr.substring(16 - pos + 1); return parseInt(tmpStr); } function checkHacking(param) { if (!param) { return false; } if ((-1 < param.indexOf(';')) || (-1 < param.indexOf('\''))) { return true; } return false; } //--- 박광서의 요청에 따라 현재 시간이 아니라, 전달되는 데이터의 시간으로 체크 한다. 2019.03.31 //--- 통신 이상 검사 : true. 통신 이상, false. 통신 정상 //--- //--- 한번도 데이터를 받은 적이 없으면 통신 이상 //--- 1. 정류기: 설정한 시간(Default 60초) 이상 데이터가 입력되지 않으면 통신 이상 //--- 2. 원격TB: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 //--- 3. 배관링크: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 //--- 4. 수위센서: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 //--- 5. 관말압력 //--- 피크타임 모드인 경우 //--- 피크타임인 경우 - 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 //--- 피크타임이 아닌 경우 - 항상 통신 정상 //--- 피크타임 모드가 아닌 경우 - 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 //--- 6. 특정정압기: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 //--- 7. 수신감도: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상 // function isNetworkError(deviceIndex, type, networkCycle, networkCycleUnit, statusDatetime) { function isNetworkError(siteKey, device, statusDatetime) { const deviceIndex = siteKey + '_' + device.type + '_' + device.deviceKey; let type = device.type; let networkCycle = device.networkCycle; let networkCycleUnit = utils.decode(device.networkCycleUnit, [ '1', '초', '2', '분', '3', '시' ], device.networkCycleUnit) ; if (type == '5') { //--- 5. 관말압력 if (device.networkMode == '4') { //--- 4. 피크타임 모드 networkCycle = device.peakNetworkCycle; networkCycleUnit = '분'; //--- 설정된 피크타임 시간이 아니면, statusDatetime을 현재 시간으로 설정하여 항상 통신 이상이 발생하지 않도록 한다. let isPeak = false; let hour = parseInt(moment().format('HH')); //--- 현재 시간 (0 ~ 24) if ((device.peakTimeStart1 <= hour) && (hour < device.peakTimeEnd1)) { isPeak = true; } if ((device.peakTimeStart2 <= hour) && (hour < device.peakTimeEnd2)) { isPeak = true; } if (isPeak == false) { statusDatetime = moment(); } } } //--- 통신 이상 여부를 검사 한다. let rtcd = true; if (statusDatetime == null) { //--- 한번도 데이터를 받은 적이 없으면 통신 이상 처리 return true; } let datetimeFr = statusDatetime; //--- 최종 도착한 데이터의 시간 let datetimeTo = moment(); //--- 현재 시간 let duration = 0; let unit = '분'; switch (type) { case '1' : unit = '초'; duration = moment.duration(datetimeTo.diff(datetimeFr)).asSeconds(); //--- 정류기는 60초 이상 데이터가 입력되지 않으면 통신 이상 처리 -> 설정한 시간으로 비교 // rtcd = (60 < duration); rtcd = (device.networkCheckTime < duration); // logger.info(`include/Utility.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, networkCycle : ${device.networkCheckTime} :: ${rtcd}`); break; case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : unit = '분'; duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes(); if (networkCycleUnit == '시') { networkCycle = networkCycle * 60; } //--- 박광서의 요청으로 임시로 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 오류 처리 (2019-03-11) rtcd = (3 * networkCycle < duration); // logger.info(`include/Utility.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, 3 * networkCycle : ${3 * networkCycle} :: ${rtcd}`); break; case '8': case 'A': case 'B': default: switch (networkCycleUnit) { case '초': unit = '초'; duration = moment.duration(datetimeTo.diff(datetimeFr)).asSeconds(); break; case '분': unit = '분'; duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes(); break; case '시': unit = '분'; duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes(); networkCycle = networkCycle * 60; break; } rtcd = (3 * networkCycle < duration); // logger.info(`include/global_utils.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, 3 * networkCycle : ${3 * networkCycle} :: ${rtcd}`); break; } return rtcd; } function saveNotificationNormal(device, datetimeTo) { let noti = {}; noti.name = '통신 정상'; noti.site = device.site; noti.device = device.id; noti.deviceKey = device.deviceKey; noti.deviceName = device.name; noti.branch = -1; noti.branchName = ''; noti.type = device.type; noti.eventDatetime = datetimeTo.format('YYYY-MM-DD HH:mm:ss'); noti.level = 'Info'; noti.description = '통신 정상'; //--- branch로 branch 이름을 가져 온다. let query = { where: { id: device.branch, deleted: false } }; modules.branches.model.table.findOne(query) .then(function(branch) { if (branch != null) { noti.branch = branch.id; noti.branchName = branch.name; } modules.notifications.model.table.create(noti) .then(function(results) { // logger.info(JSON.stringify(results)); }).catch(utils.obj.loggerError); }).catch(function(err) { logger.error(JSON.stringify(err)); modules.notifications.model.table.create(noti) .then(function(results) { // logger.info(JSON.stringify(results)); }).catch(utils.obj.loggerError); }); } function saveNotificationError(device, datetimeTo) { let noti = {}; noti.name = '통신 이상'; noti.site = device.site; noti.device = device.id; noti.deviceKey = device.deviceKey; noti.deviceName = device.name; noti.branch = -1; noti.branchName = ''; noti.type = device.type; noti.eventDatetime = datetimeTo.format('YYYY-MM-DD HH:mm:ss'); noti.level = 'Error'; noti.description = '통신 이상'; //--- branch로 branch 이름을 가져 온다. let query = { where: { id: device.branch, deleted: false } }; modules.branches.model.table.findOne(query) .then(function(branch) { if (branch != null) { noti.branch = branch.id; noti.branchName = branch.name; } modules.notifications.model.table.create(noti) .then(function(results) { // logger.info(JSON.stringify(results)); }).catch(utils.obj.loggerError); }).catch(function(err) { logger.error(JSON.stringify(err)); modules.notifications.model.table.create(noti) .then(function(results) { // logger.info(JSON.stringify(results)); }).catch(utils.obj.loggerError); }); } function stringToHex(str) { const buf = Buffer.from(str, 'utf8'); return buf.toString('hex'); } function hexToString(str) { const buf = new Buffer(str, 'hex'); return buf.toString('utf8'); } function isWindows() { return (process.env.HOMEDRIVE) ? true:false; } function isLinux() { return (process.env.HOME) ? true:false; } //--- To-Do : LocalStorage, Cookie 등을 사용하여 설정 관리 function makeSetting(id='iconSetting') { // if ((!config.func) || (!config.func.page) || (!config.func.page.setting)) { // return ''; // } // if (config.func.page.setting == false) { // return ''; // } //--- https://icons.getbootstrap.com/ //   // return ``; return ``; } //--- options.helpPage = 'url'; //--- options.helpPage = [ { name: '', url: '' } ]; //--- To-Do : 선택 목록을 보여준 후 선택시 도움말 화면 표시 function makeHelp(id='iconHelp') { // if ((!config.func) || (!config.func.page) || (!config.func.page.setting)) { // return ''; // } // if (config.func.page.setting == false) { // return ''; // } //--- https://icons.getbootstrap.com/ // return ``; // return ``; return ``; } function getClientIp(req) { var clientIp = req.body.clientIp || req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress || ''; clientIp = clientIp.split(',')[0]; clientIp = clientIp.split(':').slice(-1)[0]; return clientIp; } function datetimeFormatter(str, formatFr = 'YYYYMMDDHHmmss', formatTo = 'YYYY-MM-DD HH:mm:ss') { return (str == '') ? '': moment(str, formatFr).format(formatTo); } // async function sleep(miliseconds) { // let promise = new Promise(function(resolve, reject) { // setTimeout(function() { // resolve(); // }, miliseconds); // }); // await promise; // } module.exports = Utility;