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版本
- LTS:稳定版
- current:开发版本,存在bug
1.4 在nodejs环境中执行js代码
- 打开终端
- 输入
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:当前文件所处目录
2.7 path模块
path模块是node官方提供的、用来处理路径的模块。它提供了一系列的方法和属性。
- path.join() 用来将多个路径片段拼接成一个完整的路径字符串
- path.basename() 从路径字符串中,将文件名解析出来
- 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服务器的基本步骤
- 导入http模块
- const http = require('http')
- 创建web服务器实例
- const server = http.createServer()
- 为服务器实例绑定request事件,监听客户端请求
// 使用服务器实例的 .on()方法
server.on('request',(req,res)=>{
console.log('Someone visit our web server')
})
其中:
- req
- req是请求对象,它包含了与客户端相关的数据和属性
- req.url 是客户端请求的url地址
- req.method是客户端的method请求类型
- res
- res是响应对象
- res.end(),向客户端发送指定内容
- res.setHeader('Content-Type',"text/html; charset=utf-8") 防止响应内容
中文乱码
- 启动服务器
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. 模块化
优点
- 提高了代码的复用性
- 提高了代码的可维护性
- 实现了按需加载
模块化规范
例如:
- 使用什么样的语法格式来引用模块
- 在模块中使用什么样的语法格式向外暴露成员
分类
- 内置模块
- 由nodejs官方提供的,例如fs、path、http
- 自定义模块
- 用户自己创建的js文件,都是自定义模块
- 第三方模块
- 由第三方开发出来的模块,使用前需要先下载
加载模块
使用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
- 第一位数字:大版本
- 第二位数字:功能版本
- 第三位数字: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参数,则会把包安装为全局包。
注意:
判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明
规范的包结构
- 必须以单独的目录而存在
- 包的顶级目录下必须包含package.json这个包管理配置文件
- package.json中必须包含name、version、main这三个属性,分别代表包的名字、版本号、包的入口
开发自己的包
初始化包的基本结构
- 新建 my 文件夹,作为包的根目录
- 在 my 文件夹中,新建如下三个文件:
- package.json
- index.js
- 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,包的使用说明文档。
发布包
- 注册npm账号
- 登录npm账号
- 在终端里面输入
npm login - 输入 账号密码邮箱后,即可登录成功。
- 注意:执行命令之前,必须先把包源切换为npm官方服务器
- 在终端里面输入
- 发布
- 切换到包的根目录
- 终端里输入
npm publish - **注意:**包名不能重复
删除已发布的包
npm unpublish 包名 --force
- 只能删除72个小时内发布的包
- 删除的包在24小时内不允许重复发布
5. 模块的加载机制
优先从缓冲中加载
模块在第一次加载后会被缓存。提高了模块的加载效率
内置模块的加载机制
加载优先级最高
自定义模块的加载机制
必须指定以 ./ 或 ../ 开头的路径标识符
如果省略了文件的扩展名,则node.js会按顺序分别尝试加载以下的文件:
- 按照确切的文件名加载
- 补全.js扩展名进行加载
- 补全.json扩展名进行加载
- 补全.node扩展名进行加载
- 加载失败 终端报错
第三方模块的加载机制
假设在 'C:\tiank\project\foo.js'文件调用了require('tools'),则Node.js会按照以下顺序查找:
- C:\tiank\project\node_modules\tools
- C:\tiank\node_modules\tools
- C:\node_modules\tools
找不到就移动到再上一层父目录中,进行加载,直到文件系统的根目录。
6. Express
什么是express
专门来创建web服务器的 本质就是一个第三方包,提供了创建服务器的快速方法。和http模块类似。 express是基于http模块疯转出来的。
express能够做什么
- 前端程序员:
- 方便、快速的创建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"}
})
注意:
:id不是固定写法,例如:qq也是可以的,最后req.params返回的为{"qq":"***"}- 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路由
路由的匹配过程
- 按照定义的先后顺序
- 请求类型和请求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上
模块化路由
推荐将路由抽离为单独的模块
- 创建路由模块对应的
.js模块 - 调用
express.Router()函数创建路由对象 - 向路由对象上挂载具体的路由
- 使用
module.exports向外共享路由对象 - 使用
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函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
定义中间件函数
最简单的中间件函数
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个注意事项
- 一定要在路由之前注册中间件
- 不要忘记next函数
- 可以使用多个中间件进行处理请求
- next之后不要写其他的代码
- 多个中间件之间共享req,res
中间件的分类
- 应用级别的中间件
- 路由级别的中间件
- 错误级别的中间件
- express内置的中间件
- 第三方的中间件
应用级别的中间件
通过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个常用的中间件。
- express.static
- 快速托管静态资源的内置中间件
- html。文件。图片。css样式
- express.json
- 解析json格式的请求数据。
- express.urlencoded
- 解析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中间件解决跨域问题
- 运行
npm i cors - 使用
const cors = require('cors') - 在路由之前调用
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个请求头
如果客户端向服务器发送了额外的请求头信息,需要在服务器端,通过
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请求的分类
简单请求
- 请求方式:GET、POST、HEAD三者之一
- HTTP头部信息不超过一下字段
预检请求
- 请求方式:GET、POST、HEAD之外的请求类型
- 请求头中包含自定义头部字段
- 向服务器发送了application/json格式的数据
正式通信之前,浏览器发送OPTION请求进行预检,来获取服务器是否允许该实际请求。
区别
预检请求会发送两次请求。
JSONP接口
概念:浏览器通过。
特点:
- JSONP不属于真正的ajax请求,因为他没有使用XMLHttpRequest这个对象
- 仅支持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接口
- 获取客户端发送过来的回调函数的名字
- 得到要通过JSONP形式发送给客户端的数据
- 根据前两部得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,相应给客户端的标签进行解析
7.数据库
- 关系型数据库
- 非关系型数据库
安装配置mysql
基础sql语句
Select语句
- 查询一个表中所有的数据
SELECT * FROM 表名称
- 查询一个表中的一列
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子句
例句:
-- 查询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.在项目中操作数据库
步骤
- 安装操作mysql数据库的第三方模块(mysql)
- 通过mysql模块连接到mysql数据库
- 通过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一同发送到服务器。
- 自动发送
- 域名独立
- 过期时限
- 4kb限制
Cookie在身份认证中的作用
客户端第一次请求服务器的时候,服务器会通过响应头,发送Cookie到游览器,游览器拿到Cookie,保存到游览器中,下一次请求的时候 ,将该Cookie放到请求头中,发送给服务器。服务器根据请求头中的Cookie验明用户的身份。
Cookie不具有安全性
很容易被伪造。 如果把所有信息都存到cookie里面,那么敏感信息也就是存储到了客户端上,不安全。
Session认证机制
Session的工作原理
游览器第一次请求服务器,服务器将用户信息存储在服务器的内存中,同时生成对应的Cookie字符串,将Cookie返回给客户端,客户端保存Cookie,下一次发送请求的时候将Cookie放到请求头中,发送给服务器,服务器根据请求头中Cookie,从内存中找到对应的用户信息,认证成功后,将请求数据发送给客户端。
在Express中使用Session认证
- 安装express-session中间件
- 配置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的组成部分
- Header头部
- Payload
- 真正的用户信息,用户信息加密后字符串
- Signature
三者之间用.分隔。
JWT的使用方式
在Express中使用JWT
导入jwt相关的包
- jsonwebtoken
- 生成JWT字符串的包
- express-jwt
- 将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'){
}
})