这是我参与「第五届青训营」伴学笔记创作活动的第 3 天
fs文件系统模块
fs.readFile()
fs.readFile(path, [options], callback)
- 参数一:必填,文件路径(字符串)
- 参数二:选填,编码格式
- 参数三:必填,读取完成后的回调函数
const fs = require('fs')
fs.readFile('./files/11.txt', 'utf8', function(err, dataStr) {
// 如果读取成功,则 err 的值为 null
// 如果读取失败,则 err 的值为 错误对象,dataStr 的值为 undefined
if (err) {
return console.log('读取文件失败!' + err)
}
console.log('读取文件成功!' + dataStr)
})
fs.writeFile()
fs.writeFile(path, data, [options], callback)
- 参数一:必填,文件路径(字符串)
- 参数二:必填,要写入的内容
- 参数三:选填,编码格式(默认utf-8)
- 参数四:必填,读取完成后的回调函数
注意:
※ fs.writeFile() 只能创建文件,不能创建路径
※ fs.writeFile() 重复写入,会覆盖原来的文件
const fs = require('fs')
fs.writeFile('./files/3.txt', 'ok123', function(err) {
// 如果文件写入成功,则 err 的值等于 null
// 如果文件写入失败,则 err 的值等于一个 错误对象
if (err) {
return console.log('文件写入失败!' + err)
}
console.log('文件写入成功!')
})
路径拼接错误
文件路径如果是./或../这种相对路径,运行时会执行 当前执行node命令所在的目录+path
例:
解决方法:使用__dirname (双下划线)(使用+符号拼接 此方法可能会存在问题)
//__dirname 表示当前文件所处的目录
fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, dataStr) {})
如下会报错(路径多一个./):
fs.readFile(__dirname + './files/1.txt', 'utf8', function(err, dataStr) {})
path路径模块
path.join()
path.join([...paths]) 可以把多个路径片段拼接为完整路径
- ...paths:路径片段序列
- 返回值:拼接好的字符串
const path = require('path')
// 注意: 只有../ 会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../', './d', 'e')
console.log(pathStr) // \a\b\d\e
实际应用:
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', function(err, dataStr) {}
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
path.exename()
path.exename(path) 获取路径中的文件扩展名
- path:必填,路径字符串
- 返回值:扩展名字符串
const path = require('path')
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext) // 输出.html
http模块
基本使用
// 1. 导入 http 模块
const http = require('http')
// 2. 创建 web 服务器实例
const server = http.createServer()
// 3. 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', (req, res) => {
// req
const url = req.url // req.url 是客户端请求的 URL 地址
const method = req.method // req.method 是客户端请求的 method 类型
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)
// res
// 调用 res.setHeader() 方法,设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(str) // 调用 res.end() 方法,向客户端响应一些内容,并结束这次请求的处理过程
})
// 4. 启动服务器
server.listen(8080, () => {
console.log('server running at http://127.0.0.1:8080')
})
模块化
使用require引用其他模块,会自动执行模块内代码
exports & module.exports
- 使用require()得到的永远是
module.exports指向的对象 - 指向同一个对象,最终向外共享的结果以
module.exports指向的对象为准 - 注意:为了防止混乱,建议不要同时使用
CommonJS
规定了模块的特性和各模块间如何相互依赖
- 每个模块内部,module变量代表当前模块。
- module变量是一个对象,它的exports属性(即 module.exports)是对外的接口。
- 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块。
自定义模块加载机制
使用 require() 加载自定义模块时,必须指定以 ./或 ../开头的路径标识符。如果没有指定则 node 会把它当作内置模块或第三方模块进行加载。
在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
- ① 按照确切的文件名进行加载
- ② 补全 .js 扩展名进行加载
- ③ 补全 .json 扩展名进行加载
- ④ 补全 .node 扩展名进行加载
- ⑤ 加载失败,终端报错
第三方模块加载机制
如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ./ 或 ../ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 'C:\Users\itheima\project\foo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:
- ① C:\Users\itheima\project\node_modules\tools
- ② C:\Users\itheima\node_modules\tools
- ③ C:\Users\node_modules\tools
- ④ C:\node_modules\tools
- ⑤ 加载失败,终端报错
目录作为模块时加载机制
当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:
- ① 在被加载的目录下查找 package.json 文件,并寻找 main 属性,作为 require() 加载的入口
- ② 加载目录下的 index.js 文件
- ③ 加载失败,报告模块的缺失:Error: Cannot find module 'xxx'
npm包管理
devDependencies节点
开发依赖包,只在开发阶段用到,项目上线之后不会用到
//安装指定包,并记录到 devDependencies节点中
//简写
npm i 包名 -D //包名和-D顺序可以颠倒
//非简写
npm install 包名 --save-dev
dependencies节点
核心依赖包,开发和上线之后都会用到
安装方式:直接安装,不用写-D
切换 npm 的下包镜像源
//查看当前的下包镜像源
npm config get registry
//将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
//检查镜像源是否下载成功
npm config get registry
nrm快速切换镜像源
//通过npm包管理器,将nrm安装为全局可用的工具
npm i nrm -g
//查看所有可用的镜像源
nrm ls
//将下包的镜像源切换为taobao镜像
nrm use taobao
express
基本使用
// 1.导入 express
const express = require('express')
// 2.创建 web 服务器
const app = express()
// 3.启动服务器
app.listen(80, () => {
console.log('http://127.0.0.1')
})
GET & POST
监听GET请求
app.get() 方法,可以监听客户端的 GET 请求
app.get('请求url', function(req, res) {/*处理函数*/})
//参数1:客户端请求的URL地址
//参数2︰请求对应的处理函数
// req:请求对象(包含了与请求相关的属性与方法)
// res:响应对象(包含了与响应相关的属性与方法)
监听POST请求
app.post() 方法,可以监听客户端的 POST 请求
app.post('请求url', function(req, res) {/*处理函数*/})
//参数1:客户端请求的URL地址
//参数2︰请求对应的处理函数
// req:请求对象(包含了与请求相关的属性与方法)
// res:响应对象(包含了与响应相关的属性与方法)
res.send()方法
把处理好的数据发送给客户端
app.get('/', (req, res) => {
res.send('hello world.')
})
app.post('/', (req, res) => {
res.send('Post Request.')
})
res.query对象
获取请求路径中以查询字符串形式发送的参数(res.query默认是空对象)
app.get('/', (req, res) => {
// 请求http://127.0.0.1/?a=1&b=2
res.send(req.query) //响应{"a":"1","b":"2"}
})
req.params对象
获取请求路径中以 : 形式匹配的动态参数(req.params默认是空对象)
app.get('/user/:id/:name', (req, res) => {
// 请求http://127.0.0.1/user/666/aaa
res.send(req.params) //响应{"id":"666","name":"aaa"}
})
express.static()
静态资源托管,创建一个静态资源服务器,外部可以直接访问
app.use(express.static('public'))
/*
可以访问public中的文件
http://localhost/images/bg.jpg
http://localhost/css/style.css
http://localhost/js/login.js
*/
注意:Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。
因此,存放静态文件的目录名不会出现在 URL 中。
访问多个时,多次使用即可,访问静态资源文件时,会按照目录添加顺序查找
app.use(express.static('public1'))
app.use(express.static('public2'))
挂载路径前缀
app.use('abc',express.static('public'))
/*
现在可以通过带有 /public 前缀地址来访问 public 目录中的文件:
http://localhost/abc/images/kitten.jpg
http://localhost/abc/css/style.css
http://localhost/abc/js/app.js
*/
路由
模块化路由
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上例如app.get() ,app.post() ,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤如下:
- ①创建路由模块对应的 .js 文件
- ②调用
express.Router()函数创建路由对象 - ③向路由对象上挂载具体的路由
- ④使用
module.exports向外共享路由对象 - ⑤使用
app.use()函数注册路由模块
// 1. 导入 express
const express = require('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载具体的路由
router.get('/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})
// 4. 向外导出路由对象
module.exports = router
使用模块
const express = require('express')
const app = express()
// 1. 导入路由模块
const router = require('./my_router')
// 2. 注册路由模块
app.use('/abc', router) /* /abc是挂载路由前缀 */
// 注意: app.use() 函数的作用,就是来注册全局中间件
app.listen(80, () => {
console.log('http://127.0.0.1')
})
中间件
对请求进行预处理
Express 的中间件,本质上是一个 function 处理函数,Express 中间件的格式如下:
注意:中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。
多个中间件之间,共享同一份 req 和 res。基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
全局生效
使用 app.use(/*中间件*/) #客户端发起的任何请求,到达服务器后都会触发中间件
方法一:
const mw = (req, res, next) => {
console.log('调用了全局生效的中间件') //先定义再注册
next()
}
app.use(mw)
========
方法二:
app.use(function(req, res, next) => { //直接在注册的时候声明
console.log('调用了全局生效的中间件')
next()
})
局部生效
不使用app.use()
// 1. 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了局部生效的中间件')
next()
}
// 2. 创建路由
app.get('/', mw1, (req, res) => { //只在当前生效
res.send('Home page.')
})
app.get('/user', (req, res) => {
res.send('User page.')
})
//多个
app.get('/', [mw1, mw2], (req, res) => {})
或
app.get('/', mw1, mw2, (req, res) => {})
中间件分类
应用级别中间件
通过app.use()或app.get()或app.post(),绑定到app实例上的中间件。
路由级别中间件
绑定到express.Router()实例上的中间件,用法和应用级别中间件没有区别。
错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。
注意:错误级别的中间件, 必须注册在所有路由之后
Express内置的中间件
① express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
② express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
③ express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
第三方的中间件
非 Express 官方内置的,而是由第三方开发出来的中间件。
例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:
①运行 npm install body-parser 安装中间件
②使用 require 导入中间件
③调用 app.use() 注册并使用中间件
自定义中间件
实现步骤:
①定义中间件
②监听 req 的 data 事件
③监听 req 的 end 事件
④使用 querystring 模块解析请求体数据
⑤将解析出来的数据对象挂载为 req.body
⑥将自定义中间件封装为模块
// 导入 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')
})
跨域
CORS
CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可以解除浏览器端的跨域访问限制。
cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便地解决跨域问题。
使用步骤分为如下 3 步:
①运行 npm install cors 安装中间件
②使用 const cors = require('cors') 导入中间件
③在路由之前调用 app.use(cors()) 配置中间件
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require("cors")
app.use(cors())
CORS 响应头部
Access-Control-Allow-Origin
响应头部中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下:
Access-Control-Allow-Origin: <origin> | *
// origin 参数的值指定了允许访问该资源的外域 URL
例:只允许来自 itcast.cn 的请求
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')允许来自任何域的请求
res.setHeader('Access-Control-Allow-Origin','*')
Access-Control-Allow-Headers
默认情况下,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 对额外的请求头进行声明,否则这次请求会失败!
Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法。
简单请求
同时满足以下两大条件的请求,就属于简单请求:
① 请求方式:GET、POST、HEAD 三者之一
② HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求:
① 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
② 请求头中包含自定义头部字段
③ 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
区别:
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
JSONP接口
概念:浏览器端通过
特点:
①JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 这个对象。
②JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求。
如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了 CORS 的接口。示例代码如下:
实现JSONP接口的步骤
- 获取客户端发送过来的回调函数的名字
- 得到要通过 JSONP 形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的
在网页中使用 jQuery 发起 JSONP 请求
调用 $.ajax() 函数,提供 JSONP 的配置选项,从而发起 JSONP 请求
代理
webpack开发配置API代理解决跨域问题-devServer
下文参考链接:segmentfault.com/a/119000001…
一个完整的webpack配置代理代码
设置代理的前提条件: 1、需要使用本地开发插件:webpack-dev-server。 2、webpack-dev-server使用的是http-proxy-middleware来实现跨域代理的。 3、webpack版本: 3.0、4.0亲测有效
一个webpack配置信息:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://www.baidu.com/',
pathRewrite: {'^/api' : ''},
changeOrigin: true, // target是域名的话,需要这个参数,
secure: false, // 设置支持https协议的代理
},
'/api2': {
.....
}
}
}
};
- 配置中主要的参数说明
2.1 '/api'
捕获API的标志,如果API中有这个字符串,那么就开始匹配代理, 比如API请求/api/users, 会被代理到请求 www.baidu.com/api/users 。
2.2 target
代理的API地址,就是需要跨域的API地址。 地址可以是域名,如:http://www.baidu.com 也可以是IP地址:http://127.0.0.1:3000 如果是域名需要额外添加一个参数changeOrigin: true,否则会代理失败。
2.3 pathRewrite
路径重写,也就是说会修改最终请求的API路径。 比如访问的API路径:/api/users, 设置pathRewrite: {'^/api' : ''},后, 最终代理访问的路径:http://www.baidu.com/users, 这个参数的目的是给代理命名后,在访问时把命名删除掉。
2.4 changeOrigin
这个参数可以让target参数是域名。
2.5 secure
secure: false,不检查安全问题。 设置后,可以接受运行在 HTTPS 上,可以使用无效证书的后端服务器
mysql模块
连接数据库
// 1. 导入 mysql 模块
const mysql = require('mysql')
// 2. 建立与 MySQL 数据库的连接关系
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的 IP 地址
user: 'root', // 登录数据库的账号
password: 'root', // 登录数据库的密码
database: 'data', // 指定要操作哪个数据库
})
增
const user = { username: 'aaa', password: 'pcc4321' }
// 定义待执行的 SQL 语句
const sqlStr = 'insert into users set ?'
// 执行 SQL 语句
db.query(sqlStr, user, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) {
console.log('插入数据成功')
}
})
删
const sqlStr = 'delete from users where id=?'
db.query(sqlStr, 5, (err, results) => {
if (err) return console.log(err.message)
// 注意:执行 delete 语句之后,结果也是一个对象,也会包含 affectedRows 属性
if (results.affectedRows === 1) {
console.log('删除数据成功')
}
})
改
const user = { id: 1, username: 'aaaa', password: '0000' }
// 定义 SQL 语句
const sqlStr = 'update users set user_name = ?,pass_word = ? where id=?'
// 执行 SQL 语句
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) {
console.log('更新数据成功')
}
})
查
const sqlStr = 'select * from users'
db.query(sqlStr, (err, results) => {
// 查询数据失败
if (err) return console.log(err.message)
// 查询数据成功
// 注意:如果执行的是 select 查询语句,则执行的结果是数组
console.log(results)
})
身份认证
服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。
前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。
服务端渲染推荐使用 Session 认证机制
前后端分离推荐使用 JWT 认证机制
Session认证机制
cookie
Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
Cookie的几大特性:
①自动发送
②域名独立
③过期时限
4KB 限制
工作原理
1.安装 express-session 中间件
npm install express-session
2.配置express-session中间件
const session = require('express-session') //导入session中间件
app.use(
session({
secret: 'aaa', //secret属性值可以为任意字符串
resave: false, //固定写法
saveUninitialized: true //固定写法
})
)
3.向session中存数据
app.post('/api/login', (req, res) => {
// 判断用户提交的登录信息是否正确
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' })
}
// 将登录成功后的用户信息,保存到 Session 中
// 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
req.session.user = req.body // 用户的信息
req.session.islogin = true // 用户的登录状态
res.send({ status: 0, msg: '登录成功' })
})
4.从session中取数据
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// 从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username,
})
})
5.清空session
// 退出登录的接口
app.post('/api/logout', (req, res) => {
// 清空 Session 信息
req.session.destroy()
res.send({
status: 0,
msg: '退出登录成功',
})
})
局限性
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
注意:
- 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
- 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。
JWT认证机制
JWT(英文全称:JSON Web Token),用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
工作原理
组成部分
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
Header.Payload.Signature
JWT 字符串的具体示例:
- Payload 部分是真正的用户信息,它是用户信息经过加密之后生成的字符串。
- Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。
1.安装JWT相关的包
npm install jsonwebtoken express-jwt
// jsonwebtoken 用于生成 JWT 字符串
// express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
2.导入JWT相关包
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
3.定义 secret 密钥
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,需要专门定义一个用于加密和解密的 secret 密钥:
- 当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串
- 当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密
const secretKey = 'xxxxxxxxx' //密钥就是一个字符串,可以任曦填写
4.登录成功后生成JWT字符串
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) //30s之内有效
//在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
// 参数1:用户的信息对象
// 参数2:加密的秘钥
// 参数3:配置对象,可以配置当前 token 的有效期
// 记住:千万不要把密码加密到 token 字符中
5.将 JWT 字符串还原为 JSON 对象
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证。
此时,服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^/api//] }))
// expressJWT({ secret: secretKey } secretKey是自己定义的,用来解析Token
// .unless({ path: [/^/api//] }) 用来指定哪些接口不需要访问权限
注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上。
6.使用 req.user 获取用户信息
当 express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息了,示例代码如下:
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user, // 要发送给客户端的用户信息
})
})
7. 捕获解析 JWT 失败后产生的错误
当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理。
使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => { // 这次错误是由 token 解析失败导致的 if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '无效的token', }) } res.send({ status: 500, message: '未知的错误', }) })
常用案例
发请求
客户端
const http = require("http");
// 请求参数
const body = JSON.stringify({
msg: "hello from client",
});
const req = http.request(
"http://127.0.0.1:3000",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
},
(res) => {
const bufs = [];
res.on("data", (buf) => {
bufs.push(buf);
});
res.on("end", () => {
const buf = Buffer.concat(bufs).toString("utf-8");
const json = JSON.parse(buf);
console.log("json:", json);
});
}
);
req.end(body);
服务端
const http = require("http");
const server = http.createServer((req, res) => {
const bufs = [];
// 监听数据传输
req.on("data", (buf) => {
bufs.push(buf);
});
// 数据传输结束
req.on("end", () => {
const buf = Buffer.concat(bufs).toString("utf-8");
let msg = "11";
try {
const ret = JSON.parse(buf);
console.log(ret);
msg = ret.msg;
const responseJson = {
msg1: `receive:${msg}`,
msg2: `receive:${msg}`,
msg3: `receive:${msg}`,
};
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(responseJson));
} catch (err) {
res.end("invalid json");
}
});
});
const port = 3000;
server.listen(port, () => {
console.log("listening on : http://localhost:" + port);
});
静态文件服务
文件结构
--static
--index.html
--static_server.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const url = require("url");
// 静态文件位置(__dirname当前文件位置)
const folderPath = path.resolve(__dirname, "./static");
const server = http.createServer((req, res) => {
// expected http:127.0.0.1:3000/index.html
const info = url.parse(req.url);
// static/index.html
const filepath = path.resolve(folderPath, "." + info.path);
// stream api..
const filestream = fs.createReadStream(filepath);
filestream.pipe(res);
});
const port = 3000;
server.listen(port, () => {
console.log("listening on http://localhost:" + port);
});