一、初识 Nodejs
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine
Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
- 基于 Express 框架 (opens new window),可以快速构建 Web 应用
- 基于 Electron 框架 (opens new window),可以构建跨平台的桌面应用
- 基于 restify 框架 (opens new window),可以快速构建 API 接口项目
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
二、fs 文件系统模块
2.1 fs.readFile 读取文件
语法:
fs.readFile(path[, options], callback)
- path:文件路径
- options:配置选项,若是字符串则指定编码格式
1. encoding:编码格式(utf-8)
2. flag:打开方式 - callback:回调函数
1. err:错误信息
2. data:读取的数据,如果未指定编码格式则返回一个 Buffer
2.2 fs.writeFile 写入文件
语法:
fs.writeFile(file, data[, options], callback)
2.3 读写文件案例
原数据:
//读取文件
fs.readFile('./1.txt', 'utf-8', (err, dataStr) => {
//如果读取失败,则返回err,默认值为null,故可直接用于if判断
if (err) {
//直接return,不再输出后续代码
return console.log(err)
}
//dataStr为成功读取到的数据
console.log('读取成功' + dataStr)
//处理数据
//对dataStr做空格换行分隔,赋到Old数组上,并创建空的New数组
const arrOld = dataStr.split('rn')
const arrNew = []
//对Old数组的每一项,将'='替换为':',并push到新数组中
arrOld.forEach(item => {
arrNew.push(item.replace('=', ':'))
})
//以回车分隔将数组串联成一字符串
const newStr= arrNew.join('rn')
// console.log(typeof(newStr)) 可以看到输出类型为字符串
// console.log(newStr) 输出串联后的字符串
//写入文件
fs.writeFile('./1.txt',newStr , (err) => {
//写入失败返回的err默认为null,因此可作为判断条件
if (err) {
//err.message是错误信息
return console.log(err.message);
}
console.log('写入成功');
});
})
2.4 路径动态拼接问题 __dirname
fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, data) {
...
})
三、path 路径模块
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
3.1 路径拼接 path.join()
path.join('','')
const path = require('path')
const fs = require('fs')
// 注意 ../ 会抵消前面的路径
// ./ 会被忽略
const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
console.log(pathStr) // ade
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', function (err, dataStr) {
if (err) {
return console.log(err.message)
}
console.log(dataStr)
})
3.2 获取路径中文件名 path.basename()
使用 path.basename() 方法,可以获取路径中的最后一部分,常通过该方法获取路径中的文件名
path.basename(path[, ext])
- path: 文件路径
- ext: 文件扩展名
const path = require('path')
// 定义文件的存放路径
const fpath = '/a/b/c/index.html'
const fullName = path.basename(fpath)
console.log(fullName) // index.html
const nameWithoutExt = path.basename(fpath, '.html')
console.log(nameWithoutExt) // index
3.3 获取路径中文件扩展名 path.extname()
const path = require('path')
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext) // .html
四、http 模块
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。
4.1 创建基本 Web 服务器
const http = require('http')
// 创建 web 服务器实例
const server = http.createServer()
// 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', function (req, res) {
const url = req.url
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)
// 设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// 向客户端响应内容
res.end(str)
})
//注:要是报错可能是端口被mysql或其他软件占用,改个端口即可
server.listen(8080, function () {
console.log('server running at http://127.0.0.1:8080')
})
4.2 实现简单路由效果
const http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
const url = req.url
// 设置默认的响应内容为 404 Not found
let content = '<h1>404 Not found!</h1>'
// 判断用户请求的是否为 / 或 /index.html 首页
if (url === '/' || url === '/index.html') {
content = '<h1>首页</h1>'
}
// 判断用户请求的是否为 /about.html 关于页面
else if (url === '/about.html') {
content = '<h1>关于页面</h1>'
}
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(content)
})
server.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
五、模块化
5.1 模块化概念
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合、分解和更换的单元
模块化作用:
模块化规范是对代码进行模块化拆分和组合时需要遵守的规则,如使用何种语法格式引用模块和向外暴露成员
5.2 Node.js 中模块的分类
- 内置模块
const fs = require('fs')
const custon = require('./custom')
- 第三方模块
const moment = require('monent')
5.3 Node.js 中的模块作用域
模块作用域:和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,无法被外界访问,这种模块级别的访问限制叫做模块作用域
模块作用域作用:防止全局变量污染
5.4 module对象 / exports对象
- 自定义模块中都有一个 module 对象,存储了和当前模块有关的信息
- 在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用
- 导入自定义模块时,得到的就是 module.exports 指向的对象,且默认为空对象,需要向对象进行挂载
//自定义模块.js
const age = 20
//向module.exports对象上挂载 属性 / 方法 / 变量
module.exports.username = 'zs'
module.exports.sayHi = function(){
console.log('hi')
}
module.exports.age = age
//使用模块.js
const m = require('./自定义模块')
console.log(m)
输出:
//自定义模块.js
const age = 20
//向exports对象上挂载 属性 / 方法 / 变量
exports.username = 'zs'
exports.sayHi = function(){
console.log('hi')
}
module.exports.age = age
//以module.exports最终指向为准(相当于开辟了一个新对象)
module.exports = {
nickName: '做一只猫',
sayHello(){
console.log('hello')
}
}
//即便这时候再改exports指向不影响
exports = {
nickName: '做一只猫',
sayHello(){
console.log('hello')
}
}
//使用模块.js
const m = require('./自定义模块')
console.log(m)
5.5 CommonJS 模块化规范
5.6 模块加载机制
模块第一次加载后会被缓存,即多次调用 require() 不会导致模块的代码被执行多次,提高模块加载效率。
自定义模块加载
第三方模块加载
- 若导入第三方模块, Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块
- 如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录
- 例如,假设在 C:Usersbruceprojectfoo.js 文件里调用了 require(‘tools’),则 Node.js 会按以下顺序查找:
- C:Usersbruceprojectnode_modulestools
- C:Usersbrucenode_modulestools
- C:Usersnode_modulestools
- C:node_modulestools
目录作为模块加载
当把目录作为模块标识符进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
- 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件
- 如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module ‘xxx’
例:
1 新建test文件夹,在文件夹中放 a.js , index.js 和 package.json
//a.js中
console.log('调用了a.js')
//index.js中
console.log('调用了index.js')
//package.json中
{
"main": "./a.js"
}
3 新建1.js 并调用
//try.js
require('./test')
4 删去package.json 再次调用1.js
5 删去index.js 再次调用1.js
六、包
6.1 包的定义
- Node.js 中的第三方模块又叫做包
- 不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用
- 由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低
- 包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率
- 包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 API 之间的关系
6.2 包的使用
- 包查找地址: https://www.npmjs.com/
- 包下载地址:https://registry.npmjs.org/
- 用npm下载包,下载node的同时也将npm下载了下来,可以在终端中用
npm -v
查看npm的版本 - 可在官网看对应包的使用说明
//在终端中(vs终端也可)@版本号可不加
npm install 完整的包名@版本号
//简写:
npm i 完整的包名
//例:用moment实例化时间
//终端中
npm i moment
//代码区中
const moment = require('moment')
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
//这个包停止维护了,但还可以使用
6.3 包管理配置文件
作用: 团队开发中第三方的体积过大,因为联网即可下载第三方包,所以不需要将包也上传到项目中,而只用上传核心文件
- npm init -y
- 命令只能在英文且无空格的目录下运行
- 运行npm install命令安装包时,npm包管理工具会自动将包的名称和版本号,记录到package.json 的dependencies节点中
"dependencies": {
"moment": "^2.29.4"
}
npm install //能安装dependencies节点中所有的包
卸载包
npm uninstall 要卸载的包名
npm i 包名 -D
//完整写法如下
npm install 包名 --save-dev
为了更方便的切换下包的镜像源,我们可以安装 nrm 这个小工具,利用 nrm 提供的终端命令,可以快速查看和切换下包的镜像源
//将nrm安装为全局可用的工具
npm i nrm -g
//查看所有可用的镜像源
nrm ls
//将下包的镜像源切换为taobao 镜像
nrm use taobao
6.4 包的分类
1. 项目包
那些被安装到项目的 node_modules 目录中的包,都是项目包。
项目包又分为两类,分别是:
npm i 包名 -D //开发依赖包(会被记录到devDependencies节点下)
npm i 包名 //核心依赖包(会被记录到dependencies节点下)
2.全局包
- 在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包
- 全局包会被安装到 C:Users用户目录AppDataRoamingnpmnode_modules 目录下(新版本在安装目录下 或者你之前安装时配的路径中)
npm i 包名 -g //全局安装指定的包
npm uninstall 包名 -g //卸载全局安装的包
3.i5ting_toc
i5ting_toc 是一个可以把 md 文档转为 html 页面的小工具,使用步骤如下:
//将 i5ting_toc 安装为全局包
npm install -g i5ting_toc
//调用 i5ting_toc 实现md转html的功能
i5ting_toc -f 要转换的md文件路径 -o
6.5 规范的包结构
- 包必须以单独的目录而存在
- 包的顶级目录下要必须包含 package.json 这个包管理配置文件
- package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。
注意: 以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
七、Express
7.1 基本定义与使用
安装 Express:
npm install express
//黑马课程学习建议使用@4.17.1
const express = require('express')
// 创建 web 服务器
const app = express()
// 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
// 注意这里是向客户端发送相应了!
app.get('/user', (req, res) => {
//向客户端发送 JSON 对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
//向客户端发送文本内容
res.send('请求成功')
})
//启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
const express = require('express')
// 创建 web 服务器
const app = express()
//查询参数
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的查询参数
// req.query默认是一个空对象
// 客户端使用 http://127.0.0.1?name=zs&age=20这种查询字符串形式,发送到服务器的参数
// 可通过req.query对象访问到,如:req.query.name
console.log(req.query)
res.send(req.query)
})
// 这里的 :id 是一个动态的参数 id可以是其他任意合法的值
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认是一个空对象
console.log(req.params)
res.send(req.params)
})
//启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
7.2 托管静态资源
express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
app.use(express.static('public'))
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js
注意: Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,因此,存放静态文件的目录名不会出现在 URL 中,即public不会出现在上述http://...
的URL中
访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件
app.use(express.static('public'))
app.use(express.static('files'))
当public和files中都有index.js,会优先访问public中的
3. 挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use('/public, express.static('public'))
现在,你就可以通过带有 /public 前缀地址来访问 public 目录中的文件了:
http://localhost:3000/public/images/kitten.jpg
http://localhost:3000/public/css/style.css
http://localhost:3000/public/js/app.js
7.3 安装nodemon
作用:
- 在编写调试 Node.js 项目的时候,如果修改了项目的代码,则需要频繁的手动 close 掉,然后再重新启动,非常繁琐
- 现在,我们可以使用 nodemon(https://www.npmjs.com/package/nodemon) 这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试
安装nodemon
在终端中,运行如下命令,即可将 nodemon 安装为全局可用的工具:
npm install -g nodemon
使用 nodemon
将 node 命令替换为 nodemon 命令,使用 nodemon app.js 来启动项目。使得代码被修改之后,会被 nodemon 监听到,从而实现自动重启项目的效果。
node 1.js
nodemon 1.js
//若无法使用nodemon,管理员权限打开终端输入:set-ExecutionPolicy RemoteSigned 之后按Y
7.4 Express路由
- 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数
- 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理
- 理解:打电话给运营商客服,按下1到0的键对应不同服务,这种按键和服务绑定的映射关系,就叫路由
路由匹配的注意点:
创建路由模块:
// router.js
// 导入express
const express = require('express')
// 创建路由对象
const router = express.Router()
// 挂载具体路由
// /api是统一挂载的访问前缀
router.get('/api/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/api/user/add', (req, res) => {
res.send('Add new user.')
})
// 向外导出路由对象
module.exports = router
注册路由模块:
const express = require('express')
const router = require('./router')
const app = express()
// 注册路由模块,添加访问前缀
//app.use(expresss.static('./files'))
//app.use()的作用是注册全局中间件 expresss.static和router都是中间件
// '/api', 是统一挂载的访问前缀
app.use('/api', router)
app.listen(80, () => {
console.log('http://127.0.0.1')
})
7.5 Express 中间件
- 中间件是指业务流程的中间处理环节
- 当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
- Express 的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下:
//包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由
const mw = function(req, res, next){
next()
}
中间件注意事项:
7.5.1 全局中间件
通过 app.use() 定义的中间件为全局中间件
const express = require('express')
const app = express()
//1 常规写法
const mw = function(req, res, next){
console.log('这是一个中间件')
next()
}
app.use(mw)
//2 简化形式
app.use((req, res, next) => {
console.log('这是一个中间件')
next()
})
app.use((req, res, next) => {
req.startTime = Date.now()
next()
})
app.get('/',(req, res) =>{
res.send('hi' + startTime)
})
app.get('/',(req, res) =>{
res.send('hello' + startTime)
})
//定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第1个全局中间件')
next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第2个全局中间件')
next()
})
app.get('/user', (req, res) => {
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
7.5.2 局部中间件
不使用 app.use() 定义的中间件,叫做局部生效的中间件
const express = require('express')
const app = express()
// 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}
const mw2 = (req, res, next) => {
console.log('调用了第二个局部生效的中间件')
next()
}
//1. 调用一个中间件
app.get('/one', mw1, (req, res) => res.send('one page.'))
// 2. 调用多个中间件的两种方式
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))
app.get('/user', (req, res) => res.send('User page.'))
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
7.5.3 中间件的分类
通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件
2.路由级别的中间件
const app = express()
const router = express.Router()
router.use(function (req, res, next) {
console.log(1)
next()
})
app.use('/', router)
3.错误级别的中间件
- 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
- 错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是 (err, req, res, next)
- 错误级别的中间件必须注册在所有路由之后,否则无法捕获路由抛出的错误
const express = require('express')
const app = express()
app.get('/', (req, res) => {
throw new Error('服务器内部发生了错误!')
res.send('Home page.')
})
// 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
4.Express 内置中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
- express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
- express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
- express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
演示express.json 和 express.urlencoded
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
app.post('/book', (req, res) => {
// 在服务器端,可以通过 req,body 来获取 JSON 格式的表单数据和 url-encoded 格式的数据
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
5.第三方中间件
7.5.4* 自定义中间件
演示body–parser,仅作为演示作用,该中间件和之前express.json 和 express.urlencoded的用法没区别
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')
// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false }))
// app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
5.第三方中间件
注意:querystring 模块已被弃用,可改用JSON.parse
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 Node.js 内置的 querystring 模块
const qs = require('querystring')
// 这是解析表单数据的中间件
app.use((req, res, next) => {
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 3. 监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
7.5.5 编写GET、POST接口
编写接口
//exp.js
const express = require('express')
const router = express.Router()
// 在这里挂载对应的路由
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, // 0 表示处理成功,1 表示处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query, // 需要响应给客户端的数据
})
})
// 定义 POST 接口
router.post('/post', (req, res) => {
// 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
const body = req.body
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body,
})
})
// 定义 DELETE 接口(用于验证后面发送两次请求,即OPTION 请求和正常请求)
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE请求成功',
})
})
module.exports = router
使用接口
//use.js
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 导入路由模块
const router = require('./exp.js')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
7.5.6 CORS 跨域资源共享
CORS
- CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源
- 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制
- CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口
- CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./exp.js')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
CORS 常见响应头
//允许百度访问
res.setHeader('Access-Control-Allow-Origin', 'http://www.baidu.com')
//允许所有URL访问
res.setHeader('Access-Control-Allow-Origin', '*')
- 默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www–form-urlencoded 三者之一)
- 如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
- Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
res.setHEader('Access-Control-Allow-Methods', '*')
CORS 请求分类
简单请求
- 请求方式:GET、POST、HEAD 三者之一
- HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值 application/x-www–formurlencoded、multipart/form-data、text/plain)
预检请求
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
- 在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
*编写JSON接口
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback
// 2. 定义要发送到客户端的数据对象
const data = { name: 'zs', age: 22 }
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./16.apiRouter')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
八、数据库和身份认证
8.1 Node 操作 mysql
配置 mysql 模块
- 安装 mysql 模块
npm install mysql
- 建立连接
const mysql = require('mysql')
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'test',
})
db.query('select 1', (err, results) => {
if (err) return console.log(err.message)
console.log(results)
})
8.2 操作 mysql 数据库
- 查询数据
db.query('select * from users', (err, results) => {
if (err) return console.log(err.message)
console.log(results)
})
- 插入数据
const user = {username: '做一只猫', password:'111111'}
// ? 表示占位符
const sql = 'insert into users values(?, ?)'
// 使用数组的形式为占位符指定具体的值
db.query(sql, [user.username, user.password], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('插入成功')
})
const user = { username: '做一只猫', password: '111111' }
const sql = 'insert into users set ?'
db.query(sql, user, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('插入成功')
})
- 更新数据
const user = { username: '做一只猫', password: '111111' }
const sql = 'update users set username=?, password=? where id=?'
db.query(sql, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('更新数据成功')
})
- 更新数据简便方法
const user = {id: 4, username: '做一只猫', password: '111111' }
const sql = 'update users set ? where id=?'
db.query(sql, [user, user.id], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('更新数据成功')
})
const sql = 'delete from users where id=?'
//4代表user.id
//若SQL语句中有多个占位符,则必须使用数组为每个占位符指定具体的值
//若SQL语句中只有一个占位符,则可以省略
db.query(sql, 4, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('删除数据成功')
})
db.query('update users set status=1 where id=?', 7, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) console.log('删除数据成功')
})
8.3 Web 开发模式
服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接动态生成的。因此客户端不需要使用 Ajax 额外请求页面的数据。
app.get('/index.html', (req, res) => {
const user = { name: 'Bruce', age: 29 }
const html = `<h1>username:${user.name}, age:${user.age}</h1>`
res.send(html)
})
- 优点:
- 缺点:
前后端分离
前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。后端只负责提供 API 接口,前端使用 Ajax 调用接口。
- 优点:
Vue、React 等框架的 SSR(server side render)技术能解决 SEO 问题。
- 企业级网站,主要功能是展示,没有复杂交互,且需要良好的 SEO,可考虑服务端渲染
- 后台管理项目,交互性强,无需考虑 SEO,可使用前后端分离
- 为同时兼顾首页渲染速度和前后端分离开发效率,可采用首屏服务器端渲染 + 其他页面前后端分离的开发模式
8.4 身份认证
8.4.1 Session 认证机制
- 了解 HTTP 协议的无状态性是进一步学习 Session 认证机制的必要前提
- HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态
对于超市来说,为了方便收银员在进行结算时给 VIP 用户打折,超市可以为每个 VIP 用户发放会员卡
注意: 现实生活中的会员卡身份认证方式,在 Web 开发中的专业术语叫做 Cookie。
3. 关于 Cookie
- Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串
- 它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成
-不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器 - Cookie的几大特性:
4. Cookie 在身份认证中的作用
- 客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中
- 随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份
- 由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性
- 因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器
- 注意区分伪造跟盗取的不同
注意: 千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。
6. 提高身份认证的安全性
这种 “会员卡 + 刷卡认证” 的设计理念,就是 Session 认证机制的精髓。
8.4.2 Express 中使用 Session 认证
安装 express-session 中间件
npm install express-session
配置中间件
const session = require('express-session')
app.use(
session({
secret: '做一只猫', // secret 属性的值为任意字符串
resave: false, //固定写法
saveUninitalized: true //固定写法
})
)
向 session 中存数据
中间件配置成功后,可通过 req.session 访问 session 对象,存储用户信息
app.post('/api/login', (req, res) => {
//判断用户提交的登录信息是否正确
if(req.body.username !== 'admin' || req.body.password !== '000000'){
return res.send({status: 1, msg: '登录失败'})
}
req.session.user = req.body //将用户的信息存储到S
req.session.isLogin = true
res.send({ status: 0, msg: 'login done' })
})
从 session 取数据
app.get('/api/username', (req, res) => {
//判断用户是否登录
if (!req.session.isLogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({ status: 0, msg: 'success', username: req.session.user.username })
})
清空 session
app.post('/api/logout', (req, res) => {
// 清空当前客户端的session信息
req.session.destroy()
res.send({ status: 0, msg: '退出登录成功' })
})
8.4.3 JWT 认证机制
- Session 认证机制需要配合 Cookie 才能实现。
- 由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证
- JWT(英文全称:JSON Web Token) 是目前最流行的跨域认证解决方案
注意:
JWT 的工作原理
用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份
JWT 组成部分:
Header、Payload、Signature
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTcsInVzZXJuYW1lIjoiQnJ1Y2UiLCJwYXNzd29yZCI6IiIsIm5pY2tuYW1lIjoiaGVsbG8iLCJlbWFpbCI6InNjdXRAcXEuY29tIiwidXNlcl9waWMiOiIiLCJpYXQiOjE2NDE4NjU3MzEsImV4cCI6MTY0MTkwMTczMX0.bmqzAkNSZgD8IZxRGGyVlVwGl7EGMtWitvjGD-a5U5c
JWT 使用方式:
- 客户端会把 JWT 存储在 localStorage 或 sessionStorage 中
- 此后客户端与服务端通信需要携带 JWT 进行身份认证,将 JWT 存在 HTTP 请求头 Authorization 字段中
- 加上 Bearer 前缀
Authorization: Bearer <token>
8.4.4 Express 使用 JWT
安装
//安装多个包中间用空格隔开
npm install jsonwebtoken express-jwt
定义 secret 密钥
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 密钥可以为任意字符串
const secretKey = '做一只猫No.1'
生成 JWT 字符串
app.post('/api/login', (req, res) => {
...
res.send({
status: 200,
message: '登录成功',
// jwt.sign() 生成 JWT 字符串
// 参数:用户信息对象、加密密钥、配置对象-token有效期
// 尽量不保存敏感信息,因此只有用户名,没有密码
token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
})
})
JWT 字符串还原为 JSON 对象
- 客户端访问有权限的接口时,需通过请求头的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证
- 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
// unless({ path: [/^/api//] }) 指定哪些接口无需访问权限
app.use(expressJWT({
secret: secretKey,
//6.0版本之后要加上这个
algorithms: [‘HS256’]
}).unless({ path: [/^/api//] }))
获取用户信息
- 当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息
app.get('/admin/getinfo', (req, res) => {
console.log(req.user)
res.send({
status: 200,
message: '获取信息成功',
//data: req.user
//6.0版本后改用req.auth
data: req.auth
})
})
捕获解析 JWT 失败后产生的错误
- 当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
- 通过 Express 的错误中间件,捕获这个错误并进行相关的处理
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
return res.send({ status: 401, message: 'Invalid token' })
}
res.send({ status: 500, message: 'Unknown error' })
})
注: 本篇博客不包含时钟案例(P11-P13),时钟web案例(P18),大事件项目(P77-P96),仅包含基础知识内容,供搜索备忘。
原文地址:https://blog.csdn.net/m0_51487301/article/details/125959702
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_17903.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!