NodeJS实战项目(课程管理)

看完node.js从入门到实践之后,进行更深入的学习。这篇主要是一个实战小demo。涉及到项目从无到有的过程,可能相对于实际的项目来说偏不成熟点,但对于新手入门还是可以很好的梳理项目开发的流程的。

  1. 新建项目文件夹nodeDemo,在该文件夹目录下 npm init项目;
  2. 安装express 插件 npm install express --save-dev;
  3. 为了自动监听项目变化,重启服务,全局安装nodemon npm install nodemon,之前安装过的请忽略此步操作,可以执行 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官网
1.安装:npm install express-handlebars
github插件地址

2.引用并配置

//app.js
const exphbs = require('express-handlebars');//引入handlebars
//handlebars middleware
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));//设置入口文件,文件路径为views/layouts/main.handlebars
//设置模板引擎
app.set('view engine', 'handlebars');

3.在项目文件夹中新建views文件夹,再在其中新建layouts文件夹并在内部新建main.handlebars文件。该文件是此项目html入口文件,在body标签中需要{{{body}}}来引入,否则页面渲染不出来。注意,如果不定义该文件或路径错误,页面会报错。

此时的文件结构

使用公共模板
  1. 安装Bootstrap把对应的BootstrapCDN中的css以及js代码拷贝到main.handlebars文件中。
  2. 抽离公共组件
    此处以导航为例,在views文件夹中新建partials文件夹用来存放公共组件。例如在其中新建_navbar.handlebars组件后,只需要在引用的地方使用{{> _navbar}}即可引入。

注意partials文件夹的名称不能更改,内部文件命名方式可以更改,_文件名为handlebars的命名规则.
3.从路由传递参数
在app.js中修改路由配置项

//app.js
app.get('/',(req,res) => {
    const title = '大家好,我是茕茕'
    res.render('index',{title:title});
})

之后再在对应的文件中接收参数,此处以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系统安装配置mongoodb教程

创建数据模型
  1. npm install mongoose --save-dev在项目中安装mongoose
  2. 在app.js中引入并连接mongoose
const mongoose = require('mongoose');//引入mongoose

//链接数据库
//‘mongodb://localhost’为本地数据库地址,‘node-app’为此项目链接的数据库名称为自定义名称
mongoose.connect('mongodb://localhost/node-app',{useNewUrlParser:true})
    .then(() => {
        console.log('链接成功!')
    })
    .catch(err => {
        console.log(err);
    })

3.在项目根目录下新建 models 文件夹 并添加Idea.js 文件(文件名首字母大写代表数据模型):

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中引入并使用()

//链接数据库
mongoose.connect('mongodb://localhost/node-app',{useNewUrlParser:true})
    .then(() => {
        console.log('链接成功!')
    })
    .catch(err => {
        console.log(err);
    })

//引入模型
require('./models/idea');
const Idea = mongoose.model('ideas')
存储和拉取数据

在app.js中修改并运行项目添加一条记录到数据库中

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')//跳转到对应的地址
            })

    }

2.找到mongoose的文件夹,进入到 ****\MongoDB\Server\4.0\bin 文件中,使用命令行
./mongo
show dbs找到当前数据库
use node-app切换到数据库下面(node-app为数据库名)
show collections找到数据库中的列表,此时展示的为ideas数据表
db.ideas.fond()查询列表所有内容

命令行截图

3.添加并展示数据

//app.js中配置路由并传递参数
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}}
编辑页面(拉取/存储数据)
  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. 添加对应的路由
//编辑
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并设置成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')
            })
    })
})
删除数据
  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 ,用于存储内容
    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文件中引用

    <!-- 引用导航 -->
    {{> _navbar}}
    <div class="container">
        <!-- 引用信息提示 -->
        {{> _msg}}
        {{{body}}}
    </div>

6.最后在需要提示的功能出调用flash方法,第一个参数是定义的方法名,第二个参数是传递的信息,例如:

//删除
app.delete('/ideas/:id',(req,res) => {
    console.log(req.body)
    Idea.remove({_id:req.params.id})
        .then(() => {
            req.flash('success_msg','数据删除成功!');
            res.redirect('/ideas');
        })
})
抽离代码
  1. 在项目根路径下创建routes文件夹,并新建 ideas.js 和 users.js 文件
  2. 将app.js中关于课程的接口剪切到ideas.js中,并引入相关的插件以及对应的中间件
  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()第一个参数设置成‘/’时,路由中ideas的原文件不变,设置成‘/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')

//链接数据库
mongoose.connect('mongodb://localhost/node-app',{useNewUrlParser:true})//‘mongodb://localhost’为本地数据库地址,‘node-app’为此项目链接的数据库名称为自定义名称
    .then(() => {
        console.log('数据库链接成功!')
    })
    .catch((err) => {
        console.log(err);
    })

//引入模型
require('./models/Idea');
const Idea = mongoose.model('ideas')


//handlebars middleware
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));//设置入口文件,文件路径为views/layouts/main.handlebars
//设置模板引擎
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);//第一个参数设置成‘/’时,路由中ideas的原文件不变,设置成‘/ideas’时,原文件接口中的‘/ideas’可全部删除不用
app.use('/users',users);


//监听
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','数据添加成功!');
                res.redirect('/ideas')//跳转到对应的地址
            })
    }
})

//编辑
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','数据编辑成功!');
                res.redirect('/ideas')
            })
    })
})

//删除
router.delete('/:id',(req,res) => {
    console.log(req.body)
    Idea.remove({_id:req.params.id})
        .then(() => {
            req.flash('success_msg','数据删除成功!');
            res.redirect('/ideas');
        })
})
module.exports = router;
登录注册页面设计
  1. 在views文件夹中新建users文件夹并新增login.handlebars 和 register.handlebars 文件,具体代码此处省略
  2. 在根目录中新建public文件夹,再在其中新增img文件夹和css文件夹,存储样式以及图片等静态资源
  3. 在app.js中引入path并设置静态资源,使其能在node中正常使用
const path = require('path')
//使用静态文静
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文件中引用
注册页面数据存储
  1. 注册页面数据存储涉及到密码加密,所以首先需要安装密码加密插件: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) {//密码强度,回调函数
                        bcrypt.hash(newUser.password, salt,(err, hash) =>{//需要加密项,回调函数
                            if(err) throw err;

                            newUser.password = hash;//保存加密结果
                            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);

    //查询数据库
    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) {//前台录入密码,后台获取密码,回调函数
                // 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实现登录验证

使用原因:在登录之后,可以将登录状态保存并将登录注册按钮隐藏,显示登录后的列表页。但是一般的方法:封装一个全局变量,当登录后再改变这个变量的方法来处理,但这个变量的状态不能持久化。所以现在采用passport来实现。
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实例化的内容
    passport.authenticate('local', { 
        failureRedirect: '/users/login',//验证失败后跳转的页面
        successRedirect: '/ideas',//验证成功后跳转的页面
        failureFlash: true,//失败报错开启
    })(req,res,next)

    //查询数据库
    // 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) {//前台录入密码,后台获取密码,回调函数
    //             // 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);
            //查询数据库
            User.findOne({email:email})
                .then((user) => {
                    if(!user){
                        return done(null,false,{message:"没有这个用户!"});//是否传对应的内容,得到对应的user,出现错误时的提示
                    }

                    //密码验证
                    bcrypt.compare(password, user.password, function(err, isMatch) {//前台录入密码,后台获取密码,回调函数
                        // res == true
                        if(err) throw err;

                        if(isMatch){
                            //验证成功
                            return done(null,user);//是否传对应的内容,得到对应的user,出现错误时的提示
                        }else{
                            return done(null,user,{message:"密码错误!"});//是否传对应的内容,得到对应的user,出现错误时的提示
                        }
                    });
                })
        }
      ));
}
  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}}
导航守卫和退出登录

完成以上操作后,虽然我们可以在登录时作出判断,但是当验证通过后,我们不能跳转到对应的页面,并且控制台会打印错误。


报错图片
  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中添加序列化和反序列化代码,保证登录状态持久
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);
            //查询数据库
            User.findOne({email:email})
                .then((user) => {
                    if(!user){
                        return done(null,false,{message:"没有这个用户!"});//是否传对应的内容,得到对应的user,出现错误时的提示
                    }

                    //密码验证
                    bcrypt.compare(password, user.password, function(err, isMatch) {//前台录入密码,后台获取密码,回调函数
                        // res == true
                        if(err) throw err;

                        if(isMatch){
                            //验证成功
                            return done(null,user);//是否传对应的内容,得到对应的user,出现错误时的提示
                        }else{
                            return done(null,user,{message:"密码错误!"});//是否传对应的内容,得到对应的user,出现错误时的提示
                        }
                    });
                })
        }
      ));
        
      //序列化和反序列化 - 保证登录状态持久化
      passport.serializeUser(function(user, done) {
        done(null, user.id);
      });
       
      passport.deserializeUser(function(id, done) {
        User.findById(id, function (err, user) {
          done(err, user);
        });
      });
}

此时登录操作完全结束,接下来我们开始写退出登录逻辑。

  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');
    res.locals.user = req.user || null;
    next();
});
  1. 修改_navbar.handlebars文件导航右侧html结构
<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');
})

现在退出登录的功能也完善啦,接下来就是添加导航守卫,为了防止在没登录的情况下,故意更改路由直接访问其他页面。

  1. 在根路径下新建helps文件件并新建auth.js文件
//授权守卫
module.exports = {
    ensureAuthenticated:(req,res,next) => {
        if(req.isAuthenticated()){
            return next();
        }else{
            req.flash('error_msg','请先登录!');
            res.redirect('/users/login');
        }
    }
}
  1. 在需要用到导航守卫的地方进行引用,我们这里需要导航守卫的地方为ideas页面下,所以在routes/ideas中引入
const {ensureAuthenticated} = require('../helps/auth')

//在所有的get和delete接口中添加ensureAuthenticated导航守卫,如下:
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','数据删除成功!');
            res.redirect('/ideas');
        })
})
显示自己所添加的数据

到目前为止,登录注册导航守卫已经全部完成,但是不同的用户登录之后看到的列表数据完全相同,并都有权限去修改,这样的操作肯定是不合理的,接下来就让不同的用户登录后只能查看并操作自己的数据吧~

  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','数据添加成功!');
                res.redirect('/ideas')//跳转到对应的地址
            })
    }
})
项目打磨
  1. heroku 注册账号
  2. 微调项目代码
//package.js  修改启动命令
"scripts": {
  "start": "node app.js"
  },
//app.js 配置端口号
const port = process.env.PORT || 5000;
  1. 配合数据库
    config文件夹中新建database.js
//配置数据库
if(process.env.NODE_ENV == 'production'){
    //生产环境
    module.exports = {
        mongoURL:'线上数据库地址'
    }
}else{
    //开发环境
    module.exports = {
        mongoURL:'mongodb://localhost/node-app'
    }
}

app.js 中引用

const db = require('./config/database')

//链接数据库
mongoose.connect(db.mongoURL,{useNewUrlParser:true})//‘mongodb://localhost’为本地数据库地址,‘node-app’为此项目链接的数据库名称为自定义名称
    .then(() => {
        console.log('数据库链接成功!')
    })
    .catch((err) => {
        console.log(err);
    })
课程总结及引导

1.在根目录中新建.gitignore文件配置忽略文件
之后就是部署上线了。但是由于不明原因 heroku 无法登录注册,以下步骤就省略吧。有兴趣的可以自己尝试其他部署。
heroku-cli

源代码链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容