目录

项目效果

项目的搭建

​编辑

响应静态网页

​编辑

​编辑

结合MongoDB数据库

结合API接口

进行会话控制

项目效果

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

项目的搭建

首先我们直接使用express-generator来快速地搭建express应用骨架,输入命令行:express -e 文件名 然后使用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

Document

href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"

rel="stylesheet"

/>

记账本


2023-04-05

抽烟只抽煊赫门,一生只爱一个人

支出

25 元

class="glyphicon glyphicon-remove"

aria-hidden="true"

>

2023-04-15

3 月份发工资

收入

4396 元

class="glyphicon glyphicon-remove"

aria-hidden="true"

>

//create.ejs

添加记录

href="/css/bootstrap.css"

rel="stylesheet"

/>

添加记录


name="title"

type="text"

class="form-control"

id="item"

/>

name="time"

type="text"

class="form-control"

id="time"

/>

name="account"

type="text"

class="form-control"

id="account"

/>


然后我们修改原本的路由,让其输入/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数据库来对数据的添加,删除以及读取等操作。如果不懂这一步操作的小伙伴看完前面发的文章。

我们在我们项目中创建三个文件夹:config,db以及models。config文件用于对配置进行统一的设置,在里面单独地设置域名端口以及数据库名等信息。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中的代码,方便对数据进行展示:


记账本

添加账单


<% accounts.forEach(item =>{ %>

<%= moment(item.time).format('YYYY-MM-DD') %>

<%= item.title %>

<%= item.type=== -1 ? '支出':'收入' %>

<%= item.account %> 元

<% }) %>

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

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

//删除记录

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文件:

注册

注册



在响应注册页面

//注册

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请求。可以防止发生。

我们接着在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系列的文章就告一段落了!如果有不足之处还请见谅~~

参考链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: