node

369 阅读25分钟

1. 初步认识node.js

1.1 回顾

1.1.1 为什么js可以在游览器中被执行?

浏览器中有js解析引擎 chrome:v8 性能最好 safri:JSCore 。。。

1.1.2 为什么js可以操作浏览器的DOM和BOM

每个游览器内置了DOM、BOM这样的API函数。浏览器中的js可以调用他们。

1.1.3 浏览器中的js运行环境

浏览器的引擎、内置API

1.1.4 JS能否做后端开发?

可以,需要借助nodejs

1.2Nodejs简介

1.2.1 什么是node.js

Node.js是一个基于v8引擎的js运行环境

1.2.2 nodejs中的js运行环境

  • 浏览器是js的前端运行环境
  • nodejs是js的后端运行环境
  • nodejs无法调用DOM和BOM等浏览器内部的API

1.2.3 node怎么学?

js基础语法+node内置api模块(fs、path、http)+第三方api模块(express、mysql等)

1.3nodejs的环境安装

区分LTS和current版本

  1. LTS:稳定版
  2. current:开发版本,存在bug

1.4 在nodejs环境中执行js代码

  1. 打开终端
  2. 输入 node js目录

2. fs文件系统模块

2.1什么是fs文件系统模块

fs模块是node官方提供的、用来操作文件的模块。

// 导入
const fs = require('fs')

fs.readFile() //读取文件内容
fs.writeFile() //写入内容

2.2读取指定文件中的内容

1。fs.readFile()格式

fs.readFile(path,[encoding],callback)
// 参数1:必选参数,表示文件的路径
// 参数2:可选参数,表示以什么编码格式来读取文件
// 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果。

2。实例代码

const fs = require('fs')
fs.readFile('./file/11.txt','utf8',function(err,dataStr){
  console.log(err); // 成功返回null
  console.log(dataStr); // 失败返回undefined
})

3。判断文件是否读取成功

通过err对象是否为null

const fs = require('fs')
fs.readFile('./file/11.txt','utf8',function(err,dataStr){
  if(err){
    return console.log('文件读取失败!'+err.message)
  }
  console.log('文件读取成功,内容是'+dataStr)
})

2.3向指定的文件中写入内容

1。fs.writeFile()的语法格式

fs.writeFile(file,data,[encoding],callback)
// 参数1:必选参数,文件存放路径
// 参数2:必选参数,要写入的内容
// 参数3:可选参数,文件编码格式
// 参数4:必选参数,回调函数

2。示例代码

const fs = require('fs')
fs.writeFile('./222.txt','hello,nodejs','utf8',(err)=>{
    console.log(err) //写入成功返回null
})

3。判断文件是否写入成功

4。路径必须存在,且新写入的内容会覆盖之前的旧内容。

2.5 练习

读取文件 小红=99 小白=100 小黄=70 小黑=66 小绿=88 写入新文件内容 小红,99 小白,100 小黄,70 小黑,66 小绿,88

const fs = require('fs')
fs.readFile('../../day1_all/素材/成绩.txt','utf8',(err,dataStr)=>{
    if (err){
        return console.log('读取成绩文件失败'+err.message)
    }
    const dataArr = dataStr.split(' ')
    const dataNew = []
    dataArr.forEach((item)=>{
        dataNew.push(item.replace('=',','))
    })
    fs.writeFile('./成绩_ok.txt',dataNew.join('\r\n'),'utf8',(err)=>{
        if (err){
            return console.log(err.message)
        }
        console.log('写入成功!')
    })
})

2.6 fs模块 - 路径动态拼接的问题

出现路径拼接问题,是因为提供了 ./ 或者 ../ 开头的相对路径 解决方法: 绝对路径:移植性差。不利于维护。 __dirname:当前文件所处目录 image.png

2.7 path模块

path模块是node官方提供的、用来处理路径的模块。它提供了一系列的方法和属性。

  1. path.join() 用来将多个路径片段拼接成一个完整的路径字符串
  2. path.basename() 从路径字符串中,将文件名解析出来
  3. path.extname() 获取文件的扩展名
const path = require('path')

path.join()代码示例

const pathStr = path.join('/a','/b/c','../','d','e')
console.log(pathStr) // 输出 \a\b\d\e

const pathStr2 = path.join(__dirname,'files/1.txt')
console.log(pathStr2) // 输出 当前文件所处目录\files\1.txt

注意: ../ 会抵消前面的一层路径

path.basename()代码实例

const fpath = '/a/b/c/index.html'

var fullName = path.basename(fpath)
console.log(fullName) //输出 index.html

var nameWithoutExt = path.basename(fpath,'.html')
console.log(nameWithoutExt) //输出 index

path.extname()代码示例

const fpath = '/a/b/c/index.html'

const fext = path.extname(fpath)
console.log(fext) // 输出 .html

2.8 http模块

用来创建web服务器的模块 http.createServer()

域名

域名服务器(DNS)将域名转化为IP地址。

创建web服务器的基本步骤

  1. 导入http模块
    1. const http = require('http')
  2. 创建web服务器实例
    1. const server = http.createServer()
  3. 为服务器实例绑定request事件,监听客户端请求
// 使用服务器实例的 .on()方法
server.on('request',(req,res)=>{
  console.log('Someone visit our web server')
})

其中

  1. req
    1. req是请求对象,它包含了与客户端相关的数据和属性
    2. req.url 是客户端请求的url地址
    3. req.method是客户端的method请求类型
  2. res
    1. res是响应对象
    2. res.end(),向客户端发送指定内容
    3. res.setHeader('Content-Type',"text/html; charset=utf-8") 防止响应内容中文乱码
  3. 启动服务器
server.listen(80,()=>{
  console.log('http server running at http://127.0.0.1')
})

案例:

// 导入http模块
const http = require('http')
// 创建web服务器
const server = http.createServer()
// 绑定request事件,监听客户端的请求
server.on('request',(req,res)=>{
    const str = `请求的url为:${req.url},请求的方式为:${req.method}`
    res.setHeader('Content-Type',"text/html; charset=utf-8")
    res.end(str)
})
server.listen(8081,()=>{
    console.log('server is running at http://127.0.0.1:8081')
})

根据不同的url响应不同的html内容

// 导入http模块
const http = require('http')
// 创建web服务器
const server = http.createServer()
// 绑定request事件,监听客户端的请求
server.on('request',(req,res)=>{
    // 设置默认提示信息
    let content = '<h1>404 Not Find<h1>'
    // 进行url判断
    if (req.url === '/' || req.url === '/index.html') {
        content = '<h1>欢迎来到首页!<h1>'
    } else if (req.url === '/about.html'){
        content = '<h1>关于页面<h1>'
    }
    // 设置响应头
    res.setHeader('Content-Type','text/html; charset=utf-8')
    res.end(content)
})
// 监听端口
server.listen(8081,()=>{
    console.log('server is running at http://localhost:8081')
})

时钟案例

3. 模块化

优点

  1. 提高了代码的复用性
  2. 提高了代码的可维护性
  3. 实现了按需加载

模块化规范

例如:

  • 使用什么样的语法格式来引用模块
  • 在模块中使用什么样的语法格式向外暴露成员

分类

  1. 内置模块
    1. 由nodejs官方提供的,例如fs、path、http
  2. 自定义模块
    1. 用户自己创建的js文件,都是自定义模块
  3. 第三方模块
    1. 由第三方开发出来的模块,使用前需要先下载

加载模块

使用require方法

// 1. 加载内置的fs模块
const fs = require('fs')

// 2. 加载用户自定义模块 可以省略.js后缀
const customer = require('./customer.js')

// 3. 加载第三方模块
const moment = require('moment')

使用require() 方法加载其他模块时,会执行被加载模块中的代码

模块作用域

在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,无法被外界访问。

module.exports对象

在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。 默认为{} 案例: 定义自定义模块

const age = 20

// 向module.exports对象上挂载属性
module.exports.age = age
module.exports.username = 'tiank'
module.exports.sayHello = ()=>{
    console.log('tiank hello')
}

加载自定义模块

const test = require('./02module向外共享test')
console.log(test)
// { username: 'tiank', sayHello: [Function (anonymous)] }

永远以module.exports指向的对象为准 例如: 定义自定义模块

const age = 20

// 向module.exports对象上挂载属性
module.exports.age = age
module.exports.username = 'tiank'
module.exports.sayHello = ()=>{
    console.log('tiank hello')
}
// 让module.exports指向一个全新的对象
module.exports = {
  nickname = '小黑',
  sayHi() {
    console.log('Hi')
  }
}

加载自定义模块

const test = require('./02module向外共享test')
console.log(test)
// { nickname: '小黑', sayHi: [Function: sayHi] }

exports对象

默认情况下,exports和module.exports指向同一个对象。

console.log(exports === module.exports)
// true

注意: 这里只是把exports的指针指向了module.exports,如果更改了exports的指向,那么二者就不再是一个对象,此时require模块得到的是module.exports所指向的对象 例如:

const age = 20

// 向module.exports对象上挂载属性
module.exports.age = age
module.exports.username = 'tiank'
module.exports.sayHello = ()=>{
    console.log('tiank hello')
}
console.log(exports === module.exports)  // true

// 不改变指向赋值
exports.name = 'tiank2'
console.log(exports === module.exports)  // true

// 让module.exports指向一个全新的对象
exports = {
    nickname : '小黑',
    sayHi() {
        console.log('Hi')
    }
}
console.log(exports === module.exports)  // false

4. 包和npm

包是基于内置模块封装出来的,提供了更高级、更方便的API,极大的提高了开发效率

npm

Node Package Manager(npm包管理工具),这个包管理工具随着Node.js的安装包一起安装用户的电脑上。

安装指定版本的包

通过@版本号

npm i moment@2.22.2

包的语义化版本规范

例如:2.24.0

  1. 第一位数字:大版本
  2. 第二位数字:功能版本
  3. 第三位数字:bug修复版本

包管理配置文件

在项目根目录中,必须提供一个叫做package.json的包管理配置文件。 作用:用来记录项目中安装了哪些包,从而方便剔除node_modules目录之后,在团队成员之间共享项目的源代码。

快速创建package.json

npm init -y

运行npm install命令,会自动把包的名称和版本号,记录到package.json文件中。

dependencies

专门记录安装了哪些包,开发和项目上线之后都会用到

devDependencies

如果某些包只在项目开发阶段会用到,在项目上线后不会用到,那么这些包就记录到devDependencies中。

// 简写
npm i 包名 -D
// 完整写法
npm install 包名 --save-dev

一次性安装所有包

npm install

卸载包

npm uninstall

npm uninstall moment

卸载的包,会自动从package.json中的dependencies中移除掉。

解决下包速度慢

默认从国外的registry.npmjs.org/服务器进行下载,会很慢 解决方式:切换淘宝npm服务器为npm下包镜像源

// 查看当前的下包镜像源
npm config get registry
// 将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/

为了更方便的切换下包镜像源,可以安装nrm这个工具

// 通过npm,将nrm安装为全局可用的工具
npm i nrm -g

// 查看所有可用的镜像源
nrm ls

// 将下包的镜像源切换为 taobao 镜像
nrm user taobao 

全局包

npm install 如果提供了-g参数,则会把包安装为全局包。 注意:

判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明

规范的包结构

  1. 必须以单独的目录而存在
  2. 包的顶级目录下必须包含package.json这个包管理配置文件
  3. package.json中必须包含name、version、main这三个属性,分别代表包的名字、版本号、包的入口

开发自己的包

初始化包的基本结构

  1. 新建 my 文件夹,作为包的根目录
  2. 在 my 文件夹中,新建如下三个文件:
    1. package.json
    2. index.js
    3. README.md

初始化package.json

{
  "name": "my",// 包名不能重复
  // 描述信息
  "description": "my pack ,up to me",
  // 数组,搜索的关键词
  "keywords": [
    "moment",
    "date",
    "time",
    "parse",
    "format",
    "validate",
    "i18n",
    "l10n",
    "ender"
  ],
  "main": "./index.js", // 包的入口文件
  // 许可协议
 "license": "ISC",
  "version": "1.0.0" // 版本
}

编写index.js

编写完,使用module.exports把对应的方法共享出去。

编写包的说明文档

README.md,包的使用说明文档。

发布包

  1. 注册npm账号
    1. www.npmjs.com/
  2. 登录npm账号
    1. 在终端里面输入npm login
    2. 输入 账号密码邮箱后,即可登录成功。
    3. 注意:执行命令之前,必须先把包源切换为npm官方服务器
  3. 发布
    1. 切换到包的根目录
    2. 终端里输入npm publish
    3. **注意:**包名不能重复

删除已发布的包

npm unpublish 包名 --force
  1. 只能删除72个小时内发布的包
  2. 删除的包在24小时内不允许重复发布

5. 模块的加载机制

优先从缓冲中加载

模块在第一次加载后会被缓存。提高了模块的加载效率

内置模块的加载机制

加载优先级最高

自定义模块的加载机制

必须指定以 ./../ 开头的路径标识符 如果省略了文件的扩展名,则node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败 终端报错

第三方模块的加载机制

假设在 'C:\tiank\project\foo.js'文件调用了require('tools'),则Node.js会按照以下顺序查找:

  1. C:\tiank\project\node_modules\tools
  2. C:\tiank\node_modules\tools
  3. C:\node_modules\tools

找不到就移动到再上一层父目录中,进行加载,直到文件系统的根目录。

6. Express

什么是express

专门来创建web服务器的 本质就是一个第三方包,提供了创建服务器的快速方法。和http模块类似。 express是基于http模块疯转出来的。

express能够做什么

  1. 前端程序员:
    1. 方便、快速的创建web网站的服务器或者api接口的服务器

安装

在根目录中,运行终端命令

npm i express@4.17.1

创建最基本的web服务器

// 1.导入express
const express = require('express)
                        
// 2.创建 web服务器
const app = express()

// 3. 调用 app.listen , 启动服务器
app.listen(80,()=>{
  console.log('express server running at http://127.0.0.1')
})
                   

监听get请求

app.get('请求url',(req,res)=>{})

监听post请求

app.post('请求url',(req,res)=>{})

给客户端相应内容

res.send()方法

app.get('/user',(res,req)=>{
  res.send({name:'zs',age:25})
})

获取url中携带的参数

req.query

// 1.导入express
const express = require('express')
// 2.创建web服务器
const app = express()
// 3.启动web服务器
app.listen(80,()=>{
    console.log('express is running at http://127.0.0.1:80')
})
// 4.获取url中携带的参数
app.get('/',(req,res)=>{
    res.send(req.query)
})

获取url中的动态参数

req.params

// 1.导入express
const express = require('express')
// 2.创建 web服务器
const app = express()
// 3.启动 web服务器
app.listen(80,()=>{
    console.log('express is running at http://127.0.0.1:80')
})
// 4.获取url中的动态参数
app.get('/home/:id',(req,res)=>{
    res.send(req.params) // {"id": "first"}
})

注意:

  1. :id不是固定写法,例如:qq也是可以的,最后req.params返回的为{"qq":"***"}
  2. url也可以是这种格式/user/:id/:key,也就是说可以有多个层级,返回的数据类型为{"id": "77","key": "tiankai"}

托管静态资源

express.static()函数,通过它,可以方便的创建一个静态资源服务器。可以将图片、css文件、JS文件对外开放。 代码示例:

// 导入express模块
const express = require('express')
const path = require("path");
// 创建web服务器
const app = express()
// 监听80端口
app.listen(80,()=>{
    console.log(path.join(__dirname,'/anli'))
    console.log('express server is running at http://127.0.0.1')
})
app.get('/',(req,res)=>{
    res.send('hello')
})
// 调用app.static对外提供静态资源 路径最好是使用path模块拼接一个绝对路径,否则可能加载不出来
app.use(express.static(path.join(__dirname,'/anli')))

游览器窗口输入http://127.0.0.1/index.html即可访问./anli目录下的index.html文件。css、图片文件同理。

托管多个静态资源

app.use(express.static(path.join(__dirname,'/anli')))
app.use(express.static(path.join(__dirname,'/anli2')))
// 直接加载两个即可

挂载路径前缀

app.user('/anli',express.static(path.join(__dirname,'/anli')))

现在你如果想访问anli下的index.html,那么需要输入的地址为:http://127.0.0.1/anli/index.html

nodemon

能够监听项目文件的变动,当代码被修改后,nodemon会自动帮我们重启项目。

安装nodemon

npm install nodemon -g

使用nodemon

启动项目

nodemon app.js

Express路由

路由的匹配过程

  1. 按照定义的先后顺序
  2. 请求类型和请求url同时匹配成功,才会调用对应的处理函数

路由的最简单用法

// 1.导入express
const express = require('express')
// 2.创建 web服务器
const app = express()
// 3.启动 web服务器
app.listen(80,()=>{
  console.log('express is running at http://localhost:80')
})
// 4.挂载路由
app.get('/user',(req,res)=>{
  res.send({name:'周杰',age:25})
})
app.post('/home',(req,res)=>{
  res.send({name:'周杰伦',age:26})
})

很少会把路由挂载在app上

模块化路由

推荐将路由抽离为单独的模块

  1. 创建路由模块对应的.js模块
  2. 调用express.Router()函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用module.exports向外共享路由对象
  5. 使用app.use()函数注册路由模快

代码示例:

// 1.导入express
const express = require('express')
// 2.创建路由对象
const router = express.Router()
// 3.挂载具体的路由
router.get('/user',(req,res)=>{
    res.send('user')
})
router.post('/login',(req,res)=>{
    res.send('login')
})
// 4.向外到处路由对象
module.exports = router
// 1.导入express
const express = require('express')
// 2.创建路由对象
const router = express.Router()
// 3.挂载具体的路由
router.get('/user',(req,res)=>{
    res.send('user')
})
router.post('/login',(req,res)=>{
    res.send('login')
})
// 4.向外到处路由对象
module.exports = router

注意: app.use()函数的作用,就是来注册全局中间件。

为路由模块添加前缀

app.use('/api',userRouter)

Express中间件

有输入有输出

格式

本质上就是一个function处理函数 注意:中间件函数的形参列表中,必须包含next参数。而路由处理函数中只包含req和res

next函数的作用

next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由 image.png

定义中间件函数

最简单的中间件函数

const mw = function(req,res,next){
  console.log('这是一个最简单的中间件函数')c
  next()
}

全局生效的中间件

客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局中间件。 通过app.use(中间件函数),即可定义一个全局生效的中间件。

// 1.导入express模块
const express = require('express')
// 2.创建app服务器
const app = express()
// 3.监听80端口
app.listen(80,()=>{
    console.log('server is running at http://127.0.0.1')
})
// 4.创建一个中间件
const mw = (req,res,next)=>{
    console.log('这是一个全局中间件')
    next()
}
// 5.将创建的中间件mw注册为全局中间件
app.use(mw)
// 注册路由
app.get('/home',(req,res)=>{
    console.log('home page')
    res.send('Home Page.')
})
app.get('/user',(req,res)=>{
    console.log('user page')
    res.send('User Page.')
})

注册中间件的简单形式

app.use((req,res,next)=>{
  next()
})

中间件的作用

多个中间件之间,共享同一份req和res。

// 导入express
const express = require('express')
// 创建app服务器
const app = express()
// 监听80端口
app.listen(80,()=>{
    console.log('server is running at http://127.0.0.1')
})
// 创建全局生效的中间件
app.use((req,res,next)=>{
    const now = Date.now()
    req.startTime = now
    next()
})
// 注册路由
app.get('/home',(req,res)=>{
    res.send('Home page : '+req.startTime)
})
app.get('/user',(req,res)=>{
    res.send('User page : '+req.startTime)
})

定义多个全局中间件

可以使用app.use()连续定义多个全局中间件。中间件会按照定义的顺序来执行。

局部生效的中间件

不使用app.use()定义的中间件,就是局部生效的中间件,代码示例:

// 定义中间件函数
const mw = (req,res,next)=>{
  console.log('调用了局部生效的中间件')
  next()
}
// 使用局部中间件
// 在home路径中生效,在user路径中不会生效
app.get('/home',mw,(req,res)=>{
  res.send('Home Page')
})
app.get('/user',(req,res)=>{
  res.send('Home Page')
})

使用多个局部中间件

可以在路由中,通过如下两种的等价的方式,使用多个局部中间件:

app.get('/',mw1,mw2,(req,res)=>{})
app.get('/',[mw1,mw2],(req,res)=>{})

示例:

// 导入express
const express = require('express')
// 创建app服务器
const app = express()
// 监听80端口
app.listen(80,()=>{
    console.log('server is running at http://127.0.0.1')
})
// 创建多个中间件
const mw1 = (req,res,next)=>{
    console.log('这是第一个中间件')
    next()
}
const mw2 = (req,res,next)=>{
    console.log('这是第二个中间件')
    next()
}
const mw3 = (req,res,next)=>{
    console.log('这是第三个中间件')
    next()
}
// 注册路由,局部使用中间件
app.get('/home',(req,res)=>{
    res.send('Home page.')
})
app.get('/user',mw1,mw2,(req,res)=>{
    res.send('User page.')
})
app.get('/login',[mw2,mw3],(req,res)=>{
    res.send('Login page.')
})

中间件的5个注意事项

  1. 一定要在路由之前注册中间件
  2. 不要忘记next函数
  3. 可以使用多个中间件进行处理请求
  4. next之后不要写其他的代码
  5. 多个中间件之间共享req,res

中间件的分类

  1. 应用级别的中间件
  2. 路由级别的中间件
  3. 错误级别的中间件
  4. express内置的中间件
  5. 第三方的中间件

应用级别的中间件

通过app.use()或者app.get()或者app.post(),绑定到app实例上的中间件,叫做应用级别的中间件。

路由级别的中间件

绑定到express.Router()实例上的中间件,叫做路由级别的中间件。 它的用法和应用级别的中间件没有任何区别。 代码示例:

const app = express()
const router = express.Router()

// 路由级别的中间件
router.use((req,res,next)=>{
  console.log('Time',Date.now())
  next()
})

app.use('/',router)

错误级别的中间件

专门捕捉整个项目中发生的异常错误。 **格式:**必须有4个形参,形参顺序从前到后,分别是(err,req,res,next)

// 导入express
const express = require('express')
// 创建app服务器
const app = express()
// 监听80端口
app.listen(80,()=>{
    console.log('server is running at http://127.0.0.1')
})
// 注册路由
app.get('/user',(req,res)=>{
    throw new Error('服务器内部错误!')
    res.send('user page')
})
// 错误级别的中间件
app.use((err,req,res,next)=>{
    res.send('Error:'+err.message)
})

Express内置的中间件

从express4.16.0版本开始,express内置了3个常用的中间件。

  1. express.static
    1. 快速托管静态资源的内置中间件
    2. html。文件。图片。css样式
  2. express.json
    1. 解析json格式的请求数据。
  3. express.urlencoded
    1. 解析URL-encoded格式的请求数据
app.use(express.json())
app.use(express.urlencoded({extended:false}))
express.json
// 导入express
const express = require('express')
// 创建app服务器
const app = express()
// 监听80端口
app.listen(80,()=>{
    console.log('server is running at http://127.0.0.1')
})
// 调用express.json中间件
app.use(express.json())

// 注册路由
app.post('/user',(req,res)=>{
    // 默认情况下,可以使用req.body来接受客户端发送的请求体数据
    // 但是如果不配置解析表单数据的中间件,则req.body为undefined
    console.log(req.body)
    res.send('ok')
})
express.urlencoded
const express = require('express')
const app = express()
app.listen(80,()=>{
    console.log('server is running at http://127.0.0.1')
})
// 解析请求表单中url-encoded格式的数据
app.use(express.urlencoded({extended:false}))

app.post('/home',(req,res)=>{
    console.log(req.body)
    res.send('ok')
})

自定义中间件

Express接口

使用cors中间件解决跨域问题

  1. 运行 npm i cors
  2. 使用 const cors = require('cors')
  3. 在路由之前调用app.use(cors())

什么cors

跨域资源共享,游览器的同源安全策略默认会阻止跨域获取资源。 但是如果接口服务器配置了cors相关的http响应头,就可以解除。

cors响应头部

Access-Control-Allow-Origin

例如:只允许来自 itcast.cn 的请求

res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')

*表示运行任何域的请求

Access-Control-Allow-Headers

默认情况下,cors仅支持客户端向服务器发送的9个请求头 image.png 如果客户端向服务器发送了额外的请求头信息,需要在服务器端,通过Access-Control-Allow-Headers额外的请求头进行声明,否则这次请求会失败。

res.setHeader('Access-Control-Allow-Headers','Content-Type,X-Custom-Header')

Access-Control-Allow-Methods

默认情况下,coes仅支持客户端发起的GET、POST、HEAD请求 如果客户端希望通过PUT、DELETE请求服务器,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法。

// 允许所有的HTTP请求方法
res.setHeader('Access-Control-Allow-Methods','*')

cors请求的分类

简单请求

  1. 请求方式:GET、POST、HEAD三者之一
  2. HTTP头部信息不超过一下字段

image.png

预检请求

  1. 请求方式:GET、POST、HEAD之外的请求类型
  2. 请求头中包含自定义头部字段
  3. 向服务器发送了application/json格式的数据

正式通信之前,浏览器发送OPTION请求进行预检,来获取服务器是否允许该实际请求。

区别

预检请求会发送两次请求。

JSONP接口

概念:浏览器通过。 特点:

  1. JSONP不属于真正的ajax请求,因为他没有使用XMLHttpRequest这个对象
  2. 仅支持get请求

注意事项

如果项目中已经配置了cors,为了防止冲突,必须在配置cors中间件之前声明JSONP的接口。否则JSONP接口会被处理为开启了CORS的接口。

// 优先创建JSONP 接口,
app.get('/api/jsonp',(req,res)=>{

})

// 导入cors,解决跨域问题
const cors = require('cors')
app.use(cors())

// 引入路由模块
app.use('/api',apiRouter)

实现jsonp接口

  1. 获取客户端发送过来的回调函数的名字
  2. 得到要通过JSONP形式发送给客户端的数据
  3. 根据前两部得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的字符串,相应给客户端的标签进行解析

7.数据库

  1. 关系型数据库
  2. 非关系型数据库

安装配置mysql

基础sql语句

Select语句

  1. 查询一个表中所有的数据

SELECT * FROM 表名称

  1. 查询一个表中的一列

SELECT 列名称 FROM 表名称

-- 查询所有数据
select * from users

-- 查询指定列数据
SELECT username,password FROM users

INSERT INTO

语法格式:

INSERT INTO table_name(列1,列...) VALUES (值1,值2,...)
-- 向users表中插入一条,username:托尼 password:123
INSERT INTO users(username,password) VALUES ('托尼','123')

UPDATE

语法格式:

UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
-- 将zs的密码更新为 888888
UPDATE users SET password='888888' WHERE username='zs'

-- 将id为3的用户密码和用户状态,分别更新为admin123和1
UPDATE users SET password='admin123',status='1' WHERE id=3

DELETE

语法格式

DELETE FROM 表名称 WHERE 列名称=
-- 删除id为4的用户
DELETE FROM users WHERE id=4

WHERE子句

image.png 例句:

-- 查询status为 1 的所有用户
SELECT * FROM users WHERE status = 1

-- 查询 id 大于 2 的所有用户
SELECT * FROM users WHERE id>2

-- 查询username 不等于 admin 的所有用户
SELECT * FROM users WHERE username!= 'admin'

AND和OR运算符

and和or可在where子句中把多个条件结合起来。

-- 使用AND来显示所有状态为0且id小于3的用户
SELECT * FROM users WHERE status=0 AND id < 3

-- 使用or显示所有status为1,或者username为zs的用户
SELECT * FROM users WHERE status=1 OR username='zs'

ORDER BY

对结果集进行排序,升序。降序可以使用DESC关键字。

-- 对users表中的数据按照 status 字段进行升序排序
select * from users ORDER BY status

-- 对users表中的数据按照 status 字段进行降序排序
select * from users order by status DESC

-- 对users表中的数据按照 status 升序排序,然后再按照 password 降序排序
select * from users order by status asc,password desc

COUNT(*)

返回查询结果的总数居条数

-- 查询 users 表中 status 为 0 的总条数
select count(*) from users where status = 0

-- 使用 as 关键字给总条数起个别名 total
select count(*) as total from users where status=0

8.在项目中操作数据库

步骤

  1. 安装操作mysql数据库的第三方模块(mysql)
  2. 通过mysql模块连接到mysql数据库
  3. 通过mysql模块执行sql语句

安装mysql模块

npm i mysql

配置musql模块

// 导入mysql模块
const mysql = require('mysql')
// 建立mysql与数据库之间的联系
const db = mysql.createPool({
  host:'tiankaii.cn',
  port:'3306',
  user:'root',
  password:'Qq133242',
  database:'my_db_01'
})

测试mysql模块能否正常工作

// 导入mysql模块
const mysql = require('mysql')
// 建立mysql与数据库之间的联系
const db = mysql.createPool({
  host:'tiankaii.cn',
  port:'3306',
  user:'root',
  password:'Qq133242',
  database:'my_db_01'
})
// 测试mysql模块能否正常工作
db.query('select 1',(err,msg)=>{
  if (err) return console.log(err)
  console.log(msg)
})

返回结果:

[ RowDataPacket { '1': 1 } ]

查询语句

// 导入mysql模块
const mysql = require('mysql')
// 建立mysql和数据库之间的联系
const db = mysql.createPool({
    host:'tiankaii.cn',
    port:'3306',
    user:'root',
    password:'Qq133242',
    database:'my_db_01'
})
// 查询操作
const sqlStr = 'SELECT * FROM users'
db.query(sqlStr,(err,res)=>{
    if (err) console.log(err.message)
    console.log(res)
})

插入语句

// 导入mysql模块
const mysql = require('mysql')
// 建立和数据库之间的连接
const db = mysql.createPool({
    host:'tiankaii.cn',
    port:'3306',
    user:'root',
    password:'Qq133242',
    database:'my_db_01'
})
// 插入语句
const sqlStr = 'INSERT INTO users(username,password) VALUES (?,?)'
const user = {username:'tom',password:'Qq133242'}
// 使用数组形式传递值
db.query(sqlStr,[user.username,user.password],(err,res)=>{
    if (err) console.log(err.message)
    console.log(res)
    // OkPacket {
    //     fieldCount: 0,
    //     affectedRows: 1,
    //     insertId: 5,
    //     serverStatus: 2,
    //     warningCount: 0,
    //     message: '',
    //     protocol41: true,
    //     changedRows: 0
    // }
})

插入语句的简单写法

// 导入mysql模块
const mysql = require('mysql')
// 和数据库建立连接
const db = mysql.createPool({
    host:'tiankaii.cn',
    port:'3306',
    user:'root',
    password:'Qq133242',
    database:'my_db_01'
})
// 插入语句的简单写法
const user = {username:'man',password:'1234'}
const sqlStr = 'INSERT INTO users SET ?'
db.query(sqlStr,user,(err,res)=>{
    if (err) console.log(err.message)
    console.log(res)
})

更新语句

// 导入mysql模块
const mysql = require('mysql')
// 和数据库建立连接
const db = mysql.createPool({
    host:'tiankaii.cn',
    port:'3306',
    user:'root',
    password:'Qq133242',
    database:'my_db_01'
})
// 更新数据
const sqlStr = 'UPDATE users SET password=?,status=? WHERE id=?'
const user = {password:'4444',status:'1',id:6}
db.query(sqlStr,[user.password,user.status,user.id],(err,res)=>{
    if (err) console.log(err.message)
    console.log(res)
})

更新语句的快捷方式

// 导入mysql模块
const mysql = require('mysql')
// 和数据库建立连接
const db = mysql.createPool({
    host:'tiankaii.cn',
    port:'3306',
    user:'root',
    password:'Qq133242',
    database:'my_db_01'
})
// 更新数据的快捷方式
const sqlStr = 'UPDATE users SET ? WHERE id =?'
const user = {password:'222222',status:'0',id:6}
db.query(sqlStr,[user,user.id],(err,res)=>{
    if (err) console.log(err.message)
    console.log(res)
})

删除语句

// 导入mysql
const mysql = require('mysql')
// 和数据库建立连接
const db = mysql.createPool({
    host:'tiankaii.cn',
    port:'3306',
    user:'root',
    password:'Qq133242',
    database:'my_db_01'
})
// 删除数据
db.query('DELETE FROM users WHERE id = ?',6,(err,res)=>{
    if (err) console.log(err.message)
    console.log(res)
})

9.前后端的身份认证

http的无状态性

客户端的每次http请求都是独立的。服务器不会主动保留每次HTTP请求的状态

Cookie

Cookie是存储在用户游览器中的一段不超过4kb的字符串。 它由一个名称、一个值和其他几个用于控制Cookie有效期安全性使用范围的可选属性组成。 不同域名下的Cookie各自独立。每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。

  1. 自动发送
  2. 域名独立
  3. 过期时限
  4. 4kb限制

Cookie在身份认证中的作用

客户端第一次请求服务器的时候,服务器会通过响应头,发送Cookie到游览器,游览器拿到Cookie,保存到游览器中,下一次请求的时候 ,将该Cookie放到请求头中,发送给服务器。服务器根据请求头中的Cookie验明用户的身份。

Cookie不具有安全性

很容易被伪造。 如果把所有信息都存到cookie里面,那么敏感信息也就是存储到了客户端上,不安全。

Session认证机制

Session的工作原理

游览器第一次请求服务器,服务器将用户信息存储在服务器的内存中,同时生成对应的Cookie字符串,将Cookie返回给客户端,客户端保存Cookie,下一次发送请求的时候将Cookie放到请求头中,发送给服务器,服务器根据请求头中Cookie,从内存中找到对应的用户信息,认证成功后,将请求数据发送给客户端。

在Express中使用Session认证

  1. 安装express-session中间件
  2. 配置express-session中间件
const session = require('express-session')

// 配置
app.use(session({
  secret:'keyboard cat',  // 任意字符串
  resave:false,						// 固定写法
  saveUninitialized:true	// 固定写法
}))

向session中存数据

res.session

向session中取数据

req.session

销毁session

req.session.destory()

JWT认证机制

当前后端是跨域的时候,我们一般使用JWT认证。 JWT(JSON WEB Token)

JWT的工作原理

客户端登陆,发送给服务器请求。服务器验证账号和密码,验证通过后,将用户的信息对象,经过加密之后生成Token字符串,生成后,将token发送给客户端。客户端将Token存储到LocalStorage中。客户端再次请求时,通过请求头的Authorization字段,将token发送给服务器。服务器把token字符串还原成用户的信息对象,身份认证成功后,相应给客户端对应的内容。

JWT的组成部分

  1. Header头部
  2. Payload
    1. 真正的用户信息,用户信息加密后字符串
  3. Signature

三者之间用.分隔。

JWT的使用方式

在Express中使用JWT

导入jwt相关的包

  1. jsonwebtoken
    1. 生成JWT字符串的包
  2. express-jwt
    1. 将JWT字符串解析成JSON对象的包

定义secret密钥

用来加密和解密。

const secretKey = 'tiankai key'

生成jwt字符串

调用jsonwebtoken包提供的sign()方法

res.send({
  status:200,
  message:'登录成功',
 // 三个参数分别是:用户信息对象,加密密钥,配置对象(这里为30s后失效)
  token:jwt.sign({username:user.username},secretKey,{expiresIn:'30s'})
})

将jwt字符串还原为json对象

// expressJWT({secret:secretKey}) 来指定密钥
// unless({path:[/^\/api\//]}) 指定哪些接口不需要解密
app.use(expressJWT({secret:secretKey}).unless({path:[/^\/api\//]}))

使用req.user获取用户信息

当express-jwt这个中间件配置成功之后,就可以在那些有权限的接口中,使用req.user对象,来访问JWT字符串中解析出来的用户信息

app.get('/admin/getinfo',function(req,res){
  console.log(req.user)
  res.send({
    status:200,
    message:'获取用户信息成功',
    data:req.user
  })
  
})

全局错误处理中间件,捕捉JWT失败后产生的错误

app.use((err,req,res,next)=>{
  if(err.name === 'UnauthorizedError'){
  }
})