服务器
服务器架构
- B/S 架构:Browser(浏览器)/ Server(服务器)
- C/S 架构:Client(客户端)/ Server(服务器)
网络协议
网络协议,是指计算机伪类能够在网络中进行数据的交换,从而建立的一个规则,标准。
TCP/IP 协议
TCP/IP 协议,称为“网络通信协议”,是指互联网中最基本的协议。全世界所有的计算机都要遵循这个协议。
TCP/IP 协议是一个协议组,在协议下还包含小的协议:
- HTTP(S)
- TCP
- IP
- DNS
- ……
状态码
-
概念:指一次请求在处理中的实时状态。根据状态的不同,用指定3个数字的组合来表示请求的不同状态。
-
常见状态码
404:表示当前请求对应的资源没找到。资源可以是页面、图片、视频、路由。即Not found200:表示请求正常处理完毕。一般会表示请求成功304:指资源无修改,会直接使用缓存。500:指服务器程序错误。一般是指服务器程序有bug,即代码不对401:指请求未授权访问。即浏览器没有权限403:请求被服务器拒绝。比如防盗链,图片限制下载。
-
概念:指一次请求在处理中的实时状态。根据状态的不同,用指定3个数字的组合来表示请求的不同状态。
-
常见状态码
404:表示当前请求对应的资源没找到。资源可以是页面、图片、视频、路由。即Not found200:表示请求正常处理完毕。一般会表示请求成功304:指资源无修改,会直接使用缓存。500:指服务器程序错误。一般是指服务器程序有bug,即代码不对401:指请求未授权访问。即浏览器没有权限403:请求被服务器拒绝。比如防盗链,图片限制下载。
在浏览器中输入 URL 后发生什么
1.域名解析
“域名”就是我们平常说的网址:如https://www.xiaobinw.cn中的www.xiaobinw.cn就是我自己的域名。
真正访问一个计算机是通过计算机的 IP 地址去访问,但是 IP 地址不容易记忆,所以出现了域名。
“域名解析”指的是通过 DNS 服务器(域名解析服务器)对域名进行解析,找到对应的计算机 IP 地址。###
2.建立 TCP 链接
TCP (传输控制协议)用于保证计算机之间的数据传递的完整和安全性。
三次握手:
TCP 通过三次握手来保证数据的完整和安全,三次握手是为了保证客户端和服务端都处于正常的工作状态。
3.客户端发送请求服务器处理请求
TCP 连接成功后,浏览器就可以利用 HTTP 协议向服务器发送请求了。
服务器接收请求后,开始处理请求,处理完请求后,服务器将处理的结果返回(响应)给客户端
4.关闭 TCP 连接
客户端接收到服务器发送的数据后,需要通过 TCP 协议来断开与服务器的连接。
四次挥手
5.浏览器渲染页面
浏览器接收到服务器响应的数据后,开始对数据进行解析渲染。
Nodejs
Nodejs 简介
Nodejs 是基于 Chrome V8 引擎的 javascript 运行的。
- javascript 运行时:JS 代码的运行环境
- Chrome V8 引擎
- 浏览器内核:主要分为“渲染引擎”和 “JS 引擎”
- 渲染引擎:负责解析 HTML 和 CSS 代码
- JS 引擎:赋值解析 JS 代码
- V8 引擎是谷歌公司开发的一款 JS 引擎,目前公认的解析 JS 代码最快的引擎
- 浏览器内核:主要分为“渲染引擎”和 “JS 引擎”
Nodejs 让 javascript 代码可以运行在服务端
命令行工具
cd:进入某个文件夹
- cd 后面可以跟某个路径(建议直接拖)
- cd 后面还可以跟文件夹名字(该文件夹必须当前路径的子文件)
模块化
每个 JS 文件都是独立的模块,模块之间是没有关联的。默认情况下模块之间不能进行数据交换。
以前“模块化”是后端的一种概念,前端没有模块化,从 ES6 开始前端也引入模块化的概念。
前端模块化
引入 JS
-
在 HTML 引入 JS:需要添加一个
type="module"属性-
<script src="./index.js" type="module"></script>
-
-
在 JS 中引入 JS:一个模块中加载另一个模块
import '相对路径'-
import './a.js'
-
暴露和引入
1.1 暴露export default
function foo() {
console.log('foo')
}
export default { a: 1, foo: foo }
1.2 引入import 变量名 from '相对路径'
import a from './a'
console.log(a.a);
a.foo()
2.1 暴露export
export let a = 1
export function foo {
console.log('hello foo')
}
2.2 引入import { 变量名 } from '相对路径'
import { a } from './a.js'
// 重命名
import { a as A } './a.js'
后端模块化
CommonJS
ECMAscript 是一种规范,javascript 是这个规范的实现
CommonJS 是一种规范,NodeJS 是这个规范的实现
- 在 JS 中引入 JS:
require('./a.js')
-
暴露和引入 JS:
- 暴露
module.exports.a = 10 module.exports = { a:10 }- 引入
const a = require('./a.js') const { a } = require('./a.js')
注意:
- 当前使用 require 去加载某个模块时,除了第一次加载会运行文件以外(第一次运行完成后会进行缓存),后续的加载都会从缓存中读取文件内容。换句话说同一个文件被 require 多次,只执行一次。
- require 中的 JS 文件后缀可以省略。
- require 中的路径如果有模块名,那就说明引入的是第三方下载的模块或者是 NodeJS 原生自带的模块
内置模块
fs
- 读取文件
fs.readFile异步
// 异步
fs.readFile('./test.txt', 'utf-8', (err, data) => {
if (err) {
console.log('异步读取失败:', err);
} else {
console.log('异步读取成功', data);
}
})
- 读取文件
fs.readFileSync同步
// 同步
try {
const result = fs.readFileSync('./test.txt', 'utf-8')
console.log('同步读取成功', result);
} catch (error) {
console.log('同步读取失败', error);
}
fs.writeFile往文件里写入内容 (会覆盖,会自动创建文件(但不会创建文件夹))
fs.writeFile('./test.txt', 'hello world', (err) => {
if (err) {
console.log('写入失败', err);
} else {
console.log('写入成功');
}
})
fs.appendFile往文件追加内容
fs.appendFile('./test.txt', '\n这是新加的内容', (err) => {}
fs.copyFile复制文件
fs.copyFile('./test.txt', './test2.txt', (err) => {}
fs.unlink文件删除
fs.unlink('./test.txt', (err) => {}
fs.rename文件重命名
fs.rename('./test.txt','test1.txt',(err)=>{}
fs.mkdir创建文件夹(只能创建一个文件夹)
fs.mkdir('./public', (err) => {
if (err) {
console.log('创建文件夹失败', err);
} else {
console.log('创建文件夹成功');
}
})
fs.rmdir删除文件夹(只能删除空文件夹)
fs.rmdir('./a', (err) => {}
fs.readdir读取文件夹内容
fs.readdir('./a', (err, data) => {}
fs.access判断文件(文件夹是否存在)
fs.access('./a/b/c', (err) => {}
fs.stat查看文件(文件夹状态):判断是文件还是文件夹
fs.stat('./a', (err, stats) => {
if (err) {
console.log('查看失败', err);
} else {
const isFile = stats.isFile()
const idDir = stats.isDirectory()
console.log(isFile, idDir);
}
})
path
path 获取
basename():获取指定路径中最后一部分
const path = require('path')
const myPath = __dirname + '\\a.js'
console.log(path.basename(myPath));
dirname():获取指定路径中除了 basename 以外的其他路径
console.log(path.dirname(myPath));
extname():获取指定路径对应的扩展名
console.log(path.extname(myPath));
path 组合
join():相对路径
console.log(path.join('a','b','c')); // a/b/c
resolve():绝对路径
console.log(path.resolve('a','b','c')); // E://xxx/a/b/c
path 拆分
parse():从对象返回路径字符串
console.log(path.parse(myPath));
/* {
root: 'E:\\',
dir: 'E:\\nodejs\\02-nodejs模块',
base: 'a.js',
ext: '.js',
name: 'a'
} */
http
const http = require('http');
// 创建本地服务器来从其接收数据
// request(req):请求对象,包含了请求相关的数据和方法
// response(res):响应对象,包含响应相关的数据和方法
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!'
}));
});
server.listen(3000,()=>{
console.log('服务器开启成功端口号为:http://localhost:3000/')
}); // http://localhost:3000/
异步
同步异步
- 同步:在同一时间段内,只能做一件事情
- 异步:在同一时间段内,可以同时做多件事情
javascript 是单线程语言,从头到尾,从左到右,有错误就停止运行。
Ajax
- 可以实现与服务器的异步通信
- 局部刷新页面
异步解决的发展
- 回调函数
- Promise(ES6)
- generator(ES6)
- async / await(ES7):异步终极解决方案
Promise
const p = new Promise((resolve, reject)=>{
// 异步代码
$.ajax({
url:'/users',
success(msg){
// 成功回调
resolve(msg)
}
})
})
// 成功执行
p.then((msg) => {
return new Promise((resolve, reject) => {
$.ajax({
url: '/users',
data: {
id: msg.id
}
})
}).then((msg)=>{})
})
// 失败执行
p.catch(()=>{})
async/await
async:用于定义一个异步函数,异步函数的返回值是 Promise 对象
async function foo() { }
foo()
await:一般用于等待一个 Promise 对象,实际上就是等待一个异步处理结果
const p = new Promise((resolve, reject)=>{
// 异步代码
resolve({id:1})
})
async function foo() {
const msg = await p
console.log(msg) // {id:1}
}
foo()
axios
- 发起get请求:
axios.get(请求地址,{params: 参数}).then(function (res) {});
- 发起post请求:
axios.post(请求地址,{data: 参数}).then(function (res) {});
axios.post(请求地址, 参数).then(function (res) {});
- 综合方法:
axios({
method: 请求类型,
url: 请求地址,
params: get请求发送的数据,
data: post请求发送的数据,
})
.then(function (res){})
.cache(function (error){})
注意:res不是服务器返回的原始数据,是axios加工之后的数据;res.data是服务器返回的数据;
Express
Express 是一个基于 Nodejs 的服务框架
Express 应用程序生成器
Express 应用生成器,名称:express-generator。他的作用是可以帮助开发者快速创建一个 express 项目
全局安装
执行一下命令,来全局安装 Express 应用生成器:express-generator
npm i express-generator -g
Express 项目创建
- 通过应用程序生成器来快速创建 Express 项目
express 项目名称
- 进入到项目目录
cd 03-express
- 下载所有的依赖包
npm install
- 运行服务器
npm start
- 访问项目
http://localhost:3000
MongoDB
数据库(database),用于数据的管理。
- 关系型数据库:MySQL
- 非关系型数据库:MongoDB
使用方法
- 命令行操作 MongoDB
- 可视化图形工具中操作 MongoDB
- 通过后端代码去操作 MongoDB
1.在命令行中操作 MongoDB
找到 MongoDB 安装包中的 bin 目录下的 mongodbsh.exe
常用命令
-
查看当前 MongoDB 服务器中所有的数据库
show dbs -
查看当前执行的数据库
db -
新建/切换数据库
use 数据库名 -
查件当前数据库中所有的结合
show collections -
往集合中添加数据(如果集合不存在,会自动创建该集合)
db.集合名称.insert({name:'小斌',age:'19',sex:'男'}) -
查看某个集合中所有数据
db.集合名称.find() -
后续命令省略,因为命令行太他妈麻烦了,了解就好需要百度
2.可视化图形操作 MongoDB
简单不用做笔记
3.后端代码操作 MongoDB
mongoose 是 NodeJS 中提供的一个便捷操作 MongoDB 的库
- 下载
npm i mongoose --save
- 连接 MongoDB
// 连接 MongoDB 将 express 项目与 MongoDB 服务器连接起来
const mongoose = require('mongoose')
// 项目需要连接的数据库地址
const dbURI = 'mongodb://localhost:27017/xiaobin'
// 关闭警告
mongoose.set('strictQuery', true);
// 连接数据库
mongoose.connect(dbURI)
// 当数据库连接成功时触发下面事件
mongoose.connection.on('connected', function () {
console.log(dbURI + '数据库连接成功');
})
- 数据库集合相关配置(models)
// 1.定义数据集合的结构:定义集合中数据有哪些属性,属性的值类型是什么类型
const { Schema, model } = require('mongoose')
const usersSchema = new Schema({
userName: String,
password: String
})
// 2.定义数据集合的模型:将 schema 和数据库中的数据关联起来
// model('模型名称','schema名称','数据库中的集合名称')
const usersModel = model('usersModel', usersSchema, 'users')
router.post('/login', async function (req, res, next) {
// 1.接收前端发送的数据
// post:req.body
// get: req.query
const result = await usersModel.find(req.body)
if (result.length > 0) {
res.send({
message: '登录成功',
status: 1
})
}
});
操作数据
- 查询,查找
// 按条件查询
usersModel.find( {userName:'xiaobin'} )
// 查询所以数据
usersModel.find( )
- 新增,删除,修改
// 新增
usersModel.create( {userName:'xiaobin'} )
// 删除(一个)
usersModel.deleteOne( {_id: 1'} )
// 删除(多个)
usersModel.deleteMany( {userName:'xiaobin'} )
// 修改
usersModel.updateOne( {_id: 1}, {userName:'xiaobin'} )
注:以上所以方法都是异步方法,且这些方法的返回值都是 Promise 对象,因此需要通过 await 去等待结果。
结构及封装
三层结构
- 通过前端将数据发送到表现层
axios({
// 设置请求类型
method: 'post',
// 设置前后端链接接口
url: '/users/login',
// 设置前端发送给后端的数据
data: { }
})
.then((response) => { })
.catch((error) => { });
- 配置数据库连接(database)
// 连接 MongoDB
const mongoose = require('mongoose')
// 项目需要连接的数据库地址
const dbURI = 'mongodb://localhost:27017/xiaobin'
// 关闭警告
mongoose.set('strictQuery', true);
// 连接数据库
mongoose.connect(dbURI)
// 当数据库连接成功时触发下面事件
mongoose.connection.on('connected', function () {
console.log(dbURI + '数据库连接成功');
})
- 配置 app.js
// 引入路由
var usersRouter = require('./routes/users');
// 引入第三层(持久层)
require('./dao/database')
// 用于配置 Ajax 请求的一级路径
app.use('/users', usersRouter);
- 配置第一层表现层(routes)
var express = require('express');
var router = express.Router();
// 二级路径
router.get('/login', function(req, res, next) {
// req.body: 获取 get 请求数据
// req.query: 获取 post 请求数据
res.send( );
});
module.exports = router;
- 配置第二层服务层(service)
// 引入第三层
const { daoLogin } = require('../dao/usersDao')
// 登录
async function serviceLogin(user) {
// 调用第三层
const data = await daoLogin(user)
}
module.exports = {serviceLogin}
- 配置第三层持久层(Dao)
// 引入模型
const { usersModel } = require('./models/usersModel')
// 登录
async function daoLogin(user) {
return await usersModel.find(user)
}
module.exports = { daoLogin }
- 配置模块(models)
// 1.定义数据集合的结构:定义集合中数据有哪些属性,属性的值类型是什么类型
const { Schema, model } = require('mongoose')
const usersSchema = new Schema({
userName: String,
password: String
})
// 2.定义数据集合的模型:将 schema 和数据库中的数据关联起来
// model('模型名称','schema名称','数据库中的集合名称')
const usersModel = model('usersModel', usersSchema, 'users')
module.exports = { usersModel }
- 端口更改
app.listen(3000, () => {
console.log('服务器开启成功端口号为:http://localhost:3000');
});
express 方法
- 深层嵌套
.populate({
path: 'classId', // 第一层关联路径
populate: {
path: 'teacherId' // 第二层关联路径
}
})
- 请求限制
.limit(请求条数 - 0) // 设置请求条数
- 请求跳过
.skip((请求页数 - 1) * 请求条数) // 跳过数据的条数
- 关联集合
const studentsSchema = new Schema({
// 用于关联classes集合
classId: {
type: Schema.Types.ObjectId, // id
// ref 用于设置要关联的集合模型名称
ref: 'classesModel'
}
})
查询
- 全部查询,带数据劫持
// 获取学生数据
async function daoGetStudents({ pageSize(条), currentPage(页) }) {
// 获取数据的总条数
const total = await studentsModel.countDocuments()
// 计算总页数
const pages = Math.ceil(total / pageSize)
const students = await studentsModel
.find()
// 关联.populate('classId')
.populate({
path: 'classId',
populate: {
path: 'teacherId'
}
})
.limit(pageSize - 0) // 设置请求条数
.skip((currentPage - 1) * pageSize) // 跳过数据的条数
return {
total, pages, students
}
}
- 添加查询不带数据劫持
// 条件渲染
async function searchStudents(params) {
const students = await studentsModel.find({
// 键 值,不区分大小写
[params.selectValue]: { $regex: params.searchValue, $options: 'i' }
})
.populate({
path: 'classId',
populate: {
path: 'teacherId'
}
})
return {
students
}
}
图片上传
- 图片上传流程
主要流程
- 前端部分
- 页面上设置一个上传文件的 input标签,并设置onchange事件
- 通过onchange事件获取要上传的文件数据并通过ajax传给后端
- 后端部分
- 需要通过npm安装图片处理插件
multer - 单独新建一个处理图片的路由文件,即
routes/upload.js - 在第二步新建js中引入文件
handlerFile.js,用于在nodejs环境下配合multer插件完成图片上传 - 将图片上传后的服务器地址传回前端。前端就可以获取上传的图片的地址。可以通过img标签进行展示
- 需要通过npm安装图片处理插件
handleFile 封装工具
const multer = require('multer')
const fs = require('fs')
const path = require('path')
/**
* 文件上传
* 参数说明:接收一个 options 对象作为参数,该对象包含两个属性
* - path: 图片上传路径
* - key:与前端 formData 对象的 fieldname 相匹配(即: formData.append()方法的第一个参数)
* - size:设置图片大小
*/
function uploadFiles(options = {}) {
// 1.对参数 options 进行结垢并设置默认值
let { path = './public/temp', key = 'file', size = 1000 } = options
// 2.设置 multer 的参数,配置 diskstorage 来控制文件存储的位置及文件名
let storage = multer.diskStorage({
// 2.1 确定图片位置
destination: function (req, res, cb) {
try {
fs.accessSync(path)
} catch (error) {
fs.mkdirSync(path)
}
cb(null, path)
},
// 2.2 确定图片存储时的名字(注意:如果使用原名,可能会造成再次上传同一张图片)
filename: function (req, file, cb) {
let changedName = new Date().getTime() + '-' + file.originalname
cb(null, changedName)
}
})
// 3. 配置图片限制
let limits = {
// 限制文件大小
fileSize: 1024 * size,
// 限制文件数量
files: 5
}
// 4.生成的专门处理上传的一个工具,可以传入 storage。limits等配置
let upload = multer({ storage, limits })
// 5.返回多文件上传设置信息(同样可以上传单文件)
return upload.array(key)
}
/**
* 文件复制
* fromPath:源文件路径
* - toPath:要复制过去的新文件
* - filename:文件名
*/
function copyFiles(options = {}) {
let { fromPath = './public.temp/', toPath = './public/images/', filename } = options
let sourceFile = path.join(fromPath, filename)
let destPath = path.join(toPath, filename)
try {
fs.accessSync(toPath)
} catch (error) {
fs.mkdirSync(toPath)
}
let readStream = fs.createReadStream(sourceFile)
let writeStream = fs.createWriteStream(destPath)
readStream.pipe(writeStream)
}
/**
* 文件移动
* - fromPath:源文件路径
* - toPath:要复制过去的新文件
* - filename:文件名
*/
function moveFiles(options = {}) {
let { fromPath = './public/temp/', toPath = './public/images/', filename } = options
let sourceFile = path.join(fromPath, filename)
let destPath = path.join(toPath, filename)
try {
fs.accessSync(toPath)
} catch (error) {
fs.mkdirSync(toPath)
}
fs.renameSync(sourceFile, destPath)
}
/**
* 删除文件
* - filePath:文件路径
*/
function removeFiles(filePath = './public/temp') {
let stats = fs.statSync(filePath)
if (stats.isFile()) {
// 删除文件
fs.unlinkSync(filePath)
} else if (stats.isDirectory()) {
let filesArr = fs.readdirSync(filePath)
filesArr.forEach(file => {
removeFiles(path.resolve(filePath, file))
})
}
fs.rmdirSync(filePath)
}
module.exports = {
uploadFiles, copyFiles, moveFiles, removeFiles
}
前端部分
-
页面部分:input标签,并设置onchange事件
<input type="file" id="uploadInput"> <h1>上传图片展示</h1> <img src="" alt="" id="uploadImg"> -
ajax部分:获取图片数据,并包装为formData格式,并通过ajax发送到后端。需要注意的是,为了防止jQuery自动处理,需要传递两个参数来取消jQuery的表单自动处理
$('body').on('change', '#uploadInput', function () { //获取文件数据 let file = this.files[0]; //以表单数据方式将文件发送到后端 let fd = new FormData(); fd.append("file", file); //通过ajax发送到后端 $.ajax({ url: '/upload/image', success: function (data) { console.log(data); }, data: fd, //设置参数,防止jQuery自动处理,以便将文件数据原封不动的传给后端处理 contentType: false, processData: false, cache: false, }); })
时间插件
- 下载
npm i moment
- 引入
const moment = require('moment')
- 使用格式化时间
time: {
type: String,
default: moment().format('YYYY-MM-DD HH:mm')
}
后端部分
-
安装
multer插件npm i multer -
新建路由并在app.js中配置
var express = require('express'); var router = express.Router(); module.exports = router; -
设置图片处理的相关代码
// 引入handlerFile.jsconst { uploadFiles } = require('./../util/handleFiles'); router.post('/image', function (req, res, next) { //会针对传入的图片生成一个专门上传的图片函数 let path = 'assets'; //自定义保存图片的文件夹,会直接放在/public下 const uploadImages = uploadFiles({ path: `./public/${path}`, //formdata.append()的第一个参数 key: 'file', //图片的大小限制,1mb以内 size: 1024 }); //上传函数 uploadImages(req, res, (err) => { //图片上传失败或成功都会调用的回调函数 if (err) { //上传失败 console.log(err); res.send({ message: "上传失败", code: -1 }) } else { //上传成功 //将上传成功之后图片的路径发送给前端 res.send({ message: '上传成功', data: `../${path}/${req.files[0].filename}`, code: 200 }) } }) }) -
前端根据返回的地址进行展示
身份认证
session 的身份验证流程
- 用户输入信息
- 服务器验证是否正确,如果正确就创建 session 并存入数据库
- 服务端向客户端返回带有 session 的 cookie
- 在接下来的每次前端请求中都会带 cookie 服务器吧 sessionID 与数据库中的相匹配
- 入伙退出登录,session 会在客户端和服务端都销毁
JWT 的身份验证流程
- 用户输入信息
- 服务器验证登录信息,如果正确返回一个已签名 token (加密字符串)
- 前端将 token 存储在客户端,一般存在 localStorage 中,但也存在 sessionStorage 和 cookie
- 接下来每次前端请求都会带上 token
- 服务器验证 token 如果 token 有效继续发送求情
- 如果退出,token 会在客户端销毁,而这一步和服务器无关
步骤
-
安装
-
jsonwebtoken:用于生成 token
-
express-jwt:用于验证 token
-
npm i jsonwebtoken express-jwt
- 发送请求,服务端生成 token
const jwt = require('jsonwebtoken')
const token = jwt.sign(
{ userName }, // 用户信息
'abc', // 秘钥
{ expiresIn: 60 } // 设置token的有效期
)
- 用户登录,客户端保存 token
localStorage.setItem('token(属性名))', response.data.token(属性值))
- 验证页面,发送 token 请求
// 验证用户是否登录
function isLogin() {
axios({
method: 'post',
url: '/users/isLogin',
// 请求头
headers: {
'Authorization': 'Bearer ' + localStorage.token,
}
}).then((response) => {
console.log(response.data);
}).catch((error) => {
if (error.response.status == 401) {
alert('身份信息已过期,请从新登录')
location.href = './login.html'
}
});
}
isLogin()
- app.js 验证 token
var { expressjwt: expressJWT } = require("express-jwt");//解析JWT
app.use(expressJWT({
secret: 'abc', // 生成 token 时配置的秘钥字符串
algorithms: ["HS256"], // 设置 jwt 的算法为 HS256
credentialsRequired: false // 对于没有 token 的请求是否进行解析
}).unless({
path: ['/users/login', '/users/register', '/users/isAccess'], // 设置不需要验证 token 的路径
}))
- 一级路径,验证是否登录
// 是否登录
router.post('/isLogin', async function (req, res, next) {
// 1. 获取到 token
const token = req.get('Authorization').split(' ')[1]
// 2. 解码 token
const { userName } = jwt.verify(token, 'abc')
res.send()
}
JWT 与 session 的区别
JWT 与 session 两种认证方法的区别有两个:
-
一是在于用户状态保存的位置,session 将用户状态保存在服务端,而 JWT 将用户状态保存在客户端
-
二是 session 依赖于 cookie 而实现,在每次请求是需要带上cookie 取出响应字段与服务器端的警醒对比,而实现身份的认证,而 JWT 仅仅需要在 HTTP 的都不附上 token 由服务器 sheck signature 即可实现,无需担心 cookie 存在的 CORS 问题
RESTful
REST表现层状态装换,实际就是一种接口的命名规范。
-
url(请求路径)用来定义资源名称
-
method(请求类型)用来定义资源动作
请求类型
-
get(获取,查询)
-
pust(新增)
-
put(修改)
-
delete(删除)
请求方式
如果传递的数据中有一个唯一值 _id,我们会将 _id 放到 url 中。
// 前端(删除)
axios({
method: 'delete',
url: `/students/${_id}`,
}).then((response) => { }
// 后端(删除)
router.delete('/:_id', async function (req, res, next) {
const data = await servDelStudents(req.params._id)
res.send(data)
});
跨域
同源策略
-
一个请求至少三部分组件: 协议 + IP + 端口。
-
同源策略是浏览器的安全策略,他要求协议 + IP + 端口三者都必须一致,则为同源。
-
当我们在一个页面中,向服务器发送其他请求时,页面地址和请求地址,协议,IP,端口三者不一致则为跨域
跨域解决方案
- JSONP
- CORS
- 代理服务器
JSONP
// 前端
$.ajax({
method: 'get',
url: '/classes',
dataType: 'jsonp', // 解决跨域
success(msg){}
})
// 后端
router.get('/', async function (req, res, next) {
const data = await getClasses()
res.jsonp(data) // 解决跨域
});
缺点:只能解决 get 请求类型的跨域。
CORS
插件
cors 是 Express 的一个第三方中间件,通过安装和配置 cors 中间件,可以很方便地解决跨域问题
使用步骤分为三步
- 运行
npm install cors安装中间件 - 使用
const cors = require('cors')导入中间件 - 在路由之前调用
app.use(cors())配置中间件
代码
//设置CORS
app.all('*', function (req, res, next) {
//当允许携带cookies此处的白名单不能写’*’
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
//允许的请求头
res.header('Access-Control-Allow-Headers', 'X-Requested-With,Content-Type, Origin,Accept,Authorization');
//允许的请求方法
res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT');
//允许携带cookies
res.header('Access-Control-Allow-Credentials', true);
next();
});
代理服务器
全局安装应用生成工具
npm i express-generator -g
1.创建代理服务器
以下命令,在指定目录中创建项目
express 项目名
2.安装依赖
// 进入项目
cd 项目名
// 安装依赖
npm install
3.安装中间层
代理服务器作为客户端和目标服务器之间的中间层,当客户端将请求发送到代理服务器后,代理服务器需要借助http-proxy-middleware中间件,在把请求代理转发到目标服务器
npm i http-proxy-middleware
4.修改服务器端口
代理服务器和目标服务器的端口不能相同,代理服务器端口在bin/www文件中修改
5.配置请求转发
将代理服务器中的请求转发到目标服务器,两种方式:
- 在代理服务器中接收到请求后,通过
http-proxy-middleware中间件直接转发至目标服务器 - 在代理服务器中接收到请求后,完成一些额外的操作(例如密码加密处理)在通过
request-promise模块转发至目标服务器
- http-proxy-middleware 转发
找到项目应用根目录中的
app.js文件,在该文件中引入http-proxy-middleware
const { createProxyMiddleware } = require('http-proxy-middleware')
const option = {
// 目标服务器
target: 'http://localhost:3000',
// 默认 false 是否需要改变原始主机头为目标 URL
changeOrigin: true,
// 重写请求
pathRewrite: {
// 所有以 '/api'开头的请求,会重写为 '/'
'^/api': '/',
}
}
var app = express();
// 这行必须在express下面 proxy(options)即创建代理
app.use('/api',createProxyMiddleware(option))
- request-promise 转发
虽然客户端的大部分请求我们都可以通过代理服务器 http-proxy-middleware 中间件转发至目标服务器,但是类似于登录注册这种特殊请求,我们需要先在代理服务器处理(例如密码加密),然后发送到目标服务器,那么这个时候http-proxy-middleware中间件的自动转发就不可用了
因此这种情况下我们选择另一种模块--request-promise来处理我们的请求转发
安装依赖
npm i request request-promise
什么地方使用就调用
const rp = require('request-promise')
router.post('/login', async (req, res, next) => {
// 。。。进行密码加密操作
const data = await rp({
method: 'post',
uri: 'http://localhost:3000/users/login',
body: req.body,
json: true
})
res.send(data)
})