安装:
1、官网直接安装:
(http://nodejs.p2hp.com/)
2、使用nvm工具安装:
Releases · coreybutler/nvm-windows (github.com)
nvm -v
nvm list
nvm install 版本
nvm node_mirror https://npmmirror.com/mirrors/node/
nvm install latest
nvm install lts
nvm use 版本
同步和异步
进程和线程:
-进程(厂房): 程序运行的环境 -线程(工人): 实际进行运算的东西
同步:
- 通常代码都是自上向下执行
- 前面的代码不执行的话,后面的代码也不会执行
- 同步的代码执行会出现阻塞的情况
- 一行代码执行慢会影响整个程序的执行
function sum(a, b) {
const begin = Date.now()
while (Date.now() - begin < 10000) {}
return a + b
}
console.log(1111);
const result = sum(123, 456)
console.log(result);
console.log(2222);
解决同步问题:
Java,Python 通过多线程来解决
Node.js 通过异步来解决
异步:
异步的问题:
Promise:
promise可以存储异步调用的数据,可以帮助结局异步中回调函数的问题
有一套特殊的存储数据的方式,可以存储异步调用的结果
创建Promise:
const promise = new Promise((resolve, reject)=>{
})
通过函数向Promise中添加数据,好处是可以添加异步调用的数据
从Promise中读取数据:
可以通过Promise的实例方法then来读取Promise中存储的数据
then需要两个回调函数作为参数,回调函数用来获取Promise中的数据
- 通过resolve存储的数据,会调用第一个函数返回,可以在第一个函数中编写处理数据的代码
- 通过reject存储的数据或者出现异常时,会调用第二个函数返回,可以在第二个函数中编写处理异常的代码
const promise = new Promise((resolve,reject) => {
resolve('Test')
})
const p1 = promise.then(result => {
console.log(result)
},reason => {
console.log('错误')
})
Promise中维护的两个隐藏属性:
PromiseResult:
- 用来存储数据
PromiseState:
- 用来记录Promise的状态(三种状态)
State只能修改一次,修改后永远不会再变
流程:
当Promise创建时,PromiseState初始值变为pending
通过resolve存储数据时,PromiseState变为fulfilled,PromiseResult变为存储的数据
通过reject存储数据或出错时时,PromiseState变为rejected,PromiseResult变为存储的数据或者异常对象
当我们通过then读取数据时,相当于为Peomise设置了回调函数
如果PeomiseState变为fulfilled,则调用then的第一个回调函数来返回数据
如果PeomiseState变为rejected,则调用then的第二个回调函数来返回数据
catch():
catch()的用法与then类似,但是只需要一个回调函数作为参数,该回调函数只会在Promise被拒绝时调用
catch()相当于then(null,reason =>{})
catch()就是一个专门处理Promise异常的方法
finally():
无论时正常处理数据还是出现异常,finally总会执行
finally()的回调函数中不会接受到数据
finally()通常用来编写一些无论成功与否都要执行的代码
promise中的then() catch()都会返回一个新的Promise,finally()的返回值则不会存储到新的promise里
Promise中的静态方法:
resolve()
Promise.resolve(10)
new Promise((resolve,reject) => {
resolve(10)
})
reject()
Promise.all([...])
Promise.race([...])
Promise.any([...])
any会race类似,但是它只会返回第一个成功的Promise,如果所有的Promise都失败才会返回一个错误信息。
宏任务队列、微任务队列:
- 任务队列是将要执行的代码
- 当调用栈中的代码执行完毕后,队列中的代码才会按次序依次进入栈中执行
- 在JS中任务队列有俩种:
- 宏任务队列:大部分代码都在宏任务队列中排队
- 微任务队列:Promise的回调函数(then、catch、finally)
- 整个流程
- 执行调用栈中的代码
- 执行微任务队列中的所有任务
- 执行宏任务队列中的所有任务
queueMicrotask():
自定义Promise:
const PROMISE_STATE = {
PENDING: 0,
FULFILLED: 1,
REJECTED: 2
}
class MyPromise {
#result
#state = PROMISE_STATE.PENDING
#callbacks = []
constructor(executor) {
executor(this.#resolve.bind(this), this.#reject.bind(this))
}
#resolve(value) {
if (this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED
queueMicrotask(()=>{
this.#callbacks.forEach(cb => {
cb()
})
})
}
#reject(reason) {
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve,reject)=>{
if(this.#state === PROMISE_STATE.PENDING){
this.#callbacks.push(()=>{
resolve(onFulfilled(this.#result))
})
}else if (this.#state === PROMISE_STATE.FULFILLED) {
queueMicrotask(()=>{
resolve(onFulfilled(this.#result))
})
}
})
}
}
const mp = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('孙悟空')
}, 1000);
})
console.log(mp);
const p = mp.then((result) => {
console.log('读取数据1', result);
return '猪八戒'
}).then(r=>{
console.log('读取数据2',r);
return '沙和尚'
}).then(r=>{
console.log('读取数据3',r);
})
输出结果:
MyPromise {#resolve: ƒ, #reject: ƒ, #result: undefined, #state: 0, #callbacks: Array(0)}
读取数据1 孙悟空
读取数据2 猪八戒
读取数据3 沙和尚
async和await:
通过async可以快速创建异步函数,异步函数的返回值会自动封装到一个Peomise中返回
async function fn(){
return 10
}
let result = fn()
fn().then(r=>{
console.log(r)
})
当通过await去调用异步函数时,他会停止代码的运作,知道异步代码执行有结果时才将结果返回
function sum(a,b){
return new Promise(resolve => {
setTimeout(() => {
resolve(a + b)
}, 2000);
})
}
async function fn(){
let result = await sum(123,456)
console.log(result);
}
fn()
await只能用于async声明的异步函数中,或es模块的顶级作用域中
- await阻塞的只是异步函数内部的代码,不会影响外部代码
- 若async声明的函数中没有写await,那么它里面就会依次执行
- 当我们使用await调用函数后,当前函数后面所有的代码会在当前函数执行完毕后,放入微任务队列中
模块化
早期的网页中,没有一个实质的模块化规范,实现模块化的方式时最原始的通过Script标签来引入多个js文件,于是会出现一些问题
无法选择要引入模块的哪些内容
在复杂的模块场景下容易出错
...
在Node.js中,默认支持的模块化规范是CommonJS,在CommonJS中一个js文件就是一个模块
CommonJS规范:
- 引入模块
- 使用require('模块的路径')函数来引入模块
- 引入自定义模块时
在定义模块时,模块中的内容默认是不被外部看到的,但可以通过exports来设置向外部暴露的内容
1. exprots
2. module.exports
exports === module.exprots true
exports.a = '孙悟空'
exports.b = '猪八戒'
module.exports = {
a:'猪八戒',
b:[1,3,5,7,9],
c:()=>{
console.log(123)
}
}
- 引入核心模块时
- 直接写核心模块名字即可
- 也可以在核心模块前添加 node:
require('path')
require('node:path')
默认情况下,Node.js会将以下内容视为CommonJS模块:
- 使用.cjs为扩展名的文件
- 当前的package.json的type属性为commonjs时,扩展名为.js的文件
- 当前的package.json不包含type属性时,扩展名为.js的文件
- 文件的扩展名是mjs、cjs、json、node、js以外的值时(type不是module时)
所有的CommonJS的模块都会被包装到一个函数中
(function(exports, require, module, __filename, __dirname) {
});
ES模块化:
默认情况下,node.js中的模块化标准时CommonJS,若想使用ES的模块化,可以采用以下两种方案:
- 使用mjs作为拓展名
- 修改package.json将模块化规范设置为ES模块
导入es模块不能省略拓展名
- 通过ES模块化导入的内容都是常量
- ES模块都是在严格模式下运行的
- ES模块化在浏览器中同样支持,但是通常我们不会直接使用,通常都会结合打包工具使用
核心模块
- 核心模块是node中自带的模块,可以再node中直接使用
- window是浏览器的宿主对象,在node中是没有的
- global是node中的全局对象,作用类似于window
- ES标准下,全局对象的标准名应该是globalThis
process:
- 表示当前的node进程
- 通过该对象可以获取进程的信息,或者对进程做各种操作
- 如何使用
- process是一个全局变量,可以直接使用
- 有哪些属性方法:
- process.exit()
- process.nextTick(callback[, ...args])
- 将函数插入到tick队列中
- tick中的代码会再下一次事件循环之前执行,就是说会在微任务队列和宏任务队列中任务之前执行
- 代码的执行顺序:调用栈 -> tick队列 -> 微任务队列 -> 宏任务队列
path:
表示的路径
通过path可以用来获取各种路径
要使用path需要先对其进行引入
方法:
最终形态
const path = require('node:path')
const result = path.resolve(__dirname,'./hello.js')
console.log(result);
fs:
File System,帮助node来操作磁盘中的文件
文件操作就是所谓的 I/O 操作
使用fs模块也需要引入
const fs = require('node:fs')
fs.readFileSync()
fs.readFile()
异步读取文件的方法
需要传入回调函数
const fs = require('node:fs')
const path = require('node:path')
fs.readFile(path.resolve(__dirname,'./hello.txt'),(error,buffer)=>{
if(error){
console.log('出现错误');
}else{
console.log(buffer.toString());
}
})
不会阻塞后续代码的执行
Promise版本的fs的方法:
const path = require('node:path')
const fs = require('node:fs/promises')
const { buffer } = require('stream/consumers')
fs.readFile(path.resolve(__dirname,'./hello.txt')).then(buffer=>{
console.log(buffer.toString());
}).catch(e=>{
console.log('出现错误');
})
或者async,await语法糖的方式
(async ()=>{
try{
const buffer = await fs.readFile(path.resolve(__dirname,'./hello.txt'))
console.log(buffer.toString());
}catch(e){
console.log('出错误了');
}
})()
- fs.readFile() 读取文件
- fs.appendFile() 创建新文件,或将数据添加到已有文件中
S
fs.mkdir() 创建目录
const fs = require('node:fs/promises')
const path = require('node:path')
fs.mkdir(path.resolve(__dirname,'./hello')).then(r=>{
console.log('Success');
}).catch(err => {
console.log('创建失败');
})
- mkdir可以接受一个配置对象作为第二个参数,通过该对象可以对方法的功能进行配置
fs.rmdir() 删除目录
const fs = require('node:fs/promises')
const path = require('node:path')
fs.rmdir(path.resolve(__dirname,'./hello')).then(r=>{
console.log('Success');
}).catch(err => {
console.log('删除失败');
})
fs.rm() 删除文件
fs.rename() 重命名
fs.copyFile() 复制文件
包管理器
packfage.josn
- 是包的描述文件
- node中通过该文件对项目进行描述
- 每个node项目必须有package.json
- scripts:
- 可以自定义一些命令
- 定义以后可以直接通过npm来执行这些命令
- start 和 test 可以直接通过npm start 和 npm test执行
- 其他命令需要通过npm run xxx 执行
npm命令
npm init
npm init -y
npm install/可简写'i' 包名
1.将包下载到当前项目的node_modeules目录下
2.会在package.json中的"dependencies"属性中添加一个新属性
3.会自动添加package-lock.json文件(帮助加速npm下载,不用去动它)
npm install '包名' -g
npm uninstall '包名'
npm镜像:
npm的服务器位于国外,有时访问速度会比较慢,可以通过配置国内镜像来解决该问题,配置
1.安装cnpm(不推荐使用,容易出问题)
npm install -g cnpm --registry=https://registry.npmmirror.com
2.彻底修改npm仓库地址
npm set registry https://registry.npmmirror.com
还原到原版仓库
npm config delete registry
Yarn:
在新版本的node中,corepack中已经包含了yarn,可以通过启用corepack的方式使yarn启用。首先执行以下命令启用corepack:
查看yarn版本
若出现报错
无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本
set-ExecutionPolicy RemoteSigned
切换yarn版本,最新版:
corepack prepare yarn@stable --activate
切换为1.x.x的版本:
corepack prepare yarn@1 --activate
Yarn命令
yarn init (初始化,创建package.json)
yarn add xxx(添加依赖)
yarn add xxx -D(添加开发依赖)
yarn remove xxx(移除包)
yarn(自动安装依赖)
yarn run(执行自定义脚本)
yarn <指令>(执行自定义脚本)
yarn global add(全局安装)
yarn global remove(全局移除)
yarn global bin(全局安装目录)
HTTP协议:
当在浏览器中输入地址之后发生了什么
https://www.lilichao.com/
https://
www.lilichao.com
1.DNS解析,获取网站的ip地址
2.浏览器需要与服务器建立连接(tcp/ip) (三次握手)
3.向服务器发送请求(http请求)
4.服务器处理请求,并返回相应(http协议)
5.浏览器将响应的页面渲染
6.断开和服务器的连接(四次挥手)
客户端如何与服务器建立(断开)连接
- 通过三次握手和四次挥手
- 三次握手是客户端向服务器建立连接的过程
- 客户端向服务器发送连接请求 SYN
- 服务器收到连接请求,向客户端返回信息 SYN ACK
- 客户端向服务器发送同意连接的信息 ACK
- 四次挥手
- 客户端向服务器发送请求,通知服务器数据发送完毕,请求断开链接 FIN
- 服务器向客户端返回数据,表示知道了 ACK
- 服务器向客户端返回数据,表示收完了可以断开连接 FIN ACK
- 客户端向服务器发数据,可以断开连接 ACK
请求和响应实际上就是一段数据,只是这段数据需要准寻一个特殊的格式,这个格式由HTTP协议来规定
TCP/IP 协议族
- TCP/IP协议族中包含一组协议,这组协议规定了互联网中所有的通信的细节
- 网络通信的过程由四层组成
- 应用层(软件的层面,浏览器,服务器都属于应用层)
- 传输层(负责对数据进行拆分,把大数据拆分为一个一个小包)
- 网络层(负责给数据包,添加信息)
- 数据链路层(传输信息)
HTTP协议就是应用层的协议,用来规定客户端和服务器间通信的报文格式
什么是报文
- 浏览器和服务器之间通信是基于请求和响应的
- 浏览器向服务器发送请求(request)
- 服务器向浏览器返回响应(response)
- 浏览器向服务器发送请求相当于给服务器写信,服务器向浏览器返回响应相当于服务器给浏览器回信,这个信在HTTP协议中被称为报文
请求报文(request)
- 客户端发送给服务器的报文称为请求报文
- 请求报文的格式如下:
请求首行
第一部分
GET /05_HTTP%E5%8D%8F%E8%AE%AE/http%E5%8D%8F%E8%AE%AE.html?username=1 HTTP/1.1
- get请求主要是用来向服务器请求资源
- post请求主要用来向服务器发送数据
第二部分
/target.html?username=sunwukong HTTP/1.1
?username=admin&password=123123
- 表示请求资源的路径
- ?后面的内容叫查询字符串
- 查询字符串是一个名值对结构,一个名值对应一个值,使用 = 连接,多个名值对之间使用&分割
- get请求通过查询字符串将数据发送给服务器
- 由于查询字符串会在浏览器地址栏中明文显示,所以它安全性较差
- url地址长度有限,所以get请求无法发送较大的数据
- post请求通过请求体发送数据
- 在chrome中通过载荷可以查看
- post请求通过请求体发送数据,无法在地址栏中直接查看,所以安全性较好(并不表示post请求就一定安全,HTTP协议都不安全)
- 请求体的大小没有限制,可以发送任意大小的数据
第三部分
请求头
- 请求头也是名值对结构,用来告诉服务器我们浏览器的信息
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: max-age=0
Connection: keep-alive
Host: 127.0.0.1:5500
If-Modified-Since: Tue, 10 Jan 2023 17:07:46 GMT
If-None-Match: W/"12d-1859ca6f731"
Referer: http://127.0.0.1:5500/05_HTTP%E5%8D%8F%E8%AE%AE/http%E5%8D%8F%E8%AE%AE.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
- Accept 浏览器可以接收的文件的类型
- Accept-Encoding 浏览器允许的压缩的编码
- Accept-Language 客户端浏览器可以接受的语言
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0
请求体
响应报文
HTTP/1.1 304 Not Modified
Vary: Origin
Access-Control-Allow-Credentials: true
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Tue, 10 Jan 2023 17:07:46 GMT
ETag: W/"12d-1859ca6f731"
Content-Type: text/html; charset=UTF-8
Content-Length: 1794
Date: Tue, 10 Jan 2023 18:20:35 GMT
Connection: keep-alive
Keep-Alive: timeout=5
响应首行
- 响应状态码的描述
- 1xx 请求处理中
- 2xx 表示成功
- 3xx 表示请求的重定向
- 4xx 表示客户端错误
- 5xx 表示服务器的错误
响应头
空行
响应体
Express
- express是node中服务器软件,通过express可以快速在node中搭建一个web服务器
使用步骤:
1. 创建并初始化项目
1.使用yarn初始化
yarn init -y
2.使用npm初始化
npm init -y
2. 安装express
1.使用yarn安装
yarn add express
2.使用npm安装
npm i express
3. 创建 index.js 并编写代码
const express = require('express')
const app = express()
app.listen(3000,()=>{
console.log('服务器已启动')
})
- 若希望服务器正常访问,则还需要为服务器设置路由,路由可以根据不同的请求方式和请求地址来处理用户的请求 app.METHOD(...)
get请求
app.get('/',(request,response,next)=>{
console.log('被访问');
})
get发送请求的第二种方式
param
app.get('/hello/:id/:name',(req,res)=>{
console.log(req.params);
})
post请求
app.use(express.urlencoded())
app.post('/login',(req,res)=>{
console.log(req.body);
const username = req.body.username
const password = req.body.password
if(username === 'admin' && password === '123'){
res.send('<h1>登陆成功</h1>')
}else{
res.send('<h1>登陆失败</h1>')
}
})
启动服务器
app.listen(3000,()=>{
console.log('服务器已启动');
})
中间件
- 在express中我们可以使用app.use()定义一个中间件,中间件的作用与路由很像
app.use('/',(req,res,next) => {
console.log('收到请求');
res.send('这是通过中间件返回的响应')
})
静态资源(express.static)
- 服务器中的代码对于外部来说是不可见的,所以我们写的html页面,浏览器无法直接访问,如果希望浏览器可以访问,则需要将页面所在的目录设置为静态资源目录
- 设置static中间件后,浏览器访问时会自动去public目录寻找是否有匹配的静态资源
app.use(express.static('public'))
nodemon
代码修改后可以自动重启服务器
安装nodemon模块
1. npm i nodemon -g
2. yarn global add nodemon
1. npm i nodemon -D
2. yarn add nodemon -D
启动:
1. nodemon
2. nodemon xxx
1. npx nodemon
2. npx nodemon xxx
req.query
- 该属性主要用于
get()
方法时传递参数使用,来获取查询字符串的数据
app.get('/xxx',(req,res)=>{
console.log(req.query.xxx)
})
req.body
- 该属性主要用于
post()
方法时传递参数使用,注意,在默认情况下express不会自动解析请求体,需要通过中间件来为其增加功能
post登录
方案一
const express = require('express')
const path = require('path')
const app = express()
const USERS = [
{
username: 'admin',
password: '123',
nickname: '超级管理员'
}, {
username: 'sunwukong',
password: '123123',
nickname: '齐天大圣'
}
]
app.use(express.static(path.resolve(__dirname,'public')))
app.use(express.urlencoded())
app.post('/login',(req,res)=>{
const username = req.body.username
const password = req.body.password
for(const user of USERS){
if(user.username === username){
if(user.password === password){
res.send(`<h1>登陆成功 ${user.nickname}<h1>`)
return
}
}
}
res.send(`<h1>登陆失败<h1>`)
})
app.listen(3000,()=>{
console.log('Server is running');
})
方案二
const express = require('express')
const path = require('path')
const app = express()
const USERS = [
{
username: 'admin',
password: '123',
nickname: '超级管理员'
}, {
username: 'sunwukong',
password: '123123',
nickname: '齐天大圣'
}
]
app.use(express.static(path.resolve(__dirname,'public')))
app.use(express.urlencoded())
app.post('/login',(req,res)=>{
const username = req.body.username
const password = req.body.password
const loginUser = USERS.find((item)=>{
return item.username === username && item.password === password
})
if(loginUser){
res.send(`<h1>登陆成功 ${loginUser.nickname}<h1>`)
}else{
res.send(`<h1>登陆失败<h1>`)
}
})
app.listen(3000,()=>{
console.log('Server is running');
})
post注册
const express = require('express')
const path = require('path')
const app = express()
const USERS = [
{
username: 'admin',
password: '123',
nickname: '超级管理员'
}, {
username: 'sunwukong',
password: '123123',
nickname: '齐天大圣'
}
]
app.use(express.static(path.resolve(__dirname,'public')))
app.use(express.urlencoded())
app.post('/login',(req,res)=>{
const username = req.body.username
const password = req.body.password
for(const user of USERS){
if(user.username === username){
if(user.password === password){
res.send(`<h1>登陆成功 ${user.nickname}<h1>`)
return
}
}
}
res.send(`<h1>登陆失败<h1>`)
const loginUser = USERS.find((item)=>{
return item.username === username && item.password === password
})
if(loginUser){
res.send(`<h1>登陆成功 ${loginUser.nickname}<h1>`)
}else{
res.send(`<h1>登陆失败<h1>`)
}
})
app.post('/register',(req,res)=>{
const {username,password,repwd,nickname} = req.body
const user = USERS.find(item => {
return item.username === username || item.nickname === nickname
})
console.log(user);
if(!user){
USERS.push({
username,
password,
nickname
})
res.send('<h1>注册成功</h1>')
}else{
res.send('注册失败')
}
})
app.listen(3000,()=>{
console.log('Server is running');
})
<body>
<h1>这是一个静态网页</h1>
<hr>
<h2>Regist</h2>
<form action="/register" method="post">
<div><input type="text" name="username" placeholder="用户名"></div>
<div><input type="password" name="password" placeholder="密码"></div>
<div><input type="password" name="repwd" placeholder="确认密码"></div>
<div><input type="text" name="nickname" placeholder="昵称"></div>
<div><input type="submit" value="注册"></div>
</form>
</body>
模板引擎
ejs
安装ejs
配置express的模板引擎为ejs
app.set('view engine','ejs')
注!模板引擎需要被express选然后才能使用
配置模板路径
app.set('文件夹名','文件夹路径')
app.set('views',path.resolve(__dirname,'views'))
ejs语法
<%= %> 在ejs中输出内容时,会自动对字符串中的特殊符号进行转义,这样设计主要为了避免xss攻击
<%- %> 直接将内容输出,不会转义
<% %> 可以在其中直接编写js代码,js代码会在服务器中执行
ejs的注释方法
重定向 & res.redirect()
- 重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由的路径一种重定向)
res.redirect()
- res.redirect()函数重定向到具有指定状态的,从指定路径派生的URL,该状态为与HTTP状态码相对应的整数(正数)。默认状态为“找到302”。
Router
- router是express创建的一个对象
- router类似于一个中间件,所以你可以把在router实例化建立起的分支路由,作为一个文件引入到主文件中,用主文件中的保存变量作为一个参数给到app.use(),或者用于其他路由的.use()方法中。这样主文件中的app.use()就可以通过暴露出来的router进行路由衔接。
使用方法
创建routes文件夹并在该文件夹下创建你所需的路由,例如user.js 、goods.js等等
在相应文件中引入express和创建Router对象
const express = require('express')
const router = express.Router()
module.exports = router
在index.js中引入路由并使路由生效
1. 方法一
const userRouter = require('./routes/user.js')
app.use(userRouter)
2. 方法二
app.use('/user','./routes/user')
app.use('/goods','./routes/goods')
为防止不同路由中路径出现重复冲突的情况,可在使之生效时,在第一个参数填入不同路由的根路径
app.use('/user',userRouter)
app.use('/goods'goodsRouter)
将配置路由编写在配置中间件代码下面时,上面的中间件路由可共享
app.use(express.static(path.resolve(__dirname,'./public')))
app.use(express.urlencoded({extended:true}))
app.set("view engine",'ejs')
app.set("views", path.resolve(__dirname, "views"))
app.use(userRouter)
Cookie
- Cookie是HTTP协议中用来解决无状态问题的技术
- Cookie本质就是一个头
- 服务器以响应头的形式将Cookie发送给客户端,客户端收到后会将其存储,并在下次向服务器发送请求时将其传回,这样服务器就可以根据Cookie来识别客户端了
- 向客户端发送一个Cookie
res.cookie()
app.get('网络路径',(req,res)=>{
res.cookie('name','value')
req.send('Cookie以发送')
})
- 读取客户端发来的Cookie
Cookie一旦发送给浏览器就不能修改,但可以发送新的Cookie来替换旧的Cookie,来达到修改的目的
Cookie的不足
- cookie是由服务器创建,浏览器保存,每次浏览器访问服务器时都需要将cookie发回,导致不能在cookie中存放较多的数据
- cookie是直接存储在客户端,容易被篡改盗用
- cookie中一定不能存储敏感数据
Session
- session 是服务器中的一个对象,这个对象用来存储用户的数据
- 每一个session对象都有一个唯一的id,id会通过cookie的形式发送给客户端
- 客户端每次访问时只需将存储又id的cookie发回即可获取它在服务器中存储的数据
- 在express 可以通过 express-session 组件来实现session功能
使用方法
安装
引入
const session = require('express-session')
设置为中间件
app.use(session({
secret:'hello'
}))
session的失效
- 浏览器的Cookie失效
- 服务器中的session对象没了
- express-session默认是将session存储到内存中的,所有服务器一旦重启,session会自动重置
CSRF 攻击