😈node记账本demo笔记

基本结构搭建

  1. npm i -g express-generator : 安装 npm 包 (已经安装就第二步)

  2. express -e accounts : 快速创建文件夹, 添加ejs模板引擎的支持

  3. views中通过 ejs 写好页面 (关于页面的显示这里就不多介绍)

    img
  4. 将所需要的 js 和 css 文件存放到 public 文件夹下

  5. routes/index.js中写好增删改查

  6. npm i : 安装依赖

  7. npm start : 运行项目 (在 bin / www 文件, 端口用的 3000)

    package.json 中的 start 命令可以改为 nodemon

app.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index'); //❗ 我们只需要在这里编写代码即可
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views')); //❗ 设置模板文件存放文职
app.set('view engine', 'ejs'); ❗ // 设置模板引擎为 ejs

app.use(logger('dev'));
app.use(express.json()); //❗ 创建全局中间件 : 获取请求体(json格式)
app.use(express.urlencoded({ extended: false })); //❗ 创建全局中间件 : 获取请求体(quirestring格式)
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); //❗ 创建静态资源中间件

app.use('/', indexRouter); // ❗路由中间件

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

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

module.exports = app; // 导出, 最终是在www文件中start

Mongodb 连接

下载 mongdb — 省略

启动服务

记得在 cmd 中先启动服务 (两个 cmd 窗口分别启动)

  1. 运行命令 mongod
  2. 运行命令 mongosh 连接本机的 mongodb 服务

使用 mongoose

npm i mongoose : 先安装 mongoose

Mongoose 是一个对象文档模型库 , 为了方便使用代码操作 mongodb数据库

Mongoose 代码模块化

我们将 mongoose 的基本使用封装为一个 db 文件, 里面装一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
*
* @param {*} success 数据库连接成功的回调
* @param {*} error 数据库连接失败的回调
*/

// db/db.js

module.exports = function(success, error) {

// 判断是否传入error, 给默认失败回调
if(typeof error !== 'function') {
// 给个默认值
error = () => {
console.log('连接失败');
}
}

const mongoose = require('mongoose');

// weirdo是数据库的名称(不存在会自动创建) , 27017是mongodb的默认端口号(可以不写)
mongoose.connect('mongodb://127.0.0.1:27017/weirdo')

// 设置连接成功的回调, once只执行一次, 用on的话掉线回来后还会执行
mongoose.connection.once('open', () => {
success();
})

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

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

}

这样我们就可以在需要使用的地方使用db函数连接数据库即可

  1. 导入db文件

  2. 导入模型对象

我们还准备把数据库的一些信息单独拿出来, 做一个配置文件config, 那么以后信息需要修改的时候也比较方便

1
2
3
4
5
6
// config/config.js  配置文件
module.exports = {
DBHOST: '127.0.0.1',
DBPORT: '27017',
DBNAME: 'weirdo'
}

然后到db.js文件中使用即可

在www文件中导入并调用db函数

1
2
3
4
5
6
7
8
// www
const db = require("../db/db"); // 导入db函数

// 调用db函数 , 数据库连接上再启动http服务, 否则没有意义
db(() => {
...
...
});

创建AccountModel.js 模型对象

我们在根目录下创建一个modules文件夹, 存放一些数据库需要的模型对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// modules/AccountModel.js
const mongoose = require('mongoose')

let AccountSchema = new mongoose.Schema({
title: { // 标题
type: String,
require: true,
},
time: Date, // 时间
type: { //类型
type: Number,
default: -1,
},
account: {
type: Number,
require: true
},
remarks: {
type: String,
}
})

// 创建模型对象 : 对文档操作的封装对象
// 第一个参数是集合名称, 第二个参数为结构对象
let AccountModel = mongoose.model('accounts', AccountSchema);

//暴露模型对象
module.exports = AccountModel;

这里由于我们的时间time在最后获取到的是一个字符串, 我们要将它转换为Date型, 就要用到一个工具包

1
npm i moment

插入数据

routes/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 新增记录
router.post('/account', (req, res) => {
// 在app.js中已经添加了body-parser中间件
AccountModel.create({
...req.body,
// 修改time属性的值
time: moment(req.body.time).toDate() // 将字符串修改为时间对象
}).then(() => {
res.render('success', {msg: '添加成功hhh', url: '/account'})
}).catch((err) => {
res.status(500).send('插入失败')
})

})

读取数据

routes/index.js

这里用到了格式化时间, 同样需要导入moment, 然后这里传递momentejs

1
2
3
4
5
6
7
8
9
// 记账本的列表
router.get('/account', function(req, res, next) {
// 获取所有的账单信息
AccountModel.find().then((data) => {
res.render('list', {accounts: data, moment: moment, url: '/account/create'})
}).catch(() => {
res.status(500).send('读取失败')
})
});

然后使用格式化时间

img

另外, 由于数据库里面的id_id, 所以我们将list.ejs的相关字段改一下

其实不改也行, mongodb默认也会帮我们操作

img

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
// 删除记录 (根据id进行删除)
router.get('/account/:id', (req, res) => {
// 获取params的id参数
let id = req.params.id;
AccountModel.deleteOne({
_id: id,
}).then(() => {
res.render('success', {msg: '删除成功hhh', url: '/account'})
}).catch(() => {
res.status(500).send('删除失败')
})
})

为了使前端不要误操作, 我们给删除按钮添加一个验证

1
2
3
4
5
6
7
8
9
10
11
// list.ejs
let delBtns = document.querySelectorAll('.delBtn');
delBtns.forEach((item) => {
item.addEventListener('click', (e) => {
if(confirm('您确定要删除该条文档吗?')) {
return true;
} else {
e.preventDefault()
}
})
})

添加接口功能

为了将来我们在app, 安卓, ios中开发完程序后, 到时候直接和接口对接, 那么整个功能就完成了

首先先启动 mongod 服务, 然后本地连接 mongosh

npm start 打开我们通过 express-generator 做的记账本案例

在routes下添加以下文件

img

这里的web/index.js就是原先的index.js

另外, 在app.js中修改相关代码

1
2
3
4
5
6
const indexRouter = require("./routes/web/index");
const accountRouter = require("./routes/api/account"); // 导入 account 接口路由文件
...
...
app.use("/", indexRouter);
app.use("/api", accountRouter);

这样, 我们通过api访问到的就是接口返回的信息了

api/account.js 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
var express = require("express");
var router = express.Router();

const moment = require("moment");
const AccountModel = require("../../modules/AccountModel");

// 记账本的列表
router.get("/account", function (req, res, next) {
// 获取所有的账单信息
AccountModel.find()
.then((data) => {
res.json({
code: '0000', // 响应编号, 0000表示成功
msg: '读取成功', // 响应的信息
data: data, // 响应的数据
})
})
.catch(() => {
res.json({
code: '1001',
msg: '读取失败',
data: null
})
});
});

// 新增记录
router.post("/account", (req, res) => {
// ❗在这之前可以进行表单验证

// 在app.js中已经添加了body-parser中间件
AccountModel.create({
...req.body,
// 修改time属性的值
time: moment(req.body.time).toDate(),
})
.then((data) => {
res.json({
code: '0000',
msg: '添加成功',
data: data
})
})
.catch((err) => {
res.json({
code: '1002',
msg: '添加失败',
data: null
})
});
});

// 删除记录 (根据id进行删除)
router.delete("/account/:id", (req, res) => {
// 获取params的id参数
let id = req.params.id;
AccountModel.deleteOne({
_id: id,
})
.then(() => {
res.json({
code: '0000',
msg: '删除成功',
data: {}
})
})
.catch(() => {
res.json({
code: '1003',
msg: '删除失败',
data: null
})
});
});

// 获取单个账单信息
router.get('/account/:id', (req, res) => {
let {id} = req.params;
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', (req, res) => {
let {id} = req.params;
AccountModel.updateOne({_id: id}, req.body).then(() => {
// 更新成功后返回更新后的数据
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;

只是将之前返回的数据变为了json, 之前是 res.render 渲染 ejs 页面, 现在是返回 json 数据

这样我们就可以在apipost中进行各种操作了

img

会话控制

cookie

如果是通过 express-generator 进行创建项目的话就不用自己搭建了, 这里使用的是原生express

npm i express : 注意要安装获取 cookie 的包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const express = require('express');
//1. 安装 cookie-parser npm i cookie-parser
//2. 引入 cookieParser 包
const cookieParser = require('cookie-parser');
const app = express();

//3. 设置 cookieParser 中间件
app.use(cookieParser());

//4-1 设置 cookie
app.get('/set-cookie', (request, response) => {
response.cookie('username','wangwu'); // 不带时效性, 关闭浏览器就没了
response.cookie('email','23123456@qq.com', {maxAge: 5*60*1000 }); // 带时效性
response.send('Cookie的设置'); //响应
});

//4-2 读取 cookie
app.get('/get-cookie', (request, response) => {
console.log(request.cookies); //读取所有 cookie
console.log(request.cookies.username) // 单独读取 cookie
response.send('Cookie的读取'); //响应体
});


// 4-3 删除cookie (一般用户退出登录的时候会用到)
app.get('/delete-cookie', (req, res) => {
res.clearCookie('username');
res.send('删除成功')
})


//启动服务
app.listen(3000, () => {
console.log('服务已经启动....');
});

session

session 是保存在 服务器端的一块儿数据 ,保存当前访问用户的相关信息

它可实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息

运行流程

  1. 填写账号和密码校验身份,校验通过后服务端创建 session 信息 ,然后将 session_id 的值通过响应头返回给浏览器
  2. 在登录(请求)了一次此网站后, 下次发送请求时会自动携带 cookie,服务器通过 cookie 中的session_id 的值确定用户的身份

session 的代码操作

安装 npm i express-session 它可在 express 中对 session 进行操作

安装 npm i connect-mongo , 可以连接 mongodb数据库, 也可以做一些操作, 这样就可以将session存到数据库

记得要先把 mongodb 跑起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const express = require('express');
//1. 安装包 npm i express-session connect-mongo
//2. 引入 express-session connect-mongo
const session = require("express-session");
const MongoStore = require('connect-mongo');

const app = express();

//3. 设置 session 的中间件
app.use(session({
name: 'sid', //设置cookie的name,默认值是:connect.sid
secret: 'atguigu', //参与加密的字符串(又称签名)❗加盐

saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
//不用就不创建, 除非对匿名用户信息做记录, 就可以设置为 true

resave: true, //是否在每次请求时重新保存session (比如可以更新session的过期时间)
// 一直发请求就不会重新登录, 长时间操作过了session过期时间就又要重新登录了

store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/weirdo' //数据库的连接配置
}),
cookie: {
httpOnly: true, // 开启后前端无法通过 JS 进行访问(比如 document.cookie)
maxAge: 1000 * 300 // 这一条 是控制 sessionID 的过期时间的!!
// session 和 cookie 都可以通过这个设置
},
}))

//创建 session
app.get('/login', (req, res) => {
//设置session
// 满足某条件(比如用户名和密码输入正确)设置session
if(req.query.username === 'admin' && req.query.password === 'admin') {
req.session.username = 'zhangsan';
req.session.email = 'zhangsan@qq.com'
res.send('登录成功');
}
res.send('登录失败');
})

//获取 session
app.get('/home', (req, res) => {
console.log('session的信息');
console.log(req.session.username);
if (req.session.username) {
res.send(`你好 ${req.session.username}`);
} else{
res.send('登录 注册');
}
})

//销毁 session
app.get('/logout', (req, res) => {
//销毁session
// res.send('设置session');
req.session.destroy(() => {
res.send('成功退出');
});
});

app.listen(3000, () => {
console.log('服务已经启动, 端口 ' + 3000 + ' 监听中...');
});

session 和 cookie 的区别

  1. 存在的位置
    1. cookie:浏览器端
    2. session:服务端
  2. 安全性
    1. cookie 是以明文的方式存放在客户端的,安全性相对较低
    2. session 存放于服务器中,所以安全性 相对 较好
  3. 网络传输量
    1. cookie 设置内容过多会增大报文体积, 会影响传输效率
    2. session 数据存储在服务器,只是通过 cookie 传递 id,所以不影响传输效率
  4. 存储限制
    1. 浏览器限制单个 cookie 保存的数据不能超过 4K ,且单个域名下的存储数量也有限制
    2. session 数据存储在服务器中,所以没有这些限制

token

token 是服务端生成并返回给 HTTP 客户端的一串加密字符串, token 中保存着 用户信息

它可以实现会话控制,可以识别用户的身份,主要用于移动端 APP

工作流程

填写账号和密码校验身份,校验通过后响应 token,token 一般是在响应体中返回给客户端的

后续发送请求时,需要手动将 token 添加在请求报文中,一般是放在请求头中

特点

  • 服务端压力更小
    • 数据存储在客户端
  • 相对更安全
    • 数据加密
    • 可以避免 CSRF(跨站请求伪造)因为不能自动携带
  • 扩展性更强
    • 服务间可以共享
    • 增加服务节点更简单

和 cookie 的区别

  • token 手动携带

  • cookie 自动携带

jwt

JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token 的身份验证

JWT 使 token 的生成与校验更规范

我们可以使用 jsonwebtoken 包 来操作 token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
npm i jsonwebtoken
//导入 jsonwebtokan
const jwt = require('jsonwebtoken');

//创建 token
// jwt.sign(三个参数 : 数据, 加密字符串, 配置对象)
let token = jwt.sign({ // 创建token
username: 'zhangsan'
}, 'zhaoweirduohaoshuai', {
expiresIn: 60 // token的声明周期, 单位是秒
})

//解析(校验) token
jwt.verify(token, 'zhaoweiduohaoshuai', (err, data) => {
if(err){
console.log('校验失败~~');
return
}
console.log(data);
})

项目完善

我们先在 views 文件夹下创建 req.ejs 文件用来存放注册页面

然后我们在web文件夹下新建一个文件 auth.js 用来存放登录和注册所要用到的路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// routes/web/auth.js
var express = require("express");
var router = express.Router();

const UserModel = require('../../modules/UserModel.js')
const md5 = require('md5')
// 在存放数据的时候, 密码我们需要进行加密, 所以 npm i md5, 然后导入即可

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

// 注册用户
router.post('/reg', (req, res) => {
// 做表单验证(这里就不做了)
UserModel.create({
...req.body,
password: md5(req.body.password), // 对密码进行加密
}).then(() => {
res.render("success", { msg: "注册成功hhh", url: "/login" });
}).catch((err) => {
res.status(500).send("注册失败");
});

})

module.exports = router;

这里你会发现我们对数据库存储数据, 我们需要一个UserModel

我们在这个文件中将数据存储到数据库中, 我们就还需要一个model

所以我们在 modules 文件夹下新建一个 UserModel.js

接收两个数据, 用户名和密码, 并将数据存放在 users 数据库集合中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const mongoose = require('mongoose')

let UserSchema = new mongoose.Schema({
username: { // 标题
type: String,
required: true,
},
password: { //类型
type: String,
required: true
},
})

// 创建模型对象 : 对文档操作的封装对象
// 第一个参数是集合名称, 第二个参数为结构对象
let UserModel = mongoose.model('users', UserSchema);

//暴露模型对象
module.exports = UserModel;

然后我们在 app.js 中导入登录和注册的全局中间件

1
2
3
4
...
const authRouter = require("./routes/web/auth");
...
app.use("", authRouter);

同样我们在 views 下添加 login.ejs 页面

由于登录时我们也是需要拿到加密后的密码和数据库中的密码进行比对, 也是需要md5加密

然后我们在 auth.js 中写入登录的相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// web/auth.js
// 登录页面
router.get('/login', (req, res) => {
res.render('login')
})

// 登录操作
router.post('/login', (req, res) => {
// 获取用户名和密码
let {username, password} = req.body
// 查询数据库
UserModel.findOne({
username: username,
password: md5(password)
}).then((data) => {
if(!data) res.send('账号或者密码错误')
else {
// 写入session
req.session.username = data.username; // 待会就会根据是否有username判断session
req.session._id = data._id // 用户文档的id, 不是session ID
res.render("success", { msg: "登录成功hhh", url: "/account" });
}
}).catch(() => {
res.status(500).send("登录失败");
});
})

查询数据库时, 如果data没有值才说明账号或者密码错误, 如果登录成功那还要写入session

写入session我们就需要用到 两个包, 第一个是用express进行session操作, 第二个是连接mongdb数据库

1
npm i express-session connect-mongo

然后我们在app.js中进行导入session的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 导入express-session
const session = require("express-session");
const MongoStore = require("connect-mongo");

...

//导入配置项
const {DBHOST, DBNAME, DBPORT} = require('./config/config')
var app = express();

// 设置 session 的中间件
app.use(
session({
name: "sid", //设置cookie的name,默认值是:connect.sid
secret: "atguigu", //参与加密的字符串(又称签名)❗加盐

saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
//不用就不创建, 除非对匿名用户信息做记录, 就可以设置为 true

resave: true, //是否在每次请求时重新保存session (比如可以更新session的过期时间)
// 一直发请求就不会重新登录, 长时间操作过了session过期时间就又要重新登录了

store: MongoStore.create({
mongoUrl: `mongodb://${DBHOST}:${DBPORT}/${DBNAME}`, //数据库的连接配置
}),
cookie: {
httpOnly: true, // 开启后前端无法通过 JS 进行访问(比如 document.cookie)
maxAge: 1000 * 60 * 60 * 24 * 7, // 这一条 是控制 sessionID 的过期时间的!!
// session 和 cookie 都可以通过这个设置
},
})
);

另外我们这里的mongoUrl不能写死, 所以我们导入了配置项

用户登录检测

我们访问 account 页面, 无痕模式发现都能进行操作, 这样是不行的, 只有登录了并且返回了session才能进行操作

所以我们在web/index.js中的每一个接口前加入以下判断

1
2
3
if(!req.session.username) { // 根据是否有username判断session
return res.redirect('/login');
}

我们发现代码冗余, 于是我们将其封装为一个中间件

在根目录下创建 middlewares 文件夹存放中间件, 并将这段代码放进去

1
2
3
4
5
6
7
8
9
10
// middlewares/checkLoginMiddlewares.js
// 检测登录的中间件
module.exports = (req, res, next) => {
// 判断是否拥有session
// 这里判断的时候根据当时写入sesson具体字段即可,(比如session.username) 直接写session不行
if(!req.session.username) {
return res.redirect('/login');
}
next()
}

然后在index.js中导入即可

1
2
3
4
5
6
// 导入中间件检测登录
let checkLoginMiddleware = require('../../middlewares/checkLoginMiddleware')
...
router.get("/account", checkLoginMiddleware, function (req, res, next) {...
router.get("/account/create", checkLoginMiddleware, function (req, res, next) {...
...

退出业务

我们在auth.js中写退出相关接口

1
2
3
4
5
6
7
// 退出登录
router.post('/logout', (req, res) => {
// 销毁session
req.session.destroy(() => {
res.render('success', {msg: '退出成功', url: '/login'});
})
})

主要的操作就是销毁session然后在回调函数中跳转页面

然后我们在 list.ejs 中添加一个按钮实现点击退出

这里为什么不用 a 标签 然后里面href 写 ‘/logout’ ?

因为如果在其它端口通过link/script/image标签发送请求, 则也会导致退出登录 => 即XSRF(跨站请求伪造)

img

所以我们尽量改为post请求, 但是这样还远远不够哈, 具体的解决办法可以看自己的八股

优化路由和首页

1
2
3
4
5
// web/index.ts
// 添加首页的路由规则
router.get('/', (req, res) => {
res.redirect('/account'); // 在跳转到account页面时也会判断session的
})

我们创建一个 404.ejs 在url路径错误的情况下放一个公益页面

1
2
3
<body>
<script src="//volunteer.cdn-go.cn/404/latest/404.js"></script>
</body>

然后在app.js 中它帮我们写的位置修改一下, render 404页面

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

JWT引入

目前还是存在bug, 因为我们的接口端(api那个)不受任何约束

img

我们在 api 文件夹下创建一个 auth.js 来限制我们的登录

我们先安装 jsonwebtoken : npm i jsonwebtoken

然后在 auth.js 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// routes/api/autg.js
var express = require("express");
var router = express.Router();

const UserModel = require("../../modules/UserModel.js");
const md5 = require("md5");
const jwt = require("jsonwebtoken");

// 登录操作
router.post("/login", (req, res) => {
// 获取用户名和密码
let { username, password } = req.body;
// 查询数据库
UserModel.findOne({
username: username,
password: md5(password),
})
.then((data) => {
if (!data) {
res.json({
code: "2002",
msg: "用户名或密码错误",
data: null,
});
} else {
// 创建当前用户的token
let token = jwt.sign(
{
username: data.username,
_id: data._id,
},
"weirdo",
{
expiresIn: 60 * 60 * 24 * 7,
}
);

// 响应token(记得 npm i jsonwebtoken)
res.json({
code: "0000",
msg: "登录成功",
data: token,
});
}
})
.catch(() => {
res.status(500).send("登录失败");
res.json({
code: "2001",
msg: "数据库读取失败",
data: null,
});
});
});

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

module.exports = router;

对于api相关的接口进行token限制, 至于token如何在客户端进行的保存, 这就不知道了

这样我们在登录成功后就可以拿到token, 那么我们在访问关于account的相关接口时就要去校验

同理我们将校验token写为一个中间件, 放在middlewares文件夹中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// middlewares/checkTokenMiddlewares.js

const jwt = require('jsonwebtoken') // 记得要导入
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, "weirdo", (err, data) => {
// 检测token是否正确
if (err) {
return res.json({
code: "2004",
msg: "token校验失败",
data: null,
});
} else {
// 校验成功
next();
}
});
};

然后在api/account.js中的每个接口使用即可

img

我们现在感觉 ‘weirdo‘ 这个密钥在创建和校验都使用了, 以后改起来不太方便, 于是我们将其放到配置文件中

1
2
3
4
5
6
7
// 配置文件
module.exports = {
DBHOST: "127.0.0.1",
DBPORT: "27017",
DBNAME: "weirdo", // 这个是数据库集合名称
secret: "weirdo", // 这个是我们定义的jwt密钥
};

另外我们可以在校验token成功后保存用户的信息

img

然后我们就可以在记账本其它的接口访问这个用户数据(req.user)了

比如

img

后面的部署上线就自己去操作吧.

文件(图片)上传

注意 这时候已经使用了express-generator

文件上传也是在发送http请求报文

npm i formidable : 拿到formidable对象处理文件上传

routes/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var express = require("express");
var router = express.Router();
let {formidable} = require("formidable"); // 这里formidable是一个对象, 要解构拿到

/* GET home page. */
router.get("/", function (req, res, next) {
res.render("index", { title: "Express" });
});

// 显示网页的 (表单)
router.get("/portrait", (req, res) => {
res.render("portrait");
});

// 处理文件上传
router.post("/portrait", (req, res, next) => {
// 创建表单对象
let form = formidable({
// 设置上传文件的保存目录
uploadDir: __dirname + '/../public/images',
// 保持文件后缀
keepExtensions: true
});

// 解析请求报文
// fields储存除了文件以外的字段
// files储存
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}

// 服务器保存该图片的访问 URL : images/46b9df33709488d18ff343900.jpg
let url = '/images/' + files.portrait[0].newFilename; // 将来将此数据保存在数据库中
// 这里files.portrait是一个数组里面存放一个对象, 拿到即可

console.log(files);
res.send(url);
});
});

module.exports = router;

portrait.ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<!-- 文件上传必须的属性设置 enctype="multipart/form-data" -->
<form action="/portrait" method="post" enctype="multipart/form-data">
用户名: <input type="text" name="username"> <br>
头像 : <input type="file" name="portrait">
<button>点击提交</button>
</form>
</body>
</html>
img

get请求获取的表单

img

点击提交后 (即 post 请求 => 进行文件上传)

img


😈node记账本demo笔记
http://example.com/2023/11/27/node记账本/
作者
weirdo
发布于
2023年11月27日
更新于
2023年12月14日
许可协议