這一次整理的內容是項目主文件和如何啟動項目。
啟動項目
通過nodejs官網的例子https://nodejs.org/docs/latest-v4.x/doc/api/synopsis.html我們可以知道,在項目目錄下打開終端命令行,並且輸入如下命令即可啟動服務:
node app.js
其中app.js是項目的主文件。
那是因為這個主文件里面有創建服務和監聽端口的語句:
const http = require('http'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World\n'); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });
而我們現在使用的是express框架,它的寫法有些不一樣。express4.0之后的版本,項目目錄下會有bin/這個目錄,這個目錄專門用於自定義啟動腳本,這樣就把與啟動服務的代碼和主文件分離了,而且你可以定義多個啟動腳本,而不用去修改app.js這個主文件。
我們來看看bin/www這個文件是什么內容:
#!/usr/bin/env node
var app = require('../app'); app.set('port', PORT || 3000); var server = app.listen(app.get('port'), function() { console.log('Express server listening on port ', app.get('port'), " with pid ", process.pid); });
然后我們逐行解釋一下:
#!/usr/bin/env node
/*這一句是寫給類unix系統看的
*如果用戶沒有將nodejs裝在默認的/usr/bin路徑里
*當系統看到這一行的時候,首先會到env設置里查找nodejs的安裝路徑
*再調用對應路徑下的解釋器程序完成操作
*/
var app = require('../app');
//引入app主應用
app.set('port', PORT || 3000); //設置端口為環境變量.env文件里的PORT,如果.env里沒有,就默認3000
var server = app.listen(app.get('port'), function() { console.log('Express server listening on port ', app.get('port'), " with pid ", process.pid); });
/*app.listen(path,[callback])的寫法是啟動一個socket連接,然后在給定的端口上監聽連接
*app.get(name)的寫法是獲取app設置,設置的時候通過app.set('port', 'my port');
來設置。
*/
package.json
package.json文件描述了一個NPM包的所有相關信息,包括作者、簡介、包依賴、構建等信息。格式必須是嚴格的json格式。
{ "name": "xia", "version": "0.0.1", "private": true, "scripts": { "start": "node ./bin/www", "dev": "./node_modules/.bin/gulp develop --gulpfile gulp_dev.js", "ci": "./node_modules/.bin/gulp test --gulpfile gulp_test.js", "assets": "./node_modules/.bin/gulp build --gulpfile gulp_build.js", "assets_dev": "./node_modules/.bin/gulp build --dev --gulpfile gulp_build.js", "watch": "./node_modules/.bin/gulp watch --gulpfile gulp_build.js" }, "dependencies": { "async": "2.0.1", "body-parser": "1.13.3", "connect-multiparty": "2.0.0", "connect-redis": "3.0.2", "cookie": "0.2.3", "cookie-parser": "1.3.3", "crypto": "0.0.3", "dotenv": "1.2.0", "excel-export": "0.5.1", "express": "4.13.3", "express-session": "1.13.0", "formidable": "1.0.16", "glob": "5.0.15", "jsonfile": "2.2.2", "log4js": "0.6.35", "morgan": "1.6.1", "raven": "0.8.1", "request-promise": "1.0.2", "run-sequence": "1.1.3", "serve-favicon": "2.3.0", "sha1": "1.1.1", "swig": "1.4.2", "write-file-stdout": "0.0.2" }, "devDependencies": { "gulp-nodemon": "2.0.2", "gulp": "3.9.0", "gulp-bower": "0.0.10", "gulp-clean": "0.3.1", "gulp-cssmin": "0.1.7", "gulp-eslint": "1.0.0", "gulp-if": "1.2.5", "gulp-uglify": "1.5.3", "gulp-rev-all-fixed": "0.8.24", "gulp-livereload": "3.8.0", "gulp-plumber": "1.0.0", "gulp-replace": "0.5.4", "gulp-rev-all": "0.8.21", "gulp-sass": "2.0.4", "compass-mixins": "0.12.7", "node-sass": "3.4.2", "yargs": "3.25.0" } }
其中name和version是最重要的兩個字段,也是發布到npm平台標示,必須有。
其中private設置為true,這個包將不會發布到npm。
scripts可以設置一些自定義的腳本,我們項目的啟動服務,處理靜態文件的命令就定義在這里。
dependencies指定依賴的包,如果是開發中需要的包,可以指定在devDependencies,建議嚴格匹配包的版本。
scripts中的腳本的寫法前面都加上了./node_modules/.bin/是因為這里后面命令都是gulp構建工具的命令,而gulp包在devDependencies里,所以加上這個前綴。
而后面的gulp build --gulpfile gulp_build.js這樣的寫法是gulp構建工具執行任務時的命令。
app.js
app.js就是node項目主文件。
var express = require('express'); var session = require('express-session'); var redisStore = require('connect-redis')(session); var glob = require('glob'); var path = require('path'); var bodyParser = require('body-parser');
var swig = require('swig'); var staticTag = require('./swig/static'); var morgan = require("morgan"); var app = express(); var client; app.locals.ENV = NODE_ENV; app.locals.ENV_DEV = (NODE_ENV === 'dev'); // view engine setup app.engine('html', swig.renderFile); app.set('view engine', 'html'); app.set('view cache', false); app.set('views', path.join(__dirname, 'views')); swig.setDefaults({cache: false}); swig.setDefaults({loader: swig.loaders.fs(__dirname + '/views')}); staticTag.init(swig); app.use(session({ cookie: { maxAge: 2502000 * 1000 }, name: 'lbn_sid', secret: 'what are you thinking?', store: new redisStore({ ttl: 2502000, url: REDIS_URL }), saveUninitialized: false, resave: false })); // app.use(morgan('combined')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); app.use('/' + global.STATIC_URL, express.static(path.join(__dirname, STATIC_DIR))); var controllers = glob.sync('./controllers/*.js'); controllers.forEach(function (controller) { require(controller)(app); }); app.use(function (err, req, res, next) { res.locals = {env: NODE_ENV}; // treat as 404 if (err.message && (~err.message.indexOf('not found') || (~err.message.indexOf('Cast to ObjectId failed')))) { return next(); } res.status(500).render('500', { error: err.stack }); }); app.use(function (req, res, next) { res.status(404).render('404', { url: req.originalUrl, error: 'Not found' }); }); module.exports = app;
我們來看看里面都是什么東西:
var express = require('express'); //引入express框架 var session = require('express-session'); //設置session的中間件 var redisStore = require('connect-redis')(session); //實現redis存儲session var glob = require('glob'); //使用類似shell的模式語法匹配文件路徑 var path = require('path'); //path模塊用於處理和轉換文件路徑 var bodyParser = require('body-parser'); //解析請求的body的中間件
var swig = require('swig'); //swig模板引擎 var staticTag = require('./swig/static'); //swig模板相關設置 var morgan = require("morgan"); //控制台日志 var app = express(); //創建一個express應用 app.locals.ENV = NODE_ENV; //將環境變量NODE_ENV存在app.locals里 app.locals.ENV_DEV = (NODE_ENV === 'dev'); //是否是dev環境 // view engine setup app.engine('html', swig.renderFile); //使用swig渲染html文件 app.set('view engine', 'html'); //設置默認頁面擴展名 app.set('view cache', false); //設置模板編譯無緩存 app.set('views', path.join(__dirname, 'views')); //設置項目的頁面文件,也就是html文件的位置 swig.setDefaults({cache: false}); //關閉swig模板緩存 swig.setDefaults({loader: swig.loaders.fs(__dirname + '/views')}); //從文件載入模板,請寫絕對路徑,不要使用相對路徑 staticTag.init(swig); //這個init函數是自定義的,對swig模板做了一些自定義設置 app.use(session({ //設置session中間件的寫法,session會存在服務端 cookie: { maxAge: 2502000 * 1000 //設置最大生命周期,過了這個時間后cookie會失效,單位毫秒 }, name: 'lbn_sid', //用來保存session的cookie名稱 secret: 'what are you thinking?', //用來對session數據進行加密的字符串.這個屬性值為必須指定的屬性 store: new redisStore({ //設置session的存儲倉庫為redis數據庫 ttl: 2502000, //redis session生命周期,單位秒 url: REDIS_URL //redis緩存服務地址 }), saveUninitialized: false, //false選項不會強制存儲未初始化的session到redis里,未初始化意味着新的還沒有修改的 resave: false //如果是true選項,強制重新存儲session到redis里,即使session沒有被修改,false意味着如果沒有變化就不用重新存 })); // app.use(morgan('combined')); //morgan控制台日志,會在控制台輸出所有http請求日志,combined是標准Apache日志格式
app.use(bodyParser.json()); //bodyParser.json是用來解析請求體的json數據格式 app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); /*bodyParser.urlencoded則是用來解析我們通常的form表單提交的數據,
*也就是請求頭中包含這樣的信息:Content-Type: application/x-www-form-urlencoded
*extended選項為true會使用qs library來解析數據,false會使用querystring
來解析
*limit選項限制請求體的大小
*/
app.use('/' + global.STATIC_URL, express.static(path.join(__dirname, STATIC_DIR))); //為靜態資源的請求添加虛擬路徑,只有請求靜態資源的路徑前加了global.STATIC_URL前綴后,才可請求成功
var controllers = glob.sync('./controllers/*.js'); //獲取到controllers文件夾下的所有js文件,這些文件里都是路由 controllers.forEach(function (controller) { require(controller)(app); }); //將所有路由循環到主文件中使其生效
app.use(function (err, req, res, next) { //當請求出現500錯誤,渲染500錯誤頁面 res.locals = {env: NODE_ENV}; // treat as 404 if (err.message && (~err.message.indexOf('not found') || (~err.message.indexOf('Cast to ObjectId failed')))) { return next(); } res.status(500).render('500', { error: err.stack }); }); app.use(function (req, res, next) { //當請求出現404錯誤,渲染404錯誤頁面 res.status(404).render('404', { url: req.originalUrl, error: 'Not found' }); }); module.exports = app; //將app應用導出成模塊
這其中session的設置值得注意,session的設置寫在了app.use()中,也就是中間件中,中間件也是路由,只是所有的請求都會經過它的處理。這里設置session時有一個cookie的設置,這個cookie就是session的唯一標示,是sessionId,也就是說,第一次訪問網站的時候,在請求通過session設置的中間件時,響應頭里會設置一個set-cookie來強制瀏覽器存儲一個cookie,也就是在瀏覽器存下sessionId,然后會在node端新建一個session,這里瀏覽器存的sessionId和node端的session是對應關系,之后的請求也會經過session設置的中間件,此時的請求頭里會自動帶上瀏覽器的所有cookie,當中間件發現已經有sessionId的時候,就不會新建了,只用更新對應的session就可以了。