NodeJS_08_art-template子模板與模板繼承_MongoBooster_express-session_三大類中介軟體_全域性錯誤處理
NodeJS七天課程學習筆記_第8天 Blog綜合案例
Blog 綜合案例 (包含註冊、登入、修改密碼、登出、釋出、分頁列表、評論、個人中心、上傳頭像等)
課程內容概要:
1. 介紹art-template中的 子模板 與 模板繼承
2. 介紹表單同步提交與非同步提交的區別
3. 介紹了視覺化管理工具MongoBooster
4. 介紹了blueimp/javascript-MD5的配置和使用
5. 介紹express-session的配置與使用
6. 註冊、登入 與 退出 功能的實現
7. 介紹 Express 中介軟體 原理
8. express中的 三大類 中介軟體
注意: Node中有許多第3方模板引擎,不只有art-template,還有:
ejs (e代表effective高效之意,從json中生成html的一種魔法) 、
jade(因版權問題,後改名pug哈巴狗)、
handlebars(號稱logic-less templateheima的manmanmai專案用到)、
nunjucks(是Mozilla開發的一個純JavaScript編寫的模板引擎)
首先講art-template中的子模板(include) 以及 模板繼承(extend)語法
官方文件地址 aui.github.io/art-template/zh-cn/docs/syntax.html
第1步, 新建下面4個檔案 作為將來 被包含的子模板
公共的頂部public_top.html、
公共的底部public_footer.html、
公共的左側邊欄public_left.html、
公共的右廣告欄public_right.html
第2步,新增 node_51_base_layout.html 作為將來被各個實際頁面繼承(extend)的母板
母板裡面有個完整的骨架,
母板裡包含(include)了公共的頂部、底部、左側邊欄、右廣告欄
母板裡head引入所有頁面要用到的公共的css(如bootstrap)、js(如jquery)
同時,母板裡,
通過block語法,在head標籤中 預留了 實際頁面真正要用到的 title、css、 js空間
通過block語法,在body標籤中 預留了 實際頁面真正要用到的html 空間
node_51_base_layout.html母板完整程式碼如下:
<!DOCTPYE html>
<html lang="zh">
<head>
<link rel="icon" href="public/img/beyond.jpg" type="image/x-icon"/>
<meta charset="UTF-8">
<meta name="author" content="beyond">
<meta http-equiv="refresh" content="520">
<meta name="description" content="未聞花名-免費零基礎教程-beyond">
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
<meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,彙編,日語,英語,泰語,韓語,俄語,粵語,阿語,魔方,樂理,動漫,PR,PS,AI,AE">
<!--[if lt IE 9]>
<script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
</script>
<![endif]-->
<!-- 公共的樣式 -->
<style type="text/css">
body{
font-size: 100%;
/*宣告margin和padding是個好習慣*/
margin: 0;
padding: 0;
background-image: url("public/img/sakura4.png");
background-repeat: no-repeat;
background-position: center center;
}
</style>
<!-- 公共的樣式 -->
<link rel="stylesheet" type="text/css" href="public/css/beyondbasestylewhite5.css">
<!-- 綠色按鈕的css效果 -->
<link rel="stylesheet" type="text/css" href="public/css/beyondbuttongreen.css">
<!-- 公共的JS特效 -->
<script type="text/javascript" src="public/js/nslog.js"></script>
<!-- jquery -->
<script type="text/javascript" src="public/js/jquery2.1.4.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="public/lib/bootstrap/dist/css/bootstrap.css">
<script type="text/javascript" src="public/lib/bootstrap/dist/js/bootstrap.js"></script>
<!-- 這個標題 要留一個坑,給後代去實現自己的title -->
<title>
{{ block 'block_1_title'}}
beyond心中の動漫神作
{{ /block }}
</title>
<!-- 這個head中要 要留一個坑,給後代去連結自己的css或js-->
{{ block 'block_2_head_css_js'}}
{{ /block }}
</head>
<body>
<!-- include進來公共的頂部、底部、左側邊欄、右廣告欄 -->
{{ include './public_top.html' }}
{{ include './public_left.html' }}
<!-- 這個body最後要 要留一個坑,給後代去實現自己的content-->
{{ block 'block_3_body_content'}}
這是母板預設的正文<br/>
{{ /block }}
{{ include './public_right.html' }}
{{ include './public_footer.html' }}
<!-- 這個body最後要 要留一個坑,給後代去實現自己的js-->
{{ block 'block_4_body_js'}}
{{ /block }}
</body>
</html>
第3步,現在就可以新建首頁node_51_index.html了 ,
該首頁 繼承(extend) 自 base_layout.html母板
node_51_index.html完整程式碼如下:
<!-- 第0步,繼承母板 -->
{{ extend './node_51_base_layout.html' }}
<!-- 第1步,自己的標題 -->
{{ block 'block_1_title'}}
自己的標題_首頁
{{ /block }}
<!-- 第2步,自己的head中的css或js -->
{{ block 'block_2_head_css_js'}}
<style type="text/css">
body{
background-color: rgba(166,166,166,0.1);
}
</style>
<script type="text/javascript">
alert('我是index的head中的js')
</script>
{{ /block }}
<!-- 第3步,自己的正文 -->
{{ block 'block_3_body_content'}}
自己的正文<br/>
{{ /block }}
<!-- 第4步,自己的正文最後的js程式碼 -->
{{ block 'block_4_body_js'}}
<script type="text/javascript">
alert('我是index的body最後的js')
</script>
{{ /block }}
入口檔案node_51_index.js程式碼如下:
function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}
// 匯入框架
var express = require('express')
var path = require('path')
// 建立伺服器物件
var appServer = express()
// 監聽埠,並啟動服務
appServer.listen(5267,function (error) {
if (error) {
return NSLog('啟動失敗: ' + error)
}
NSLog('服務啟動成功')
})
// -----------------------------------
// 靜態資源請求時的 staticFileUrlPrefix
var staticFileUrlPrefix = '/public/'
// var staticFileUrlPrefix = '/public'
// 訪問也只能使用 localhost:5267/public/img/beyond.jpg
// 磁碟上的靜態資源目錄
var staticFilePath = './public/'
// var staticFilePath = 'public'
var callbackFunction = express.static(staticFilePath)
appServer.use(staticFileUrlPrefix,callbackFunction)
// 再開一個靜態資源目錄
appServer.use('/uploads/',express.static(path.join(__dirname,'uploads')))
// -----------------------------------
// 指明:對於 所有後綴為html 的模板檔案 使用模板引擎
var templateFileSuffix = 'html'
appServer.engine(templateFileSuffix,require('express-art-template'))
// 下面這一句引數配置,可有可無
appServer.set('view options',{
debug: process.env.NODE_ENV !== 'production'
})
// 注意:如果不想把模板檔案放在預設的views目錄下,則可以通過下面程式碼更改設定
// appServer.set('views','其他目錄')
// -----------------------------------
// 使用middleware中介軟體body-parser進行post請求體中資料解析
var bodyParser = require('body-parser')
// 設定解析 application/x-www-form-urlencoded
appServer.use(bodyParser.urlencoded({extended: false}))
// 設定解析 application/json
appServer.use(bodyParser.json())
// -----------------------------------
// 自定義路由設計的目的是:
// 1.讓主入口程式的職責更加單一,程式碼更加簡潔
// 1.1 建立服務
// 1.2 做一些服務相關的配置,比如:
// 1.2.1 靜態資源配置
// 1.2.2 模板引擎配置
// 1.2.3 body-parse 解析表單
// 1.2.4 掛載自定義路由
// 1.2.5 監聽埠,啟動服務
// 使用自定義的路由模組 必須使用./
// 注意: 配置模板引擎和body-parser, 一定要在掛載路由之前
var beyondRouter = require('./node_51_router')
appServer.use(beyondRouter)
路由檔案node_51_router.js程式碼如下:
function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};
/*
自定義路由模組的職責是:
專門處理所有的路由
根據不同的請求方式和路徑,採取相應的處理方法
*/
// express 專門提供了路由的處理方法
var express = require('express')
// ---------------使用formidable解析上傳的圖片--------------------
var formidable = require('formidable')
// var util = require('util')
var path = require('path')
// -----------------------------------
// 1.使用express專門提供的路由器處理路由
var router = express.Router()
// -----------------------------------
// 時間格式化
// var BeyondDateFormatFunction = require('./BeyondDateFormat')
// ----------------首頁-------------------
router.get('/',function (request,response) {
response.render('index/node_51_index.html')
})
// 3.在模組檔案最後,匯出router
module.exports = router
通過node_51_index.js入口 載入 node_51_router.js路由之後
啟動伺服器,渲染效果如下:
Blog專案開始
第1步. 專案初始化
首先npm init -y ,生成package.json
然後git init,然後手動新建.gitignore檔案
第2步. npm 安裝 mongoose和express和art-template和express-art-template
第3步. 專案目錄
public目錄下有img和css和js和lib目錄
views目錄下放著html模板, 分成了登入註冊模組、文章模組
第4步. 路由設計
請求路由 | 方法 | GET引數 | POST引數 | 是否需要登入許可權(沒用到) | 備註 |
/ | GET | 首頁index.html即文章列表 | |||
/register | GET | 註冊頁面register.html | |||
/register | POST | email,password,username | 處理註冊的POST請求 | ||
/login | GET | 登入頁面login.html | |||
/login | POST | email,password | 處理登入POST請求 |
首先處理的路由是 / , 渲染首頁node_52_index.html
由於資料庫內暫時還沒有資料,所以首頁只用幾個假的資料先填充,以保證樣式正常
首頁的渲染效果如下:
首頁node_52_index.html程式碼(暫未使用模板引擎渲染)如下:
<!-- 第0步,繼承母板 -->
{{ extend './node_52_base_layout.html' }}
<!-- 第1步,自己的標題 -->
{{ block 'block_1_title'}}
未聞花名_多使用者部落格系統_首頁
{{ /block }}
<!-- 第2步,自己的head中的css或js -->
{{ block 'block_2_head_css_js'}}
<style type="text/css">
body{
background-color: rgba(166,166,166,0.1);
}
</style>
<script type="text/javascript">
// alert('我是index的head中的js')
</script>
{{ /block }}
<!-- ||||||||||||||||||||||||||||||||| -->
<!-- 第3步,自己的正文 -->
{{ block 'block_3_body_content'}}
<section class="container">
<ul class="media-list">
<li class="media">
<div class="media-left">
<a href="#">
<img width="40" height="40" class="media-object" src="public/img/beyond.jpg" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/topics/123">未聞花名</a></h4>
<p>面碼 發起了話題 • 1314 人關注 • 32 個回覆 • 1992 次瀏覽 • 2006-06-07 22:20</p>
</div>
</li>
<li class="media">
<div class="media-left">
<a href="#">
<img width="40" height="40" class="media-object" src="public/img/beyond.jpg" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/topics/123">龍與虎</a></h4>
<p>逢阪大河 發起了話題 • 520 人關注 • 5 個回覆 • 871 次瀏覽 • 2008-05-20 13:14</p>
</div>
</li>
<li class="media">
<div class="media-left">
<a href="#">
<img width="40" height="40" class="media-object" src="public/img/beyond.jpg" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/topics/123">輕音少女</a></h4>
<p>平澤唯 發起了話題 • 67 人關注 • 1 個回覆 • 244 次瀏覽 • 2010-11-11 17:20</p>
</div>
</li>
<li class="media">
<div class="media-left">
<a href="#">
<img width="40" height="40" class="media-object" src="public/img/beyond.jpg" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/topics/123">這個殺手不太冷</a></h4>
<p>mathilda 發起了話題 • 5 人關注 • 0 個回覆 • 133 次瀏覽 • 2011-06-18 10:36</p>
</div>
</li>
</ul>
<nav aria-label="Page navigation" style="text-align:center;">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="active"><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</section>
{{ /block }}
<!-- ||||||||||||||||||||||||||||||||| -->
<!-- 第4步,自己的正文最後的js程式碼 -->
{{ block 'block_4_body_js'}}
<script type="text/javascript">
// alert('我是index的body最後的js')
</script>
{{ /block }}
注意: 首頁node_52_index.html 是 繼承(extend)自 母板 node_52_base_layout.html
母板node_52_base_layout.html程式碼如下:
<!DOCTPYE html>
<html lang="zh">
<head>
<link rel="icon" href="public/img/beyond.jpg" type="image/x-icon"/>
<meta charset="UTF-8">
<meta name="author" content="beyond">
<meta http-equiv="refresh" content="520">
<meta name="description" content="未聞花名-免費零基礎教程-beyond">
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
<meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,彙編,日語,英語,泰語,韓語,俄語,粵語,阿語,魔方,樂理,動漫,PR,PS,AI,AE">
<!--[if lt IE 9]>
<script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
</script>
<![endif]-->
<!-- 公共的樣式 -->
<style type="text/css">
body{
font-size: 100%;
/*宣告margin和padding是個好習慣*/
margin: 0;
padding: 0;
background-image: url("public/img/sakura4.png");
background-repeat: no-repeat;
background-position: center center;
}
</style>
<!-- 公共的樣式 -->
<link rel="stylesheet" type="text/css" href="public/css/beyondbasestylewhite5.css">
<!-- 綠色按鈕的css效果 -->
<link rel="stylesheet" type="text/css" href="public/css/beyondbuttongreen.css">
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="public/lib/bootstrap/node_52_v337_bootstrap.css">
<!-- 公共的JS特效 -->
<script type="text/javascript" src="public/js/nslog.js"></script>
<!-- 這個標題 要留一個坑,給後代去實現自己的title -->
<title>
{{ block 'block_1_title'}}
beyond心中の動漫神作
{{ /block }}
</title>
<!-- 這個head中要 要留一個坑,給後代去連結自己的css或js-->
{{ block 'block_2_head_css_js'}}
{{ /block }}
</head>
<body>
<!-- include進來公共的頂部、底部、左側邊欄、右廣告欄 -->
{{ include './node_52_public_top.html' }}
<!-- 這個body最後要 要留一個坑,給後代去實現自己的content-->
{{ block 'block_3_body_content'}}
這是母板預設的正文<br/>
{{ /block }}
<!-- jquery -->
<script type="text/javascript" src="public/js/jquery2.1.4.js"></script>
<script type="text/javascript" src="public/lib/bootstrap/node_52_v337_bootstrap.js"></script>
{{ include './node_52_public_footer.html' }}
<!-- 這個body最後要 要留一個坑,給後代去實現自己的js-->
{{ block 'block_4_body_js'}}
{{ /block }}
</body>
</html>
注意:
母板node_52_base_layout.html中用到的子模板node_52_public_top.html 以及
子模板node_52_public_footer.html程式碼分別如下:
子模板node_52_public_top.html程式碼如下: (一會兒寫了登入註冊後,再完善)
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">
<img width="90px" src="public/img/vwhm2.png" alt="">
</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<!-- <ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul> -->
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
</form>
<ul class="nav navbar-nav navbar-right">
{{ if user }}
<a class="btn btn-default navbar-btn" href="/topics/new">發起</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><img width="20" height="20" src="public/img/beyond.jpg" alt=""> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li class="dropdown-current-user">
當前登入使用者: {{ 一會寫 }}
</li>
<li role="separator" class="divider"></li>
<li><a href="#">個人主頁</a></li>
<li><a href="/settings/profile">設定</a></li>
<li><a href="/logout">退出</a></li>
</ul>
</li>
{{ else }}
<a class="btn btn-primary navbar-btn" href="/login">登入</a>
<a class="btn btn-success navbar-btn" href="/register">註冊</a>
{{ /if }}
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
子模板node_52_public_footer.html程式碼如下:
<footer id="copyright">
<p style="font-size:14px;text-align:center;font-style:italic;">
Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>
</p>
</footer>
目前為止,首頁,效果如下:
接下來,點選首頁->右上角按鈕->彈出下拉選單->單擊註冊按鈕
註冊頁面效果如下:
開始渲染node_52_register.html註冊頁面: (包括使用ajax傳送非同步請求(POST註冊)):
程式碼如下:
<!-- 第0步,繼承母板 -->
{{ extend './node_52_base_layout.html' }}
<!-- 第1步,自己的標題 -->
{{ block 'block_1_title'}}
未聞花名_多使用者部落格系統_註冊
{{ /block }}
<!-- 第2步,自己的head中的css或js -->
{{ block 'block_2_head_css_js'}}
<link rel="stylesheet" type="text/css" href="public/css/login.css">
<script type="text/javascript">
// alert('我是index的head中的js')
</script>
{{ /block }}
<!-- ||||||||||||||||||||||||||||||||| -->
<!-- 第3步,自己的正文 -->
{{ block 'block_3_body_content'}}
<div class="main">
<div class="header">
<a href="/">
<img src="/public/img/vwhm2.png" alt="" style="border:1px solid rgba(191,191,191,0.2);">
</a>
<p></p>
</div>
<!--
表單具有預設的提交行為,預設是同步的,同步表單提交,瀏覽器會鎖死(轉圈兒)等待服務端的響應結果。
表單的同步提交之後,無論服務端響應的是什麼,都會直接把響應的結果覆蓋掉當前頁面。
注意: 同步提交返回的結果 是直接覆蓋掉 當前頁面的內容
後來有人想到了非同步提交的辦法,來解決這個問題。
-->
<form id="id_form_register" method="post" action="/register">
<div class="form-group">
<label for="email">郵箱</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Email" autofocus>
</div>
<div class="form-group">
<label for="username">暱稱</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Username">
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Password">
</div>
<button type="submit" class="btn btn-success btn-block">註冊</button>
</form>
<div class="message">
<p>已有賬號? <a href="/login">點選登入</a>.</p>
</div>
<p></p>
</div>
{{ /block }}
<!-- ||||||||||||||||||||||||||||||||| -->
<!-- 第4步,自己的正文最後的js程式碼 -->
{{ block 'block_4_body_js'}}
<!-- 使用blueimp-md5加密 (算了,只在服務端md5加密了)-->
<script type="text/javascript" src="public/js/md5.min.js"></script>
<script type="text/javascript">
// alert('我是index的body最後的js')
$('#id_form_register').on('submit',function (event) {
// 阻止預設的事件行為
event.preventDefault()
// 獲取表單資料 (使用jQuery中表單物件的serialize方法)
var formData = $(this).serialize()
// 傳送ajax請求之前應該要先做客戶端表單驗證
$.ajax({
url: '/register',
type: 'post',
data: formData,
dataType: 'json',
success: function (responseObj) {
/*
err為0, 註冊成功,客戶端重定向(因為伺服器重定向對 非同步請求 無效)
err為1, email已被註冊過
err為2, username已被佔用
err為500, 伺服器崩潰
*/
var err = responseObj.err
switch(err){
case 0:
window.location.href = '/'
break
case 1:
window.alert('email已註冊')
break
case 2:
window.alert('username已存在')
break
case 3:
window.alert('email已註冊或username已存在')
break
case 500:
window.alert('伺服器崩潰' + responseObj.msg)
break
default:
window.alert('未知異常')
}
}
})
})
</script>
{{ /block }}
接下來編寫服務端的程式碼, 以便處理ajax提交post過來的註冊請求
這兒我們使用mongodb + mongoose中介軟體
第1步, 開啟命令列,執行mongod 啟動mongodb資料庫服務
第2步,新建node_52_userdao.js, 建立User資料模型Schema
(注意: 後面我們還會建立articledao.js用來操作文章入庫的CRUD)
(注意: 後面我們還會建立commentdao.js用來操作評論入庫的CRUD)
node_52_userdao.js程式碼如下:
function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};
// ----------------初始化-------------------
var mongoose = require('mongoose')
// 連線資料庫 db2,預設埠是: 27017
mongoose.connect('mongodb://localhost/db2')
var Schema = mongoose.Schema
// 設計使用者表結構
var userSchema = new Schema({
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
password: {
type: String,
required: true
},
pubTime: {
type: String,
required: true,
/*
type: Date,
default: Date.now
預設值: 只寫一個函式名,不加括號就不會立即呼叫,
因為一旦加括號,就會立即呼叫,會立刻執行
*/
default: ''
},
usersex: {
type: Number,
required: false,
default: 0
},
userage: {
type: Number,
// enum: [8,9,10,11,12,13,14,15,16],
default: 13
},
userimg: {
// 使用者頭像url
type: String,
required: false,
default: 'beyond.jpg'
},
userdescription: {
type: String,
required: false,
default: 'vwhm.net'
},
userstatus: {
// 0無限制,1限制評論,2限制釋出,3限制登入
type: Number,
enum: [0,1,2,3],
default: 0
}
})
// 核心程式碼: 直接匯出 模型建構函式
// 引數1: User 首字母必須大寫,且必須是單數;
// 這樣就能自動生成users表(集合)
module.exports = mongoose.model('User',userSchema)
第3步,安裝和配置body-parser中介軟體,自動化處理表單請求
第4步,儲存入庫,並將操作結果的json資料 回寫瀏覽器
這個儲存之前,要先查詢 email 或者 username是否已經存在,
使用到了mongoose官方文件 中的 or 語法,mongoosejs.com/docs/api.html#query_Query-or
也可以參照mongodb官方文件 中的 or 條件語法
在node_52_router.js中,處理post過來的register請求時,
先判斷資料庫中是否已經存在email和username
如果查詢錯誤,那麼給瀏覽器報錯500,Server Error如果email已註冊,那麼給瀏覽器報錯err = 1
如果username已存在,那麼給瀏覽器報錯err = 2
如果使用or查詢,為了少寫一個介面,那麼給瀏覽器報錯err = 3,email已註冊或username已存在
如果查詢結果為null,那麼執行註冊save操作,
如果save結果出錯,那麼給瀏覽器報錯500
如果save成功,那麼給瀏覽器返回{"err":0,"msg":"註冊成功"}
瀏覽器就會收到非同步請求返回的結果,進行客戶端跳轉了
在這期間, 插句題外話,推薦了一個客戶端軟體 MongoBooster 對MongoDB資料庫進行視覺化管理
安裝後, 通過url localhost:27017 進行連線mongodb資料庫
然後就可以選擇db2資料庫,執行查詢語句了...
在這期間,還插了一句題外話,使用md5 中介軟體
客戶端直接引入,然後使用
<script src="public/js/md5.min.js"></script>
就可以使用了
var hash = md5("password");
服務端先 npm install blueimp-md5 ,然後使用:
var hash = md5("password");
最後,處理註冊請求的node_52_router.js程式碼如下:
function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};
/*
自定義路由模組的職責是:
專門處理所有的路由
根據不同的請求方式和路徑,採取相應的處理方法
*/
// express 專門提供了路由的處理方法
var express = require('express')
// blueimp-md5對password加密再存入資料庫
var md5 = require('./md5')
// ---------------使用formidable解析上傳的圖片--------------------
var formidable = require('formidable')
// var util = require('util')
var path = require('path')
// -----------------------------------
// 1.使用express專門提供的路由器處理路由
var router = express.Router()
// ----------------引入dao模組-------------------
// 先對dao初始化
var UserDaoFunction = require('./node_52_userdao')
// 時間格式化
var BeyondDateFormatFunction = require('./BeyondDateFormat')
// ----------------首頁-------------------
router.get('/',function (request,response) {
response.render('index/node_52_index.html')
})
// ----------------註冊頁面-------------------
router.get('/register',function (request,response) {
response.render('index/node_52_register.html')
})
// ----------------進行註冊-------------------
router.post('/register',function (request,response) {
/*
先判斷資料庫中是否已經存在email和username
如果查詢錯誤,報錯500,Server Error
如果email已註冊,報錯err = 1
如果username已存在,報錯err = 2
如果使用or查詢,為了少寫一個介面,報錯err = 3,email已註冊或username已存在
*/
// 直接獲取body-parser中post過來的表單
var bodyObj = request.body
// 使用blueimp-md5進行加密 (可加兩次)
bodyObj.password = md5(md5(bodyObj.password))
// 手動添加當前時間
bodyObj.pubTime = BeyondDateFormatFunction(new Date(),'yyyy-MM-dd hh:mm:ss')
UserDaoFunction.findOne({
$or: [
{
email: bodyObj.email
},
{
username: bodyObj.username
}
]
})
.then(function (data) {
// then方法的引數1是: resolveCallback
NSLog('data: ' + data)
if (data === null) {
// 來到這兒說明 可以進行真正的註冊
// new 一個物件, 然後執行save方法並生成一個promise,為了避免callback hell 我們把它return, 目的是使用鏈式的then方法
var promise_2 = new UserDaoFunction(bodyObj).save()
return promise_2
}
// 來到這兒,說明有data 則表示 已存在了
response.status(200).json({
err: 3,
msg: 'email已註冊或username已存在'
})
},function (error) {
// then方法的引數2是: rejectCallback
// 如果查詢失敗,則返回500錯誤
NSLog('error: ' + error)
// express 內部 提供了一個json方法,自動把物件轉成json
response.status(500).json({
err: 500,
msg: error
})
})
.then(function (data) {
// then方法的引數1是: promise_2的resolveCallback
// 儲存成功
response.status(200).json({
err: 0,
msg: '註冊成功'
})
},function (error) {
// then方法的引數2是: promise_2的rejectCallback
// 儲存失敗
response.status(500).json({
err: 500,
msg: error
})
})
})
// 3.在模組檔案最後,匯出router
module.exports = router
註冊效果執行如下:
開啟MongoBooster,使用command + R重新整理一下, 檢視資料庫如下:
再次強調了一下, 服務端重定向,對於瀏覽器的非同步請求無效!!! 記住就ok
表單具有預設的提交行為,預設是同步的,表單同步提交,瀏覽器會鎖死(轉圈兒)等待服務端的響應結果。
表單的同步提交之後,無論服務端響應的是什麼,都會直接把響應的結果覆蓋掉當前頁面。
因此,以前的開發時,還要實現資料回顯,即在重新渲染頁面時,把前面提交過來的資料 填充到表單元素中
注意: 同步提交返回的結果 是直接覆蓋掉 當前頁面的內容後來有人想到了非同步提交的辦法,來解決這個問題。
如今github仍然使用的是表單同步提交, 就是因為這樣由服務端統一渲染,比較安全,雖然伺服器壓力大一些
補充一下,github 是一個 ruby on rails專案
由於express預設是不支援cookie和session的,
因此, 下面 使用express-session中介軟體,來儲存使用者登入的狀態
express-session 官方文件: npmjs.com/package/express-session
第1步, 安裝
npm install express-session --save
第2步, 配置 (一定要在appServer.use(router)之前)
// ---------express-session中介軟體配置------------------
// express-session 步驟2
appServer.use(session({
// 金鑰,為了安全起見
secret: 'vwhm.net',
//
resave: false,
// true表示 尚未使用 就進行初始化一個sessionID
saveUninitialized: true
}))
第3步, 使用request.session.user 即可 設定或讀取 session
(注意:此狀態下的session 不是持久化的, 只是記憶體裡,重啟伺服器就沒有了)
我們在UserDao儲存註冊使用者的時候,就將save()方法返回的user物件,存入session內
request.session.user = userFromDB
第4步, 在渲染node_52_index.html時, 將session中的user物件渲染過去
如果session有user,那麼 顯示 使用者名稱
如果session沒有user,那麼 顯示 登入和註冊
第5步, 登出的話,只要 置null 並 delete request.session.user即可
效果如下:
node_52_index.js程式碼如下:
function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}
// --------匯入框架---------------------------
var express = require('express')
var path = require('path')
// express-session 步驟1
var session = require('express-session')
// --------建立並啟動伺服器--------------------------
var appServer = express()
// 監聽埠,並啟動服務
appServer.listen(5267,function (error) {
if (error) {
return NSLog('啟動失敗: ' + error)
}
NSLog('服務啟動成功')
})
// --------靜態資源配置---------------------------
// 靜態資源請求時的 staticFileUrlPrefix
var staticFileUrlPrefix = '/public/'
// var staticFileUrlPrefix = '/public'
// 訪問也只能使用 localhost:5267/public/img/beyond.jpg
// 磁碟上的靜態資源目錄(強烈推薦轉成絕對路徑)
// var staticFilePath = './public/'
// var staticFilePath = 'public'
var staticFilePath = path.join(__dirname,'public')
var callbackFunction = express.static(staticFilePath)
appServer.use(staticFileUrlPrefix,callbackFunction)
// 再開一個靜態資源目錄
appServer.use('/uploads/',express.static(path.join(__dirname,'uploads')))
// ---------art-template模板引擎配置-------------------
// 指明:對於 所有後綴為html 的模板檔案 使用模板引擎
var templateFileSuffix = 'html'
appServer.engine(templateFileSuffix,require('express-art-template'))
// 下面這一句引數配置,可有可無
appServer.set('view options',{
debug: process.env.NODE_ENV !== 'production'
})
// 注意:如果不想把模板檔案放在預設的views目錄下,則可以通過下面程式碼更改設定
// appServer.set('views','其他目錄')
// ---------body-parser中介軟體配置---------------------
// 使用middleware中介軟體body-parser進行post請求體中資料解析
var bodyParser = require('body-parser')
// 設定解析 application/x-www-form-urlencoded
appServer.use(bodyParser.urlencoded({extended: false}))
// 設定解析 application/json
appServer.use(bodyParser.json())
// ---------express-session中介軟體配置------------------
// express-session 步驟2
// 該中介軟體 會為request增加一個session的成員,預設型別為物件
// 本例中,session是在記憶體中儲存的,一旦伺服器重啟就不在了
// 在實際生產環境下,需要對session進行持久化
appServer.use(session({
// 自定義金鑰,為了安全起見
secret: 'vwhm.net',
//
resave: false,
// true表示 無論用不用session 都進行初始化一個sessionID
saveUninitialized: true
}))
// -----------------------------------
// 自定義路由設計的目的是:
// 1.讓主入口程式的職責更加單一,程式碼更加簡潔
// 1.1 建立服務
// 1.2 做一些服務相關的配置,比如:
// 1.2.1 靜態資源配置
// 1.2.2 模板引擎配置
// 1.2.3 body-parse 解析表單
// 1.2.4 掛載自定義路由
// 1.2.5 監聽埠,啟動服務
// 使用自定義的路由模組 必須使用./
// 注意: 配置模板引擎和body-parser, 一定要在掛載路由之前
var beyondRouter = require('./node_52_router')
appServer.use(beyondRouter)
node_52_router.js程式碼如下:
function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};
/*
自定義路由模組的職責是:
專門處理所有的路由
根據不同的請求方式和路徑,採取相應的處理方法
*/
// express 專門提供了路由的處理方法
var express = require('express')
// blueimp-md5對password加密再存入資料庫
var md5 = require('./md5')
// ---------------使用formidable解析上傳的圖片--------------------
var formidable = require('formidable')
// var util = require('util')
var path = require('path')
// -----------------------------------
// 1.使用express專門提供的路由器處理路由
var router = express.Router()
// ----------------引入dao模組-------------------
// 先對dao初始化
var UserDaoFunction = require('./node_52_userdao')
// 時間格式化
var BeyondDateFormatFunction = require('./BeyondDateFormat')
// ----------------首頁-------------------
router.get('/',function (request,response) {
response.render('index/node_52_index.html',{
/*
在渲染index.html時, 將session中的user物件渲染過去
如果session有user,那麼 顯示 使用者名稱
如果session沒有user,那麼 顯示 登入和註冊
*/
user: request.session.user
})
})
// ----------------註冊頁面-------------------
router.get('/register',function (request,response) {
response.render('index/node_52_register.html')
})
// ----------------進行註冊-------------------
router.post('/register',function (request,response) {
/*
先判斷資料庫中是否已經存在email和username
如果查詢錯誤,報錯500,Server Error
如果email已註冊,報錯err = 1
如果username已存在,報錯err = 2
如果使用or查詢,為了少寫一個介面,報錯err = 3,email已註冊或username已存在
*/
// 直接獲取body-parser中post過來的表單
var bodyObj = request.body
// 使用blueimp-md5進行加密 (可加兩次)
bodyObj.password = md5(md5(bodyObj.password))
// 手動添加當前時間
bodyObj.pubTime = BeyondDateFormatFunction(new Date(),'yyyy-MM-dd hh:mm:ss')
UserDaoFunction.findOne({
$or: [
{
email: bodyObj.email
},
{
username: bodyObj.username
}
]
})
.then(function (data) {
// then方法的引數1是: resolveCallback
NSLog('data: ' + data)
if (data === null) {
// 來到這兒說明 可以進行真正的註冊
// new 一個物件, 然後執行save方法並生成一個promise,為了避免callback hell 我們把它return, 目的是使用鏈式的then方法
var promise_2 = new UserDaoFunction(bodyObj).save()
return promise_2
}
// 來到這兒,說明有data 則表示 已存在了
response.status(200).json({
err: 3,
msg: 'email已註冊或username已存在'
})
},function (error) {
// then方法的引數2是: rejectCallback
// 如果查詢失敗,則返回500錯誤
NSLog('error: ' + error)
// express 內部 提供了一個json方法,自動把物件轉成json
response.status(500).json({
err: 500,
msg: error
})
})
.then(function (userFromDB) {
// then方法的引數1是: promise_2的resolveCallback
// 儲存成功的話, 記錄到session中
request.session.user = userFromDB
response.status(200).json({
err: 0,
msg: '註冊成功'
})
},function (error) {
// then方法的引數2是: promise_2的rejectCallback
// 儲存失敗
response.status(500).json({
err: 500,
msg: error
})
})
})
// ----------------登出請求-------------------
router.get('/logout',function (request,response) {
// 清除session 並 重定向到首頁
request.session.user = null
delete request.session.user
response.redirect('/')
})
// 3.在模組檔案最後,匯出router
module.exports = router
最後再把登入功能實現一下 (後面的上傳頭像、釋出、評論、找回密碼等等等以後再寫)
node_52_login.html程式碼如下:
<!-- 第0步,繼承母板 -->
{{ extend './node_52_base_layout.html' }}
<!-- 第1步,自己的標題 -->
{{ block 'block_1_title'}}
未聞花名_多使用者部落格系統_註冊
{{ /block }}
<!-- 第2步,自己的head中的css或js -->
{{ block 'block_2_head_css_js'}}
<link rel="stylesheet" type="text/css" href="public/css/login.css">
<script type="text/javascript">
// alert('我是index的head中的js')
</script>
{{ /block }}
<!-- ||||||||||||||||||||||||||||||||| -->
<!-- 第3步,自己的正文 -->
{{ block 'block_3_body_content'}}
<div class="main">
<div class="header">
<a href="/">
<img src="/public/img/vwhm2.png" alt="" style="border:1px solid rgba(191,191,191,0.2);">
</a>
<h1>使用者登入</h1>
</div>
<form id="id_form_login">
<div class="form-group">
<label for="">郵箱</label>
<input type="email" class="form-control" id="" name="email" placeholder="Email" autofocus>
</div>
<div class="form-group">
<label for="">密碼</label>
<a class="pull-right" href="">忘記密碼?</a>
<input type="password" class="form-control" id="" name="password" placeholder="Password">
</div>
<div class="checkbox">
<label>
<input type="checkbox">記住我
</label>
</div>
<button type="submit" class="btn btn-success btn-block">登入</button>
</form>
<div class="message">
<p>沒有賬號? <a href="/register">點選建立</a>.</p>
</div>
</div>
{{ /block }}
<!-- ||||||||||||||||||||||||||||||||| -->
<!-- 第4步,自己的正文最後的js程式碼 -->
{{ block 'block_4_body_js'}}
<!-- 使用blueimp-md5加密 (算了,只在服務端md5加密了)-->
<script type="text/javascript" src="public/js/md5.min.js"></script>
<script type="text/javascript">
// alert('我是index的body最後的js')
$('#id_form_login').on('submit',function (event) {
// 阻止預設的事件行為
event.preventDefault()
// 獲取表單資料 (使用jQuery中表單物件的serialize方法)
var formData = $(this).serialize()
// 傳送ajax請求之前應該要先做客戶端表單驗證
$.ajax({
url: '/login',
type: 'post',
data: formData,
dataType: 'json',
success: function (responseObj) {
/*
err為0, 登入成功,客戶端重定向(因為伺服器重定向對 非同步請求 無效)
err為1, 郵箱或密碼錯誤
err為500, 伺服器崩潰
*/
var err = responseObj.err
switch(err){
case 0:
window.location.href = '/'
break
case 1:
window.alert('郵箱或密碼錯誤')
break
case 500:
window.alert('伺服器崩潰' + responseObj.msg)
break
default:
window.alert('未知異常')
}
}
})
})
</script>
{{ /block }}
包含了登入處理請求的node_52_router.js完整程式碼如下:
function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};
/*
自定義路由模組的職責是:
專門處理所有的路由
根據不同的請求方式和路徑,採取相應的處理方法
*/
// express 專門提供了路由的處理方法
var express = require('express')
// blueimp-md5對password加密再存入資料庫
var md5 = require('./md5')
// ---------------使用formidable解析上傳的圖片--------------------
var formidable = require('formidable')
// var util = require('util')
var path = require('path')
// -----------------------------------
// 1.使用express專門提供的路由器處理路由
var router = express.Router()
// ----------------引入dao模組-------------------
// 先對dao初始化
var UserDaoFunction = require('./node_52_userdao')
// 時間格式化
var BeyondDateFormatFunction = require('./BeyondDateFormat')
// ----------------首頁-------------------
router.get('/',function (request,response) {
response.render('index/node_52_index.html',{
/*
在渲染index.html時, 將session中的user物件渲染過去
如果session有user,那麼 顯示 使用者名稱
如果session沒有user,那麼 顯示 登入和註冊
*/
user: request.session.user
})
})
// ----------------註冊頁面-------------------
router.get('/register',function (request,response) {
response.render('index/node_52_register.html')
})
// ----------------進行註冊-------------------
router.post('/register',function (request,response) {
/*
先判斷資料庫中是否已經存在email和username
如果查詢錯誤,報錯500,Server Error
如果email已註冊,報錯err = 1
如果username已存在,報錯err = 2
如果使用or查詢,為了少寫一個介面,報錯err = 3,email已註冊或username已存在
*/
// 直接獲取body-parser中post過來的表單
var bodyObj = request.body
// 使用blueimp-md5進行加密 (可加兩次)
bodyObj.password = md5(md5(bodyObj.password))
// 手動添加當前時間
bodyObj.pubTime = BeyondDateFormatFunction(new Date(),'yyyy-MM-dd hh:mm:ss')
UserDaoFunction.findOne({
$or: [
{
email: bodyObj.email
},
{
username: bodyObj.username
}
]
})
.then(function (data) {
// then方法的引數1是: resolveCallback
NSLog('data: ' + data)
if (data === null) {
// 來到這兒說明 可以進行真正的註冊
// new 一個物件, 然後執行save方法並生成一個promise,為了避免callback hell 我們把它return, 目的是使用鏈式的then方法
var promise_2 = new UserDaoFunction(bodyObj).save()
return promise_2
}
// 來到這兒,說明有data 則表示 已存在了
response.status(200).json({
err: 3,
msg: 'email已註冊或username已存在'
})
},function (error) {
// then方法的引數2是: rejectCallback
// 如果查詢失敗,則返回500錯誤
NSLog('error: ' + error)
// express 內部 提供了一個json方法,自動把物件轉成json
response.status(500).json({
err: 500,
msg: error
})
})
.then(function (userFromDB) {
// then方法的引數1是: promise_2的resolveCallback
// 儲存成功的話, 記錄到session中
request.session.user = userFromDB
response.status(200).json({
err: 0,
msg: '註冊成功'
})
},function (error) {
// then方法的引數2是: promise_2的rejectCallback
// 儲存失敗
response.status(500).json({
err: 500,
msg: error
})
})
})
// ----------------登出請求-------------------
router.get('/logout',function (request,response) {
// 清除session 並 重定向到首頁
request.session.user = null
delete request.session.user
response.redirect('/')
})
// ----------------登入介面-------------------
router.get('/login',function (request,response) {
response.render('index/node_52_login.html')
})
// ----------------進行登入-------------------
router.post('/login',function (request,response) {
// 1. 獲取表單,對密碼二次加密
// 直接獲取body-parser中post過來的表單
var bodyObj = request.body
// 使用blueimp-md5進行加密 (可加兩次)
bodyObj.password = md5(md5(bodyObj.password))
// 2. 使用UserDao查詢
UserDaoFunction.findOne({
email: bodyObj.email,
password: bodyObj.password
})
.then(function (userFromDB) {
// 如果 userFromDB 為 null,表示 登入失敗
if (userFromDB === null) {
return response.status(200).json({
err: 1,
msg: '郵箱或密碼錯誤'
})
}
// 來到這兒說明登入成功,記錄session
request.session.user = userFromDB
response.status(200).json({
err: 0,
msg: '登入成功'
})
},function (error) {
// then方法的引數2是: rejectCallback
// 如果查詢失敗,則返回500錯誤
NSLog('error: ' + error)
// express 內部 提供了一個json方法,自動把物件轉成json
response.status(500).json({
err: 500,
msg: error
})
})
})
// 3.在模組檔案最後,匯出router
module.exports = router
最終效果如下: ((後面的上傳頭像、使用xheditor釋出、評論、找回密碼等等等以後再寫))
NodeJS七天課程學習筆記_第8天 中介軟體_全域性錯誤處理
推薦了chrome外掛: EditThisCookie
推薦了模擬各種post和get等請求的工具: PostMan
一張圖說明中介軟體的原理
中介軟體: 實質上是一種包裝方法
中介軟體分為幾種:
1. 不關心任何請求路徑的中介軟體,如use方法
appServer.use( function(request,response,next ){
NSLog('請求被攔截下來了')
// 請求又被放行了
next()
}
)
意思是任何請求,都會進入這個中介軟體
任何請求都會被這個use方法攔截下來, 後面的中介軟體就無法再獲取到該請求了
除非在use方法最後一行, 呼叫next() ,放行該請求
2. 只關心 以/public開頭的中介軟體, 如 /public/img/beyond.jpg或者/public/js/jquery.js
像這樣的只有是以/public開頭的請求,才會被攔截下來(不關心是post還是get方式)
appServer.use('/public', function(request,response,next ){
NSLog('請求被攔截下來了')
// 請求又被放行了
next()
}
)
3. 嚴格匹配 請求方式 與 請求路徑的中介軟體, 如get 和 post等
像這樣的只有是/login的Get請求,才會被攔截下來
appServer.get('/login', function(request,response,next ){
NSLog('請求被攔截下來了')
// 請求一般被處理了,不會再放行了, 要放行也可以...
next()
}
)
4. 錯誤處理中介軟體, 集中處理錯誤, use的引數只有1個函式, 該匿名函式 必須是四個引數appServer.use(function(error,request,response,next ){
console.error(error.stack)
// 請求一般被處理了,不會再放行了, 要放行也可以...
next()
}
)
node_53.js完整程式碼如下:
function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}
// 演示express中的三種中介軟體
var express = require('express')
var appServer = express()
/*
第1種,不關心任何請求路徑的中介軟體:
use有兩個引數:
引數1: 函式(帶3個引數)
任何請求,都會被攔截下來
除非 手動呼叫next()才會將請求放行
*/
appServer.use(function (request,response,next) {
NSLog('第1種_1:' + request.url,false)
next()
})
appServer.use(function (request,response,next) {
NSLog('第1種_2:' + request.url,false)
next()
})
/*
第2種,不關心請求方式, 只關心請求路徑是以xxx開頭的中介軟體: use有兩個引數:
引數1: 以xxx開頭的路徑
引數2: 函式(帶3個引數)
任何以xxx開頭的請求,都會被攔截下來
除非 手動呼叫next()才會將請求放行
*/
appServer.use('/public',function (request,response,next) {
// 如果此時瀏覽器輸入: localhost/public/img/beyond.jpg
// 注意這時的url打印出來 就不再包含/public字首了
// 而是隻有後半部分: /img/beyond.jpg
NSLog('第2種_以public開頭:' + request.url,false)
})
/*
第3種,路由級別的中介軟體
既嚴格匹配請求方式(get/post/put/delete等), 又嚴格匹配請求路徑的中介軟體
use有兩個引數:
引數1: 嚴格匹配的路徑
引數2: 函式(帶3個引數)
任何精準匹配路徑 並且 符合請求方式 的請求,都會被攔截下來
除非 手動呼