NodeJS實戰(zhàn)項目(課程管理)

看完node.js從入門到實踐之后,進行更深入的學習。這篇主要是一個實戰(zhàn)小demo。涉及到項目從無到有的過程,可能相對于實際的項目來說偏不成熟點,但對于新手入門還是可以很好的梳理項目開發(fā)的流程的。

  1. 新建項目文件夾nodeDemo,在該文件夾目錄下 npm init項目;
  2. 安裝express 插件 npm install express --save-dev;
  3. 為了自動監(jiān)聽項目變化,重啟服務(wù),全局安裝nodemon npm install nodemon,之前安裝過的請忽略此步操作,可以執(zhí)行 npm root -g來獲取全局安裝的插件路徑看看之前有無安裝過
  4. 新建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}}}來引入,否則頁面渲染不出來。注意,如果不定義該文件或路徑錯誤,頁面會報錯。

此時的文件結(jié)構(gòu)

使用公共模板
  1. 安裝Bootstrap把對應(yīng)的BootstrapCDN中的css以及js代碼拷貝到main.handlebars文件中。
  2. 抽離公共組件
    此處以導(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

windows系統(tǒng)安裝配置mongoodb教程

創(chuàng)建數(shù)據(jù)模型
  1. npm install mongoose --save-dev在項目中安裝mongoose
  2. 在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ù))
  1. 修改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}}
  1. 添加對應(yīng)的路由
//編輯
app.get('/ideas/edit/:id', (req, res) => {
    Idea.findOne({_id:req.params.id})
        .then(idea => {
            res.render('ideas/edit',{
                idea:idea
            });
        })
})
  1. 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>
  1. 安裝method-override 允許您在客戶端不支持它的地方使用HTTP動詞,如PUT或DELETE。
    命令行 : npm install method-override
    method-override文檔
  2. 引用 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ù)
  1. 修改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}}
  1. 修改app.js,添加刪除功能:
//刪除
app.delete('/ideas/:id',(req,res) => {
    console.log(req.body)
    Idea.remove({_id:req.params.id})
        .then(() => {
            res.redirect('/ideas');
        })
})
對用戶的操作進行提醒
  1. 安裝 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,
  }))
  1. 安裝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');
        })
})
抽離代碼
  1. 在項目根路徑下創(chuàng)建routes文件夾,并新建 ideas.js 和 users.js 文件
  2. 將app.js中關(guān)于課程的接口剪切到ideas.js中,并引入相關(guān)的插件以及對應(yīng)的中間件
  3. 在ideas.js定義const router = express.Router();,并替換接口方法中app為router
  4. 最后module.exports = router;
  5. 在app.js中加載ideas.js路由const ideas = require('./routes/ideas'),并使用app.use('/ideas',ideas);
    app.use()第一個參數(shù)設(shè)置成‘/’時,路由中ideas的原文件不變,設(shè)置成‘/ideas’時,原文件接口中的‘/ideas’可全部刪除
  6. 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è)計
  1. 在views文件夾中新建users文件夾并新增login.handlebars 和 register.handlebars 文件,具體代碼此處省略
  2. 在根目錄中新建public文件夾,再在其中新增img文件夾和css文件夾,存儲樣式以及圖片等靜態(tài)資源
  3. 在app.js中引入path并設(shè)置靜態(tài)資源,使其能在node中正常使用
const path = require('path')
//使用靜態(tài)文靜
app.use(express.static(path.join(__dirname,'public')));
  1. 最后在main.handlebars文件中引入css樣式表<link rel="stylesheet" href="/css/style.css">
注冊頁面驗證
  1. 在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('驗證成功!')
    }
})
  1. 將報錯信息組件化并在main.handlebars文件中引用
注冊頁面數(shù)據(jù)存儲
  1. 注冊頁面數(shù)據(jù)存儲涉及到密碼加密,所以首先需要安裝密碼加密插件:npm install bcrypt
  2. 在users.js中引入const bcrypt = require('bcrypt');
  3. 使用
//注冊
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

  1. 在app.js中引用
const passport = require('passport');
require('./config/passport')(passport);
  1. 修改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');
    //             }
    //         });
    //     })
})
  1. 在根目錄中新建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)錯誤時的提示
                        }
                    });
                })
        }
      ));
}
  1. 最后新增一個全局變量來顯示錯誤信息,在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();
});
  1. 修改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)的頁面,并且控制臺會打印錯誤。


報錯圖片
  1. 在app.js 中session 中間件下面引用,注意一定要在session 中間件下面,不然會報錯
//session 中間件
app.use(session({
    secret: 'secret',//秘鑰,自定義
    resave: true,
    saveUninitialized: true,
  }))

app.use(passport.initialize());
app.use(passport.session());
  1. 在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é)束,接下來我們開始寫退出登錄邏輯。

  1. 先在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();
});
  1. 修改_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>
  1. 在routes/users.js中添加退出功能
//退出登錄
router.get('/logout',(req,res) => {
    req.logout();
    req.flash('success_msg','退出成功!');
    res.redirect('/users/login');
})

現(xiàn)在退出登錄的功能也完善啦,接下來就是添加導(dǎo)航守衛(wèi),為了防止在沒登錄的情況下,故意更改路由直接訪問其他頁面。

  1. 在根路徑下新建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');
        }
    }
}
  1. 在需要用到導(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ù)吧~

  1. 在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
    },
})
  1. 在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)的地址
            })
    }
})
項目打磨
  1. heroku 注冊賬號
  2. 微調(diào)項目代碼
//package.js  修改啟動命令
"scripts": {
  "start": "node app.js"
  },
//app.js 配置端口號
const port = process.env.PORT || 5000;
  1. 配合數(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

源代碼鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容