一、 前后端交互流程

1.前端发起请求 – 我应该知道 我要给什么 我想得到什么
2.服务器拦截请求 通过路由分发任务
3.分发给控制器 控制解析这个请求,拿到前端发送的参数
4.把操作数据库的东西发给给 数据持久的方法
5.拿到数据库操作后的结果 结果可能会有几种1.数据操作成功 2.数据操作失败 3.拿到数据
6.根据业务逻辑将数据库的结果返回前端
7.拿到后返响应给前端的数据 根据业务逻辑 渲染页面

二、 服务器文件夹

项目根目录
静态资源目录 public
路由 router
控制器 controller
数据持久化 modules
服务器配置 config
服务器主文件 app.js

三、 配置服务器

  1. 初始化服务器

npx express-generator --view=ejs 项目名

  1. 自定义配置服务器
  • 安装nrm包 这个包是用来修改 npm源的
    npm install 代表的意思是通过npm 安装包 npm install nrm -g 这里的-g代表全局安装
    nrm test 测试所有的镜像源的网络延迟
    nrm use taobao 将镜像源修改成淘宝

  • 准备工作完毕 开始创建项目
    步骤 初始化项目
    npm init 命令初始化 初始化完 多了一个package.json 的文件 这个文件就是该项目的描述文件 类似与作者 项目依赖
    2步骤下载 express框架 通过express 框架去搭建服务器 项目基础搭建完毕 需要开始写代码

  • 下载组件框架及各个模块

    npm i express morgan cookie-parser express-session sarve-favicon -s

1. 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
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
const express = require('express')
const logger = require('morgan')
// 引入cookie模块
const cookie = require('cookie-session')
// 引入session模块
const session = require('express-session')
// 引入router文件
const usersRouter = require('/routers/usersRouter')
// 引入拼接路径
const path = require('path')

// express 实例化
const app = express()
// 设置日志模块
app,use(logger('dev'))
// 设置post请求转换格式
app.use(express.json())
app.use(express.urlencoded({extended:false}))
// 配置cookie
app.use(cookie())
// 配置session对象
app.use(session({
secret:'nclksdnlck', // 对sessionId的 cookie进行签名,防止被篡改
name: 'shop', //生成浏览器端 cookie的名字
cookie: {
maxAge:1000*10
},
saveUninitialized: true, // 湿粉扑强制未出化的session进行存储
resave:false, // 是否每此都重新保存对话
rolling:true, // 在每次请求时是否重新设置cookie,可以实现重置 cookie 有效期
}))

// 路由拦截 受保护的页面
app.use('/拦截路径',(req,res,next)=> {
if(req.session.sign) {
next()
}else {
res.redirect('登陆路径')
}
})

// 设置静态资源目录
app.use(express.static(path.join(__dirname,'静态资源文件夹')))

// 设置路由接口
app.use('请求路径',userRouter)

// 没有匹配到,则返回404
app.use((req,res)=> {
res.status(404)
res.send('404')
})

// 服务端发生错误,返回500
app.use((err,req,res,next)=> {
res.status(500)
res.json({
err
})
})

// 端口监听
app.listen(端口号,()=> {
console.log('服务器启动成功')
})

2. router文件

userRouter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require('express')
const router = express.Router()
const userCtrl = require('控制器路径')

// get 请求可以通过ajax的get或者浏览器的url地址栏
// post 请求只能通过ajax的post请求
router.get('/',(req,res)=> {
res.send('获取所有用户的信息')
})

// 处理请求路径
router.post('请求路径',相应的控制器方法)
router.get('请求路径',相应的控制器方法)

// 暴露
module.exports = router

3. ctrl文件

userCtrl.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
// 引入封装的控制器模块
const userModel = require('模块路径')
// 引入加盐加密文件
const {passMd5,jiemi}

module.exports = {
// 登陆控制器
async login(re1,res) {
const { userName, userPass } = req.body
// 与数据库对比
const result = await usersModel.login(userName)
if (result.length == 0) {
return resp.json({
code: 1001,
msg:'账号密码错误'
})
}
// 截取密码的前6为 得到盐
const salt = result[0].l_pwd.substr(0, 6)
const newPassWord = jiemi(userPass, salt)
// 判断解密的密码与数据库的密码是否一致
if (result[0].l_pwd == newPassWord) {
// 生成session 记录当前用户登陆成功
// req.session 访问session对象
req.session.sign = true // 表示用户已经登陆成功

resp.json({
code: 200,
msg:'ok'
})
} else {
resp.json({
code: 1001,
msg:'账号或者密码错误'
})
},
// 注册控制器
async register(req, resp) {
const { userName, userPass } = req.body

// 写入数据库之前在此判断用户名是否存在
// 1. 先判断用户名是否已经被注册
const checkResult = await usersModel.check(userName)
// 如果有,则返回信息并退出
if (checkResult.length > 0) {
resp.json({
code: 1001,
msg:'当前用户已存在'
})
return
}

// 2. 如果没有,则将密码加密
const newPass = passMd5(userPass)
console.log('加密之后的密码',newPass);
const result = await usersModel.register(userName,newPass)
resp.json({
code: 200,
msg:'ok'
})
},
}

4. Modules文件

indexModule.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
const mysql = require('mysql')

const mysqlConfig = {
host:'localhost',
port:3306,
user;'root',
password:'密码',
database:'表'
}

// 连接池配置
function query(sql,options) {
return new Promise((resolve,reject)=> {
// 创建连接池
let pool = mysql.createPool(mysqlConfig)
// 连接操作数据库
pool.getConnection((err,connection)=> {
if(err) {
reject(err)
}else {
connection.query(sql,options,(err,res)=> {
if(err) {
reject(err)
}else {
resolve(res)
}
})
}
// 释放连接池
connection.release()
})
})
}
userModules.js

控制器函数封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
check(userName) {
const sql = `select * from login where l_user = ?`
return query(sql,[userName])
},
register(userName, password) {
const sql = `insert into login (l_user,l_pwd) values(?,?)`
return query(sql,[userName,password])
},
login(userName) {
const sql = 'select * from login where l_user = ?'
return query(sql,[userName])
}
}

5. utils文件

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
// 内置加密模块
const crypto = require('crypto')

// 随机生成六位数
function createRandom() {
const ran = Math.random().toString().substring(0,6)
return ran
}

module.exports = {
// Md5加密
passMd5(str) {
let md5 = crypto.createHash('md5')
// 加盐
let yan = createRandom()
let result = md5.update(str+yan).digest('hex')
return yan + result
},
jiemi(str,salt) {
let md5 = crypto.createHash('md5')
let result = md5.update(str+salt).digest('hex')
return salt + result
}
}

四、模块说明

1. 加密处理

前端加密可以防止直接传输用户的原始密码。

后端加密防止密码泄露、撞库。

加密方法:

  • 哈希算法、HMAC 算法、签名、对称性加密算法、非对称性加密算法。
    有些加密后的信息是不可逆的,这种算法就只能暴力破解(撞库),比如 md5.

md5加密:nodejs内置一个crypto加密模块

1
2
3
const crypto = require('crypto')
let md5 = crypto.createHash('md5')
let result = md5.update(加密的信息).digest

2. 注册

  • 前端:用户输入账号后—发送请求—判断用户名是否存在

  • 后端:

    • 如果同时多个用户进行注册,有可能会出现用户名重复的注册的情况
    • 写入数据之前,必须还要判断该用户名是否存在,如果不存在在写入数据,如果存在则像前端返回该用户已存在,请重新注册。
    • 写入用户信息时,不能将明文密码存入数据库
      • 后端拿到前端的密码
      • 后端生成盐,随机数(6位):salt
      • 将密码与盐一起md5,得到新的密码
      • 为了解决登录时随机数的问题,在将盐拼接在新密码前面
      • 在写入数据库

3. 登陆

  • 前端:将用户输入的数据传给后端
  • 后端:
    • 查数据,根据userName查询对应的数据
    • 将数据库中的密码去除,截取前6为,得到盐
    • 然后用相同的加密方法得到用户登陆时的输入的密码md5
    • 将盐+密码与数据库中的密码比较

4. 登陆凭证

当访问受保护的页面的时候,若用户没有登陆或者权限不够,则不能能够访问,强制重定向到登录页面。

  • 会话:

    会话是指用户与系统进行通讯的过程,一般应用于网络,而网络是基于 HTTP 通信的。

    而 HTTP 协议是一种无状态的协议,1次请求结束后,连接就会断开,而不会去维护连接时所传输的信息。 就是说当你登陆成功后,下一次请求后端,后端无法识别是否是上一次的用户。

    所以为了解决这个问题,需要通过一些手段,在用户登录成功后,去记录登录信息,那么下一次请求的时候,可以凭借这个登录信息去识别用户。

  • 会话的几种方式

    • session & cookie
    • token(jwt)
    • 三方登陆

4.1、cookie

​ 概念:存储在浏览器本地的缓存数据,每次浏览器在发起请求时,都会自动携带本站的cookie数据。

​ 优缺点:

  • 速度块

  • 安全性比较低,存储数据量较小

  • 存储位置:浏览器

    使用:

    1
    2
    3
    4
    5
    // 安装
    npm i cookie-parser
    // 配置app.js
    const cookie = require('cookie-parser')
    app.use(cookie())

4.2、session

概念:在服务端存储用户信息

优缺点:

  • 安全性较高
  • 占用服务其资源
  • 存储位置:服务器

4.3、工作原理

  • 浏览器带着 用户名 和 密码 访问 /login;

  • 服务器端验证 用户名 和 密码 正确后,那么会通过 session 生成会话信息,该信息保存在 session 中;

  • 然后服务端将 session 的 sessionId返回给浏览器存储在 cookie 中;

  • 之后浏览器的每一次请求,都会自动携带 cookie 信息的;

  • 服务器从请求中获取 cookie 信息,将得到的 sessionIdseesion 中的数据比对,如果存在则登录,正常返回数据,如果不存在则不给前端返回数据。

5. app.use

app.use([path], callback, [callback...])
当请求路径匹配的时候就会执行后面的回调函数。路径的默认值是 /,是针对于每一个请求路径的。

  • path:路径,默认 /,可选参数;

  • callback:回调函数,有四个参数
    ** 四个参数,依次为:err, req, res, next
    ** 三个参数,依次为:req, res, next
    ** 两个参数,依次为:req,res

  • next: 是一个函数,默认情况下,路径匹配后,会执行 app.use 的 callback,执行后当前程序就终止掉。如果调用 next 函数,那么程序就会向下传递,下一个匹配路径的中间件也会执行。

6. 路由拦截

需求: 网站中受保护的页面和请求很多,如何一次性进行判断?

关键:如何判断哪些路由是受保护的?(判断某一个路径不能访问)

规定:以 /protected 开头的路径被保护起来

处理:

  • 受保护的页面统一放在 public/protected 文件夹内;
  • 受保护的接口,统一以 /protected 开头;

7. 数据库连接池

场景:假如没有连接池,那么我们在使用JDBC程序的时候,就会反复的创建连接,销毁连接,这样做比较消耗资源,并不能做到连接的反复利用。这样做如果这个程序的用户人数很多,那么对性能的影响就会很大。

处理:所以为了优化性能,就需要自己去创建一个连接池,然后每次从连接池里面获取连接。使用完了连接,放回连接池,做到连接的反复使用

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
const mysqlConfig = {
host: 'localhost',
port: 3306,
user: 'root',
password: 'l19980907',
database:'demo3'
}

function query(sql, options) {
return new Promise((resolve, reject) => {
// 创建连接池
let pool = mysql.createPool(mysqlConfig)
pool.getConnection((err, connection) => {
console.log('连接池', connection);
if (err) {
reject(err)
} else {
connection.query(sql, options, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
}
// 释放连接池
connection.release()
})
})
}

module.exports = query