Node.js知识点总结
Node.js其本质和浏览器一样是一个JavaScript运行环境。浏览器的运行环境为V8引擎+浏览器内置API(BOM、DOM、Canvas);而node.js的运行环境是V8引擎+node提供的API(fs、path、http)。它使JavaScript可以编写后端。
基本知识
fs文件系统模块
-
它提供一系列的方法和属性,用来满足用户对文件的操作需求
-
使用语法:
-
加载 fs 模块,得到 fs 对象
const fs = require('fs')
- 写入文件内容语法:
fs.writeFile('文件路径', '写入内容', err => {
// 写入后的回调函数
})
- 读取文件内容的语法:
fs.readFile('文件路径', (err, data) => {
// 读取后的回调函数
// data 是文件内容的 Buffer 数据流
})
- 需求:向 test.txt 文件写入内容并读取打印
/**
* 目标:使用 fs 模块,读写文件内容
* 语法:
* 1. 引入 fs 模块
* 2. 调用 writeFile 写入内容
* 3. 调用 readFile 读取内容
*/
// 1.加载fs模块对象
const fs = require('fs')
// 2.写入文件内容
fs.writeFile('./test.txt', 'hello,Node.js', (err) => {
if(err) console.log(err)
else console.log('写入成功')
})
// 3.读取文件内容
fs.readFile('./test.txt', (err, data) => {
if (err) console.log(err)
// data是buffer16进制数据流对象
// toString()转换成字符串
//<Buffer 68 65 6c 6c 6f 2c 4e 6f 64 65 2e 6a 73>
else console.log(data.toString())
})
path路径模块
- 它是用来处理路径的模块
- 使用语法:
- 加载 path 模块,得到 path对象
const path = require('path')
- 将多个路径片段拼接成一个完整路径字符串
const path = require('path')
const fs = require('fs')
// 注意: ../ 会抵消前面的路径
// const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
// console.log(pathStr) // \a\b\d\e
// fs.readFile(__dirname + '/files/1.txt')
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', function(err, dataStr) {
if (err) {
return console.log(err.message)
}
console.log(dataStr)
})
- 从路径字符串中,将文件名解析出来
const path = require('path')
// 定义文件的存放路径
const fpath = '/a/b/c/index.html'
// const fullName = path.basename(fpath)
// console.log(fullName)
const nameWithoutExt = path.basename(fpath, '.html')
console.log(nameWithoutExt) //输出index
- 获取路径中的扩展名部分
const path = require('path')
// 这是文件的存放路径
const fpath = '/a/b/c/index.html'
const fext = path.extname(fpath)
console.log(fext)//输出.html
http 模块
是用来创建 web 服务器的模块,通过 http模块提供的http.createServer() 方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而提供资源服务
- 基本语法:
// 1. 导入 http 模块
const http = require('http')
// 2. 创建 web 服务器实例
const server = http.createServer()
// 3. 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', function (req, res) {
console.log('Someone visit our web server.')
})
// 4. 启动服务器
server.listen(8080, function () {
console.log('server running at http://127.0.0.1:8080')
})
URL端口号
- 是标记服务器里对应的服务程序,值为(0-65535 之间的任意整数)
- http 协议,默认访问的是 80 端口
req、res
- req:是请求对象,包含了与客户端相关的数据和属性
- res:是响应对象,包含了与服务器相关的数据和属性
const http = require('http')
const server = http.createServer()
// req 是请求对象,包含了与客户端相关的数据和属性
server.on('request', (req, res) => {
// req.url 是客户端请求的 URL 地址
const url = req.url
// req.method 是客户端请求的 method 类型
const method = req.method
const str = `Your request url is ${url}, and request method is ${method}`
console.log(str)
// 调用 res.end() 方法,向客户端响应一些内容
res.end(str)
})
server.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
模块化
CommonJS 标准
Node.js中每一个文件都是一个独立的模块,项目由多个模块组成
由于各个模块的属性和函数是私有的,如果对外使用,则需要标准语法导入、导出,即 CommonJS 标准
- 导出语法:
module.exports = {
对外属性名: 模块内私有变量
}
- 导入语法:
const 变量名 = require('模块名或路径')
// Node.js 环境内置模块直接写模块名(例如:fs,path,http)
// 自定义模块:写模块文件路径(例如:./utils.js)
-
代码实现:
-
导出
/**
* 目标:基于 CommonJS 标准语法,封装属性和方法并导出
*/
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
// 导出
module.exports = {
url: baseURL,
arraySum: getArraySum
}
- 导入
/**
* 目标:基于 CommonJS 标准语法,导入工具属性和方法使用
*/
// 导入
const obj = require('./utils.js')
console.log(obj)
const result = obj.arraySum([5, 1, 2, 3])
console.log(result)
ECMAScript标准-默认导出和导入
-
CommonJS 规范为 Node.js 环境中默认的,后来官方推出 ECMAScript 标准语法
-
注意:Node.js 默认只支持 CommonJS 标准语法,如果想要在当前项目环境下使用 ECMAScript 标准语法,请新建 package.json 文件设置 type: ‘module’ 来进行设置
{ “type”: "module" }
- 导出语法:
export default {
对外属性名: 模块内私有变量
}
- 导入语法:
import 变量名 from '模块名或路径'
-
语法实现:
-
导出:
/**
* 目标:基于 ECMAScript 标准语法,封装属性和方法并"默认"导出
*/
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
// 默认导出
export default {
url: baseURL,
arraySum: getArraySum
}
- 导入:
/**
* 目标:基于 ECMAScript 标准语法,"默认"导入,工具属性和方法使用
*/
// 默认导入
import obj from './utils.js'
console.log(obj)
const result = obj.arraySum([10, 20, 30])
console.log(result)
ECMAScript标准-命名导出和导入
- 命名导出语法:
export 修饰定义语句
- 命名导入语法:
import { 同名变量 } from '模块名或路径'
-
语法实现:
-
utils.js 导出
/**
* 目标:基于 ECMAScript 标准语法,封装属性和方法并"命名"导出
*/
export const baseURL = 'http://hmajax.itheima.net'
export const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
- index.js 导入
/**
* 目标:基于 ECMAScript 标准语法,"命名"导入,工具属性和方法使用
*/
// 命名导入
import {baseURL, getArraySum} from './utils.js'
console.log(obj)
console.log(baseURL)
console.log(getArraySum)
const result = getArraySum([10, 21, 33])
console.log(result)
npm包
-
将模块,代码,其他资料整合成一个文件夹,这个文件夹就叫包
-
包分类:
项目包:主要用于编写项目和业务逻辑
软件包:封装工具和方法进行使用
包管理工具
- 初始化清单文件: npm init -y
- 下载软件包:npm i 软件包名称
- 使用软件包
/**
* 目标:使用 npm 下载 dayjs 软件包来格式化日期时间
* 1. (可选)初始化项目清单文件,命令:npm init -y
* 2. 下载软件包到当前项目,命令:npm i 软件包名称
* 3. 使用软件包
*/
// 3. 使用软件包
const dayjs = require('dayjs')
const nowDateStr = dayjs().format('YYYY-MM-DD')
console.log(nowDateStr)
- 使用流程图:
npm全局软件包-nodemon
- 本地软件包:只在当前项目内使用,存在于 node_modules
- 全局软件包:本机所有项目使用,存在于系统设置的位置
- nodemon 作用:替代 node 命令,检测代码更改,自动重启程序
- 使用:
- 安装:npm i nodemon -g (-g 代表安装到全局环境中)
- 运行:nodemon 待执行的目标 js 文件
express
基本知识
- 就是一个 npm上的第三方包,提供了快速创建Web服务器的便捷方法
- 相较于http,express是基于内置的 http模块进一步封装出来的,开发效率更高
1.安装:
npm i express@4.17.1
2.创建基本的Web 服务器
3…监听 GET 、POST请求
4.把内容响应给客户端 res.send()
5.获取URL 中携带的查询参数:通过 req.query 对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数
6.获取URL 中的动态参数:通过 req.params 对象,可以访问到URL中,通过:匹配到的动态参数
// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串
res.send('请求成功')
})
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的 查询参数
// 注意:默认情况下,req.query 是一个空对象
console.log(req.query)
res.send(req.query)
})
// 注意:这里的 :id 是一个动态的参数
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认也是一个空对象
console.log(req.params)
res.send(req.params)
})
// 3. 启动 web 服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
express路由
- 在 Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系
- 由3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数
1.路由匹配过程:
- 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数
用法:
1)创建路由模块对应的 .js文件
2)调用 express.Router()函数创建路由对象
3)向路由对象上挂载具体的路由
4)使用 module.exports 向外共享路由对象
var express = require('express') //1.导入express
var router = express.Router() //2.创建路由对象
router.get('/user/list', function (req,res) { //3.挂载获取用户列表的路由
res.send('Get user list')
})
router.post('/user/add', function (req,res) { //4.挂载添加用户的路由
res.send('Add new user')
})
moudle.export = router //5.向外导出路由对象
5)使用app.use()函数注册路由模块
//1.导入路由模块
const userRouter = require('./router/user.js')
//2.使用app.use()注册路由模块,并添加统一的访问前缀 /api
app.use('/api',userRouter)
express中间件
- 当一个请求到达 Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
//包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由
const mw = function(req, res, next){
next()
}
const express = require('express')
const app = express()
// // 定义一个最简单的中间件函数
// const mw = function (req, res, next) {
// console.log('这是最简单的中间件函数')
// // 把流转关系,转交给下一个中间件或路由
// next()
// }
// // 将 mw 注册为全局生效的中间件
// app.use(mw)
// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
console.log('这是最简单的中间件函数')
next()
})
app.get('/', (req, res) => {
console.log('调用了 / 这个路由')
res.send('Home page.')
})
app.get('/user', (req, res) => {
console.log('调用了 /user 这个路由')
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
使用express写接口
// 导入 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')
})
数据库
- 数据库(database)是用来组织、存储和管理数据的仓库
- MySQL是目前使用最广泛、流行度最高的开源免费数据库
- SQL 是一门数据库编程语言,能够让我们以编程的形式,操作数据库里面的数据。
基本操作
1.连接数据库
2.了解主界面组成部分
3.创建数据库
4.创建数据表
5.向表中写入数据
使用SQL管理数据库
- 掌握使用SQL从数据表中:查询数据(select)、插入数据(insertinto)、更新数据(update)、删除数据(delete)
//获取数据表
select * from users
//从user表中获取username和password的内容
select username, password from user
- 插入数据
- update修改数据
把 users表中id为7的用户密码,更新为888888
- 删除数据
从 users表中,删除id 为4的用户,示例如下
- where语句
//1.查询status为1的所有用户
SELECT * FROM use WHERE status=1
//2.查询id大于4的所有用户
OM use WHERE status>4
在项目中操作MySQL
1.安装 mysql 模块
npm i mysql
2.配置mysql
const mysql = require('mysql')
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'test',
})
3.检测是否能正常工作
db.query('select 1', (err, results) => {
if (err) return console.log(err.message)
console.log(results)
})
4.使用 mysql 模块操作 MySQL 数据库
- 查询数据
// 查询 users 表中所有的数据
/* const sqlStr = 'select * from users'
db.query(sqlStr, (err, results) => {
// 查询数据失败
if (err) return console.log(err.message)
// 查询数据成功
// 注意:如果执行的是 select 查询语句,则执行的结果是数组
console.log(results)
}) */
- 插入数据
// 向 users 表中,新增一条数据,其中 username 的值为 Spider-Man,password 的值为 pcc123
/* const user = { username: 'Spider-Man', password: 'pcc123' }
// 定义待执行的 SQL 语句
const sqlStr = 'insert into users (username, password) values (?, ?)'
// 执行 SQL 语句
db.query(sqlStr, [user.username, user.password], (err, results) => {
// 执行 SQL 语句失败了
if (err) return console.log(err.message)
// 成功了
// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象
// 可以通过 affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
console.log('插入数据成功!')
}
}) */
------------------------------------------------------------------
//插入数据便捷方式
/* const user = { username: 'Spider-Man2', 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 user = { id: 6, username: 'aaa', password: '000' }
// 定义 SQL 语句
const sqlStr = 'update users set username=?, password=? where id=?'
// 执行 SQL 语句
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message)
// 注意:执行了 update 语句之后,执行的结果,也是一个对象,可以通过 affectedRows 判断是否更新成功
if (results.affectedRows === 1) {
console.log('更新成功')
}
}) */
-----------------------------------------------------------------------------
// 演示更新数据的便捷方式
/* const user = { id: 6, username: 'aaaa', password: '0000' }
// 定义 SQL 语句
const sqlStr = 'update users set ? where id=?'
// 执行 SQL 语句
db.query(sqlStr, [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('删除数据成功')
})
- 标记删除:
由于使用 delete 语句会真正删除数据,保险起见,使用标记删除的形式,模拟删除的动作。即在表中设置状态status字段,标记当前的数据是否被删除
const sqlStr = 'update users set status=? where id=?'
db.query(sqlStr, [1, 6], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) {
console.log('标记删除成功')
}
})
前后端的身份认证
Web开发模式
目前主流的有两类:基于服务端渲染的传统Web开发模式、基于前后端分离的新型Web开发模式
1)服务端渲染的传统Web开发模式:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接动态生成的→客户端不需要使用 Ajax 额外请求页面的数据
利:前端耗时短;有利于SEO
缺点:占用服务器资源;不利于前后的分离
app.get('/index.html', (req, res) => {
//1.要渲染的数据
const user = { name: 'Bruce', age: 29 }
//2.服务器端通过字符串的拼接,动态生成HTML内容
const html = `<h1>username:${user.name}, age:${user.age}</h1>`
//3.把生成好的页面内容响应给客户端→故客户端拿到的是带有真实数据的HTML页面
res.send(html)
})
2)前后端分离的 Web 开发模式:即后端只负责提供API 接口,前端使用 Ajax 调用接口的开发模式
- 利:前端专注页面开发,后端专注接口开发;页面局部刷新,无需重新请求页面
- 不利:不利于 SEO。完整的 HTML 页面在浏览器拼接完成,因此爬虫无法爬取页面的有效信息。→利用Vue、React等前端框架的SSR(serverside render)技术能够很好的解决SEO问题
3)应用场景:
- 企业级网站,主要功能为展示,无复杂交互且需要良好的SEO,故采用服务器端渲染;
- 后台管理项目,交互性较强,不需要考虑SEO→前后端分离的 Web 开发模式
身份认证
- 身份认证又称为鉴权,通过一定的手段,完成对用户身份的确认。
- 服务端渲染推荐使用session认证机制;前后端分离推荐使用JWT认证机制
session认证机制
-
工作原理:
-
安装express-session中间件
npm install express-session
- 配置中间件:需要通过 app.use()来注册 session 中间件
const session = require('express-session')
app.use(
session({
secret: '做一只猫', // secret 属性的值为任意字符串
resave: false, //固定写法
saveUninitalized: true //固定写法
})
)
- 向sessin中存数据:通过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 })
})
- 清空数据
app.post('/api/logout', (req, res) => {
// 清空当前客户端的session信息
req.session.destroy()
res.send({ status: 0, msg: '退出登录成功' })
})
- 局限性:Session 认证机制需要配合 Cookie 才能实现。由于Cookie默认不支持跨域访问→当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session认证
JWT认证机制
-
当前端需要跨域请求后端接口的时候,推荐使用JWT认证机制
-
工作原理:
-
JWT组成部分:Header(头部)、Payload(有效荷载)、Signature(签名),三者使用
.
分隔 -
使用方法:把 JWT 放在 HTTP 请求头的 Authorization 字段中
Authorization: Bearer <token>
Express 使用 JWT
1)安装JWT相关的包
ljsonwebtoken 用于生成 JWT 字符串
express-jwt用于将 JWT 字符串解析还原成JSON 对象
//安装多个包中间用空格隔开
npm install jsonwebtoken express-jwt
2)导入 JWT 相关的包:使用 require() 函数,分别导入 JWT相关的两个包
//1.导入用于生成字符串的包
const jwt = require('jsonwebtoken')
//2.导入用于将客户端发送过来的IWT字符串,解析还原称JSON对象的包
const expressJWT = require('express-jwt')
3)定义secret 密钥:
为保证 JWT 字符串安全性,需定义一个用于加密和解密的secret密钥
/ 密钥可以为任意字符串
const secretKey = 'dimple'
4)在登录成功后生成 JWT 字符串
调用 jsonwebtoken 包提供的sign()方法,将用户的信息加密成JWT字符串,响应给客户端
app.post('/api/login', (req, res) => {
//用户登录成功后,生成JWT字符串,通过token属性响应给客户端
res.send({
status: 200,
message: '登录成功',
// 调用jwt.sign() 生成 JWT 字符串,三个参数分别是用户信息对象,加密密钥,配置对象
token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '30s'})
})
})
5)将JWT 字符串还原为 JSON 对象
// unless({ path: [/^\/api\//] }) 指定哪些接口无需访问权限
app.use(expressJWT({
secret: secretKey
}).unless({ path: [/^\/api\//] }))
6)使用req.user 获取用户信息
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
})
})
7)捕获解析JWT 失败后产生的错误
app.use((err, req, res, next) => {
//token解析失败导致的错误
if (err.name === 'UnauthorizedError') {
return res.send({ status: 401, message: 'Invalid token' })
}
//其它原因导致的错误
res.send({ status: 500, message: 'Unknown error' })
})