跳到主要內容

Node.js Report Server

這是一個下午想到的,簡單的Node.js Report Server
目的是讓懂SQL又不太懂後端程式的人可以利用這個Report Server來產出一些基本的報表
當然,他要會使用ajax接json的資料,這邊的報表只有吐json出來而已

概念是:
Step 1. user簡單定義report config,將預執行的report sql配置於config file中,並可以動態配置where子句來作查詢

Step 2. 將report config放在指定資料夾($REPORT_SERVER_HOME/report)內

Step 3. 然後就可以透過REST GET的方式取得該report的json output
curl -sS http://yout_ip:your_port/report/rest/simple.rpt?MEMBER_ID=0066f7b4&USER_ID=xxxx@oooo.com.tw -X GET

PS. 附加提供整體report的查看,如果要看有哪些configured report,則可以透過下面REST GET呼叫
curl -sS http://yout_ip:your_port/report/restdoc -X GET

實作部分,專案資料結構:
|____routes
| |____index.js.......................router管理
|____views
| |____(Skip)
|____report
| |____simple.rpt....................report config file
|____lib
| |____db-manager.js.............資料庫聯結程式,將來可以修改為連接不同資料庫
| |____db-config.js.................資料庫設定檔案,目前為mysql
|____public
| |____(Skip)
|____node_modules
| |____(Skip)
|____app.js............................server啟動點
|____package.json

先來看config file的配置,透過指定主sql (select...),並配合事先拆解好的conditions(field=將來要跟url參數對應的key,condition=欲附加到sql子句的sub sql string),其中conditions可以設定多組condition
$ vi report/simple.rpt                                                                                                                                               
{
  "sql" : "select * from member_info where 1=1 ",
  "conditions": [
    {"field":"MEMBER_ID", "condition":"and member_id = ?"},
    {"field":"USER_ID", "condition":"and user_id = ?"}                                                                                                                                                    
  ]
}

接下來看app.js這支主要啟動檔案,裡面僅比express gen出來的code多加了兩個router(紅色部分)
$ vi app.js
/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path')
  , dbm = require('./lib/db-manager');

var app = express();

app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.get('/report/rest/:rpt', routes.getRestFromConfig);
app.get('/report/restdoc', routes.getRestDoc);

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

接下來看routes/index.js,這支主要負責接收app.js pass過來的routing,然後呼叫report主要查詢模組作查詢動作
$ vi routes/index.js
var dbm = require('../lib/db-manager')
  , url = require('url');
/*
 * GET home page.
 */
exports.index = function(req, res){
  res.render('index', { title: 'Express' }); 
};

/**
 * module getRestFromConfig
 */
exports.getRestFromConfig = function(req, res){
  console.log('Do report of : %s', req.params.rpt);
  var url_parts = url.parse(req.url, true);
  dbm.generateReport(req.params.rpt, url_parts.query, function(err, rows, fiels){
    res.writeHead(200,{'Content-Type': 'application/json'} );
    res.end(JSON.stringify(rows));
  }); 
}

/**
 * module getRestDoc
 */
exports.getRestDoc = function(req, res){
  dbm.generateReportDoc(function(err, files){
    res.writeHead(200,{'Content-Type': 'application/json'} );
    res.end(JSON.stringify(files));                                                                                                                                                                      
  }); 
}

接下來看主要report的產生模組,這邊主要是把config file抓出來,然後直接組sql語句與condition,最後作查詢並回覆查詢結果
$ vi lib/db-manager.js
var config = require('./db-config')
  , db = config.db
  , fs = require('fs')
  , CONFIG_PATH = '/root/project/report/report/';

/**
 * module generateReportDoc
 */
exports.generateReportDoc = function(callback) {
  //1. list files in CONFIG_PATH
  var files = fs.readdirSync(CONFIG_PATH);

  //2. read the configure file
  var filesVo = new Array();
  for ( i in files ) {
    var confStr = fs.readFileSync(CONFIG_PATH + files[i], "utf8");
    var vo = JSON.parse(confStr);
    vo.config_file = files[i];
    filesVo.push(vo);                                                                                                                                                                                      
  }

  //3. show json
  var err = null;
  callback(err, filesVo);
}

/**
 * module generateReport
 */
exports.generateReport = function(rpt, params, callback) {
  /* Get report configure file */
  var fileName = CONFIG_PATH + rpt;
  var confStr = fs.readFileSync(fileName, "utf8");
  console.log('[Report Config]\n%s', confStr);

  /* Combine the report config to sql */
  var conf = JSON.parse(confStr);
  var sql = conf.sql;
  var cond = new Array();
  if(conf.conditions) {
    conf.conditions.forEach(function(v){
      console.log('[info]field=%s, condition=%s', v.field, v.condition);
      console.log('[info]got field value:', params[v.field]);
      if(params[v.field]){
        console.log('[info]find field mapping, add to sql....');
        sql += (' ' + v.condition);
        cond.push(params[v.field]);
      }
    });
  }
  
  /* Send to DB query */
  console.log('[SQL] %s', sql);
  console.log('[COND]%s', cond);
  db.query(sql, cond, function(err, rows, fiels) {
    if(err) return next(err);
    callback(err, rows, rows);
  });
}

透過建制上述程式碼,就可以有一個json output的report server了,接下來只要專心地處理前端頁面的變化就可以了噢∼

                                                                                                                                                                                    

這個網誌中的熱門文章

Oracle LISTAGG

同事介紹的一個Oracle的好用查詢:LISTAGG
SELECT A.GROUP_ID,A.KEY, LISTAGG(A.VALUE,'; ')WITHINGROUP(ORDERBYA.VALUE)as GG  fromSYS_PROPERTIESaGROUP byA.GROUP_ID,A.KEY
LISTAGG可以將group後的結果會總顯示於一個欄位 上述SQL原本A.VALUE會是一個row一個row的排列 使用LISTAGG之後,可以將A.VALUE顯示在同一個row中 並且可以指定間隔符號(在此設定為';') 針對某一些報表查詢非常有用唷 :D

Flashback Query

感謝我的好友提供的: 在Oracle好用的指令...
Flashback Query: 讓異動過的資料表回到歷史時間點 Step 1. è
show parameter undo_retention  PS: 這個參數顯示系統上設定歷史保留時間(ex: 900 = 900秒) Step 2. è CREATETABLE XXXXX_0815 ASSELECT *  FROM XXXXXASOFTIMESTAMPTO_TIMESTAMP('2011-08-15 12:00:00','YYYY-MM-DD HH24:MI:SS');
PS: 透過as of timestamp方式取出該時間點的表格資料

Xoom 3.2 Upgrade

雖然Xoom早已經更新到3.2 但是台灣遲遲沒有更新版本@@ 終於按奈不住好奇心了... XDA上找到給604(台版Xoom)的3.2版本ROM(美版的) 動手刷拉~~ 出奇的順利阿... 下面是兩個相關網址:
http://forum.xda-developers.com/showthread.php?t=1165998
http://forum.xda-developers.com/showthread.php?t=1080963
我這邊是刷第二各網址下載的3.2 ROM(http://www.mediafire.com/?adsa081m3z8uykd) 3.1的就沒去測拉... 也沒有再去破解(遲早的ㄅ:P)
使用心得: 1. 順~比3.0操頻還順 2. 原生就支援micro-sd 3. 目前看是不支援USB...缺憾@@ 4. 因為美版的關係,所以有Books跟Videos兩個APP,但是因為網路地區的關係不能用就是了... 5. 沒有原生中文輸入法,不過對我沒差...我都用Google拼音 :D