美文网首页
NodeJS实战项目(课程管理)

NodeJS实战项目(课程管理)

作者: 茕茕微凉 | 来源:发表于2019-05-29 11:20 被阅读0次

    看完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

    源代码链接

    相关文章

      网友评论

          本文标题:NodeJS实战项目(课程管理)

          本文链接:https://www.haomeiwen.com/subject/vvrkoqtx.html