美文网首页
课程管理系统

课程管理系统

作者: 海因斯坦 | 来源:发表于2018-11-22 17:36 被阅读0次

    使用node实现简单的增删改查

    一.handlebars模板引擎的使用

    handlebars的安装

    npm i express
    npm i express-hndlebars
    

    handlebars的使用
    模板引擎的目录结构,必须如下图所示:
    文件夹名称必须是views,views目录下必须有一个layouts文件夹,layouts文件夹下有一个handlebars文件,作为模板渲染的主文件,所有的其他handlebars都会渲染到这个文件中。

    |---app.js
    |---views
        |---layouts
            |---main.handlebars
        |---index.handlebars
    

    app.js中设置模板引擎:

    const express = require('express');
    const exphbs = require('express-handlebars');
    const app = express();
    //设置模板引擎
    app.engine('handlebars', exphbs({defaultLayout: 'main'}));
    app.set('view engine', 'handlebars');
    app.get('/',(req,res) => {
      res.render('index');
    });
    

    main.handlebars:所有的handlebars都会被渲染到这个文件中

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport"
            content="width=device-width, user-scalable=no>
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
       {{{body}}}
    </body>
    </html>
    

    index.handlebars:是你路由指定的渲染模板

    <h1>这里是课程项目</h1>
    

    二.添加课程

    //添加课程
    app.get('/ideas/add',(req,res) => {
      res.render('ideas/add')
    });
    

    使用body-parser解析请求体
    添加课程时,我们需要使用post请求,post请求包含请求体,在Node原生的http模块中,请求体需要使用流来进行接收和解析。body-parser是express使用的HTTP请求体解析的中间件,可以解析JSON、Raw、文本、URL-encoded格式的请求体。
    body-parser的使用
    通过使用body-parser.json()方法可以解析application/json格式的文件
    通过使用body-parser.urlencoded()方法可以解析application/x-www-form-urlencoded表单格式的数据
    //使用body-parser中间件解析请求体
    const bodyParser = require('body-parser');
    // 解析 application/json
    app.use(bodyParser.urlencoded());
    const jsonParser = bodyParser.json();
    // 解析 application/x-www-form-urlencoded
    const urlencodedParser = bodyParser.urlencoded({ extended: false })
    //解析后的数据在req.body中
    app.post('/ideas',urlencodedParser,(req,res) => {
      res.render('ideas/index',{
        title:req.body.title,
        details:req.body.details
      })
    });
    
    

    后台错误验证
    表单提交时,需要进行错误验证。表单提交的信息是否正确(是否全部填写,填写部分是否有要求等),在这里我们需要提交两个数据。
    如下面代码所示:创建一个error数组,如果req.body.title不存在表示没有输入这个内容,将提示文字添加到error数组中。根据数组的长度来验证是否有错误,如果有错误,需要错误提示。error.handlebars用来描述错误提示。

    app.post('/ideas',urlencodedParser,(req,res) => {
      //后台错误验证
      const error = [];
      if(!req.body.title){
        error.push({text:'请输入标题'});
      }
      if(!req.body.details){
        error.push({text:'请输入详情'});
      }
    
      if(error.length > 0){
        res.render('ideas/add',{
          //实现自动填写
          title:req.body.title,
          details:req.body.details,
          //实现错误提示
          errors:error
        })
      }else{
        res.render('ideas/index',{
          title:req.body.title,
          details:req.body.details
        })
      }
    });
    

    error.handlebars

    {{#each errors}}
      <div class="alert alert-danger">{{text}}</div>
    {{/each}}
    

    如果errors数组不存在,那么就没有渲染的内容。
    main.handlebars

    <div class="container">
         {{> error}}
         {{{body}}}
    </div>
    

    三.增

    添加课程以后,我们需要将添加的课程存入数据库中。在需要展示时载从数据库中调取。
    mongoose的使用
    1.连接数据库 :node-course是我们自己定义的数据库名称

    const mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost/node-course');
    

    2.创建集合
    数据库集合通常创建在models文件中,每一个文件代表一个集合。比如Idea.js表示的是集合Idea

    |---app.js
    |---models
        |---Idea.js
    

    Idea.js:

    const mongoose = require('mongoose');
    
    const Schema = mongoose.Schema;
    
    //创建一个Schema
    const IdeaSchema = new Schema({
      title:{
        type:String,
        required:true
      },
      details:{
        type:String,
        require:true
      }
    });
    
    //创建集合idea
    const Idea = mongoose.model('ideas',IdeaSchema);
    //导出集合对象
    module.exports = Idea;
    

    3.保存数据

    const Idea = require('./models/Idea');
     //添加到数据库
        const newCourse = {
          title:req.body.title,
          details:req.body.details
        };
    //集合创建的示例对象就是要保存的文档(数据)
        new Idea(newCourse)
          .save()
          .then(()=>{
            res.redirect('/ideas');
          })
    

    四.查

    通过调用数据库查看数据

    app.get('/ideas',(req,res) => {
      Idea.find({})
        .then((idea)=>{
          res.render('ideas/index',{
            ideas:idea
          })
        })
    });
    

    查看数据可以直接使用Idea.find({})静态方法,获取到的是一个数组,我们需要将这个数组渲染到ideas/index.handlebars.
    ideas/index.handlebars

     {{#each ideas}}
        <div class="card card-body">
          <h3>{{title}}</h3>
          <h3>{{details}}</h3>
        </div>
      {{/each}}
    

    五.改

    我们有时候需要对课程信息进行编辑
    跳转到编辑页面:

     {{#each ideas}}
        <div class="card card-body">
          <h3>{{title}}</h3>
          <h3>{{details}}</h3>
          <a href="/ideas/edit/{{id}}" class="btn btn-dark btn-block">编辑</a>
        </div>
      {{/each}}
    

    注意:上面的编辑按钮中的{{id}}来自数据库,每一个文档都有一个特定的id。我们在这里可以直接获取到。
    跳转到编辑页面

    app.get('/ideas/edit/:id',(req,res) => {
      Idea.findOne({_id:req.params.id})
        .then((idea) => {
          res.render('ideas/edit',{
            title:idea.title,
            details:idea.details
          })
        })
    });
    

    进行编辑(改)
    修改通常是使用put方法,但是表单一般只支持get和post方法,想要让form支持put或者delete等方法,需要使用中间件method-override
    method-override中间件的使用
    1.使用method-override
    //使用method-override支持put和delete等http请求方法
    const methodOverride = require('method-override');
    app.use(methodOverride('_method'))
    

    2.修改form表单内容
    修改action和添加一个隐藏的input框

     <form action="/ideas/{{id}}?_method=PUT" method = "post">
         //需要在这里添加一个隐藏的input
          <input type="hidden" name="_method" value="PUT">
          <button type="submit" class="btn btn-primary">提交</button>
        </form>
    
    

    使用put进行修改:
    数据库的修改时先查找到数据,然后跟操作对象一样将数据修改完,然后记得保存。

    app.put('/ideas/:id',urlencodedParser,(req,res) => {
      const course = {
        title:req.body.title,
        details:req.body.details
      }
      Idea.findOne({_id:req.params.id})
        .then((idea) => {
          idea.title = req.body.title;
          idea.details = req.body.details;
          idea.save()
            .then(()=>{
              res.redirect('/ideas')
            })
        })
    });
    

    六.删

    如果我们想要删除课程,那么不需要跳转到新的页面,直接在当前页面删除就行。由于删除使用delete方法,因此我们同样需要使用method-override,因此将请求放到form表单中。

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

    删除操作:

    //删除
    app.delete('/ideas/:id',urlencodedParser,(req,res) => {
      Idea.remove({_id:req.params.id})
        .then(()=>{
          res.redirect('/ideas');
        })
    })
    

    七.对用户的操作进行提醒

    我们在进行增删改查的时候,需要对用户的操作进行提醒,比如修改成功后,提示修改成功,删除成功后,提示删除成功,以及出现错误时,提示错误。connect-flash是nodejs中的一个模块,flash是一个暂存器,而且暂存器里面的值使用过一次便被清空,适合用来做网站的提示信息。flash 是 session 中一个用于存储信息的特殊区域。消息写入到 flash 中,在跳转目标页中显示该消息。flash 是配置 redirect 一同使用的,以确保消息在目标页面中可用
    安装

    npm i express-session connect-flash
    

    使用
    在app.js中引入

    const session = require('express-session');
    const flash = require('connect-flash');
    

    在app中使用flash中间件

    app.use(session({
      secret: 'secret',
      resave: true,
      saveUninitialized: true
    }));
    app.use(flash());
    

    使用完flash中间件以后,所有的req中都存在一个flash方法,可以存储内容。req.flash('success')。将flash中存入的变量存入res.locals全局变量中,假如我要在网站中使用flash中存的error和success变量,加可以把它们传入locals变量中,这样所有的模板都可以拿到这个变量。注意flash存储的变量都是只能使用一次,使用完毕就会被移除
    定义falsh变量:req.flash(success_msg)表示定义一个success_msg变量

    //将flash中存入的变量存入res.locals对象中
    app.use(function(req,res,next){
      res.locals.success_msg = req.flash('success_msg');
      res.locals.error_msg = req.flash('error_msg');
      next();
    });
    

    给flash变量赋值,一般是在res.redirect()前面进行赋值req.flash('success_msg',"删除成功")

    app.delete('/ideas/:id',urlencodedParser,(req,res) => {
      Idea.remove({_id:req.params.id})
        .then(()=>{
          req.flash('success_msg',"数据删除成功");
          res.redirect('/ideas');
        })
    })
    

    req.flash赋值以后,res.locals中的变量就能够获取到这个值,那么在任何模板中,都可以使用这个值。
    _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}}
    

    七.路由管理

    目前,我们所有的中间件的使用和路由的设置都在app.js中,这样的话就导致整个app.js文件显得臃肿,而且之后可能还有新的路由设置,因此我们需要对路由进行管理。顶级express对象具有创建新的router对象的功能,这个新的router对象可以用来帮助我们实现路由管理。
    app.js

    const app = express();
    const idea = require('./routes/idea');
    //使用idea routes
    //这里的/表示根目录,之后的router.get(/idea)都是在这个根目录下进行组合的
    app.use('/',idea);
    

    app.use('/',idea)表示所有的/下面的路由都在idea中进行管理(idea是一个迷你路由router)
    idea.js

    const express = require('express');
    const router = express.Router();
    //添加课程
    router.get('/ideas/add',(req,res) => {
      res.render('ideas/add')
    });
    module.exports = router;
    

    通过express.Router()创建一个新的router对象,用来代替app管理指定路径下(/)的所有路由

    八.注册页面的实现##

    |---routes
        |---user.js
    //注册页面
    router.get('/users/register',(req,res) => {
      res.render('users/register.handlebars')
    });
    
    //注册
    router.post('/users/register',urlencodedParser,(req,res) => {
      console.log(res.body);
      res.send('注册成功')
    });
    

    register.handlebars

    <form action="/users/register" method="POST">
            <div class="form-group">
              <label for="name">用户名</label>
              <input type="text" class="form-control" name="name" required>
            </div>
            <div class="form-group">
              <label for="email">邮箱</label>
              <input type="email"  name="email" class="form-control" required>
            </div>
            <div class="form-group">
              <label for="password">密码</label>
              <input type="password" name="password"  class="form-control" required>
            </div>
            <div class="form-group">
              <label for="password2">确认密码</label>
              <input type="password" name="password2" class="form-control" required>
            </div>
            <button type="submit" class="btn btn-primary">注册</button>
          </form>
    

    1.注册表单错误信息后台处理
    只要是设计到表单的提交,通常都需要进行错误处理,比如密码验证,密码长度处理等。

    router.post('/users/register',urlencodedParser,(req,res) => {
      const 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',{
          name:req.body.name,
          email:req.body.email,
          password:req.body.password,
          password2:req.body.password2,
          errors:errors
        })
      }
    });
    

    2.如果没有错误,保存到数据库中

    
      if(errors.length > 0){
        res.render('users/register',{
          name:req.body.name,
          email:req.body.email,
          password:req.body.password,
          password2:req.body.password2,
          errors:errors
        })
      }else{
      //  如果没有错就保存到数据库中
        const newUser = {
          name:req.body.name,
          email:req.body.email,
          password:req.body.password
        }
        new User(newUser)
          .save()
          .then(() => {
            res.redirect('/ideas');
          })
      }
    

    3.保存到数据库之前,同样需要验证用户名,邮箱等是否已经注册过了

    //验证邮箱是否存在
    User.find({email:req.body.email})
          .then((user) =>{
            if(user.length > 0){
              req.flash('error_msg',"使用的邮箱已注册,请使用新的邮箱")
              res.redirect('/users/register');
            }else{
    //验证用户名是否存在
              User.find({name:req.body.name})
                .then((user) =>{
                  if(user.length > 0){
                    req.flash('error_msg',"用户名已存在,请使用其他的用户名")
                    res.redirect('/users/register');
                  }else{
                    const newUser = {
                      name:req.body.name,
                      email:req.body.email,
                      password:req.body.password
                    }
                    new User(newUser)
                      .save()
                      .then(() => {
                        req.flash('success_msg','注册成功');
                        res.redirect('/ideas');
                      })
                  }
                })
            }
          })
    

    4.加密操作
    用户注册时,密码保存到数据库一定是明文的,而需要进行一定的加密。这里使用bcrypt进行加密。
    安装:

    npm i bcrypt
    

    使用

     const newUser =new User({
                      name:req.body.name,
                      email:req.body.email,
                      password:req.body.password
                    });
                    const saltRounds = 10;//加密强度
                    const myPlaintextPassword = req.body.password;//加密对象
                    bcrypt.genSalt(saltRounds, function(err, salt) {
                      bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
                        newUser.password = hash;
                        newUser.save()
                          .then(() => {
                            req.flash('success_msg','注册成功');
                            res.redirect('/ideas');
                          })
                      });
                    });
    

    加密后的密码为:

     "password" : "$2b$10$CRirGkbEvmwNbfBpx21Uyesju3MWyb9oU432dNFPAwvW5C9H8KzqW }
    

    九.登陆

    登陆时,首先通过用户名或者邮箱从数据库中查找用户,如果用户存在则进行密码验证。

    router.post('/users/login',urlencodedParser,(req,res) => {
    //  从数据库中通过用户名或者邮箱进行查询,如果有这个用户且密码正确则进行登陆
      User.findOne({email:req.body.email})
        .then((user) => {
          if(user){
          //  验证密码
            bcrypt.compare(req.body.password, user.password, function(err, isMatch) {
              // res == true
              if(isMatch){
                res.redirect('/ideas');
              }else{
                req.flash('error_msg',"您输入的密码不正确");
                res.redirect('/users/login')
              }
            });
          }
        })
    });
    

    用户登陆状态的持久化
    用户登陆成功以后,在退出之前应该都是登陆状态,这需要passport模块来帮助我们实现。同时,登陆,注册等提示应该消失。而且是在所有的页面消失,因此我们需要一个全局的变量来控制它。这就是app.locals.user。app.locals在整个应用生命周期内都是有效的,但是这里的app必须是app.js中唯一的那一个,不能是在一个文件内创建的新的app。
    关于passport的使用可以查看passport
    1.使用passport进行登陆验证和持久话
    app.js中

    const app = express();
    const passport = require('passport');
    app.use(passport.initialize());
    app.use(passport.session());
    //passport持久数据时涉及到session,需要对session进行序列化和反序列化。(同时需要安装session等npm)
      passport.serializeUser(function(user, done) {
        done(null, user.id);
      });
    
      passport.deserializeUser(function(id, done) {
        User.findById(id, function (err, user) {
          done(err, user);
        });
      });
    //定义验证的策列
      passport.use(new LocalStrategy(
        {usernameField:"email"}, //验证对象改为email
        function(email, password, done) {
          User.findOne({ email: email })
            .then((user) => {
              if(!user){
                return done(null,false,{message:'没有该用户'});
              }else{
                //用户存在密码验证
                bcrypt.compare(password,user.password, (err, isMatch) => {
                  if(err){
                    throw err;
                  }else{
                    if(isMatch){
                      app.locals.user = true;
                      return done(null,user)
                    }else{
                      return done(null,false,{message:'密码错误'});
                    }
                  }
                });
              }
            })
        }
      ));
    

    在登陆时进行验证

    router.post('/users/login',urlencodedParser,(req,res,next) => {
    //  passport进行登陆验证
      passport.authenticate('local', {
        successRedirect:'/ideas',
        failureRedirect: '/users/login',
        failureFlash: true    //是否使用flash进行提示,如果使用需要定义res.locals.error
      })(req, res, next);
    });
    

    2.使用app.locals.user进行状态的控制


    从app.js中引入app
    const app = require('../app.js').app;
    

    验证密码通过以后,通过app.locals.user = true;来持久登陆状态

    router.post('/users/login',urlencodedParser,(req,res) => {
    //  从数据库中通过用户名或者邮箱进行查询,如果有这个用户且密码正确则进行登陆
      User.findOne({email:req.body.email})
        .then((user) => {
          if(user){
          //  验证密码
            bcrypt.compare(req.body.password, user.password, function(err, isMatch) {
              // res == true
              if(isMatch){
                app.locals.user = true;
                req.flash('success_msg','登陆成功');
                res.redirect('/ideas');
              }else{
                req.flash('error_msg',"您输入的密码不正确");
                res.redirect('/users/login')
              }
            });
          }
        })
    });
    

    handlebars文件中通过这个变量控制登陆和注册的显示:

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

    十.注销登陆

    注销时,需要清理许多用户信息,这里使用passport来帮助我们进行注销。
    安装

    npm i pssport
    

    使用:通过req.logout来实现注销,注销时需要将持久的变量设置为false。变成未登陆状态。

    router.get('/users/logout',(req,res) => {
      req.logout();
      app.locals.user = false;
      req.flash('success_msg',"退出登陆成功");
      res.redirect('/users/login')
    });
    
    

    十一.导航守卫

    在没有进行登陆时,用户应该不能访问任何页面。也就是说永不能通过输入网址进行页面访问。也就是说我们需要对所有的get请求进行守卫。这里同样需要用到passport模块通过自定义中间件来实现。

    |---helpers
        |---auth.js
    module.exports = {
      ensureAuthenticated:(req,res,next) => {
        if(req.isAuthenticated()){
          return next();
        }else{
          req.flash('error_msg',"请先登陆");
          res.redirect('/users/login');
        }
      }
    }
    

    上面使用的req.isAuthenticated必须先安装passport模块才能够使用。

    |---router
        |---idea.js
    //导航守卫
    const {ensureAuthenticated} = require('../helpers/auth');
    //添加课程
    router.get('/ideas/add',ensureAuthenticated,(req,res) => {
      res.render('ideas/add')
    });
    
    //查
    router.get('/ideas',ensureAuthenticated,(req,res) => {
      Idea.find({})
        .then((idea)=>{
          res.render('ideas/index',{
            ideas:idea
          })
        })
    });
    

    在路由时,第二个参数时是导航守卫的中间件

    十二.数据的管理

    每一个用户对应有自己的课程,也只能对自己的课程进行编辑和删除。因此需要对用户的数据进行管理。否则的话,无论什么人进行什么操作都会影响到其他的人的课程。
    解决办法:在每次添加课程时,把用户的信息添加进去。
    1.添加用户字段
    model/Idea.js

    const IdeaSchema = new Schema({
      title:{
        type:String,
        required:true
      },
      details:{
        type:String,
        required:true
      },
    //把用户的信息添加进去
      user:{
        type:String,  
        required:true
      }
    });
    

    router/idea.js:将user:req.user.id添加到数据库中。

    //添加到数据库
        const newCourse = {
          title:req.body.title,
          details:req.body.details,
          user:req.user.id
        };
        new Idea(newCourse)
          .save()
          .then(()=>{
            res.redirect('/ideas');
          })
    
    

    观察数据库中的结果:多了一个user字段

    { "_id" : ObjectId("5bf90f0ae5436e489cb3e29c"), "details" : "html" }
    { "_id" : ObjectId("5bf9139ec99ee92c70e3dc4f"),  "details" : "test", "user" : "5bf904946607d339d8b8a30b" }
    

    2.每次查看时,都通过这个user字段来进行筛选,只能查看具有这个字段的用户信息

    //增加了筛选条件{user:req.user.id}
    
    router.get('/ideas',ensureAuthenticated,(req,res) => {
      Idea.find({user:req.user.id})
        .then((idea)=>{
          res.render('ideas/index',{
            ideas:idea
          })
        })
    });
    

    3.编辑
    如果我们每次进入一个账号先获取到他的url,然后再使用另外一个账号进行登陆,这样的话还是能够进行操作的。因此这里也需要进行设置。必须验证他的user和req.user.id是否相同。

    //跳转到编辑页面
    router.get('/ideas/edit/:id',ensureAuthenticated,(req,res) => {
      Idea.findOne({_id:req.params.id})
        .then((idea) => {
          // 增加user和请求的id的验证
          if(idea.user !== req.user.id ){
            req.flash('error_msg','非法操作');
            res.redirect('/ideas')
          }else{
            res.render('ideas/edit',{
              title:idea.title,
              details:idea.details,
              id:idea._id
            })
          }
        })
    });
    

    相关文章

      网友评论

          本文标题:课程管理系统

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