'use strict'; /** * 辅助方法扩展 * * @author CaiAoLin * @date 2017/9/28 * @version */ const zeroRange = 0.0000000001; const fs = require('fs'); const path = require('path'); const streamToArray = require('stream-to-array'); const _ = require('lodash'); const bc = require('../lib/base_calc.js'); const Decimal = require('decimal.js'); module.exports = { _: _, /** * 生成随机字符串 * * @param {Number} length - 需要生成字符串的长度 * @param {Number} type - 1为数字和字符 2为纯数字 3为纯字母 * @return {String} - 返回生成结果 */ generateRandomString(length, type = 1) { length = parseInt(length); length = isNaN(length) ? 1 : length; let randSeed = []; let numberSeed = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let stringSeed = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; switch (type) { case 1: randSeed = stringSeed.concat(numberSeed); stringSeed = numberSeed = null; break; case 2: randSeed = numberSeed; break; case 3: randSeed = stringSeed; break; default: break; } const seedLength = randSeed.length - 1; let result = ''; for (let i = 0; i < length; i++) { const index = Math.ceil(Math.random() * seedLength); result += randSeed[index]; } return result; }, /** * 字节转换 * @param {number} bytes - 字节 * @return {string} - 大小 */ bytesToSize(bytes) { if (parseInt(bytes) === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); // return (bytes / Math.pow(k, i)) + ' ' + sizes[i]; return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; }, /** * 浮点乘法计算 * @param {number} arg1 - 乘数 * @param {number} arg2 - 被乘数 * @return {string} - 结果 */ accMul(arg1, arg2) { let m = 0; const s1 = arg1.toString(); const s2 = arg2.toString(); try { m += s1.split('.')[1] !== undefined ? s1.split('.')[1].length : 0; } catch (e) { throw e; } try { m += s2.split('.')[1] !== undefined ? s2.split('.')[1].length : 0; } catch (e) { throw e; } return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m); }, accAdd(arg1, arg2) { let r1; let r2; try { r1 = arg1.toString().split('.')[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split('.')[1].length; } catch (e) { r2 = 0; } const c = Math.abs(r1 - r2); const m = Math.pow(10, Math.max(r1, r2)); if (c > 0) { const cm = Math.pow(10, c); if (r1 > r2) { arg1 = Number(arg1.toString().replace('.', '')); arg2 = Number(arg2.toString().replace('.', '')) * cm; } else { arg1 = Number(arg1.toString().replace('.', '')) * cm; arg2 = Number(arg2.toString().replace('.', '')); } } else { arg1 = Number(arg1.toString().replace('.', '')); arg2 = Number(arg2.toString().replace('.', '')); } return (arg1 + arg2) / m; }, // 四舍五入或末尾加零,实现类似php的 sprintf("%.".decimal."f", val); roundNum(val, decimals) { if (val !== '') { val = parseFloat(val); if (decimals < 1) { val = (Math.round(val)).toString(); } else { let num = val.toString(); if (num.lastIndexOf('.') === -1) { num += '.'; num += this.makezero(decimals); val = num; } else { const valdecimals = num.split('.')[1].length; if (parseInt(valdecimals) < parseInt(decimals)) { num += this.makezero(parseInt(decimals) - parseInt(valdecimals)); val = num; } else if (parseInt(valdecimals) > parseInt(decimals)) { val = parseFloat(val) !== 0 ? Math.round(this.accMul(val, this.makemultiple(decimals))) / this.makemultiple(decimals) : this.makedecimalzero(decimals); let num = val.toString(); if (num.lastIndexOf('.') === -1) { num += '.'; num += this.makezero(decimals); val = num; } else { const valdecimals = num.split('.')[1].length; if (parseInt(valdecimals) < parseInt(decimals)) { num += this.makezero(parseInt(decimals) - parseInt(valdecimals)); val = num; } } } } } } return val; }, // 生成num位的0 makezero(num) { const arr = new Array(num); for (let i = 0; i < num; i++) { arr[i] = 0; } return arr.join(''); }, // 生成num位的10倍数 makemultiple(num) { return Math.pow(10, parseInt(num)); }, // 根据单位获取小数位数 findDecimal(unit) { let value = this.ctx.tender.info.precision.other.value; const changeUnits = this.ctx.tender.info.precision; for (const d in changeUnits) { if (changeUnits[d].unit !== undefined && changeUnits[d].unit === unit) { value = changeUnits[d].value; break; } } return value; }, /** * 显示排序符号 * * @param {String} field - 字段名称 * @return {String} - 返回字段排序的符号 */ showSortFlag(field) { const sort = this.ctx.sort; if (!(sort instanceof Array) || sort.length !== 2) { return ''; } sort[1] = sort[1].toUpperCase(); return (sort[0] === field && sort[1] === 'DESC') ? '' : '-'; }, /** * 判断是否为ajax请求 * * @param {Object} request - 请求数据 * @return {boolean} 判断结果 */ isAjax(request) { let headerInfo = request.headers['x-requested-with'] === undefined ? '' : request.headers['x-requested-with']; headerInfo = headerInfo.toLowerCase(); return headerInfo === 'xmlhttprequest'; }, /** * 模拟发送请求 * * @param {String} url - 请求地址 * @param {Object} data - 请求数据 * @param {String} type - 请求类型(POST) POST | GET * @param {String} dataType - 数据类型 json|text * @return {Object} - 请求结果 */ async sendRequest(url, data, type = 'POST', dataType = 'json') { // 发起请求 try { const response = await this.ctx.curl(url, { method: type, data, dataType, }); if (response.status !== 200) { throw '请求失败'; } return response.data; } catch(err) { throw '请求失败'; } }, /** * 深度验证数据 * * @param {Object} rule - 数据规则 * @return {void} */ validate(rule) { // 先用内置的验证器验证数据 this.ctx.validate(rule); // 然后再验证是否有多余的数据 const postData = this.ctx.request.body; delete postData._csrf; const postDataKey = Object.keys(postData); const ruleKey = Object.keys(rule); // 自动增加字段则填充上,以防判断出错 if (postData.create_time !== undefined) { ruleKey.push('create_time'); } for (const tmp of postDataKey) { // 规则里面没有定义则抛出异常 if (ruleKey.indexOf(tmp) < 0) { throw '参数不正确'; } } }, /** * 拆分path * * @param {String|Array} paths - 拆分字符 * @param {String} symbol - 拆分符号 * @return {Array} - 拆分结果 */ explodePath(paths, symbol = '.') { const result = []; paths = paths instanceof Array ? paths : [paths]; for (const path of paths) { // 拆分数据 const pathArray = path.split(symbol); // 用户缓存循环的数据 const tmpArray = []; for (const tmp of pathArray) { // 每次循环都追加一个数据进去 tmpArray.push(tmp); const tmpPathString = tmpArray.join(symbol); // 判断是否已经存在有对应数据 if (result.indexOf(tmpPathString) >= 0) { continue; } result.push(tmpPathString); } } return result; }, /** * 基于obj, 拷贝sObj中的内容 * obj = {a: 1, b: 2}, sObj = {a: 0, c: 3}, 返回{a: 0, b: 2, c: 3} * @param obj * @param sObj * @returns {any} */ updateObj(obj, sObj) { if (!obj) { return JSON.parse(JSON.stringify(sObj)); } const result = JSON.parse(JSON.stringify(obj)); if (sObj) { for (const prop in sObj) { result[prop] = sObj[prop]; } } return result; }, /** * 在数组中查找 * @param {Array} arr * @param name - * @param value * @returns {*} */ findData(arr, name, value) { if (!arr instanceof Array) { throw '该方法仅用于数组查找'; } if (arr.length === 0) { return undefined; } for (const data of arr) { if (data[name] == value) { return data; } } return undefined; }, /** * 检查数字是否为0 * @param {Number} value * @return {boolean} */ checkZero(value) { return !(value && Math.abs(value) > zeroRange); }, /** * 检查数字是否相等 * @param {Number} value1 * @param {Number} value2 * @returns {boolean} */ checkNumberEqual(value1, value2) { if (value1 && value2) { return Math.abs(value2 - value1) > zeroRange; } else { return (!value1 && !value2) } }, /** * 比较编码 * @param str1 * @param str2 * @param symbol * @returns {number} */ compareCode(str1, str2, symbol = '-') { if (!str1) { return -1; } else if (!str2) { return 1; } const path1 = str1.split(symbol); const path2 = str2.split(symbol); for (let i = 0, iLen = Math.min(path1.length, path2.length); i < iLen; i++) { if (path1 < path2) { return -1; } else if (path1 > path2) { return 1; } } return path1.length - path2.length; }, /** * 树结构节点排序,要求最顶层节点须在同一父节点下 * @param treeNodes * @param idField * @param pidField */ sortTreeNodes (treeNodes, idField, pidField) { const result = []; const getFirstLevel = function (nodes) { let result; for (const node of nodes) { if (!result || result > node.level) { result = node.level; } } return result; }; const getLevelNodes = function (nodes, level) { const children = nodes.filter(function (a) { return a.level = level; }); children.sort(function (a, b) { return a.order - b.order; }) return children; }; const getChildren = function (nodes, node) { const children = nodes.filter(function (a) { return a[pidField] = node[idField]; }); children.sort(function (a, b) { return a.order - b.order; }); return children; }; const addSortNodes = function (nodes) { for (let i = 0; i< nodes.length; i++) { result.push(nodes[i]); addSortNodes(getChildren(nodes[i])); } }; const firstLevel = getFirstLevel(treeNodes); addSortNodes(getLevelNodes(treeNodes, firstLevel)); }, /** * 判断当前用户是否有指定权限 * * @param {Number|Array} permission - 权限id * @return {Boolean} - 返回判断结果 */ hasPermission(permission) { let result = false; try { const sessionUser = this.ctx.session.sessionUser; if (sessionUser.permission === undefined) { throw '不存在权限数据'; } let currentPermission = sessionUser.permission; if (currentPermission === '') { throw '权限数据为空'; } // 管理员则直接返回结果 if (currentPermission === 'all') { return true; } currentPermission = currentPermission.split(','); permission = permission instanceof Array ? permission : [permission]; let counter = 0; for (const tmp of permission) { if (currentPermission[tmp] !== undefined) { counter++; } } result = counter === permission.length; } catch (error) { result = false; } return result; }, /** * 递归创建文件夹(fs.mkdirSync需要上一层文件夹已存在) * @param pathName * @returns {Promise} */ async recursiveMkdirSync(pathName) { const upperPath = path.dirname(pathName); if (!fs.existsSync(upperPath)) { await this.recursiveMkdirSync(upperPath); } await fs.mkdirSync(pathName); }, /** * 字节 保存至 本地文件 * @param buffer - 字节 * @param fileName - 文件名 * @returns {Promise} */ async saveBufferFile(buffer, fileName) { // 检查文件夹是否存在,不存在则直接创建文件夹 const pathName = path.dirname(fileName); if (!fs.existsSync(pathName)) { await this.recursiveMkdirSync(pathName); } await fs.writeFileSync(fileName, buffer); }, /** * 将文件流的数据保存至本地文件 * @param stream * @param fileName * @returns {Promise} */ async saveStreamFile(stream, fileName) { // 读取字节流 const parts = await streamToArray(stream); // 转化为buffer const buffer = Buffer.concat(parts); // 写入文件 await this.saveBufferFile(buffer, fileName); }, /** * 检查code是否是指标模板数据 * @param {String} code * @returns {boolean} */ validBillsCode(code) { const reg1 = /(^[0-9]+)([a-z0-9\-]*)/i; const reg2 = /([a-z0-9]+$)/i; return reg1.test(code) && reg2.test(code); }, getNumberFormatter(decimal) { if (decimal <= 0) { return "0"; } let pre = "0."; for (let i = 0; i < decimal; i++) { pre += "#" } return pre; }, /** * 根据单位查找对应的清单精度 * @param {tenderInfo.precision} list - 清单精度列表 * @param {String} unit - 单位 * @returns {number} */ findPrecision(list, unit) { if (unit) { for (const p in list) { if (list[p].unit && list[p].unit === unit) { return list[p]; } } } return list.other; }, /** * 检查数据中的精度 * @param {Object} Obj - 检查的数据 * @param {Array} fields - 检查的属性 * @param {Number} precision - 精度 * @constructor */ checkFieldPrecision(Obj, fields, precision = 2) { if (Obj) { for (const field of fields) { if (Obj[field]) { Obj[field] = this.round(Obj[field], precision); } } } }, /** * 过滤无效数据 * * @param obj * @param fields - 有效数据的数组 */ filterValidFields(data, fields) { if (data) { const result = {}; for (const prop in data) { if (fields.indexOf(prop) !== -1) { result[prop] = data[prop]; } } return result; } else { return data; } }, // 加减乘除方法,为方便调用,兼容num为空的情况 // 加减法使用base_calc,乘除法使用Decimal(原因详见demo/calc_test) /** * 加法 num1 + num2 * @param num1 * @param num2 * @returns {number} */ add(num1, num2) { return bc.add(num1 ? num1 : 0, num2 ? num2: 0); }, /** * 减法 num1 - num2 * @param num1 * @param num2 * @returns {number} */ sub(num1, num2) { return bc.sub(num1 ? num1 : 0, num2 ? num2 : 0); }, /** * 乘法 num1 * num2 * @param num1 * @param num2 * @returns {*} */ mul(num1, num2, digit = 6) { return Decimal.mul(num1 ? num1 : 0, num2 ? num2 : 0).toDecimalPlaces(digit).toNumber(); }, /** * 除法 num1 / num2 * @param num1 - 被除数 * @param num2 - 除数 * @returns {*} */ div(num1, num2, digit = 6) { if (num2 && !this.checkZero(num2)) { return Decimal.div(num1 ? num1: 0, num2).toDecimalPlaces(digit).toNumber(); } else { return null; } }, /** * 四舍五入(统一,方便以后万一需要置换) * @param {Number} value - 舍入的数字 * @param {Number} decimal - 要保留的小数位数 * @returns {*} */ round(value, decimal) { //return value ? bc.round(value, decimal) : null; return value ? Decimal.round(value, decimal).toNumber() : null; }, /** * 汇总 * @param array * @returns {number} */ sum(array) { let result = 0; for (const a of array) { result = this.plus(result, a); } return result; }, };