UNPKG

enhancer-data-bridge

Version:

A bridge between Enhancer Clould and user business datasource

481 lines (439 loc) 17.3 kB
var mysql = require('mysql'); var logger = require('log4js').getLogger('io'); var escapeHTML =require('escape-html'); var DatabaseService = require('../database-service'); var mocker = require('../mock'); var config = require('../config'); var pattern = { variableAndIdentifier: /(@(\d+-)?\w+(\.\w+)*@)|(\$(\d+-)?\w+(\.\w+)*\$)/g, variable: /@(\d+-)?\w+(\.\w+)*@/g, clientVariable: /(@\d+-\w+(\.\w+)*@)|(\$\d+-\w+(\.\w+)*\$)/g, identifier: /\$(\d+-)?\w+(\.\w+)*\$/g }; var os = require('os'); var path = require('path'); var customModuleBase = path.resolve(__dirname, '../repository/project/custom-module'); if (os.platform() === 'win32') { customModuleBase = customModuleBase.replace(/\\/g, '/') + '/'; } else { customModuleBase = customModuleBase + '/'; } var requireNocacheDefine = "var requireNocache = require('require-nocache')(module);"; var requireNocache = require('require-nocache')(module); var MESSAGE = { en: { BEFORE_SQL_SYNTAX_ERR: 'The before sql function has syntax err. Reason: ', AFTER_SQL_SYNTAX_ERR: 'The after sql function has syntax err. Reason: ', AFTER_SQL_EXE_ERR: 'Error occured when executing after sql script. Reason: ', SQL_COMPILE_ERR: 'Error occured when compile sql. Reason: ', PROCEDURE_SYNTAX_ERR: 'Error occured when compile procedure. Reason: ', PROCEDURE_EXE_ERR: 'Error occured when executing procedure. Reason: ', TIMEOUT: 'Execute timeout. Please check if the done() method is called correctly', OP_SUCC: 'Operation successfully!', OP_FAILED: 'Operation failed!' }, 'zh-cn': { BEFORE_SQL_SYNTAX_ERR: 'SQL执行前脚本有语法错误,请检查书写是否正确。原因:', AFTER_SQL_SYNTAX_ERR: 'SQL执行后脚本有语法错误,请检查书写是否正确。原因:', AFTER_SQL_EXE_ERR: '执行SQL执行后脚本时发生错误,请检查书写是否正确。原因:', SQL_COMPILE_ERR: '解析 SQL 时发生错误,请检查书写是否正确。原因:', PROCEDURE_SYNTAX_ERR: '代码有语法错误,请检查书写是否正确。原因:', PROCEDURE_EXE_ERR: '执行过程时发生错误,原因: ', TIMEOUT: '执行超时。请检查 done 方法是否被正确地调用,确保后续程序执行。', OP_SUCC: '操作成功!', OP_FAILED: '操作失败!' } }; exports.before = function(req, res, next) { if (req.method === 'OPTIONS') { res.sendStatus(200); return; } var dbConfiguration; try { dbConfiguration = JSON.parse(req.body.database || req.query.database); } catch (e) { return next( new Error('Invalid database setting for preview.') ); } var criteria; try { criteria = JSON.parse(req.body.criteria || req.query.criteria); } catch (e) { return res.end('Invalid criteria'); } if (!criteria.query) { return res.end(); } // Mock server vars for user debug var serverVars = req.body.serverVars || req.query.serverVars; try { serverVars = JSON.parse(decodeURIComponent(serverVars)); } catch(e) { serverVars = {}; } // Append Session Variables var vbs = req.session._variables; if (vbs) { for (var i in vbs) { serverVars[i.toUpperCase()] = vbs[i]; } } criteria.serverVars = serverVars; criteria.sourceId = req.params[0]; req.criteria = criteria; req.dbService = new DatabaseService(dbConfiguration); next(); }; exports.queryData = function(req, res, next) { try { req.dbService.criteriaQuery(req.criteria.connectionName, req.criteria, function(err, result) { if (err) { logger.error('Error occured when executing SQL: ', err.message); logger.error('Origin SQL:\n', req.criteria.query); logger.error('Origin Params:\n', req.criteria.params); logger.error('Origin Filters:\n', req.criteria.filters); logger.error('Final Executed SQL:\n', err.sql); res.jsonp({ success: false, message: '<div style="width: 100%; text-align: left">' + '<div class="ui-state-error">' + err.message + ' CODE: ' + err.code + '</div>' + '<br>--- Debug Info ---<br><br>' + '<span class="ui-state-error">Origin SQL</span><br>' + escapeHTML(req.criteria.query) + '<br><br><span class="ui-state-error">Origin Params</span><br>' + JSON.stringify(req.criteria.params) + '<br><br><span class="ui-state-error">Final Executed SQL</span><br>' + escapeHTML(err.sql) + '</div>' }); return; } res.jsonp({ success: true, data: result }); }); } catch(err) { logger.error('Error occured when executing SQL:\n', err.message); logger.error('Origin SQL:\n', req.criteria.query); logger.error('Origin Params:\n', req.criteria.params); res.jsonp({ success: false, message: err.message }); return; } }; exports.exportData = function(req, res, next) { var format = req.criteria.exportFormat || "xlsx"; var formatter = DataFormatter[ format ]; if (typeof formatter !== 'function') { return next(new Error('Invalid format: ' + format)); } req.criteria.format = 'object'; req.dbService.criteriaQuery(req.criteria.connectionName, req.criteria, function(err, result) { if (err) { return next(err); } result = formatter(result.rows, req.criteria.colModel, req.criteria.title); var name = encodeURIComponent(req.criteria.title ? req.criteria.title : (req.criteria.sourceId + '_' + (new Date()).getTime()) ) + '.' + format; res.set({ "Content-Type": "application/octet-stream", "Content-Disposition": "attachment; filename=" + name }); res.send(result); }); }; var xlsx = require( "node-xlsx" ); var fs = require( "fs" ); var DataFormatter = { xlsx: function( result, colModel, title ) { colModel = colModel || []; var header = []; // generate header; for ( var i = 0; i < colModel.length; i++ ) { header.push( colModel[ i ].name ); } // generate data; var data = []; if (title) { data.push(['', title]); } data.push(header); var row; var colName; var rd; var val; for ( var i = 0; i < result.length; i++ ) { rd = result[i]; row = []; for ( var j = 0; j < colModel.length; j++ ) { colName = colModel[j].id; val = rd[colName]; if (typeof val === 'undefined') { val = rd[colName.toUpperCase()]; } if (val instanceof Date) { val = val.toISOString(); } row.push(val); } data.push( row ); } title = (title || 'sheet1').substring(0, 30) .replace(/\\|\/|\?|\*/g, '_') .replace(/\[/g, '(') .replace(/\]/g, ')') return xlsx.build( [ { name: title, data: data } ] ); }, csv: function(result, colModel) { return result; }, json: function( result, colModel ) { return result; }, xml: function( result, colModel ) { return result; } }; var ESqlCompiler = require('../esql-compiler'); exports.execute = function(req, res, next) { var dbConfiguration; try { dbConfiguration = JSON.parse(req.body.database); } catch (e) { return next( new Error('Invalid database setting for preview.') ); } var MSG = MESSAGE[req.cookies.lang || 'zh-cn']; var params, procedure; try { params = JSON.parse( req.body.params ); procedure = JSON.parse( req.body.procedure ); } catch (e) { return next(new Error('Invalid params or procedure')); } var params2 = {}; for (var i in params) { params2[i.toUpperCase()] = params[i]; } params = params2; if (procedure.escapeHTML !== false) { for (var i in params) { params[i] = typeof params[i] === 'string' ? escapeHTML(params[i]) : params[i]; } } // Mock server vars for user debug var serverVars = req.body.serverVars; try { serverVars = JSON.parse(decodeURIComponent(serverVars)); } catch(e) { serverVars = {}; } // Append Session Variables var vbs = req.session._variables; if (vbs) { for (var i in vbs) { serverVars[i.toUpperCase()] = vbs[i]; } } var f; procedure.type = procedure.type || 'sql'; if (procedure.type === 'sql') { var beforeSql; var compiledSqlFunc; var afterSql; try { beforeSql = procedure.beforeSql || 'done()'; beforeSql = beforeSql.replace( pattern.variable, function( s ) { return /\@\w+(\.\w+)*\@/.test(s) ? 'Enhancer.getVariable("' + s.replace(/\@/g, '').toUpperCase() + '")' : 'parameters["' + s.replace(/\@/g, '').toUpperCase() + '"]'; }).replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) { return 'requireNocache(' + $1 + customModuleBase; }); beforeSql = 'function(parameters, serverVariables, Enhancer, done) {\n' + beforeSql + '\n}'; beforeSql = eval( "(" + beforeSql + ")" ); } catch ( err ) { return res.jsonp({ success: false, message: MSG.BEFORE_SQL_SYNTAX_ERR + err.message, debugInfo: JSON.stringify(err) }); } try { compiledSqlFunc = ESqlCompiler.compile(procedure.func); } catch (err) { return res.jsonp({ success: false, message: MSG.SQL_COMPILE_ERR + err.message, debugInfo: err.message }); } try { if (procedure.afterSql) { afterSql = procedure.afterSql.replace( pattern.variable, function( s ) { return /\@\w+(\.\w+)*\@/.test(s) ? 'Enhancer.getVariable("' + s.replace(/\@/g, '').toUpperCase() + '")' : 'parameters["' + s.replace(/\@/g, '').toUpperCase() + '"]'; }).replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) { return 'requireNocache(' + $1 + customModuleBase; }); afterSql = 'function(parameters, serverVariables, Enhancer, done) {\n' + afterSql + '\n}'; afterSql = eval( "(" + afterSql + ")" ); } } catch ( err ) { return res.jsonp({ success: false, message: MSG.AFTER_SQL_SYNTAX_ERR + err.message, debugInfo: JSON.stringify(err) }); } f = function(parameters, serverVariables, Enhancer, done) { beforeSql(parameters, serverVariables, Enhancer, function(err, result, params) { if (err) { return done(err); } if (result && !result.success) { return done(null, result); } if (params) { for (var i in params) { parameters[i.toUpperCase()] = params[i]; } } compiledSqlFunc(procedure.connectionName, parameters, serverVariables, Enhancer , function(err, result) { if (err) { if (typeof afterSql === 'function') { serverVariables['SQL_RESULT'] = {success:false, message: err.message}; serverVariables['SQL_ERROR'] = err; try { afterSql(parameters, serverVariables, Enhancer, done); } catch(ex) { ex.message = MSG.AFTER_SQL_EXE_ERR + ex.message + '. Before SQL error:' + err.message; done(ex); } return; } return done(err); } if (typeof afterSql === 'function') { serverVariables['SQL_RESULT'] = result; try { afterSql(parameters, serverVariables, Enhancer, done); } catch(ex) { ex.message = MSG.AFTER_SQL_EXE_ERR + ex.message; done(ex); } return; } done(null, result); }); }); } } else if (procedure.type === 'proc') { var funcStr; try { funcStr = procedure.func.replace( pattern.variable, function( s ) { return /\@\w+(\.\w+)*\@/.test(s) ? 'Enhancer.getVariable("' + s.replace(/\@/g, '').toUpperCase() + '")' : 'parameters["' + s.replace(/\@/g, '').toUpperCase() + '"]'; }).replace(/require\(\s*(\'|\")@custom\//g, function(s, $1) { return 'requireNocache(' + $1 + customModuleBase; }); funcStr = 'function(parameters, serverVariables, Enhancer, done) {' + funcStr + '\n}'; f = eval( "(" + funcStr + ")" ); } catch ( err ) { return res.jsonp({ success: false, message: MSG.PROCEDURE_SYNTAX_ERR + err.message, debugInfo: JSON.stringify(err) }); } } // Set timer var completed = false; try { // Mock enhancer; var Enhancer = mocker.mockEnhancer(dbConfiguration, serverVars, req, res); // If in official environment, a timeout limitation must be set. var timeout = config.isOfficialEnv ? 3000 : procedure.timeout; if (timeout) { setTimeout(function() { if (completed) { return; } completed = true; res.jsonp({ success: false, message: MSG.TIMEOUT }); res.jsonp = res.send = res.end = function() { logger.log('The request has been timeout and the response has been ended.'); } }, timeout); } var t1 = new Date().getTime(); f( params, serverVars, Enhancer, function(err, result) { if (req.conn) { req.conn.destroy(); } if (completed) { return; } completed = true; var t2 = new Date().getTime(); logger.debug('PROCEDURE COST: ', (t2 - t1) + 'ms' ); if (err) { // Find error tips var errorMessage; for (i in procedure.errMsg) { if (err.code === i || ~err.message.indexOf(i)) { errorMessage = procedure.errMsg[i]; break; } } errorMessage = errorMessage || (procedure.errMsg.default || MSG.PROCEDURE_EXE_ERR) + 'Reason: ' + err.message; res.jsonp({ success: false, message: errorMessage + '<br>Origin Message: ' + err.message, errorCode: err.code, debugInfo: err.debugInfo }); return; } result = result || serverVars['SQL_RESULT'] || {}; result.message = result.message || (result.success ? procedure.succMsg : procedure.errMsg.default) || (result.success ? MSG.OP_SUCC : MSG.OP_FAILED); res.jsonp(result); } ); } catch ( err ) { if (completed) { return; } completed = true; return res.jsonp({ success: false, message: MSG.PROCEDURE_EXE_ERR + err.message.replace(customModuleBase, '@custom/'), errInfo: JSON.stringify(err) }); } };