Node.js 博客搭建 一. 學習需求 Node 的安裝運行 會安裝node,搭建node環境 會運行node。 基礎模塊的使用 Buffer:二進位數據處理模塊 Event:事件模塊 fs:文件系統模塊 Net:網路模塊 Http:http模塊 ... NPM(node包管理工具) 第三方nod ...
Node.js 博客搭建
一. 學習需求
Node 的安裝運行
會安裝node,搭建node環境
會運行node。
基礎模塊的使用
Buffer:二進位數據處理模塊
Event:事件模塊
fs:文件系統模塊
Net:網路模塊
Http:http模塊
...
NPM(node包管理工具)
第三方node模塊(包)的管理工具,可以使用該下載工具安裝第三方模塊。,當然也可以創建上傳自己的模塊。
參考
假定已經理解並掌握了入門教程的所有內容。在易出錯的地方將進行簡要的說明。
其它
這是最不起眼,但也是最必不可少的——你得準備一個博客的靜態文件。
博客的後臺界面,登錄註冊界面,文章展示界面,首頁等。
二. 項目需求分析
一個博客應當具備哪些功能?
前臺展示
- 點擊下一頁,可以點擊分類導航。
- 可以點擊進入到具體博文頁面
- 下方允許評論。顯示發表時間。允許留言分頁。
- 右側有登錄註冊界面。
後臺管理
- 管理員賬號:登陸後看到頁面不一樣,有後臺頁面。
- 允許添加新的分類。從後臺添加新的文章。
- 編輯允許markdown寫法。
- 評論管理。
三. 項目創建,安裝及初始化
技術框架
本項目採用了以下核心技術:
- Node版本:6.9.1——基礎核心的開發語言
(安裝後查看版本:cmd視窗:node -v)
(查看方式:cmd視窗:node -v
)
- Express
一個簡潔靈活的node.js WEB應用框架,提供一系列強大的特性幫助我們創建web應用。
- Mongodb
用於保存產生的數據
還有一系列第三方模塊和中間件:
- bodyParser,解析post請求數據
- cookies:讀寫cookie
- swig:模板解析引擎
- mongoose:操作Mongodb數據
- markdown:語法解析生成模塊
...
初始化
在W ebStorm創建一個新的空工程,指定文件夾。
打開左下角的Terminal輸入:
npm init
回車。然後讓你輸入name:(code),輸入項目名稱,然後後面都可以不填,最後在Is it OK?
處寫上yes。
完成這一步操作之後,系統就會在當前文件夾創建一個package.json
的項目文件。
項目文件下麵擁有剛纔你所基本的信息。後期需要更改的話可直接在這裡修改。
第三方插件的安裝
- 以Express為例
在命令行輸入:
npm install --save express
耐心等待一段時間,安裝完成後,json文件夾追加了一些新的內容:
json { //之前內容........ "author": "", "license": "ISC", "dependencies": { "express": "^4.14.0" }
表示安裝成功。
同理,使用npm install --save xxx
的方法安裝下載以下模塊:
- body-parser
- cookies
- markdown
- mongoose
- swig
所以安裝完之後的package.json文件是這樣的。
{
"name": "blog",
"version": "1.0.0",
"description": "this is my first blog.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.2",
"cookies": "^0.6.2",
"express": "^4.14.0",
"markdown": "^0.5.0",
"mongoose": "^4.7.5",
"swig": "^1.4.2"
}
}
在這個json中,就能通過依賴模塊(dependencies
)看到各個第三方模塊的版本信息
切記:依賴模塊安裝,要聯網!
安裝完成之後
第二個文件夾放的是你的第三方模塊。
此外還需要別的文件,完整的結構是這樣的——
接下來就把缺失的文件目錄自己建立起來。
完成著一系列操作之後,就把app.js作為應用程式的啟動(入口頁面)。
創建應用
以下代碼創建應用,監聽埠
// 載入express
var express=require('express');
//創建app應用,相當於=>Node.js Http.createServer();
var app=express();
//監聽http請求
app.listen(9001);
運行(ctrl
+shift
+c
)之後就可以通過瀏覽器訪問了。
用戶訪問:
- 用戶通過URL訪問web應用,比如
http://localhost:9001/
這時候會發現瀏覽器呈現的內容是這樣的。
web後端根據用戶訪問的url處理不同的業務邏輯。
路由綁定——
在Express框架下,可以通過app.get()
或app.post()
等方式,把一個url路徑和(1-n)個函數進行綁定。當滿足對應的規則時,對應的函數將會被執行,該函數有三個參數——
javascript app.get('/',function(req,res,next){ // do sth. }); // req:request對象,保存客戶請求相關的一些數據——http.request // res:response對象,服務端輸出對象,停工了一些服務端相關的輸出方法——http.response // next:方法,用於執行下一個和路徑相匹配的函數(行為)。
- 內容輸出
通過res.send(string)
發送內容到客戶端。
app.get('/',function(req,res,next){
res.send('<h1>歡迎光臨我的博客!</h1>');
});
運行。這時候網頁就列印出了h1標題的內容。
註意,js文件編碼如果不為UTF-8,網頁文件顯示中文會受到影響。
三. 模板引擎的配置和使用
使用模板
現在,我想向後端發送的內容可不是一個h1標題那麼簡單。還包括整個博客頁面的html內容,如果還是用上面的方法,麻煩就大了。
怎麼辦呢?關鍵步驟在於html和js頁面相分離(類似結構和行為層的分離)。
模板的使用在於後端邏輯和前端表現的分離(前後端分離)。
模板配置
基本配置如下
// 定義模板引擎,使用swig.renderFile方法解析尾碼為html的文件
var swig=require('swig');
app.engine('html',swig.renderFile);
// 設置模板存放目錄
app.set('views','./views');
// 註冊模板引擎
app.set('view engine','html');
swig.setDefaults({cache:false});
配置模板的基本流程是:
請求swig模塊
=>定義模板引擎
=>註冊模板引擎
=>設置調試方法
我們可以使用var swig=require('swig');
定義了swig方法。
以下進行逐行解析——
定義模板引擎
app.engine('html',swig.renderFile);
第一個參數:模板引擎的名稱,同時也是模板引擎的尾碼,你可以定義打開的是任何文件格式,比如json,甚至tdl等。
第二個參數表示用於解析處理模板內容的方法。
第三個參數:使用swig.renderFile方法解析尾碼為html的文件。
設置模板目錄
現在就用express組件提供的set方法標設置模板目錄:
app.set('views','./views');
定義目錄時也有兩個參數,註意,第一個參數必須為views
!第二個參數可以是我們所給出的路徑。因為之前已經定義了模板文件夾為views
。所以,使用對應的路徑名為./views
。
註冊模板引擎
app.set('view engine','html');
還是使用express提供了set方法。
第一個參數必須是字元串'view engine'
。
第二個參數和app.engine
方法定義的模板引擎名稱(第一個參數)必須是一致的(都是“html”)。
重回app.get
現在我們回到app.get()方法裡面,使用res.render()
方法重新渲染指定內容
app.get('/',function(req,res,next){
/*
* 讀取指定目錄下的指定文件,解析並返回給客戶端
* 第一個參數:模板文件,相對於views目錄,views/index.html
* */
res.render('index');
});
這時候,我們定義了返回值渲染index文件,就需要在views文件夾下新創建一個index.html
。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>歡迎來到我的第一個博客!<h1>
</body>
</html>
render方法還可以接受第二個參數,用於傳遞模板使用的第二個數據。
好了。這時候再刷新頁面,就出現了index的內容。
調試方法
我們在不停止伺服器的情況下,重新修改index的文件內容,發現並沒有刷新。
什麼問題呢?出於性能上考慮,node把第一次讀取的index放到了內容中,下次訪問時,就是緩存中的內容了,而不是真正的index文件。因此需要重啟。
開發過程中,為了減少麻煩,需要取消模板緩存。
swig.setDefaults({cache:false});
當然,當項目上線時,可以把這一段刪除掉。
四. 靜態文件托管
在寫模板文件時,經常引入一些外鏈的css,js和圖片等等。
css怎麼引入?
如果我們直接在首頁的head區域這麼寫:
<link rel="stylesheet" type="text/css" href="css.css"/>
再刷新,發現對css.css的引用失敗了。
問題不在於css.css是否存在,而在於請求失敗。因為外鏈文件本質也是一個請求,但是在app.js中還沒有對應設置。
如果這麼寫:
app.get('/css.css', function (req,res,next) {
res.send('body {background: red;}');
});
發現沒有效果。
打開http://localhost:9001/css.css
發現內容是這樣的:
搞笑了。預設發送的是一個html。因此需要設定一個header
app.get('/css.css', function (req,res,next) {
res.setHeader('content-type','text/css');
res.send('body {background: red;}');
});
ctrl+F5,就解析了紅色背景了。
同樣的,靜態文件需要完全分離,因此這種方法也是不行的。
靜態文件托管目錄
最好的方法是,把所有的靜態文件都放在一個public的目錄下,劃分並存放好。
然後在開頭就通過以下方法,把public目錄下的所有靜態文件都渲染了:
app.use('/public',express.static(__dirname+'/public'));
以上方法表示:當遇到public文件下的文件,都調用第二個參數里的方法(註意是兩個下劃線)。
當用戶訪問的url以public開始,那麼直接返回對應__dirname+'public'
下的文件。因此我們的css應該放到public下。
引用方式為:
<link rel="stylesheet" type="text/css" href="../public/css.css"/>
然後到public文件下創建一個css.css,設置body背景為紅色。原來的app.get方法就不要了。
至此,靜態文件什麼的都可以用到了
小結
在以上的內容中,我們實現了初始化項目,可以調用html和css文件。基本過程邏輯是:
用戶發送http請求(url)=>解析路由=>找到匹配的規則=>指定綁定函數,返回對應內容到用戶。
訪問的是public:靜態——直接讀取指定目錄下的文件,返回給用戶。
=>動態=>處理業務邏輯
那麼整個基本雛形就搭建起來了。
五. 分模塊開發與實現
把整個網站放到一個app.js中,是不利於管理和維護的。實際開發中,是按照不同的功能,管理代碼。
根據功能劃分路由(routers)
根據本項目的業務邏輯,分為三個模塊就夠了。
- 前臺模塊
- 後臺管理模塊
- API模塊:通過ajax調用的介面。
或者,使用app.use
(路由設置)劃分:
app.use('/admin',require('./routers/admin'));
解釋:當用戶訪問的是admin文件下的內容,這調用router文件夾下admin.js文件。下同。
app.use('/api',require('./routers/api'));
後臺app.use('/',require('./routers/main'));
前臺
好了。重寫下以前的代碼,去掉多餘的部分。
// 載入express
var express=require('express');
//創建app應用,相當於=>Node.js Http.createServer();
var app=express();
// 設置靜態文件托管
app.use('/public',express.static(__dirname+'/public'))
// 定義模板引擎,使用swig.renderFile方法解析尾碼為html的文件
var swig=require('swig');
app.engine('html',swig.renderFile);
// 設置模板存放目錄
app.set('views','./views');
// 註冊模板引擎
app.set('view engine','html');
// 調試優化
swig.setDefaults({cache:false});
//app.use('/admin',require('./routers/admin'));
//app.use('/api',require('./routers/api'));
//app.use('/',require('./routers/main'));
//監聽http請求
app.listen(9001);
在routers
創建一個admin.js,同理再創建一個api.js,一個main.js
怎麼訪問不同文件夾下的文件?
比如,我想訪問一個如http://localhost:9001/admin/user
這樣的地址,這樣按理來說就應該調用admin.js(分路由)。
所以編輯admin.js
var express=require('express');
// 創建一個路由對象,此對象將會監聽admin文件下的url
var router=express.Router();
router.get('/user',function(req,res,next){
res.send('user');
});
module.exports=router;//把router的結果作為模塊的輸出返回出去!
註意,在分路由中,不需要寫明路徑,就當它是在admin文件下的相對路徑就可以了。
儲存,然後回到app.js,應用app.use('/admin',require('./routers/admin'));
再打開頁面,就看到結果了。
同理,api.js也如法炮製。
var express=require('express');
// 創建一個路由對象,此對象將會監聽api文件夾下的url
var router=express.Router();
router.get('/user',function(req,res,next){
res.send('api-user');
});
module.exports=router;//把router的結果作為模塊的輸出返回出去!
再應用app.use('api/',require('./routers/api'))
。重啟伺服器,結果如下
首頁也如法炮製
路由的細分
前臺路由涉及了相當多的內容,因此再細化分多若幹個路由也是不錯的選擇。
每個內容包括基本的分類和增刪改
- main模塊
/
——首頁
/view
——內容頁
- api模塊
/
——首頁
/login
——用戶登陸
/register
——用戶註冊
/comment
——評論獲取
/comment/post
——評論提交
- admin模塊
/
——首頁
用戶管理
/user
——用戶列表分類管理
/category
——分類目錄/category/add
——分類添加/category/edit
——分類編輯/category/delete
——分類刪除文章管理
/article
——內容列表/article/add
——添加文章/article/edit
——文章修改/article/delete
——文章刪除評論管理
/comment
——評論列表/comment/delete
——評論刪除
開發流程
功能開發順序
用戶——欄目——內容——評論
一切操作依賴於用戶,所以先需要用戶。
欄目也分為前後臺,優先做後臺。
內容和評論相互關聯。
編碼順序
- 通過Schema定義設計數據儲存結構
- 功能邏輯
- 頁面展示
六. 資料庫連接,表結構
比如用戶,在SCHEMA文件夾下新建一個users.js
如何定義一個模塊呢?這裡用到mongoose模塊
var mongoose=require('mongoose');//引入模塊
除了在users.js請求mongoose模塊以外,在app.js也需要引入mongoose。
// 載入express
var express=require('express')
//創建app應用,相當於=>Node.js Http.createServer();
var app=express();
// 載入資料庫模塊
var mongoose=require('mongoose');
// 設置靜態文件托管
app.use('/public',express.static(__dirname+'/public'))
// 定義模板引擎,使用swig.renderFile方法解析尾碼為html的文件
var swig=require('swig');
app.engine('html',swig.renderFile);
// 設置模板存放目錄
app.set('views','./views');
// 註冊模板引擎
app.set('view engine','html');
// 調試優化
swig.setDefaults({cache:false});
/*
* 根據不同的內容劃分路由器
* */
app.use('/admin',require('./routers/admin'));
app.use('/api',require('./routers/api'));
app.use('/',require('./routers/main'));
//監聽http請求
mongoose.connect();
app.listen(9001);
建立連接資料庫(每次運行都需要這樣)
mongoose使用需要安裝mongodb資料庫。
mongodb安裝比較簡單,在官網上下載了,制定好路徑就可以了。
找到mongodb的bin文件夾。啟動mongod.exe——通過命令行
命令行依次輸入:
f:
cd Program Files\MongoDB\Server\3.2\bin
總之就是根據自己安裝的的路徑名來找到mongod.exe就行了。
開啟資料庫前需要指定參數,比如資料庫的路徑。我之前已經在項目文件夾下創建一個db文件夾,然後作為資料庫的路徑就可以了。
除此之外還得指定一個埠。比如27018
mongod --dbpath=G:\node\db --port=27018
然後回車
信息顯示:等待鏈接27018,證明開啟成功
下次每次關機後開啟伺服器,都需要做如上操作。
接下來要開啟mongo.exe。
命令行比較原始,還是可以使用一些可視化的工具進行連接。在這裡我用的是robomongo。
直接在國外網站上下載即可,下載不通可能需要科學上下網。
名字隨便寫就行了,埠寫27018
點擊鏈接。
回到命令行。發現新出現以下信息:
表示正式建立連接。
數據保存
鏈接已經建立起來。但裡面空空如也。
接下來使用mongoose操作資料庫。
可以上這裡去看看文檔。文檔上首頁就給出了mongoose.connect()
方法。
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var Cat = mongoose.model('Cat', { name: String });
var kitty = new Cat({ name: 'Zildjian' });
kitty.save(function (err) {
if (err) {
console.log(err);
} else {
console.log('meow');
}
});
connect方法接收的第一個參數,就是這個'mongodb://localhost:27018'
。第二個參數是回調函數。
資料庫鏈接失敗的話,是不應該開啟監聽的,所以要把listen放到connect方法裡面。
mongoose.connect('mongodb://localhost:27018/blog',function(err){
if(err){
console.log('資料庫連接錯誤!');
}else{
console.log('資料庫連接成功!');
app.listen(9001);
}
});
運行,console顯示,資料庫鏈接成功。
註意,如果出現錯誤,還是得看看編碼格式,必須為UTF-8。
回到users.js的編輯上來,繼續看mongoose文檔。
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var blogSchema = new Schema({
title: String,
author: String,
body: String,
comments: [{ body: String, date: Date }],
date: { type: Date, default: Date.now },
hidden: Boolean,
meta: {
votes: Number,
favs: Number
}
});
通過mongoose.Schema構造函數,生成一個Schema對象。
new出的Schema對象包含很多內容,傳入的對象代表資料庫中的一個表。每個屬性代表表中的每一個欄位,每個值代表該欄位存儲的數據類型。
在這裡,users.js需要暴露的內容就是用戶名和密碼。
// 載入資料庫模塊
var mongoose=require('mongoose');
// 返回用戶的表結構
module.exports= new mongoose.Schema({
// 用戶名
username: String,
// 密碼
password: String
});
然後在通過模型類來操作表結構。在項目的models文件夾下創建一個User.js
var mongoose=require('mongoose');
var usersSchema=require('../schemas/users');
module.exports=mongoose.model('User',usersSchema);
這樣就完成了一個模型類的創建。
模型怎麼用?還是看看文檔給出的使用方法。
// 創建一個表結構對象
var schema = new mongoose.Schema({ name: 'string', size: 'string' });
// 根據表結構對象創建一個模型類
var Tank = mongoose.model('Tank', schema);
構造函數如何使用:
var Tank = mongoose.model('Tank', yourSchema);
var small = new Tank({ size: 'small' });
small.save(function (err) {
if (err) return handleError(err);
// saved!
})
// or
Tank.create({ size: 'small' }, function (err, small) {
if (err) return handleError(err);
// saved!
})
七. 用戶註冊的前端邏輯
引入首頁
用戶註冊首先得載入一個首頁。
在views下麵新建一個main文件夾,然後把你之前寫好的index.html放進去。
所以回到main.js中。渲染你已經寫好的博客首頁。
var express=require('express');
// 創建一個路由對象,此對象將會監聽前臺文件夾下的url
var router=express.Router();
router.get('/',function(req,res,next){
res.render('main/index');
});
module.exports=router;//把router的結果作為模塊的輸出返回出去!
保存,然後重啟app.js,就能在localhost:9001看到首頁了。
當然這個首頁很醜,你可以自己寫一個。
原來的路徑全部按照項目文件夾的結構進行修改。
邏輯
註冊登錄一共有三個狀態。
一開始就是註冊,如果已有賬號就點擊登錄,出現登錄彈窗。
如果已經登錄,則顯示已經登錄狀態。並有註銷按鈕。
<div class="banner-wrap">
<div class="login" id="register">
<h3>註冊</h3>
<span>用戶:<input name="username" type="text"/></span><br/>
<span>密碼:<input name="password" type="text"/></span><br/>
<span>確認:<input name="repassword" type="text"/></span><br/>
<span><input class="submit" type="button" value="提交"/></span>
<span>已有賬號?馬上<a href="javascript:;">登錄</a></span>
</div>
<div class="login" id="login" style="display:none;">
<h3>登錄</h3>
<span>用戶:<input type="text"/></span><br/>
<span>密碼:<input type="text"/></span><br/>
<span><input type="button" value="提交"/></span>
<span>沒有賬號?馬上<a href="javascript:;">註冊</a></span>
</div>
jquery可以這麼寫:
$(function(){
// 登錄註冊的切換
$('#register a').click(function(){
$('#login').show();
$('#register').hide();
});
$('#login a').click(function(){
$('#login').hide();
$('#register').show();
});
});
當點擊註冊按鈕,應該允許ajax提交數據。地址應該是api下的user文件夾的register,該register文件暫時沒有創建,所以不理他照寫即可。
// 點擊註冊按鈕,通過ajax提交數據
$('#register .submit').click(function(){
// 通過ajax提交交
$.ajax({
type:'post',
url:'/api/user/register',
data:{
username:$('#register').find('[name="username"]').val(),
password:$('#register').find('[name="password"]').val(),
repassword:$('#register').find('[name="repassword"]').val()
},
dataType:'json',
success:function(data){
console.log(data);
}
});
});
允許網站,輸入用戶名密碼點擊註冊。
雖然報錯,但是在chrome的network下的header可以看到之前提交的信息。
挺好,挺好。
八. body-paser的使用:後端的基本驗證
後端怎麼響應前臺的ajax請求?
首先,找到API的模塊,增加一個路由,回到api.js——當收到前端ajax的post請求時,路由列印出一個register字元串。
var express=require('express');
// 創建一個路由對象,此對象將會監聽api文件夾下的url
var router=express.Router();
router.post('/user/register',function(req,res,next){
console.log('register');
});
module.exports=router;//把router的結果作為模塊的輸出返回出去!
這時候,就不會顯示404了。說明路由處理成功。
如何獲取前端post的數據?
這就需要用到新的第三方模塊——body-parser
。
相關文檔地址:https://github.com/expressjs/body-parser
bodyParser.urlencoded(options)
Returns middleware that only parses
urlencoded
bodies. This parser accepts only UTF-8 encoding of the body and supports automatic inflation ofgzip
anddeflate
encodings.A new
body
object containing the parsed data is populated on therequest
object after the middleware (i.e.req.body
). This object will contain key-value pairs, where the value can be a string or array (whenextended
isfalse
), or any type (whenextended
istrue
).
var bodyParser=require('body-parser');
app.use(bodyParser.urlencoded(extended:true));
在app.js中,加入body-parser。然後通過app.use()方法調用。此時的app.js是這樣的:
// 載入express
var express=require('express');
//創建app應用,相當於=>Node.js Http.createServer();
var app=express();
// 載入資料庫模塊
var mongoose=require('mongoose');
// 載入body-parser,用以處理post提交過來的數據
var bodyParser=require('body-parser');
// 設置靜態文件托管
app.use('/public',express.static(__dirname+'/public'))
// 定義模板引擎,使用swig.renderFile方法解析尾碼為html的文件
var swig=require('swig');
app.engine('html',swig.renderFile);
// 設置模板存放目錄
app.set('views','./views');
// 註冊模板引擎
app.set('view engine','html');
// 調試優化
swig.setDefaults({cache:false});
// bodyParser設置
app.use(bodyParser.urlencoded({extended:true}));
/*
* 根據不同的內容劃分路由器
* */
app.use('/admin',require('./routers/admin'));
app.use('/api',require('./routers/api'));
app.use('/',require('./routers/main'));
//監聽http請求
mongoose.connect('mongodb://localhost:27018/blog',function(err){
if(err){
console.log('資料庫連接錯誤!');
}else{
console.log('資料庫連接成功!');
app.listen(9001);
}
});
配置好之後,回到api.js,就能在router.post方法中,通過req.body
得到提交過來的數據。
router.post('/user/register',function(req,res,next){
console.log(req.body);
});
重啟app.js,然後網頁再次提交數據。
出現console信息:
後端的表單驗證
拿到數據之後,就是進行基本的表單驗證。比如
- 用戶名是否符合規範(空?)
- 是否被註冊
- 密碼是否符合規範
- 重覆密碼是否一致
其中,檢測用戶名是否被註冊需要用到資料庫查詢。
所以按照這個邏輯,重新歸下類:
// 基本驗證=>用戶不得為空(錯誤代碼1),密碼不得為空(錯誤代碼2),兩次輸入必須一致(錯誤代碼3)
// 資料庫查詢=>用戶是否被註冊。
返回格式的初始化
我們要對用戶的請求進行響應。對於返回的內容,應該做一個初始化,指定返回信息和錯誤代碼
// 統一返回格式
var responseData=null;
router.use(function(req,res,next){
responseData={
code:0,
message:''
}
next();
});
寫出判斷邏輯,通過res.json返回給前端
res.json方法就是把響應的數據轉化為一個json字元串。再直接return出去。後面代碼不再執行。
router.post('/user/register',function(req,res,next){
var username=req.body.username;
var password=req.body.password;
var repassword=req.body.repassword;
//用戶名是否為空
if(username==''){
responseData.code=1;
responseData.message='用戶名不得為