關於
《Nodejs開發加密貨幣》,是一個加密貨幣產品的詳細開發文檔,涉及到使用Nodejs開發產品的方方面面,從前端到後臺、從服務器到客戶端、從PC到移動、加密解密、區款鏈等各個環節。代碼完全開源、文章免費分享。 相關資源見 http://ebookchain.org
QQ交流羣: 185046161
前言
在入門文章部分,我們已經知道,Nodejs的應用最終都可以合併成一個文件,爲了開發方便,纔將其拆分成多個文件。
被拆分的那個文件,自然是我們重點研究的對象,通常這個文件就是App.js或server.js,大家稱之爲入口程序
。
顯然Ebookcoin用的就是app.js。這一篇,我們就來閱讀一下該文件,學習研究它的整體架構流程。
源碼
地址: https://github.com/Ebookcoin/ebookcoin/blob/master/app.js
類圖
js原本無類,因此它的類圖並不好處理,僅能大致給出它與其他模塊的關聯關係。
解讀
直接讀代碼看看。
1.配置處理
任何一個應用,都會提供一些參數。對這些參數的處理,有很多種方案。但總的來說,通常需要提供一種理想環境,即默認配置,同時給你一種方法自行修改。
(1)全局默認配置
通常默認參數較少時,可以硬編碼到代碼裏。但更靈活的方式,就是使用單獨文件。這裏就使用了文件 ./config.json
來保存全局配置,如:
{
"port": 7000,
"address": "0.0.0.0",
"serveHttpAPI": true,
"serveHttpWallet": true,
"version": "0.1.1",
"fileLogLevel": "info",
"consoleLogLevel": "log",
"sharePort": true,
...
使用時,只需要require
就可以了。源碼:
var appConfig = require("./config.json"); // app.js 4行
不過,爲了靈活性,默認值通常允許用戶修改。
(2)使用commander
組件,引入命令行選項
上一篇已經分享,commander
是Nodejs第三方組件(使用npm安裝),常被用來開發命令行工具,用法極爲簡單。源碼:
// 1行
var program = require('commander');
// 19行
program
.version(packageJson.version)
.option('-c, --config <path>', 'Config file path')
.option('-p, --port <port>', 'Listening port number')
.option('-a, --address <ip>', 'Listening host name or ip')
.option('-b, --blockchain <path>', 'Blockchain db path')
.option('-x, --peers [peers...]', 'Peers list')
.option('-l, --log <level>', 'Log level')
.parse(process.argv);
這樣,就可以在命令行執行命令時,加帶-c
,-p
等選項,例如:
node app.js -p 8888
這時,該選項就以program.port
的形式被保存,於是手動修改一下:
// 39行
if (program.port) {
appConfig.port = program.port;
}
這是處理Nodejs應用全局配置的一種常用且簡單的方式,值得學習。
更多內容,請閱讀上一篇對commander
組件的詳細介紹。
2.異常捕捉
我們在第一部分總結時,特意提到異常要捕捉
,這裏我們很輕鬆就可以看出來,代碼對全局異常處理的方式。
注意:對於domain
模塊,已經不提倡使用,這部分代碼將再後續的更新中去除,這裏僅做了解就是了。
(1)使用uncaughtException
捕捉進程異常
// 65行
process.on('uncaughtException', function (err) {
// handle the error safely
logger.fatal('System error', { message: err.message, stack: err.stack });
process.emit('cleanup');
});
(2)使用domain
模塊捕獲全局異常
// 96行
var d = require('domain').create();
d.on('error', function (err) {
logger.fatal('Domain master', { message: err.message, stack: err.stack });
process.exit(0);
});
d.run(function () {
...
另外,對各個模塊,也使用了domain
// 415行
var d = require('domain').create();
d.on('error', function (err) {
scope.logger.fatal('domain ' + name, {message: err.message, stack: err.stack});
});
...
3.模塊加載
這纔是真正的重點,不過看過代碼,發現一切都那麼幹淨利略,也沒有多層回調
那些大坑
,其實是用了async
流程管理組件。
整體使用async.auto
進行順序調用
;在加載modules
時,又使用async.parallel
,使其並行運作;當發生錯誤時,清理工作用到了async.eachSeries
。
下圖是手工簡單畫的,說明了從代碼103-438行之間,各模塊的加載運行順序。後面針對具體代碼會使用UML圖來代替。
下篇,我們也有必要對async
組件進行詳解梳理。這裏,您只要能猜出代碼意圖,就不用太操心async的用法。下面,讀讀有關源碼:
(1)初始網絡
從packages.json
裏看到使用了Express
框架。通過前面部分的介紹,知道必須在入口程序裏,初始化纔對。具體如何調用的?下面的代碼,顯然十分熟悉。
Express是Nodejs重要的web開發框架,這裏的網絡network
本質上就是以Express爲基礎的web應用,自然白皮書
纔會宣揚基於Http協議
。
// 215行
network: ['config', function (cb, scope) {
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
if (scope.config.ssl.enabled) {
var privateKey = fs.readFileSync(scope.config.ssl.options.key);
var certificate = fs.readFileSync(scope.config.ssl.options.cert);
var https = require('https').createServer({
...
說明:這是async.auto
常用的方法,network
用到的任何需要回調的方法(這裏是config
),都放在這個數組裏,最後的回調函數(function (cb, scope) {//code}
),可以巧妙的調用,如: scope.config
。
這裏的代碼,僅僅初始化服務,沒有做太多實質的事情,真正的動作在下面。
(2)構建鏈接
從下面的代碼開始,我們才能看到這個應用的本質。270行代碼用到了network
等,如下:
// 270行
connect: ['config', 'public', 'genesisblock', 'logger', 'build', 'network', function (cb, scope) {
接着,下面的代碼,加載了幾個中間件,告訴我們,該應用接受ejs
模板驅動的html文件。視圖文件和圖片、樣式等靜態文件都在public
文件夾等,這些信息絕對比官方文檔還有用。
// 277行
scope.network.app.engine('html', require('ejs').renderFile);
scope.network.app.use(require('express-domain-middleware'));
scope.network.app.set('view engine', 'ejs');
scope.network.app.set('views', path.join(__dirname, 'public'));
scope.network.app.use(scope.network.express.static(path.join(__dirname, 'public')));
...
再下來,就是對請求參數和響應數據的處理,包括對節點peers
中黑名單、白名單的過濾等,最後啓動服務操作:
// 336行
scope.network.server.listen(scope.config.port, scope.config.address, function (err) {
(3)加載邏輯
看代碼知道,其核心邏輯功能應該是:賬戶管理、交易和區塊鏈。這些模塊,有其執行順序,我們需要在後面的篇章中,分別單獨介紹。
// 379行
logic: ['dbLite', 'bus', 'scheme', 'genesisblock', function (cb, scope) {
// 嵌套了async.auto
async.auto({
...
account: ["dbLite", "bus", "scheme", 'genesisblock', function (cb, scope) {
new Account(scope, cb);
}],
transaction: ["dbLite", "bus", "scheme", 'genesisblock', "account", function (cb, scope) {
new Transaction(scope, cb);
}],
block: ["dbLite", "bus", "scheme", 'genesisblock', "account", "transaction", function (cb, scope) {
new Block(scope, cb);
}]
}, cb);
...
(4)加載模塊
上面所有代碼的執行結果,都要被這裏的各模塊共享。下面的代碼說明,各個模塊都採用一致(不一定一樣)的參數和處理方法,這樣處理起來簡單方便:
// 411行
modules: ['network', 'connect', 'config', 'logger', 'bus', 'sequence', 'dbSequence', 'balancesSequence', 'dbLite', 'logic', function (cb, scope) {
// 對每個模塊都使用`domain`監控其錯誤
Object.keys(config.modules).forEach(function (name) {
tasks[name] = function (cb) {
var d = require('domain').create();
d.on('error', function (err) {
...
});
d.run(function () {
...
});
}
});
// 讓各個模塊並行運行
async.parallel(tasks, function (err, results) {
cb(err, results);
});
這裏的模塊既然都是並行處理,研究它們就不需要分先後了。
總結
這篇文章開始深入代碼,但是仍然較爲粗略。不過,對整個應用的基本架構已經瞭然。繼續深入研究,方向路線也已然清晰。
代碼中還有很多細節,我們並沒有逐行介紹。個人認爲,讀代碼就像看文章,先要概覽,逐步深入,不一定一開始就逐字逐句去讀,那樣效率低、效果差。
對於這個app.js文件,成手讀它可能就是分分鐘的事情,而寫出來卻要羅嗦這麼多。如果,你並沒有覺得很輕鬆,甚至理解很困難,那麼可能缺少對commander
、domain
和async
等組件或模塊的瞭解,請看相關分享或官方文檔。
鏈接
本系列文章即時更新,若要掌握最新內容,請關注下面的鏈接
本源文地址: https://github.com/imfly/bitcoin-on-nodejs