看完node.js從入門到實踐之后,進行更深入的學習。這篇主要是一個實戰(zhàn)小demo。涉及到項目從無到有的過程,可能相對于實際的項目來說偏不成熟點,但對于新手入門還是可以很好的梳理項目開發(fā)的流程的。
- 新建項目文件夾nodeDemo,在該文件夾目錄下
npm init項目; - 安裝express 插件
npm install express --save-dev; - 為了自動監(jiān)聽項目變化,重啟服務(wù),全局安裝nodemon
npm install nodemon,之前安裝過的請忽略此步操作,可以執(zhí)行npm root -g來獲取全局安裝的插件路徑看看之前有無安裝過 - 新建app.js
const express = require('express');
const app = express();
const port = 5000;
//配置路由
app.get('/',(req,res) => {
res.send('index');
})
app.get('/about', (req, res) => {
res.send('about');
})
app.listen(port,() =>{
console.log(`Server started on ${port}`);
});
模板引擎handlebars
handlebars官網(wǎng)
1.安裝:npm install express-handlebars
github插件地址
2.引用并配置
//app.js
const exphbs = require('express-handlebars');//引入handlebars
//handlebars middleware
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));//設(shè)置入口文件,文件路徑為views/layouts/main.handlebars
//設(shè)置模板引擎
app.set('view engine', 'handlebars');
3.在項目文件夾中新建views文件夾,再在其中新建layouts文件夾并在內(nèi)部新建main.handlebars文件。該文件是此項目html入口文件,在body標簽中需要{{{body}}}來引入,否則頁面渲染不出來。注意,如果不定義該文件或路徑錯誤,頁面會報錯。

使用公共模板
- 安裝Bootstrap把對應(yīng)的BootstrapCDN中的css以及js代碼拷貝到main.handlebars文件中。
- 抽離公共組件
此處以導(dǎo)航為例,在views文件夾中新建partials文件夾用來存放公共組件。例如在其中新建_navbar.handlebars組件后,只需要在引用的地方使用{{> _navbar}}即可引入。
注意partials文件夾的名稱不能更改,內(nèi)部文件命名方式可以更改,_文件名為handlebars的命名規(guī)則.
3.從路由傳遞參數(shù)
在app.js中修改路由配置項
//app.js
app.get('/',(req,res) => {
const title = '大家好,我是煢煢'
res.render('index',{title:title});
})
之后再在對應(yīng)的文件中接收參數(shù),此處以index.handlebars為例
//index.handlebars
<h1 class="display-3">{{title}}</h1>
前端添加頁面&后端錯誤驗證
1.express 版本在4以上是,不需要安裝body-parser,直接引入var bodyParser = require('body-parser')即可運用,否則需要npm install body-parser。
//app.js
const bodyParser = require('body-parser');
//body-parser 中間件
var jsonParser = bodyParser.json()
var urlencodedParser = bodyParser.urlencoded({ extended: false })
2.添加表單頁面,在views文件夾中新建ideas文件夾并新增add.handlebars文件
//add.handlebars
{{#each errors}}
<div class="alert alert-danger">{{text}}</div>
{{else}}
{{/each}}
<div class="card card-body">
<h3>想學的課程</h3>
<form action="/ideas" method="post">
<div class="form-group">
<label for="title">標題</label>
<input type="text" class="form-control" name="title" value="{{title}}">
</div>
<div class="form-group">
<label for="details">詳情</label>
<textarea class="form-control" name="details">{{details}}</textarea>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
3.修改app.js文件
app.get('/ideas/add', (req, res) => {
res.render('ideas/add');
})
app.post('/ideas',urlencodedParser,(req, res) => {
// console.log(req.body);
let errors = [];
if(!req.body.title){
errors.push({text:'請輸入標題!'})
}
if(!req.body.details){
errors.push({text:'請輸入詳情!'})
}
if(errors.length > 0){
res.render('ideas/add',{errors:errors,title:req.body.title,details:req.body.details});
}else{
res.send('ok');
}
})
安裝mongoodb
創(chuàng)建數(shù)據(jù)模型
-
npm install mongoose --save-dev在項目中安裝mongoose - 在app.js中引入并連接mongoose
const mongoose = require('mongoose');//引入mongoose
//鏈接數(shù)據(jù)庫
//‘mongodb://localhost’為本地數(shù)據(jù)庫地址,‘node-app’為此項目鏈接的數(shù)據(jù)庫名稱為自定義名稱
mongoose.connect('mongodb://localhost/node-app',{useNewUrlParser:true})
.then(() => {
console.log('鏈接成功!')
})
.catch(err => {
console.log(err);
})
3.在項目根目錄下新建 models 文件夾 并添加Idea.js 文件(文件名首字母大寫代表數(shù)據(jù)模型):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const IdeaSchema = new Schema({
title: {
type:String,
require:true
},
details: {
type:String,
require:true
},
date: {
type:Date,
default:Date.now
},
})
mongoose.model('ideas',IdeaSchema);//將IdeaSchema放到模型中
4.在app.js中引入并使用()
//鏈接數(shù)據(jù)庫
mongoose.connect('mongodb://localhost/node-app',{useNewUrlParser:true})
.then(() => {
console.log('鏈接成功!')
})
.catch(err => {
console.log(err);
})
//引入模型
require('./models/idea');
const Idea = mongoose.model('ideas')
存儲和拉取數(shù)據(jù)
在app.js中修改并運行項目添加一條記錄到數(shù)據(jù)庫中
app.post('/ideas',urlencodedParser, (req, res) => {
console.log(req.body);
let errors = [];
if(!req.body.title){
errors.push({text:'請輸入標題!'});
}
if (!req.body.details) {
errors.push({ text: '請輸入詳情!' });
}
if (errors.length > 0){
res.render('ideas/add',{
errors:errors,
title: req.body.title,
details: req.body.details
});
}else{
// res.send('ok')
const newUser = {
title: req.body.title,
details: req.body.details
}
new Idea(newUser).save()
.then(idea => {
res.redirect('/ideas')//跳轉(zhuǎn)到對應(yīng)的地址
})
}
2.找到mongoose的文件夾,進入到 ****\MongoDB\Server\4.0\bin 文件中,使用命令行
./mongo
show dbs找到當前數(shù)據(jù)庫
use node-app切換到數(shù)據(jù)庫下面(node-app為數(shù)據(jù)庫名)
show collections找到數(shù)據(jù)庫中的列表,此時展示的為ideas數(shù)據(jù)表
db.ideas.fond()查詢列表所有內(nèi)容

3.添加并展示數(shù)據(jù)
//app.js中配置路由并傳遞參數(shù)
app.get('/ideas', (req, res) => {
Idea.find({})
.sort({date:'desc'})//降序排列
.then(ideas => {
res.render('ideas/index',{ideas:ideas});
})
})
在ideas文件夾中新建index.handlebars文件:
{{#each ideas}}
<div class="card card-body mb-2">
<h3>{{title}}</h3>
<p>{{details}}</p>
</div>
{{else}}
<p>還沒有任何想學的課程</p>
{{/each}}
編輯頁面(拉取/存儲數(shù)據(jù))
- 修改ideas文件夾中新建index.handlebars文件:
{{#each ideas}}
<div class="card card-body mb-2">
<h3>{{title}}</h3>
<p>{{details}}</p>
<a class="btn btn-dark btn-block" href="/ideas/edit/{{id}}">編輯</a>
</div>
{{else}}
<p>還沒有任何想學的課程</p>
{{/each}}
- 添加對應(yīng)的路由
//編輯
app.get('/ideas/edit/:id', (req, res) => {
Idea.findOne({_id:req.params.id})
.then(idea => {
res.render('ideas/edit',{
idea:idea
});
})
})
- ideas文件夾中新建edit.handlebars文件
{{#each errors}}
<div class="alert alert-danger">{{text}}</div>
{{else}}
{{/each}}
<div class="card card-body">
<h3>想學的課程</h3>
<form action="/ideas" method="post">
<div class="form-group">
<label for="title">標題</label>
<input type="text" class="form-control" name="title" value="{{idea.title}}">
</div>
<div class="form-group">
<label for="details">詳情</label>
<textarea class="form-control" name="details">{{idea.details}}</textarea>
</div>
<button type="submit" class="btn btn-primary">編輯</button>
</form>
</div>
- 安裝method-override 允許您在客戶端不支持它的地方使用HTTP動詞,如PUT或DELETE。
命令行 :npm install method-override
method-override文檔 - 引用 method-override
//app.js
const methodOverride = require('method-override');
//method-override 中間件
app.use(methodOverride('_method'))
在edit.handlebars文件使用,修改form標簽action的屬性以及添加一個隱藏的input并設(shè)置成put提交形式
{{#each errors}}
<div class="alert alert-danger">{{text}}</div>
{{else}}
{{/each}}
<div class="card card-body">
<h3>想學的課程</h3>
<form action="/ideas/{{idea.id}}?_method=PUT" method="post">
<input type="hidden" name="_method" value="PUT">
<div class="form-group">
<label for="title">標題</label>
<input type="text" class="form-control" name="title" value="{{idea.title}}">
</div>
<div class="form-group">
<label for="details">詳情</label>
<textarea class="form-control" name="details">{{idea.details}}</textarea>
</div>
<button type="submit" class="btn btn-primary">編輯</button>
</form>
</div>
6.添加編輯接口
//app.js
//編輯
app.put('/ideas/:id',urlencodedParser,(req,res) => {
// res.send('PUT');
Idea.findOne({
_id:req.params.id
})
.then(idea => {
idea.title = req.body.title;
idea.details = req.body.details;
idea.save()
.then(() => {
res.redirect('/ideas')
})
})
})
刪除數(shù)據(jù)
- 修改index.handlebars文件,添加刪除按鈕:
//index.handlebars
{{#each ideas}}
<div class="card card-body mb-2">
<h3>{{title}}</h3>
<p>{{details}}</p>
<a class="btn btn-dark btn-block" href="/ideas/edit/{{id}}">編輯</a>
<form action="/ideas/{{id}}?_method=DELETE" method="POST">
<input type="hidden" name="method" value="DELETE" />
<input type="submit" class="btn btn-danger btn-block" value="刪除"/>
</form>
</div>
{{else}}
<p>還沒有任何想學的課程</p>
{{/each}}
- 修改app.js,添加刪除功能:
//刪除
app.delete('/ideas/:id',(req,res) => {
console.log(req.body)
Idea.remove({_id:req.params.id})
.then(() => {
res.redirect('/ideas');
})
})
對用戶的操作進行提醒
- 安裝 express-session ,用于存儲內(nèi)容
npm install express-session
//app.js
const session = require('express-session');
//express-session 中間件
app.use(session({
secret: 'secret',//秘鑰,自定義
resave: true,
saveUninitialized: true,
}))
- 安裝connect-flash
npm install connect-flash
//app.js
const flash = require('connect-flash');
//flash 中間件
app.use(flash());
3.配置全局變量
//app.js
//配置全局變量
app.use((req,res,next) => {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
next();
});
4.在partials 文件夾中新建文件msg.handlebars文件編寫信息提示
{{#if success_msg}}
<div class="alert alert-success">{{success_msg}}</div>
{{/if}}
{{#if error_msg}}
<div class="alert alert-danger">{{error_msg}}</div>
{{/if}}
5.在main.handlebars文件中引用
<!-- 引用導(dǎo)航 -->
{{> _navbar}}
<div class="container">
<!-- 引用信息提示 -->
{{> _msg}}
{{{body}}}
</div>
6.最后在需要提示的功能出調(diào)用flash方法,第一個參數(shù)是定義的方法名,第二個參數(shù)是傳遞的信息,例如:
//刪除
app.delete('/ideas/:id',(req,res) => {
console.log(req.body)
Idea.remove({_id:req.params.id})
.then(() => {
req.flash('success_msg','數(shù)據(jù)刪除成功!');
res.redirect('/ideas');
})
})
抽離代碼
- 在項目根路徑下創(chuàng)建routes文件夾,并新建 ideas.js 和 users.js 文件
- 將app.js中關(guān)于課程的接口剪切到ideas.js中,并引入相關(guān)的插件以及對應(yīng)的中間件
- 在ideas.js定義
const router = express.Router();,并替換接口方法中app為router - 最后
module.exports = router; - 在app.js中加載ideas.js路由
const ideas = require('./routes/ideas'),并使用app.use('/ideas',ideas);
app.use()第一個參數(shù)設(shè)置成‘/’時,路由中ideas的原文件不變,設(shè)置成‘/ideas’時,原文件接口中的‘/ideas’可全部刪除 - users.js 模塊抽離亦然。
//app.js
const express = require('express');
const exphbs = require('express-handlebars');//引入handlebars
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const methodOverride = require('method-override');
const session = require('express-session');
const flash = require('connect-flash');
const app = express();
//加載路由
const ideas = require('./routes/ideas')
const users = require('./routes/users')
//鏈接數(shù)據(jù)庫
mongoose.connect('mongodb://localhost/node-app',{useNewUrlParser:true})//‘mongodb://localhost’為本地數(shù)據(jù)庫地址,‘node-app’為此項目鏈接的數(shù)據(jù)庫名稱為自定義名稱
.then(() => {
console.log('數(shù)據(jù)庫鏈接成功!')
})
.catch((err) => {
console.log(err);
})
//引入模型
require('./models/Idea');
const Idea = mongoose.model('ideas')
//handlebars middleware
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));//設(shè)置入口文件,文件路徑為views/layouts/main.handlebars
//設(shè)置模板引擎
app.set('view engine', 'handlebars');
//body-parser 中間件
var jsonParser = bodyParser.json()
var urlencodedParser = bodyParser.urlencoded({ extended: false })
//method-override 中間件
app.use(methodOverride('_method'));
//session 中間件
app.use(session({
secret: 'secret',//秘鑰,自定義
resave: true,
saveUninitialized: true,
}))
//flash 中間件
app.use(flash());
//配置全局變量
app.use((req,res,next) => {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
next();
});
//配置路由
app.get('/',(req,res) => {
const title = '大家好,我是煢煢'
res.render('index',{title:title});
})
app.get('/about', (req, res) => {
res.render('about');
})
app.get('/ideas/add', (req, res) => {
res.render('ideas/add');
})
//使用routes
app.use('/ideas',ideas);//第一個參數(shù)設(shè)置成‘/’時,路由中ideas的原文件不變,設(shè)置成‘/ideas’時,原文件接口中的‘/ideas’可全部刪除不用
app.use('/users',users);
//監(jiān)聽
const port = 5000;
app.listen(port,() =>{
console.log(`Server started on ${port}`);
});
//ideas.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const router = express.Router();
//引入模型
require('../models/Idea');
const Idea = mongoose.model('ideas')
//body-parser 中間件
var jsonParser = bodyParser.json()
var urlencodedParser = bodyParser.urlencoded({ extended: false })
router.get('/', (req, res) => {
Idea.find({})
.sort({date:'desc'})//降序排列
.then(ideas => {
res.render('ideas/index',{ideas:ideas});
})
})
//編輯
router.get('/edit/:id', (req, res) => {
Idea.findOne({_id:req.params.id})
.then(idea => {
res.render('ideas/edit',{
idea:idea
});
})
})
//添加
router.post('/',urlencodedParser,(req, res) => {
// console.log(req.body);
let errors = [];
if(!req.body.title){
errors.push({text:'請輸入標題!'})
}
if(!req.body.details){
errors.push({text:'請輸入詳情!'})
}
if(errors.length > 0){
res.render('ideas/add',{errors:errors,title:req.body.title,details:req.body.details});
}else{
// res.send('ok');
const newUser = {
title: req.body.title,
details: req.body.details
}
new Idea(newUser).save()
.then(idea => {
req.flash('success_msg','數(shù)據(jù)添加成功!');
res.redirect('/ideas')//跳轉(zhuǎn)到對應(yīng)的地址
})
}
})
//編輯
router.put('/:id',urlencodedParser,(req,res) => {
// res.send('PUT');
Idea.findOne({
_id:req.params.id
})
.then(idea => {
idea.title = req.body.title;
idea.details = req.body.details;
idea.save()
.then(() => {
req.flash('success_msg','數(shù)據(jù)編輯成功!');
res.redirect('/ideas')
})
})
})
//刪除
router.delete('/:id',(req,res) => {
console.log(req.body)
Idea.remove({_id:req.params.id})
.then(() => {
req.flash('success_msg','數(shù)據(jù)刪除成功!');
res.redirect('/ideas');
})
})
module.exports = router;
登錄注冊頁面設(shè)計
- 在views文件夾中新建users文件夾并新增login.handlebars 和 register.handlebars 文件,具體代碼此處省略
- 在根目錄中新建public文件夾,再在其中新增img文件夾和css文件夾,存儲樣式以及圖片等靜態(tài)資源
- 在app.js中引入path并設(shè)置靜態(tài)資源,使其能在node中正常使用
const path = require('path')
//使用靜態(tài)文靜
app.use(express.static(path.join(__dirname,'public')));
- 最后在main.handlebars文件中引入css樣式表
<link rel="stylesheet" href="/css/style.css">
注冊頁面驗證
- 在users.js中完成驗證
router.post('/register',urlencodedParser,(req,res) => {
// console.log(req.body);
// res.send('注冊')
let errors = [];
if(req.body.password != req.body.password2){
errors.push({text:'兩次密碼不一致!'})
}
if(req.body.password.length < 4){
errors.push({text:'密碼長度不能小于4!'})
}
if(errors.length > 0){
//有誤
res.render('users/register',{
errors:errors,
name:req.body.name,
email:req.body.email,
password:req.body.password,
password2:req.body.password2
})
}else{
res.send('驗證成功!')
}
})
- 將報錯信息組件化并在main.handlebars文件中引用
注冊頁面數(shù)據(jù)存儲
- 注冊頁面數(shù)據(jù)存儲涉及到密碼加密,所以首先需要安裝密碼加密插件:
npm install bcrypt - 在users.js中引入
const bcrypt = require('bcrypt'); - 使用
//注冊
router.post('/register',urlencodedParser,(req,res) => {
// console.log(req.body);
// res.send('注冊')
let errors = [];
if(req.body.password != req.body.password2){
errors.push({text:'兩次密碼不一致!'})
}
if(req.body.password.length < 4){
errors.push({text:'密碼長度不能小于4!'})
}
if(errors.length > 0){
//有誤
res.render('users/register',{
errors:errors,
name:req.body.name,
email:req.body.email,
password:req.body.password,
password2:req.body.password2
})
}else{
// res.send('驗證成功!')
//是否已注冊
User.findOne({email:req.body.email})
.then((user) => {
if(user){
req.flash('error_msg','郵箱已存在,請更換郵箱注冊!');
res.redirect('/users/register');
}else{
let newUser = new User({
name:req.body.name,
email:req.body.email,
password:req.body.password,
})
bcrypt.genSalt(10, function(err, salt) {//密碼強度,回調(diào)函數(shù)
bcrypt.hash(newUser.password, salt,(err, hash) =>{//需要加密項,回調(diào)函數(shù)
if(err) throw err;
newUser.password = hash;//保存加密結(jié)果
newUser.save()
.then( (user) => {
req.flash('success_msg','賬號注冊成功!');
res.redirect('/users/login');
})
.catch( (err) => {
req.flash('error_msg','賬號注冊失??!');
res.redirect('/users/register');
})
});
});
}
})
}
})
登錄驗證
主要驗證代碼如下:
//登錄
router.post('/login',urlencodedParser,(req,res) => {
// console.log(req.body);
//查詢數(shù)據(jù)庫
User.findOne({email:req.body.email})
.then((user) => {
if(!user){
req.flash('error_msg','用戶不存在!')
res.redirect('/users/login');
return false;
}
//密碼驗證
bcrypt.compare(req.body.password, user.password, function(err, isMatch) {//前臺錄入密碼,后臺獲取密碼,回調(diào)函數(shù)
// res == true
if(err) throw err;
if(isMatch){
//驗證成功
req.flash('success_msg','登錄成功!');
res.redirect('/ideas');
}else{
req.flash('error_msg','密碼錯誤!')
res.redirect('/users/login');
}
});
})
})
使用passport實現(xiàn)登錄驗證
使用原因:在登錄之后,可以將登錄狀態(tài)保存并將登錄注冊按鈕隱藏,顯示登錄后的列表頁。但是一般的方法:封裝一個全局變量,當?shù)卿浐笤俑淖冞@個變量的方法來處理,但這個變量的狀態(tài)不能持久化。所以現(xiàn)在采用passport來實現(xiàn)。
1.安裝npm install passport-local npm install passport
- 在app.js中引用
const passport = require('passport');
require('./config/passport')(passport);
- 修改routes/users.js登錄功能代碼
//登錄
router.post('/login',urlencodedParser,(req,res,next) => {
//‘local’為passport實例化的內(nèi)容
passport.authenticate('local', {
failureRedirect: '/users/login',//驗證失敗后跳轉(zhuǎn)的頁面
successRedirect: '/ideas',//驗證成功后跳轉(zhuǎn)的頁面
failureFlash: true,//失敗報錯開啟
})(req,res,next)
//查詢數(shù)據(jù)庫
// User.findOne({email:req.body.email})
// .then((user) => {
// if(!user){
// req.flash('error_msg','用戶不存在!')
// res.redirect('/users/login');
// return false;
// }
// //密碼驗證
// bcrypt.compare(req.body.password, user.password, function(err, isMatch) {//前臺錄入密碼,后臺獲取密碼,回調(diào)函數(shù)
// // res == true
// if(err) throw err;
// if(isMatch){
// //驗證成功
// req.flash('success_msg','登錄成功!');
// res.redirect('/ideas');
// }else{
// req.flash('error_msg','密碼錯誤!')
// res.redirect('/users/login');
// }
// });
// })
})
- 在根目錄中新建config 文件夾并新建passport.js
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
//加載model
require('../models/Users')
const User = mongoose.model('users');
module.exports = (passport) => {
passport.use(new LocalStrategy(
{usernameField:'email'},//驗證的對象
(email,password,done) => {
// console.log(email,password);
//查詢數(shù)據(jù)庫
User.findOne({email:email})
.then((user) => {
if(!user){
return done(null,false,{message:"沒有這個用戶!"});//是否傳對應(yīng)的內(nèi)容,得到對應(yīng)的user,出現(xiàn)錯誤時的提示
}
//密碼驗證
bcrypt.compare(password, user.password, function(err, isMatch) {//前臺錄入密碼,后臺獲取密碼,回調(diào)函數(shù)
// res == true
if(err) throw err;
if(isMatch){
//驗證成功
return done(null,user);//是否傳對應(yīng)的內(nèi)容,得到對應(yīng)的user,出現(xiàn)錯誤時的提示
}else{
return done(null,user,{message:"密碼錯誤!"});//是否傳對應(yīng)的內(nèi)容,得到對應(yīng)的user,出現(xiàn)錯誤時的提示
}
});
})
}
));
}
- 最后新增一個全局變量來顯示錯誤信息,在app.js中修改全局變量
//配置全局變量
app.use((req,res,next) => {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
- 修改views/partials/_errors.handlebars文件
{{#if error}}
<div class="alert alert-danger">{{error}}</div>
{{/if}}
{{#each errors}}
<div class="alert alert-danger">{{text}}</div>
{{else}}
{{/each}}
導(dǎo)航守衛(wèi)和退出登錄
完成以上操作后,雖然我們可以在登錄時作出判斷,但是當驗證通過后,我們不能跳轉(zhuǎn)到對應(yīng)的頁面,并且控制臺會打印錯誤。

- 在app.js 中session 中間件下面引用,注意一定要在session 中間件下面,不然會報錯
//session 中間件
app.use(session({
secret: 'secret',//秘鑰,自定義
resave: true,
saveUninitialized: true,
}))
app.use(passport.initialize());
app.use(passport.session());
- 在passport.js中添加序列化和反序列化代碼,保證登錄狀態(tài)持久
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
//加載model
require('../models/Users')
const User = mongoose.model('users');
module.exports = (passport) => {
passport.use(new LocalStrategy(
{usernameField:'email'},//驗證的對象
(email,password,done) => {
// console.log(email,password);
//查詢數(shù)據(jù)庫
User.findOne({email:email})
.then((user) => {
if(!user){
return done(null,false,{message:"沒有這個用戶!"});//是否傳對應(yīng)的內(nèi)容,得到對應(yīng)的user,出現(xiàn)錯誤時的提示
}
//密碼驗證
bcrypt.compare(password, user.password, function(err, isMatch) {//前臺錄入密碼,后臺獲取密碼,回調(diào)函數(shù)
// res == true
if(err) throw err;
if(isMatch){
//驗證成功
return done(null,user);//是否傳對應(yīng)的內(nèi)容,得到對應(yīng)的user,出現(xiàn)錯誤時的提示
}else{
return done(null,user,{message:"密碼錯誤!"});//是否傳對應(yīng)的內(nèi)容,得到對應(yīng)的user,出現(xiàn)錯誤時的提示
}
});
})
}
));
//序列化和反序列化 - 保證登錄狀態(tài)持久化
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
}
此時登錄操作完全結(jié)束,接下來我們開始寫退出登錄邏輯。
- 先在app.js中添加一個全局變量,來控制導(dǎo)航上登錄注冊按鈕的顯示狀態(tài)
//配置全局變量
app.use((req,res,next) => {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
res.locals.user = req.user || null;
next();
});
- 修改_navbar.handlebars文件導(dǎo)航右側(cè)html結(jié)構(gòu)
<ul class="navbar-nav ml-auto">
{{#if user}}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" id="navbarDropdownMenuLink">想學的課程</a>
<div class="dropdown-menu">
<a href="/ideas" class="dropdown-item">Idea</a>
<a href="/ideas/add" class="dropdown-item">添加</a>
</div>
</li>
<li class="nav-item">
<a href="/users/logout" class="nav-link">退出</a>
</li>
{{else}}
<li class="nav-item">
<a href="/users/login" class="nav-link">登錄</a>
</li>
<li class="nav-item">
<a href="/users/register" class="nav-link">注冊</a>
</li>
{{/if}}
</ul>
- 在routes/users.js中添加退出功能
//退出登錄
router.get('/logout',(req,res) => {
req.logout();
req.flash('success_msg','退出成功!');
res.redirect('/users/login');
})
現(xiàn)在退出登錄的功能也完善啦,接下來就是添加導(dǎo)航守衛(wèi),為了防止在沒登錄的情況下,故意更改路由直接訪問其他頁面。
- 在根路徑下新建helps文件件并新建auth.js文件
//授權(quán)守衛(wèi)
module.exports = {
ensureAuthenticated:(req,res,next) => {
if(req.isAuthenticated()){
return next();
}else{
req.flash('error_msg','請先登錄!');
res.redirect('/users/login');
}
}
}
- 在需要用到導(dǎo)航守衛(wèi)的地方進行引用,我們這里需要導(dǎo)航守衛(wèi)的地方為ideas頁面下,所以在routes/ideas中引入
const {ensureAuthenticated} = require('../helps/auth')
//在所有的get和delete接口中添加ensureAuthenticated導(dǎo)航守衛(wèi),如下:
router.get('/add',ensureAuthenticated,(req, res) => {
res.render('ideas/add');
})
//刪除
router.delete('/:id',ensureAuthenticated,(req,res) => {
console.log(req.body)
Idea.remove({_id:req.params.id})
.then(() => {
req.flash('success_msg','數(shù)據(jù)刪除成功!');
res.redirect('/ideas');
})
})
顯示自己所添加的數(shù)據(jù)
到目前為止,登錄注冊導(dǎo)航守衛(wèi)已經(jīng)全部完成,但是不同的用戶登錄之后看到的列表數(shù)據(jù)完全相同,并都有權(quán)限去修改,這樣的操作肯定是不合理的,接下來就讓不同的用戶登錄后只能查看并操作自己的數(shù)據(jù)吧~
- 在models/idea.js中添加user字段
const IdeaSchema = new Schema({
title: {
type:String,
require:true
},
user:{
type:String,
require:true
},
details: {
type:String,
require:true
},
date: {
type:Date,
default:Date.now
},
})
- 在routes/ideas中修改
router.get('/',ensureAuthenticated, (req, res) => {
Idea.find({user:req.user.id})
.sort({date:'desc'})//降序排列
.then(ideas => {
res.render('ideas/index',{ideas:ideas});
})
})
//編輯
router.get('/edit/:id',ensureAuthenticated,(req, res) => {
Idea.findOne({_id:req.params.id})
.then(idea => {
if(idea.user != req.user.id){
req.flash('error_mag','非法操作!');
res.redirect('/ideas');
}else{
res.render('ideas/edit',{
idea:idea
});
}
})
})
//添加
router.post('/',urlencodedParser,(req, res) => {
// console.log(req.body);
let errors = [];
if(!req.body.title){
errors.push({text:'請輸入標題!'})
}
if(!req.body.details){
errors.push({text:'請輸入詳情!'})
}
if(errors.length > 0){
res.render('ideas/add',{errors:errors,title:req.body.title,details:req.body.details});
}else{
// res.send('ok');
const newUser = {
title: req.body.title,
details: req.body.details,
user:req.user.id
}
new Idea(newUser).save()
.then(idea => {
req.flash('success_msg','數(shù)據(jù)添加成功!');
res.redirect('/ideas')//跳轉(zhuǎn)到對應(yīng)的地址
})
}
})
項目打磨
- heroku 注冊賬號
- 微調(diào)項目代碼
//package.js 修改啟動命令
"scripts": {
"start": "node app.js"
},
//app.js 配置端口號
const port = process.env.PORT || 5000;
- 配合數(shù)據(jù)庫
config文件夾中新建database.js
//配置數(shù)據(jù)庫
if(process.env.NODE_ENV == 'production'){
//生產(chǎn)環(huán)境
module.exports = {
mongoURL:'線上數(shù)據(jù)庫地址'
}
}else{
//開發(fā)環(huán)境
module.exports = {
mongoURL:'mongodb://localhost/node-app'
}
}
app.js 中引用
const db = require('./config/database')
//鏈接數(shù)據(jù)庫
mongoose.connect(db.mongoURL,{useNewUrlParser:true})//‘mongodb://localhost’為本地數(shù)據(jù)庫地址,‘node-app’為此項目鏈接的數(shù)據(jù)庫名稱為自定義名稱
.then(() => {
console.log('數(shù)據(jù)庫鏈接成功!')
})
.catch((err) => {
console.log(err);
})
課程總結(jié)及引導(dǎo)
1.在根目錄中新建.gitignore文件配置忽略文件
之后就是部署上線了。但是由于不明原因 heroku 無法登錄注冊,以下步驟就省略吧。有興趣的可以自己嘗試其他部署。
heroku-cli