目录

项目效果

项目的搭建

​编辑

响应静态网页

​编辑

​编辑

结合MongoDB数据库

结合API接口

进行会话控制


项目效果

案例实现账单的添加删除查看用户登录注册功能比较简单,但是案例主要是使用前段时间学习知识进行实现的,主要包括express服务搭建以及使用,并结合MongoDB数据库数据进行存储以及操作,同时编写相应的API接口最后进行会话控制,确保数据安全。如果以下的某一些部分感觉不太理解的话可以看我之前对应文章案例中使用到知识点都是前面文章有涉及到的。

项目的搭建

首先我们直接使用expressgenerator快速地搭建express应用骨架,输入命令行expresse 文件名 然后使用npm i来进行项目依赖下载。完成之后可以package.json中对运行命令 “start“: “node./bin/www修改为 start“: “nodemon ./bin/www“,这样后续的内容修改服务器就会自动运行了。接下来运行:npm start,进行服务的启动。服务默认监听3000端口我们输入http://127.0.0.1:3000进行访问。出现以下页面即服务搭建成功。

接下来我们需要路由规则进行配置我们可以app.js文件中进行查看app.use(‘/’, indexRouter);我们可以找到对应路由导入var indexRouter = require(‘./routes/index‘); 因此我们routes文件夹下面的index.js文件进行路由配置

//index.js

var express = require('express');
var router = express.Router();

// 记账本列表
router.get('/account', function(req, res, next) {
   res.send('账单列表');
});

// 记账本列表添加
router.get('/account/create', function(req, res, next) {
  res.send('添加记录');
});

module.exports = router;

输入不同的路径得到不同的结构

响应静态网页

我们事先准备好了两个页面一个为账单页面一个为添加页面。我们借助res.rend()可以对ejs中的内容响应浏览器功能来进行操作。在views文件夹下面创建两个ejs文件。将账单页面以及添加页面加入

//list.ejs

<!DOCTYPE html>
<html lang="en"&gt;
  <head&gt;
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
      rel="stylesheet"
    />
    <style>
      label {
        font-weight: normal;
      }
      .panel-body .glyphicon-remove{
        display: none;
      }
      .panel-body:hover .glyphicon-remove{
        display: inline-block
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>记账本</h2>
          <hr />
          <div class="accounts">
            <div class="panel panel-danger">
              <div class="panel-heading">2023-04-05</div>
              <div class="panel-body">
                <div class="col-xs-6">抽烟只抽煊赫门,一生只爱一个人</div>
                <div class="col-xs-2 text-center">
                  <span class="label label-warning">支出</span>
                </div>
                <div class="col-xs-2 text-right">25 元</div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            <div class="panel panel-success">
              <div class="panel-heading">2023-04-15</div>
              <div class="panel-body">
                <div class="col-xs-6">3 月份发工资</div>
                <div class="col-xs-2 text-center">
                  <span class="label label-success">收入</span>
                </div>
                <div class="col-xs-2 text-right">4396 元</div>
                <div class="col-xs-2 text-right">
                  <span
                    class="glyphicon glyphicon-remove"
                    aria-hidden="true"
                  ></span>
                </div>
              </div>
            </div>
            
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
//create.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>添加记录</title>
    <link
      href="/css/bootstrap.css"
      rel="stylesheet"
    />
    <link href="/css/bootstrap-datepicker.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <h2>添加记录</h2>
          <hr />
          <form method="post" action="/account">
            <div class="form-group">
              <label for="item">事项</label>
              <input
                name="title"
                type="text"
                class="form-control"
                id="item"
              />
            </div>
            <div class="form-group">
              <label for="time">发生时间</label>
              <input
                name="time"
                type="text"
                class="form-control"
                id="time"
              />
            </div>
            <div class="form-group">
              <label for="type">类型</label>
              <select name="type" class="form-control" id="type">
                <option value="-1">支出</option>
                <option value="1">收入</option>
              </select>
            </div>
            <div class="form-group">
              <label for="account">金额</label>
              <input
                name="account"
                type="text"
                class="form-control"
                id="account"
              />
            </div>
            
            <div class="form-group">
              <label for="remarks">备注</label>
              <textarea  name="remarks" class="form-control" id="remarks"></textarea>
            </div>
            <hr>
            <button type="submit" class="btn btn-primary btn-block">添加</button>
          </form>
        </div>
      </div>
    </div>
    <script src="/js/jquery.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/bootstrap-datepicker.min.js"></script>
    <script src="/js/bootstrap-datepicker.zh-CN.min.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>

然后我们修改原本的路由,让其输入/account时为账单页表页面,输入/account/create时为列表添加页面。

// 记账本列表
router.get('/account', function(req, res, next) {
  res.render('list');
});

// 记账本列表添加
router.get('/account/create', function(req, res, next) {
  res.render('create');
});

结合MongoDB数据库

接下来我们结合前几篇文章我们学习到的MongoDB数据库来对数据的添加,删除以及读取操作。如果不懂这一步操作小伙伴看完前面发的文章

我们在我们项目创建三个文件夹:configdb以及modelsconfig文件用于对配置进行统一设置,在里面单独地设置域名端口以及数据库名等信息db文件夹中创建db.js,主要进行导入mongoose,连接mongoose服务,设置成功以及失败回调信息module用于创建文档结构对象

//config.js
module.exports={
    DBHOST:'127.0.0.1',
    DBPORT:27017,
    DBNAME:'bilibili',
    secret:'atguigu'
}
//db.js
module.exports = function (success, error) {
    //导入配置文件
    const {DBHOST,DBPORT,DBNAME}=require('../config/config');

    //导入mongoose
    const mongoose = require('mongoose');
    //连接mongodb服务
    mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);

    mongoose.connection.once('open', () => {
        success();

    })

    //设置连接错误的回调
    mongoose.connection.on('error', () => {
        error();

    });
    //设置连接关闭的回调
    mongoose.connection.on('close', () => {
        console.log('连接关闭');
    });
}

接着再www文件中导入db.js中的函数,在文件调用函数,传入两个函数,成功的回调以及失败的回调,成功的回调直接写原本启动http服务的代码,确保数据库连接成功之后再启动http服务。失败的回调我们直接输出失败即可

//www
const db = require('../db/db');
//连接数据库再来启动http服务
db(()=>{

//原本文件中的代码

}
},()=>{
  console.log("连接失败")
})

接着想要操作数据库,我们需要先准备好模型文件:

//models/AccountModel.js
const mongoose = require('mongoose');
//创建文档结构对象
let AccountSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    time: Date,
    type: {
        type: Number,
        required: true
    },
    account: {
        type: Number,
        required: true
    },
    remarks: {
        type: String
    }
});
//创建文档模型对象
let AccountModel = mongoose.model('accounts', AccountSchema);
//暴露模型对象
module.exports = AccountModel;

模型文件准备好之后,我们在对路由文件,routes/index.js文件来进行操作,在原本的文件中添加新增数据的操作:

//新增记录
router.post('/account', function(req, res, next) {
  AccountModel.create({
    ...req.body,
    time:moment(req.body.time).toDate()
  }).then(data=>{
    res.render('success',{msg:'添加成功~~',url:'/account'});
  }).catch(err=>{
    res.status(500).send('插入失败~~');
  })
});

这里面使用到一个moment包,主要是用于日期进行转换为对象形式方便后续的操作。需要在文件中导入const moment=require(‘moment‘);

接着我们添加账单,可以使用数据库可视化工具看到自己添加的数据,我使用的是Navicat新建连接选择Mongo,连接成功之后就可以看到对应数据库,以及相应的集合

以上就是我们所添加的数据,但是我们需要在页表页面上也可以看到对应数据,我们在路由配置文件中进行读取数据的操作。

// 记账本列表
router.get('/account',function(req, res, next) {
  //获取所有账单信息
  AccountModel.find().sort({time:-1}).exec().then(data=>{
    res.render('list',{accounts:data,moment:moment});

  }).catch(err=>{
    res.status(500).send('读取失败~~')
  })
  
});

接着需要修改list.ejs中的代码,方便对数据进行展示

<!DOCTYPE html>
<html lang="en">
  <body>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 col-lg-8 col-lg-offset-2">
          <div class="row text-right">
            <div class="col-xs-12" style="padding-top: 20px;">
              <form action="/logout" method="post">
                <button class="btn btn-danger">退出</button>
              </form>
            </div>
          </div>
          <hr>
          <div class="row">
            <h2 class="col-xs-6">记账本</h2>
            <h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2>
          </div>
   
          <hr />
          <div class="accounts">
            <% accounts.forEach(item =>{ %>
            <div class="panel <%= item.type=== -1 ? 'panel-danger':'panel-success' %>">
              <div class="panel-heading"><%= moment(item.time).format('YYYY-MM-DD') %></div>
              <div class="panel-body">
                <div class="col-xs-6"><%= item.title %></div>
                <div class="col-xs-2 text-center">
                  <span class="label <%= item.type=== -1 ? 'label-warning':'label-success' %>"><%= item.type=== -1  ? '支出':'收入' %></span>
                </div>
                <div class="col-xs-2 text-right"><%= item.account %> 元</div>
                <div class="col-xs-2 text-right">
                    <a class="delBtn" href="/account/<%= item._id %>">
                       <span
                        class="glyphicon glyphicon-remove"
                        aria-hidden="true"
                      ></span>
                    </a>
                </div>
              </div>
            </div>
            <% }) %>          
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

接着我们来对数据进行删除操作,我们在list.ejs中对叉号绑定一个事件,当用户确定删除时再进行删除数据,防止数据误删。

 <div class="col-xs-2 text-right">
      <a class="delBtn" href="/account/<%= item._id %>">
        <span  class="glyphicon glyphicon-remove" aria-hidden="true"></span>
      </a>
 </div>

  <script>
    let delBtns=document.querySelectorAll('.delBtn');
    delBtns.forEach(item =>{
      item.addEventListener('click',function(e){
        if(confirm('您确定要删除该文档吗?')){
          return true;
        }else{
          e.preventDefault();
        }
      })
    })
  </script>

并在路由中设置删除的操作:

//删除记录
router.get('/account/:id',(req,res)=>{

  //获取params 的 id参数
  let id=req.params.id;
  //删除
  AccountModel.deleteOne({_id:id}).then(data=>{
    res.render('success',{msg:'删除成功~~',url:'/account'});
  }).catch(err=>{
    res.status(500).send('删除失败~~')
  })

});

结合API接口

当我们需要将项目推广到更多的客户端程序时,而不仅仅是局限在我们的浏览器进行访问时,我们就需要为它添加对应的API接口,同样的前几篇文章也有介绍了API接口的详细内容。如果不是太了解的小伙伴可以回头去看看

为了更好地区分,我们在routes文件夹下创建一个名为web的文件,将原本的路由配置文件放入其中,再创建一个名为api的文件夹,创建一个名为account.js的文件用于存放API路由的配置。文件路径修改之后需要修改引入该文件的路径。并在app.js中导入并使用:

const accountRouter=require('./routes/api/account');

app.use('/api',accountRouter);

api的路由文件中实现创建账单接口、删除账单接口、获取条数据接口以及更新账单接口。对应代码如下

// /api/account.js
const express = require('express');
const jwt = require('jsonwebtoken');
//导入moment
const moment = require('moment');
const AccountModel = require('../../models/AccountModel');
//导入中间件
let checkTokenMiddleware = require('../../middlewares/checkTokenMiddleware')
const router = express.Router();

// 记账本列表
router.get('/account', checkTokenMiddleware,function (req, res, next) {
    AccountModel.find().sort({ time: -1 }).exec().then(data => {
        res.json({
            //响应code: '0000',
            //响应信息
            msg: '读取成功',
            //响应数据
            data: data
        })
    }).catch(err => {
        res.json({
            //响应code: '1001',
            //响应信息
            msg: '读取失败',
            //响应数据
            data: null
        })
    })

});

// 记账本列表添加
router.get('/account/create',checkTokenMiddleware, function (req, res, next) {
    res.render('create');
});

//新增记录
router.post('/account', checkTokenMiddleware,function (req, res) {

    AccountModel.create({
        ...req.body,
        time: moment(req.body.time).toDate()
    }).then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '创建成功',
            //响应数据
            data: data
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1002',
            //响应信息
            msg: '创建失败',
            //响应数据
            data: null
        })
    })

});

//删除记录
router.delete('/account/:id', checkTokenMiddleware,(req, res) => {
    //获取params 的 id参数
    let id = req.params.id;
    //删除
    AccountModel.deleteOne({ _id: id }).then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '删除成功',
            //响应数据
            data: {}
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1003',
            //响应信息
            msg: '删除失败',
            //响应数据
            data: null
        })
    })
});
//获取当个账单信息
router.get('/account/:id', checkTokenMiddleware,(req, res) => {
    //获取params 的 id参数
    let id = req.params.id;
    //查询数据库
    AccountModel.findById(id).then(data => {
        res.json({
            //响应码 
            code: '0000',
            //响应信息
            msg: '读取成功',
            //响应数据
            data: data
        })
    }).catch(err => {
        res.json({
            //响应码 
            code: '1004',
            //响应信息
            msg: '读取失败',
            //响应数据
            data: null
        })
    })
});

//更新单个账单信息
router.patch('/account/:id', checkTokenMiddleware,(req, res) => {
    //获取params 的 id参数
    let id = req.params.id;
    AccountModel.updateOne({ _id: id }, req.body).then(data => {
        //再次查询数据库
        AccountModel.findById(id).then(data => {
            res.json({
                //响应码 
                code: '0000',
                //响应信息
                msg: '更新成功',
                //响应数据
                data: data
            }).catch(err => {
                res.json({
                    //响应码 
                    code: '1004',
                    //响应信息
                    msg: '读取失败',
                    //响应数据
                    data: null
                })
            })
        }).catch(err => {
            res.json({
                //响应码 
                code: '1005',
                //响应信息
                msg: '更新失败',
                //响应数据
                data: null
            })

        })
    });

})
module.exports = router;

如何对我们写好的接口做测试呢,在前面的文章中,介绍Apipost软件测试接口,我们来尝试一下,发一个GET请求来获取表单的信息数据。成功得到对应的数据。

进行会话控制

接下来我们使用我们学过的session以及token来对数据进行保护。具体的知识点可以看我前几篇发的文章。

接下来,我们为项目加一注册的页面,在routes中web文件夹下创建一个auth.js文件,用户配置注册以及登录时的session相关信息。我们同样使用模板引擎来响应注册页面。将该创建好的路由文件在app.js中进行导入以及使用。

创建一个reg.ejs文件:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>注册</title>
  <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4">
        <h2>注册</h2>
        <hr />
        <form method="post" action="/reg">
          <div class="form-group">
            <label for="item">用户名</label>
            <input name="username" type="text" class="form-control" id="item" />
          </div>
          <div class="form-group">
            <label for="time">密码</label>
            <input name="password" type="password" class="form-control" id="time" />
          </div>
          <hr>
          <button type="submit" class="btn btn-primary btn-block">注册</button>
        </form>
      </div>
    </div>
  </div>
</body>

</html>

在响应注册页面

//注册
router.get('/reg',(req,res)=>{
    res.render('auth/reg');
});

我们需要先创建用户模型,后续才能对其进行插入数据库以及设置session等操作。

const mongoose = require('mongoose');
//创建文档结构对象
let UserSchema = new mongoose.Schema({
    username:String,
    password:String

});
//创建文档模型对象
let UserModel = mongoose.model('users', UserSchema);
//暴露模型对象
module.exports = UserModel;

将该模型导入到对应的配置文件中,并进行注册等相关操作:

/导入用户模型
const UserModel=require('../../models/UserModel');
const md5=require('md5');

//注册用户
router.post('/reg',(req,res)=>{
    UserModel.create({...req.body,password:md5(req.body.password)}).then(data=>{
        res.render('success',{msg:'注册成功',url:'/login'});

    }).catch(err=>{
        res.status(500).send('注册失败')
    })
});

接下来实现用户登录功能,我们同样创建一个login.ejs文件放置登录页面模板,复制注册页面的代码,将对应的文字以及路径修改一下即可。这部分不进行代码展示。进行登录的相关操作,当用户登录之后,我们需要对他的session进行写入,并返回sessionid。

//登录
router.get('/login',(req,res)=>{
    res.render('auth/login');
});
//登录操作
router.post('/login',(req,res)=>{
    //获取用户名密码
    let {username,password}=req.body;
    UserModel.findOne({username:username,password:md5(password)}).then(data=>{
        if(!data){
            return res.send('账号或者密码错误~~')
        }
        //写入session
        req.session.username=data.username;
        req.session._id=data._id;
        //登录成功响应
        res.render('success',{msg:'登录成功',url:'/account'});

    }).catch(err=>{
        res.status(500).send('登录失败')
    })
});

这部分需要先安装express-session以及connect-mongo,并在app.js中进行导入,并设置中间件

//导入 express-session 
const session = require("express-session");
const MongoStore = require('connect-mongo');

//导入配置项
const {DBHOST, DBPORT, DBNAME} = require('./config/config');
//设置 session 的中间件
app.use(session({
  name: 'sid',   //设置cookie的name,默认值是:connect.sid
  secret: 'atguigu', //参与加密字符串(又称签名)  加盐
  saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
  resave: true,  //是否在每次请求时重新保存session  20 分钟    4:00  4:20
  store: MongoStore.create({
    mongoUrl: `mongodb://${DBHOST}:${DBPORT}/${DBNAME}` //数据库的连接配置
  }),
  cookie: {
    httpOnly: true, // 开启前端无法通过 JS 操作
    maxAge: 1000 * 60 * 60 * 24 * 7 // 这一条控制 sessionID 的过期时间的!!!
  },
}))

当我们登录成功之后我们可以在数据库看到我们对应的session信息。

写入之后,我们还需要判断用户是否登录,若用户没有进行登录则拒绝访问,跳转到登录页面。我们在web文件夹下的index.js中编写一个中间件

//检测登录的中间件
const checkLoginMiddleware = (req, res, next) => {
    //判断
    if(!req.session.username){
      return res.redirect('/login');
    }
    next();
  }

并在下面的路由规则中使用它。这里只对查看记账本列表做演示,其他的一致。

// 记账本列表
router.get('/account',checkLoginMiddleware,function(req, res, next) {
  AccountModel.find().sort({time:-1}).exec().then(data=>{
    res.render('list',{accounts:data,moment:moment});

  }).catch(err=>{
    res.status(500).send('读取失败~~')
  })
  
});

接下来继续在auth.js中实现退出登录功能

//退出登录
router.post('/logout',(req,res)=>{
    //销毁session
    req.session.destroy(()=>{
        res.render('success',{msg:'退出成功',url:'/login'})
    })
})

退出登录界面,部分进行修改,防止CSRF跨站请求伪造。它会导致用户的session被获取。大部分的CSRF跨站请求伪造都是使用一个天生具有跨域能力的标签。但是它们发送的请求都是get请求,因此我们将原本的退出修改为post请求。可以防止发生。

<div class="col-xs-12" style="padding-top: 20px;">
   <form action="/logout" method="post">
        <button class="btn btn-danger">退出</button>
   </form>
</div>

我们接着在web文件夹下的index.js对首页添加路由规则

//添加首页路由规则
router.get('/',(req,res)=>{
  //重定向
  res.redirect('/account');
});

app.js中添加404的响应的模板,在views中创建一个404.ejs文件。

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  res.render('404');
});

以上的操作,我们使用了session对网页端进行了约束接下来我们使用token来对接口来进行约束。在api文件夹下创建一个auth.js文件对其进行设置。这部分就不再详细介绍了,文件相应的代码如下

var express = require('express');
var router = express.Router();
//导入jwt
const jwt=require('jsonwebtoken');
//读取配置项
const {secret} =require('../../config/config');
//导入用户模型
const UserModel=require('../../models/UserModel');
const md5=require('md5');


//登录操作
router.post('/login',(req,res)=>{
    //获取用户名密码
    let {username,password}=req.body;
    UserModel.findOne({username:username,password:md5(password)}).then(data=>{
        if(!data){
            return res.json({
                code:'2002',
                msg:'用户名或者密码错误',
                data:null
            })
        }
        //创建当前用户token
        let token=jwt.sign({
            username:data.username,
            _id:data._id
        },secret,{
            expiresIn:60 * 60 * 24 *7
        });
        //响应token
        res.json({
            code:'0000',
            msg:'登录成功',
            data:token
        })
        

    }).catch(err=>{
        res.json({
            code:'2001',
            msg:'数据库读取失败',
            data:null
        })
    })
});

//退出登录
router.post('/logout',(req,res)=>{
    //销毁session
    req.session.destroy(()=>{
        res.render('success',{msg:'退出成功',url:'/login'})
    })
})


module.exports = router;

中间件文件:

//checkTokenMiddleware.js
const jwt=require('jsonwebtoken');
//读取配置项
const {secret} =require('../config/config');
module.exports = (req, res, next) => {
    //获取token
    let token = req.get('token');
    //判断
    if (!token) {
        return res.json({
            code: '2003',
            msg: 'token 缺失',
            data: null
        })
    }
    //校验token
    jwt.verify(token, secret, (err, data) => {
        if (err) {
            return res.json({
                code: '2004',
                msg: '校验失败',
                data: null
            })
        }
         //保存用户的信息
         req.user=data;
        //如果执行成功
        next();

    });

}

通过Apipost来进行校验,当没有携带token时,获取不到数据,当设置请求头token数据时,能够获取到对应的数据。

好啦!本文就到这里了,Node.js系列的文章就告一段落了!如果有不足之处还请见谅~~

原文地址:https://blog.csdn.net/weixin_51735748/article/details/134699918

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_19681.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注