Node.js開發(fā)Web后臺服務(wù)目錄 一、簡介Node.js 是一個(gè)基于Google Chrome V8 引擎的 JavaScript 運(yùn)行環(huán)境。Node.js 使用了一個(gè)事件驅(qū)動、非阻塞式 I/O 的模型,使其輕量又高效。Node.js 的包管理器 npm,是全球最大的開源庫生態(tài)系統(tǒng)。 能方便地搭建響應(yīng)速度快、易于擴(kuò)展的網(wǎng)絡(luò)應(yīng)用,Node.js 使用事件驅(qū)動, 非阻塞I/O 模型而得以輕量和高效,非常適合在分布式設(shè)備上運(yùn)行的數(shù)據(jù)密集型的實(shí)時(shí)應(yīng)用。 官網(wǎng):https:///en/ 簡單說Node.js就是運(yùn)行在服務(wù)器端的JavaScript,是現(xiàn)在流行的語言中能同時(shí)運(yùn)行在前端與后臺的程序語言,你可以把JavaScript想像成Java與C#。相關(guān)技術(shù): 數(shù)據(jù)庫:MongoDB,非關(guān)系型數(shù)據(jù)庫,NoSQL(Not only SQL) MVC框架:AngularJS Web服務(wù)器:Express 模板引擎:jade、ejs、htmljs、swig、hogan.js 二、搭建Node.js開發(fā)環(huán)境2.1、安裝Node.js去官網(wǎng)下下載最新版本的Node.js一步一步按提示安裝即可,如果安裝失敗就手動安裝,將Node.js的安裝位置配置到環(huán)境變量的path中。 安裝完成后啟動命令行,測試: 2.2、安裝IDE開發(fā)Node.js插件如果不使用IDE開發(fā)項(xiàng)目效率較低,在很多主流的集成開發(fā)環(huán)境(IDE)中都可以安裝插件支持Node.js開發(fā),如Eclipse,這里我們以HBuilder為例: 啟動HBuilder->工具->插件安裝 安裝成功后就可以新建Node.js項(xiàng)目了: 這里選擇Hello World,新建好的項(xiàng)目如下: hello-world-server.js文件就是一個(gè)簡單的web服務(wù)器,右鍵選擇“運(yùn)行方式”->"Node Application" 控制臺提示“Server running at http://127.0.0.1:1337/”在瀏覽器查看的效果如下: 三、第一個(gè)Node.js程序在上面的示例中,我們是通過IDE完成編譯與運(yùn)行的,其實(shí)手動運(yùn)行也可以,比如編寫一段代碼如下: server.js //依賴一個(gè)http模塊,相當(dāng)于java中的import,與C#中的using var http = require('http'); //創(chuàng)建一個(gè)服務(wù)器對象 server = http.createServer(function (req, res) { //設(shè)置請求成功時(shí)響應(yīng)頭部的MIME為純文本 res.writeHeader(200, {"Content-Type": "text/plain"}); //向客戶端輸出字符 res.end("Hello World\n"); }); //讓服務(wù)器監(jiān)聽本地8000端口開始運(yùn)行 server.listen(8000,'127.0.0.1'); console.log("server is runing at 127.0.0.1:8000"); 在node環(huán)境下解釋運(yùn)行: 運(yùn)行結(jié)果: 引入 required 模塊:我們可以使用 require 指令來載入 Node.js 模塊。 第一行請求(require)Node.js 自帶的 http 模塊,并且把它賦值給 http 變量。 四、NPM(Node.js包管理器)NPM是隨同NodeJS一起安裝的包管理工具,能解決NodeJS代碼部署上的很多問題,常見的使用場景有以下幾種: 官網(wǎng):https://www./ 4.1、查看npm版本 4.2、升級npm 如果你安裝的是舊版本的 npm,可以很容易得通過 npm 命令來升級 npm install npm -g
4.3、安裝模塊npm install <Module Name> -參數(shù) 如果帶參數(shù)-g表示全局安裝,否則只是安裝到某個(gè)目錄下。 以下實(shí)例,我們使用 npm 命令安裝常用的 Node.js web框架模塊 express 4.4、卸載模塊我們可以使用以下命令來卸載 Node.js 模塊。 如先使用安裝指令安裝bootstrap: npm install bootstrap 再使用卸載指令刪除模塊: npm uninstall bootstrap 可以到 /node_modules/ 目錄下查看包是否還存在 4.5、模塊列表使用模塊列表命令可以方便的看到當(dāng)前項(xiàng)目中依賴的包: 4.6、更新模塊我們可以使用以下命令更新模塊: 4.7、搜索模塊npm search 模塊名稱
4.8、NPM 常用命令 4.9、更換NPM 鏡像因?yàn)閚pm的服務(wù)器在國外,在網(wǎng)絡(luò)狀態(tài)不好的情況下引入一個(gè)模塊會因?yàn)榫W(wǎng)絡(luò)延遲而失敗,可以更換成國內(nèi)速度更快的鏡像服務(wù)器,這里以使用淘寶 NPM 鏡像(http://npm./)為例: npm install -g cnpm --registry=https://registry.npm. 這樣就可以使用 cnpm 命令來安裝模塊了: 這是一個(gè)完整 npmjs.org 鏡像,你可以用此代替官方版本(只讀),同步頻率目前為 10分鐘 一次以保證盡量與官方服務(wù)同步。 五、ExpressExpress 是一個(gè)簡潔而靈活的 node.js Web應(yīng)用框架, 提供了一系列強(qiáng)大特性幫助你創(chuàng)建各種 Web 應(yīng)用,和豐富的 HTTP 工具。 Express官網(wǎng): http:/// Express4.x API:http:///zh-cn/4x/api.html 5.2、Express框架核心特性可以設(shè)置中間件來響應(yīng) HTTP 請求。 定義了路由表用于執(zhí)行不同的 HTTP 請求動作。 可以通過向模板傳遞參數(shù)來動態(tài)渲染 HTML 頁面。 豐富的 HTTP 快捷方法和任意排列組合的 Connect 中間件,讓你創(chuàng)建健壯、友好的 API 變得既快速又簡單。 Express 不對 Node.js 已有的特性進(jìn)行二次抽象,我們只是在它之上擴(kuò)展了 Web 應(yīng)用所需的基本功能。 5.3、安裝 Express安裝 Express 并將其保存到依賴列表中: npm install express --save 以上命令全局安裝express。也可安裝時(shí)指定安裝中間件。 body-parser - node.js 中間件,用于處理 JSON, Raw, Text 和 URL 編碼的數(shù)據(jù)。 npm install cookie-parser --save 默認(rèn)這些模塊都已經(jīng)添加。 5.4、第一個(gè)Express框架實(shí)例接下來我們使用 Express 框架來輸出 "Hello World"。 創(chuàng)建一個(gè)目錄,如Project,進(jìn)入命令行: 使用npm install express 導(dǎo)入express模塊。 在目錄下創(chuàng)建hello.js文件,如下所示: //引入express模塊 var express = require('express'); //創(chuàng)建一個(gè)app對象,類似一個(gè)web 應(yīng)用(網(wǎng)站) var app = express(); //接受指定路徑的請求,指定回調(diào)函數(shù) app.get('/', function (req, res){ res.send('Hello World'); }); //創(chuàng)建一個(gè)web服務(wù)器,可以認(rèn)為就是web服務(wù)器對象 //監(jiān)聽8081端口,當(dāng)監(jiān)聽成功時(shí)回調(diào) var server = app.listen(8081, function () { var host = server.address().address; //地址 var port = server.address().port; //端口 console.log("應(yīng)用實(shí)例,訪問地址為 http://%s:%s", host, port); }); }) 使用node執(zhí)行js: 運(yùn)行結(jié)果: 5.5、使用Nodeclipse開發(fā)Express項(xiàng)目如果直接使用記事本效率會不高,nodeclipse插件可以方便的創(chuàng)建一個(gè)Express項(xiàng)目,步驟如下: 創(chuàng)建好的項(xiàng)目如下: app.js是網(wǎng)站: var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var index = require('./routes/index'); var users = require('./routes/users'); var app = express(); //指定視圖引擎為ejs app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', index); app.use('/users', users); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app; bin\www是web服務(wù)器: #!/usr/bin/env node /** * 依賴模塊,導(dǎo)入 */ var app = require('../app'); var debug = require('debug')('nodejsexpress:server'); var http = require('http'); /** * 從上下文環(huán)境中獲得監(jiān)聽端口,如果空則3000 */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * 創(chuàng)建Web服務(wù)器 */ var server = http.createServer(app); /** * 開始監(jiān)聽 */ server.listen(port); server.on('error', onError); //指定發(fā)生錯(cuò)誤時(shí)的事件 server.on('listening', onListening); //當(dāng)監(jiān)聽成功時(shí)的回調(diào) /** * 規(guī)范化端口 */ function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } /** *錯(cuò)誤事件監(jiān)聽 */ function onError(error) { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; //錯(cuò)誤處理 switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); //結(jié)束程序 break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } /** * 當(dāng)用戶訪問服務(wù)器成功時(shí)的回調(diào) */ function onListening() { var addr = server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); } routers/index.js路由,有點(diǎn)類似控制器或Servlet: var express = require('express'); var router = express.Router(); /* 獲得首頁 */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); module.exports = router; views/index.ejs首頁視圖: <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %></h1> <p>Welcome to <%= title %></p> </body> </html> 在www上右鍵選擇“運(yùn)行方式”->“Node Application”運(yùn)行結(jié)果: 5.6、ejs基礎(chǔ)ejs是一個(gè)Express Web應(yīng)用的模板引擎,在NodeJS開發(fā)中可以選擇的模板引擎可能是所有Web應(yīng)用開發(fā)中范圍最廣的,如jade、ejs、htmljs、swig、hogan.js,但ejs是最容易上手的,與jsp,asp,php的原始模板引擎風(fēng)格很像。 官網(wǎng):http://www./ 添加一個(gè)product.js路由: var express = require('express'); var router = express.Router(); /* 產(chǎn)品 */ router.get('/', function(req, res, next) { var products=[]; products.push({name:"ZTE U880",price:899.8}); products.push({name:"HuWei 榮耀8",price:1899.8}); products.push({name:"iPhone 7 Plus 128G",price:5899.8}); //將product視圖與指定的對象渲染后輸出到客戶端 res.render('product', { title: '天狗商城', pdts:products}); }); module.exports = router; 在views目錄下添加product.ejs視圖,這里是一個(gè)簡單的MVC: <!DOCTYPE html> <html> <head> <title> <%= title %> </title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1><%= title %> - 產(chǎn)品列表</h1> <table border="1" width="80%"> <tr> <th>序號</th> <th>名稱</th> <th>價(jià)格</th> </tr> <%pdts.forEach(function(pdt,index){%> <tr> <td> <%=index+1%> </td> <td> <%=pdt.name%> </td> <td> <%=pdt.price%> </td> </tr> <%});%> </table> <ul> <% for(var i=0; i<pdts.length; i++) {%> <li> <%=pdts[i].name%> </li> <% } %> </body> </html> 修改app,注冊定義好的模塊product: var index = require('./routes/index'); var users = require('./routes/users'); var pdts = require('./routes/product'); var app = express(); //指定視圖引擎為ejs app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', index); app.use('/users', users); app.use('/pdt', pdts); 運(yùn)行結(jié)果: 5.7、lodash這是一個(gè)具有一致接口、模塊化、高性能等特性的 JavaScript 工具庫??梢苑浅7奖愕牟僮鱦son。 官網(wǎng):http:/// 安裝: npm i -g npm npm i --save lodash 安裝時(shí)先用cd切換到當(dāng)前項(xiàng)目下。 如果瀏覽器使用可以直接引入: <script src="lodash.js"></script> 后臺Node.js使用,可以引入模塊: //導(dǎo)入lodash模塊 var _= require('lodash'); var products=[]; products.push({name:"ZTE U880",price:899.8}); products.push({name:"HuWei 榮耀8",price:1899.8}); products.push({name:"iPhone 7 Plus 128G",price:5899.8}); //1、取出第一個(gè)元素 var obj1=_.first(products); console.log(obj1.name); //ZTE U880 //2、取出最后一個(gè)元素 var obj2=_.last(products); console.log(obj2.name); //iPhone 7 Plus 128G //3、指定查找條件返回符合條件的索引 var obj3=_.findIndex(products,function(obj){ return obj.price>=1000&&obj.name.indexOf("7")>0; }); console.log(obj3); //2 //4、指定查找條件返回查找到的對象 var obj4=_.find(products,function(obj){ return obj.price>=1000&&obj.name.indexOf("7")>0; }); console.log(obj4); //{ name: 'iPhone 7 Plus 128G', price: 5899.8 } //5、排序 var obj5=_.orderBy(products,["price","name"],["desc","asc"]); console.log(obj5); //[ { name: 'iPhone 7 Plus 128G', price: 5899.8 }, //{ name: 'HuWei 榮耀8', price: 1899.8 }, //{ name: 'ZTE U880', price: 899.8 } ] //6、查找價(jià)格為1899.8的產(chǎn)品的key var obj6=_.findKey(products,{price:1899.8}); console.log(obj6); //1 API的使用非常簡單,但需要注意版本,可以現(xiàn)查現(xiàn)用,API地址:https:///docs/4.17.2 5.8、參數(shù)5.8.1、URL中的參數(shù)占位Checks route params (req.params), ex: /user/:id 127.0.0.1:3000/index,這種情況下,我們?yōu)榱说玫絠ndex,我們可以通過使用req.params得到,通過這種方法我們就可以很好的處理Node中的路由處理問題,同時(shí)利用這點(diǎn)可以非常方便的實(shí)現(xiàn)MVC模式; //獲得產(chǎn)品根據(jù)Id router.get('/:id/:category',function(request,res,next){ res.send(request.params.id+","+request.params.category); }); 運(yùn)行結(jié)果: 5.8.2、URL中的QueryStringChecks query string params (req.query), ex: ?id=12 127.0.0.1:3000/index?id=12,這種情況下,這種方式是獲取客戶端get方式傳遞過來的值,通過使用req.query.id就可以獲得,類似于PHP的get方法; router.get('/:id',function(request,res,next){
res.send("name:"+request.query.name);
});
運(yùn)行結(jié)果: 5.8.3、HTTP正文中的參數(shù)在post請求中獲得表單中的數(shù)據(jù)。 Checks urlencoded body params (req.body), ex: id= 127.0.0.1:300/index,然后post了一個(gè)id=2的值,這種方式是獲取客戶端post過來的數(shù)據(jù),可以通過req.body.id獲取,類似于PHP的post方法; 頁面:
View Code
代碼: router.post('/add',function(request,res,next){ var entity={name:request.body.name,price:request.body.price}; products.push(entity); //將product視圖與指定的對象渲染后輸出到客戶端 res.render('product', { title: '天狗商城', pdts:products,msg:"添加成功"}); }); 結(jié)果: 5.9、JSON如果需要Node.js向外提供返回JSON的接口,Express也是非常方便的,可以使用原來在瀏覽器中使用到的JSON對象,這是一個(gè)瀏覽器內(nèi)置對象在服務(wù)可以直接使用: 將對象序列化成字符: 結(jié)果: 反序列化,將字符轉(zhuǎn)換成對象: //將字符串轉(zhuǎn)換成JavaScript對象 var markStr='{"name":"mark","weight":"188"}'; var mark=JSON.parse(markStr); alert(mark.name+","+mark.weight); 結(jié)果: Express已經(jīng)封裝了一個(gè)json方法,直接調(diào)用該方法就可以序列化對象: /* 產(chǎn)品 */ router.get('/rest', function(req, res, next) { res.json(products); }); 運(yùn)行結(jié)果:
練習(xí):完成一個(gè)圖書管理的功能,圖書包含(編號,名稱,作者,圖片,價(jià)格),實(shí)現(xiàn): a)、非AJAX的CRUD,使用Node.js+Express+ejs的動態(tài)技術(shù)。 b)、AJAX的CRUD,使用Node.js+Express+jQuery+HTML技術(shù)實(shí)現(xiàn)。 c)、使用RestFul風(fēng)格的服務(wù)完成第個(gè)作業(yè),get,post,delete,put請。 六、RESTful(表述性狀態(tài)轉(zhuǎn)移)REST是英文Representational State Transfer的縮寫,中文稱之為“表述性狀態(tài)轉(zhuǎn)移” REST是一種分布式服務(wù)架構(gòu)的風(fēng)格約束,像Java、.Net(WCF、WebAPI)都有對該約束的實(shí)現(xiàn),使URL變得更加有意義,更加簡潔明了,如: http://www./products/1 get請求 表示獲得所有產(chǎn)品的第1個(gè) http://www./products/product post請求 表示添加一個(gè)產(chǎn)品 http://www./products/1/price get請求 表示獲得第1個(gè)產(chǎn)品的價(jià)格 http://www./products/1 delete請求 刪除編號為1的產(chǎn)品 REST設(shè)計(jì)需要遵循的原則 謂詞 其它還有: NodeJS+Express可以很容易的實(shí)現(xiàn)REST application/x-www-form-urlencoded multipart/form-data application/json res.setHeader('Content-Type', 'application/json;charset=utf-8'); 示例代碼cars.js: var express = require('express'); var router = express.Router(); var _= require('lodash'); var cars=[]; cars.push({id:201701,name:"BMW",price:190,speed:"210km/h",color:"白色"}); cars.push({id:201702,name:"BYD",price:25,speed:"160km/h",color:"紅色"}); cars.push({id:201703,name:"Benz",price:300,speed:"215km/h",color:"藍(lán)色"}); cars.push({id:201704,name:"Honda",price:190,speed:"170km/h",color:"黑色"}); cars.push({id:201705,name:"QQ",price:130,speed:"210km/h",color:"白色"}); /* Get */ /*獲得所有汽車*/ /*url /cars/*/ router.get('/', function(req, res, next) { res.json(cars); }); /*Get*/ /*獲得汽車通過id*/ /*url:/cars/:id */ router.get('/:id', function(req, res, next) { //從路徑中映射參數(shù),轉(zhuǎn)換成數(shù)字 var id=parseInt(req.params.id); var car=_.find(cars,{id:id}); res.json(car); }); /*Post*/ /*添加汽車*/ /*url:/cars/car */ router.post('/car', function(req, res, next) { var car=req.body; //從請求正文中獲得json對象 car.id=_.last(cars).id+1; //將編號修改為最后一輛車的編號+1 cars.push(car); //將汽車對象添加到集合中 res.json(car); //將添加成功的車以json的形式返回 }); /*Put*/ /*修改汽車*/ /*url:/cars/car */ router.put('/car', function(req, res, next) { var car=req.body; //從請求正文中獲得json對象 console.log(req.body); var index=_.findIndex(cars,{id:parseInt(car.id)}); //根據(jù)id獲得車在集合中的下標(biāo) cars[index]=car; //替換原對象 //res.json(car); //將修改后的車以json的形式返回 res.send({status:"success", message:"更新成功!"}); }); /*Delete*/ /*刪除汽車*/ /*url:/cars/:id */ router.delete('/id/:id', function(req, res, next) { //獲得url中的編號參數(shù) var id=parseInt(req.params.id); var index=_.findIndex(cars,{id:id}); //根據(jù)id獲得車在集合中的下標(biāo) cars.splice(index,1); //在cars數(shù)組中刪除下標(biāo)從index開始的1條數(shù)據(jù) res.send({status:"success", message:"刪除成功!"}); }); module.exports = router; 示例代碼app.js: var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var index = require('./routes/index'); var users = require('./routes/users'); var pdts = require('./routes/product'); var task = require('./routes/task'); var cars = require('./routes/cars'); var app = express(); //指定視圖引擎為ejs app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', index); app.use('/users', users); app.use('/pdt', pdts); app.use("/task",task); app.use("/cars",cars); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app; /* Get */ /*Get*/ /*Post*/
參數(shù)中的json格式一定要使用標(biāo)準(zhǔn)格式,注意引號,注意Content-Type,默認(rèn)的Content-Type類型是:application/x-www-form-urlencoded /*Put*/ /*Delete*/ 七、示例下載 |
|