Node.js
初识Node.js
What is Node.js
Node.js是一个基于Chrome V8引擎的Javascript的运行环境
/*
1. 浏览器是Javascript的前端运行环境
2. Node.js是Javascript的后端运行环境
*/
Node.js运行环境 = V8引擎 + 内置API(fs/path/http/JS内置对象/...)
- 学习路径
- 浏览器中的
Javascript学习路径Javascript基础语法 + 浏览器内置API(DOM + BOM)+ 第三方库
Node.js学习路径Javascript基础语法 +Node.js内置API(fs/path/http)+ 第三方API模块(express、mysql)
- 浏览器中的
Install Node.js
查看node版本号
node -v
在Node.js中执行Javascript代码
node 要执行的js文件路径
fs文件系统模块
What is fs
fs模块是Node.js官方提供的,用来操作文件的模块。提供了一系列的方法和属性,用来满足用户对文件的操作需求。fs.readFile()- 读取文件中的内容fs.writeFile()- 指定文件写入内容
- 使用时,先导入
const fs = require('fs') - 例子
// 导入fs模块 const fs = require('fs'); // 如果出现路径拼接问题,是因为使用了 ./ 或者 ../ 开头的相对路径 /** * 解决方式: * 1. 提供完成的路径(移植性非常差,难以维护) * 2. 使用node提供的 __dirname - 表示当前文件所处的目录 */ console.log(__dirname); // C:\Users\one\Desktop\yunfan\代码\node // 读取文件 /** * 参数1:读取文件的存放路径 * 参数2:读取文件是采用的编码方式,一般默认指定utf8 * 参数3:回调函数,获取读取失败和成功的结果 err dataStr */ fs.readFile(`${__dirname}/data.json`, 'utf8', (err, dataStr) => { if (err) { console.log(' -- err -- ', err.message); return; } console.log(' -- dataStr -- ', dataStr); }) // 写入文件 /** * 参数1:读取文件的存放路径 * 参数2:写入的内容 * 参数3:回调函数,获取读取失败和成功的结果 err dataStr * 默认以utf8的格式写入 */ fs.writeFile(`${__dirname}/files/text.txt`, '写入的内容', (err) => { // 如果文件写入成功,err默认为null // 如果文件写入失败,err是一个错误对象 if (err) { console.log(' -- err-- ', err); return; } console.log(' -- 文件写入成功 -- '); })
path路径模块
What is path
- path模块是
Node.js官方提供的,用来处理路径的模块。提供了一系列的方法和属性,用来满足用户对路径的处理需求。path.join()- 将多个路径片段拼接成一个完整的路径字符串path.basename()- 从路径字符串中,将文件名解析出来
- 使用时,先导入
const path = require('path') - 例子
const path = require('path') let pathStr = path.join('/a', '/b/c') console.log(' -- pathStr -- ', pathStr); // \a\b\c /** * ../ 会抵消前面一个路径 * . 会被处理掉 */ pathStr = path.join('/a', '/b/c', '../', './d', 'e') console.log(' -- pathStr -- ', pathStr); // \a\b\d\e const filePath = path.join(__dirname, './files/text/txt') console.log(' -- filePath -- ', filePath); // C:\Users\one\Desktop\yunfan\代码\node\files\text\txt
HTTP模块
What is http
http模块是Node.js官方提供的,用来创建web服务器的模块http.createServer()可以将一台普通电脑,变成一台Web服务器
- 消费资源的计算机 - 客户端;对外提供网络资源的计算机 - 服务器
- 要使用,先导入
const http = require('http') - 例子
// 导入http模块 const http = require('http'); const port = 8080; // 创建服务器实例 const server = http.createServer(); /** * 为服务器实例绑定request事件 * 使用服务器实例的 .on()方法,为服务器绑定一个 request 事件 */ server.on('request', (req, res) => { // 客户端请求的url地址,从端口号后面开始 const url = req.url; // 客户端请求的类型 const method = req.method; const str = `Your request url is ${url} and request method is ${method}`; console.log(str); // 发送中文时,会产生中文乱码问题。此时需要设置响应头的编码方式 res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end(`请求成功了!${str}`) }) /** * 启动服务器 * 调用 server.listen(端口号,cb回调)方法,即可启动web服务器 */ server.listen(port, () => { console.log(`http server running at http://127.0.0.1/${port}`); })
Node.js中的模块化
Node.js模块分类
Node.js中根据模块来源不同,将模块分类3大类:- 内置模块(由
Node.js官方提供的,例如:fs/path/http等) - 自定义模块(用户创建的每个
.js文件) - 第三方模块(由第三方开发,使用前需要先下载)
- 例如
momentnpm install moment const moment = require('moment')
- 例如
- 内置模块(由
Express相关
使用自定义路由模块化
apiRouter.js
const express = require('express');
const router = express.Router();
// http://localhost/api/get?name=elio&age=26
router.get('/get', (req, res) => {
const data = req.query;
res.send({
status: 200,
meg: '请求成功!',
data
})
})
module.exports = router
expressServer.js
// 导入express
const express = require('express');
const apiRouter = require('./apiRouter.js')
// 创建web服务器
const app = express();
// 中间件 - 路由中间件
app.use('/api', apiRouter);
// 启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1');
})
跨域
CORS相关的请求头
Access-Control-Allow-Origin
1. 指定允许访问该外部资源的外域URL
res.setHeader('Access-Control-Allow-Origin', 'https://www.baidu.com')
2. 允许来自任何域的访问
res.setHeader('Access-Control-Allow-Origin', '*')
Access-Control-Allow-Headers
- 默认情况下,
CORS仅支持 客户端向服务器 发送9种请求头AcceptAccept-LanguageContent-LanguageDPRDownlinkSave-DataViewport-WidthWidthContent-Typetext/plainmultipart/form-dateapplication/x-www-form-urlencode
- 如果客户端向服务器发送额外的请求头信息,需要在服务器通过
Access-Control-Allow-Headers对额外的请求头进行声明
// 多个请求头用英文逗号分割
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Customer-Header')
Access-Control-Allow-Methods
- 默认情况下,
CORS仅支持GET/POST/HEAD请求 - 客户端希望进行
PUT/DELETE等方式进行请求,需要在服务器通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE')
res.setHeader('Access-Control-Allow-Methods', '*')
CORS请求分类
-
简单请求
- 请求方式:
GET/POST/HEAD HTTP头部信息不超过以下几种字段:无自定义头部信息、客户端向服务器 发送9种请求头。- 客户端与服务器之间只会发生一次请求
- 请求方式:
-
预检请求(以下三者满足其一)
- 请求方式:除了
GET/POST/HEAD之外的请求方式 - 请求头种包含自定义头部字段
- 向服务器发送了
application/json格式的数据 - 客户端与服务器之间会发生两次请求
在客户端和服务器正式通信之前,客户端会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求。这一次的OPTION请求称为“预检请求”。 服务器成功响应预检请求后,才会发送真正的请求,并携带真实的数据
- 请求方式:除了
编写JSONP接口
- 概念:浏览器通过
<script>标签的src属性,请求服务器上的数据,服务器返回一个函数的调用。这种请求数据的方式叫做JSONP - 特点
- 不属于真正的
ajax请求,未使用XMLHttpRequest对象 - 仅支持
GET请求
- 不属于真正的
- 注意事项
- 如果项目中已经配置了
CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP接口,否则JSONP接口会被处理成开启了CORS的接口。const express = require('express'); const cors = require('cors'); const app = express(); // 先创建JSONP接口 app.get('/jsonp', (req, res) => {}) // 再配置CORS中间件 app.use(cors()); app.listen(80, () => { console.log('web server is running!'); })
- 如果项目中已经配置了
- 步骤
-
获取客户发送过来的回调函数名称
-
得到要通过
JSONP形式发送给客户端的数据 -
根据前两步得到的数据,拼接出一个函数调用的字符串
-
把上一步拼接得到的字符串,响应给客户端的
<script>标签进行解析执行app.get('/jsonp', (req, res) => { const funcName = req.query.callback; const data = { name: 'elio', age: 26, message: 'get request' }; const scriptString = `${funcName}(${JSON.stringify(data)})` res.send(scriptString) })<script>的src方式<body> <script> function funTest(data) { console.log(' -- success -- ', data); } </script> <script src="http://127.0.0.1:3008/jsonp?callback=funTest"></script> </body>JQuery的JSONP【有误,待修正】<button onclick="jsonpClick()">JSONP请求</button> function jsonpClick() { $.ajax({ method: 'get', url: 'http://127.0.0.1/jsonp', dataType: 'jsonp', // 表示发起JSONP请求 success: (res) => { console.log(' == res == ', res); } }) }
-
Mysql模块
安装与配置mysql模块
安装
本地安装mysql即可
配置
// 导入mysql模块
const mysql = require('mysql')
// 建立与mysql数据库的连接
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的ip
user: 'root', // 登录数据库的用户名
password: 'hello', // 登录数据库的密码
database: 'elio_system' // 指定操作的数据库
})
// 检测 mysql 模块是否能正常工作
db.query('select 1', (err, results) => {
if (err) return console.log(err.message)
console.log(results)
})
查看数据库的ip - select SUBSTRING_INDEX(host,':',1) as ip , count from information_schema.processlist group by ip;
操作数据库表
查询表数据
// 查询表数据
const sqlStr = 'select * from eduinf'
db.query(sqlStr, (err, results) => {
if (err) return console.log('-- err -- ')
console.log(results) // 结果时个数组
})
插入表数据
// 1. 要插入到admin表中的数据对象
const admin = {name: 'test', pwd: 'test'}
// 2. 待执行的 SQL 语句,其中英文的 ? 表示占位符
const sqlInsertStr = 'insert into admin(admin_name, admin_pwd) values(?, ?)'
db.query(sqlInsertStr, [admin.name, admin.pwd], (err, results) => {
if (err) return console.log(' -- err -- ', err)
console.log(results)
if (results.affectedRows) console.log('插入数据成功!')
})
/**
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 2,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}
*/
更新数据
// 更新数据
const admin = {id: 2, name: 'test', pwd: 'test123'}
const sqlStr = 'update admin set admin_pwd=? where admin_id=?'
db.query(sqlStr, [admin.pwd, admin.id], (err, results) => {
if (err) return console.log(' -- err -- ', err)
console.log(results)
if (results.affectedRows) console.log('数据更新成功!')
})
删除数据
// 删除数据
const sqlStr = 'delete from admin where admin_id=?'
db.query(sqlStr, 2, (err, results) => {
if (err) console.log(' -- err -- ', err.message)
if (results.affectedRows) console.log('删除数据成功!')
})
前后端的身份认证
Web开发模式
-
服务端渲染
- 优点
- 前端耗时少
- 有利于SEO(页面由后端渲染,爬虫容易爬取页面内容)
- 缺点
- 占用服务器资源
- 不利于前后端分离,开发效率低
- 优点
-
前后端分离
依赖于ajax技术广泛应用 后端只提供API接口 前端使用ajax调用接口- 优点
- 开发体验好 - 前后端分离,业务逻辑清晰
- 用户体验好 - ajax局部刷新
- 减轻服务器端压力
- 优点
【如何取舍】-【根据实际业务】
- 没有api请求的可以使用服务端渲染
- 前端业务逻辑复杂的,选用前后端分离
身份认证
- 服务端渲染 -
session认证机制 - 前后端分离 -
JWT认证机制
Session认证机制
HTTP协议的无状态性
客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态
如何突破HTTP无状态协议
- Cookie
-
Cookie是存储在用户浏览器中的一段不超过4KB的字符串。
- 由一个 名称(name)、值(value)和其他几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。
- 不同域名下的Cookie各自独立。每当用户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
-
特性
- 自动发送、域名独立、过期时限、4KB限制
-
Cookie不具有安全性
- 不要使用Cookie存储重要且隐私的数据
-
Session的工作原理
【Client】提交账号/密码
【Server】
验证账号/密码。
将登录成功后的用户信息存储在服务器的内存中,同时生成对应的Cookie字符串。通过响应头将Cookie发送给客户端
【Client】自动把Cookie存储在当前域名下
【Client】再一次发送请求时,请求头携带当前域名下所有未过期的Cookie发送给服务器
【Server】
服务器根据请求头携带的Cookie,从内存中查找出对应的信息。
用户的身份认证成功后,服务器针对当前用户生成特定的响应内容返回给浏览器
Express中使用Session认证
-
安装
express-session依赖包npm install express-session -
配置
express-session中间件const express = require('express') // 创建express的服务器实例 const app = express(); //导入session中间件 const session = require('session') // 配置session中间件 app.use(session({ secret: 'keyboard cat', // secret属性值可以为任意字符串 resave: false, // 固定写法 saveUninitalized: true // 固定写法 })) -
向
Session中存数据- 当
express-session中间件配置成功后,即可通过req.session来访问和使用session对象,从而存储相关信息
app.post('/api/login',(req, res) => { const data = req.query req.session.user = req.query req.session.isLogin = true res.send({ status: 200, msg: '登录成功!' }) }) - 当
-
从
session中取数据app.get('/api/getUser',(req, res) => { if (!req.session.isLogin) { return res.send({ status: 2001, msg: 'fail' }) } res.send({ status: 200, msg: 'success', data: req.session.user }) }) -
清空
Session- 只会清空当前用户的Session,不是所有- 业务场景:用户退出系统
app.post('api/logout', (req,res) => { req.session.destory(); res.send({ status: 200, msg: '退出成功' }) })
JWT认证机制
Session认证的局限性
Session认证机制需要配合Cookie才能实现。Cookie不支持跨域访问,当前后端进行跨域请求时,需要很多额外的配置。
what is JWT
JWT(JSON Web Token)是目前最流行的跨域认证解决方案
JWT工作原理
【Client】提交账号密码
【Serve】
验证账号密码
验证通过后,将用户的信息对象经过加密生成Token字符串 【加密】
发送给客户端
【Client】将Token存储到WebStorage中(LocalStorage/SessionStorage)
【Client】再一次发送请求时,通过请求头的 Authorization 字段,将Token发送给服务器
【Server】
服务器把Token还原成用户的信息对象 【解密】
用户身份认证成功后,服务器针对当前用户生成特定的响应内容返回给浏览器
【总结】
- 用户的信息通过
Token的形式,保存在客户端浏览器中。服务器通过还原Token来认证用户身份
JWT组成部分
三部分组成:Header(头部)、Payload(有效荷载)、Signature(签名)。三者之间用 . 分割
- Payload: 真正的用户信息,是用户信息经过加密后生成的字符串
- Header/Signature:安全性相关的部分,为了保证Token的安全
JWT的使用方式
- 客户端接收到服务器返回的
JWT后,存储在WebStorage中 - 此后每次通信客户端把
JWT放在HTTP请求头的Authorization字段中Authorization: Bearer <token>
在Express中使用JWT
安装JWT相关的包
npm install jsonwebtoken express-jwt
jsonwebtoken- 用于生成JWT字符串express-jwt- 用于将JWT字符串解析还原成JSON对象
导入JWT相关的包
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
定义secret密钥
-
为了保证
JWT字符串的安全性,防止JWT在网络传输过程中被破解,需要专门定义一个用于加密和解密的secret密钥- 生成
JWT之前,使用secret密钥进行加密,得到一个JWT字符串 JWT还原JSON时,需要使用同一个secret密钥进行解密
- 生成
-
定义一个普通字符串即可,越复杂越好
// secret密钥的本质:就是一个字符串 const secretKey = 'jtsecret*@*^&^'
登录成功后生成JWT字符串
- 调用
jsonwebtoken包提供的sign()方法,将用户信息加密成JWT字符串,响应给客户端//登录接口 app.post('/spi/login', (req, res) => { res.send({ status: 200, message: '登录成功', // 三个参数:用户信息对象,加密密钥,配置对象 token: jwt.sgin({username: username}, secretKey, {expiresIn: '30s'}) }) })
将JWT还原成JSON对象
-
客户端每次访问有权限的接口时,都需要主动通过请求头中的
Authorization字段,将Token发送到服务器进行身份验证 -
服务器通过
express-jwt解析token为JSON对象// 使用app.use()来注册中间件 // expressJWT({secret: secretKey})解析Token // .unless({path:[/^/api//]})用来指定哪些接口不需要访问权限 // 【注意】:只要配置成功了express-jwt这个中间件,可以把解析的用户信息挂载到req.user属性上
捕获解析JWT失败后产生的错误
- 当
express-jwt解析Token时。客户端发送的Token不合法或过期,或产生一个解析失败的错误。通过Express的错误中间件捕获这个错误并进行相关的处理app.use((err, req, res, next) => { // token解析失败导致的错误 if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '无效的token' }) } // 其他的错误 res.send({ status: 500, message: '未知错误' }) })
【完整代码】
const express = require('express')
const app = express();
// 1. 导入 JWT 相关的包
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 解析 post 表单数据的中间件
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
// 2. secret密钥
const secretKey = '^jwt&secret@elio^'
// 4. 注册将 JWT 还原成 JSON 对象的中间件
// 【注意】只要配置成功 express-jwt 这个中间件,就可以把解析出来的用户信息挂载到 req.user 这个属性上
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^/api//]}))
// 登录接口
app.post('/api/login', (req, res) => {
const userInfo = req.body;
if (userInfo.username !== 'admin' && userInfo.password !== 'admin000') {
return res.send({
status: 2000,
message: '登录失败'
})
}
const tokenStr = jwt.sign({ username: userInfo.username }, secretKey, {expiresIn: '30s'})
res.send({
status: 200,
message: '登录成功',
token: tokenStr
})
})
// 获取用户信息
app.get('/getUserInfo', (req, res) => {
// 5. 使用 req.auth.user 获取用户信息,并响应给客户端
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功',
data: req.user
})
})
// 6. 声明一个全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
// token解析失败导致的错误
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的token'
})
}
// 其他的错误
res.send({
status: 500,
message: '未知错误'
})
})
app.listen(8888, () => {
console.log('express server running at http://127.0.0.1:8888')
})
补充说明
express-jwt版本太高引发的问题
node.js 使用 express-jwt 报错:expressJWT is not a function_expressjwt设置中间件没有生效_潮汐未见潮落的博客-CSDN博客
Node-Server-Project
GitHub - github.com/elio1900/no…