一、Express介绍:
1、官方网址:
2、理解:
express 是一个基于 Node.js 的WEB应用开发框架,它封装了很多功能,便于开发 WEB 应用(HTTP 服务)
二、Express的基本使用:

1、Express下载:npm i express

2、Express使用:
// 1、引入模块
const express = require('express');
// 2、创建应用对象
let app = express()
// 3、创建路由规则
app.get('/', (request, response) => {
// 设置响应
response.end('Hello Express!')
// response.send({ name: 'zs', age: 20 })
})
// 4、监听端口启动服务
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
如果报下面的错,检查一下 node的版本,我用 14.21.3版本不行,用了22.14.0可以

三、Express的路由:
1、理解:
路由确定了
应用程序如何处理客户端对特定端点的需求
2、组成:
请求方法、路径、回调函数
3、使用:
① 语法: app.请求方法(路径, 回调函数)
② 请求方法:
(1)get:
app.get('/', (request, response) => {
// 设置响应
response.end('Hello Express!')
// response.send({ name: 'zs', age: 20 })
})
(2)post:
app.post('/a', (request, response) => {
// 设置响应
response.end('post: a')
})
(3)all:
*可用于错误处理
// 请求具体的路径 /b 才能执行该方法中的回调函数
app.all('/b', (request, response) => {
// 设置响应
response.end('all: b')
})
// 在 express的4.18.2版本中可用,在5.1.0版本中报错
// 可用于错误处理,匹配不到任何路径的时候,就会执行该方法中的回调函数
// app.all('*',(req, res) => {
// response.end('****')
// });
③ 获取请求报文的参数:
(1)获取请求路径:request.path
(2)获取请求参数:request.query
(3)获取ip:request.ip
(4)获取请求头中的某个值:request.get(属性名)
request.get()方法不能直接用,必须要传参
特殊情况之会话控制:需要看本文的第八部分
(5)获取请求体(需要看本文的第四部分):
在
Express中,请求体的数据默认是undefined,因为没有内置的请求体解析器。要正确获取请求体的内容,需要手动添加请求体解析中间件。
Express 从 4.16.0 版本开始内置了替代 body-parser 的中间件:
const express = require('express');
const app = express();
// 解析 JSON 格式请求体
app.use(express.json()); // Content-Type: application/json
// 解析表单格式请求体
app.use(express.urlencoded({ extended: true })); // Content-Type: application/x-www-form-urlencoded
(6)完整代码:
// 1、引入模块
const express = require('express');
// 2、创建应用对象
let app = express()
// 3、创建路由规则
app.get('/aaa', (request, response) => {
// 原生获取:
console.log(request.method) // GET
console.log(request.url) // /aaa?account=123
console.log(request.httpVersion) // 1.1
console.log(request.headers)
// express获取:
// 获取请求路径
console.log(request.path) // /aaa
// 获取请求参数
console.log(request.query) // [Object: null prototype] { account: '123' }
// 获取ip
console.log(request.ip)
// 获取请求头中的某个值
console.log(request.get('host'))
response.end('hello')
})
// 4、监听端口启动服务
app.listen(3001, () => {
console.log('服务已经启动,3001端口监听中...');
})
④ 获取动态路由参数:
(1)理解:
路径中的参数(动态路径)
(2)语法: app.请求方法('/:动态名称', 回调函数)
获取动态的路由参数的时候,参数名称和定义的动态名称保持一致!
(3)使用:
app.get('/:id', (request, response) => {
// 获取动态路由参数
console.log(request.params) // [Object: null prototype] { id: 'iam' }
console.log(request.params.id) // iam
response.end('hello')
})
⑤ 响应设置:
可以使用连接符.进行连贯操作
(1)设置响应的状态码:response.status(状态码)
(2)设置响应头:response.set(属性, 属性值)
特殊情况之会话控制:需要看本文的第八部分
(3)设置响应体:response.send(数据)
也可以用于结束响应
(4)设置重定向:response.redirect("路径")
要在响应体之前进行设置!
响应的状态码是302,会将网页重定向到响应头设置的Location属性对应的值。如果Location的值是null,则重定向到响应重定向设置的路径
(5)设置下载响应:response.download("路径")
response.download()方法在下载文件时,会自动在响应头设置Content-Disposition头部字段为attachment,并根据传入的文件路径或文件名设置下载的文件名。但是,有时候这个自动设置的文件名可能不符合预期,可能是因为文件名中包含特殊字符或编码问题。此时需要手动设置下面的请求头:
response.setHeader('Content-Disposition', 'attachment; filename="文件名.扩展名"');
(6)设置json响应体:response.json("json格式的数据")
将
json数据直接显示在客户端的页面上,忽略response.send的内容
(7)设置文件内容响应:response.sendFile("绝对路径")
将文件的内容直接显示客户端的页面上,如果是
html文件,则会进行解析后进行显示
不允许写本地的绝对路径,要写相对于网站的根目录的绝对路径。不能直接写模版文件路径!
(8)完整代码:
// 1、引入模块
const express = require('express');
// 2、创建应用对象
let app = express()
// 3、创建路由规则
app.get('/home', (request, response) => {
// 原生设置:
// response.statusCode = 500; // 设置状态码
// response.statusMessage = "hahah" // 设置状态信息
// response.setHeader("Content-Type", "text/plain;charset=utf-8") // 设置响应头
// response.write("hello") // 设置响应体
// response.end("原生设置响应") // 结束响应
// express设置:
// response.status(500) // 设置状态码
// response.set({ "111": "qiqi", "222": "xixi" }) // 设置响应头
// response.set("333", "gg") // 设置响应头
// response.redirect("https://www.baidu.com/") // 设置重定向
response.setHeader('Content-Disposition', 'attachment; filename="package.json"');
response.download("./package.json") // 设置下载
// response.json({ "name": "qiqi", "age": 18 }) // 设置json响应体
// response.sendFile(__dirname+"/resource/response.html")
// response.sendFile(__dirname+"/resource/1.js")
response.send("express设置响应体,不用设置响应头,中文也不会乱码") // 设置响应体
})
// 4、监听端口启动服务
app.listen(3001, () => {
console.log('服务已经启动,3001端口监听中...');
})
4、路由模块化:express.Router()
① 定义语法:
文件名称:
9_路由模块化定义.js
const express = require("express")
// 引入路由应用模块
let router = express.Router()
// 使用路由模块
router.get("/home", (req, res) => {
res.send("首页")
})
// 路由模块使用中间件
// router.use('全局中间件的函数名')
// 暴露整体的路由配置
module.exports = router
② 使用语法:app.use([路由统一前缀,]路由变量)
路由统一前缀 会和 定义的路由进行拼接,访问的时候,要访问拼接好的完整路由
文件名称:
10_路由模块化使用.js
const express = require("express")
let app = express()
let router = require('./9_路由模块化定义.js')
// 使用路由模块化
app.use(router) // 访问:http://localhost:3000/home
// app.use('/a',router) // 访问:http://localhost:3000/a/home
app.get("*", (req, res) => {
res.send("404")
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
四、Express的中间件:
1、理解:
中间件(
Middleware)本质是一个回调函数
中间件函数可以像路由回调一样,访问请求对象和响应对象
2、作用:
简化代码:封装公共的操作
3、分类:
① 全局中间件:
每一个请求到达服务端后,都会执行该函数
② 路由中间件:
每一个请求到达服务端后,会根据路由进行筛选,然后再执行该函数
4、定义并使用全局中间件:
① 声明语法:
声明一个名为
appmiddleware的中间件
function appmiddleware(request, response, next) {
console.log('我是全局中间件的内容,可用于错误处理');
next() // 放行
}
② 参数解析:
request:当前路由的请求对象response:当前路由的响应对象next:执行完中间件的代码后,继续执行路由的回调函数
③ 使用:app.use(中间件名称)
// 引入模块
const express = require('express');
// 创建应用对象
let app = express()
// 定义中间件
function appmiddleware(request, response, next) {
console.log('我是中间件');
next() // 放行
}
// 使用中间件
app.use(appmiddleware)
④ 案例:
将每一个请求的路径和
ip都放在一个日志文件中
// 引入模块
const express = require('express');
// 创建应用对象
let app = express()
let fs = require('fs')
let path = require('path')
// 定义中间件
function appmiddleware(request, response, next) {
const {url,ip} = request
console.log('我是中间件',url,ip);
fs.appendFileSync(path.join(__dirname, 'log.txt'), `${url} ${ip}\n`)
next() // 放行
}
// 应用中间件
app.use(appmiddleware)
// 创建路由规则
app.get('/home', (request, response) => {
response.end('home')
})
app.get('/list', (request, response) => {
response.end('list')
})
// 监听端口启动服务
app.listen(3001, () => {
console.log('服务已经启动,3001端口监听中...');
})
5、定义并使用路由中间件:
① 声明语法:
声明一个名为
checkmiddleware的中间件
function checkmiddleware(request, response, next) {
console.log('我是路由中间件');
next() // 放行
}
② 参数解析:
request:当前路由的请求对象response:当前路由的响应对象next:执行完中间件的代码后,继续执行路由的回调函数
③ 使用:app.请求方法(路径, 中间件名称, 回调函数)
// 引入模块
const express = require('express');
// 创建应用对象
let app = express()
// 定义中间件
function checkmiddleware(request, response, next) {
console.log('我是路由中间件');
next() // 放行
}
// 使用中间件
app.get('/home', 'checkmiddleware', (request, response) => {
response.end('home')
})
④ 案例:
根据请求的路径中携带的参数进行返回值的判断
// 引入模块
const express = require('express');
// 创建应用对象
let app = express()
// 定义中间件
function checkmiddleware(request, response, next) {
const {query: {code}} = request
if (code && code == 123) {
next() // 放行
} else {
response.end('no Permission')
}
}
// 创建路由规则
app.get('/home', checkmiddleware, (request, response) => {
response.end('home')
})
app.get('/list', (request, response) => {
response.end('list')
})
// 监听端口启动服务
app.listen(3001, () => {
console.log('服务已经启动,3001端口监听中...');
})
6、设置静态资源中间件:
① 语法:app.use(express.static(静态资源文件夹的路径))
参数是路径,不是文件!!!
// 引入模块
const express = require('express');
// 创建应用对象
let app = express()
// resource就是网站的根目录
app.use(express.static(__dirname + '/resource'))
② 注意事项:
(1)index.html 文件是默认打开的资源
(2)静态资源中间件和路由规则同时匹配,则显示那个先匹配的
静态资源中间件的填写的路径目录中有
index.html文件,路由规则匹配中也有index.html。
如果访问协议+域名+端口号+/,则显示静态资源中间件的index.html
如果访问协议+域名+端口号+index.html,则显示最开始匹配的那个


(3)路由响应动态资源,静态资源中间件响应静态资源
③ 作用:
将本地文件系统中的资源(如
图片、CSS、JS文件)通过HTTP服务暴露出来,使得浏览器可以通过HTTP请求(而不是本地文件协议file://)来加载这些资源。
7、使用中间件处理请求体数据:body-parser
① 安装:
npm i body-parser
② 导入:
const bodyParser = require('body-parser')
③ 获取中间件函数:
// 获取 application/json 格式的中间件
const jsonParser = bodyParser.json()
// 获取 application/x-www-form-urlencoded 格式的中间件
const urlencodedParser = bodyParser.urlencoded()
④ 使用中间件函数:
以使用路由中间件函数为例,
GET请求进行登录页面的展示,POST请求进行用户输入数据的获取

login.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: linear-gradient(#141e30, #243b55);
}
.login {
display: flex;
flex-direction: column;
align-items: center;
width: 400px;
padding: 40px;
background-color: rgba(0, 0, 0, 0.2);
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.4);
}
.login h2 {
color: #fff;
margin-bottom: 30px;
}
.login .login_box {
position: relative;
width: 100%;
}
.login .login_box input {
outline: none;
border: none;
width: 100%;
padding: 10px 0;
margin-bottom: 30px;
color: #fff;
font-size: 16px;
border-bottom: 1px solid #fff;
background-color: transparent;
}
.login .login_box label {
position: absolute;
top: 0;
left: 0;
padding: 10px 0;
color: #fff;
pointer-events: none;
transition: all 0.5s;
}
.login .login_box input:focus+label,
.login .login_box input:valid+label {
top: -20px;
color: #03e9f4;
font-size: 12px;
}
.submit {
position: relative;
left: 50%;
transform: translateX(-50%);
background: none;
border: none;
padding: 10px 20px;
color: #03e9f4;
text-decoration: none;
transition: all 0.5s;
}
.submit:hover {
cursor: pointer;
color: #fff;
border-radius: 5px;
background-color: #03e9f4;
box-shadow: 0 0 5px #03e9f4,0 0 25px #03e9f4,0 0 50px #03e9f4,0 0 100px #03e9f4;
}
.submit span {
position: absolute;
}
.submit span:first-child {
top: 0;
left: -100%;
width: 100%;
height: 2px;
background: linear-gradient(to right, transparent, #03e9f4);
animation: move1 1s linear infinite;
}
.submit span:nth-child(2) {
right: 0;
top: -100%;
width: 2px;
height: 100%;
background: linear-gradient(transparent, #03e6f4);
animation: move2 1s linear 0.25s infinite;
}
.submit span:nth-child(3) {
right: -100%;
bottom: 0;
width: 100%;
height: 2px;
background: linear-gradient(to left, transparent, #03e9f4);
animation: move3 1s linear 0.5s infinite;
}
.submit span:last-child {
left: 0;
bottom: -100%;
width: 2px;
height: 100%;
background: linear-gradient(#03e9f4, transparent);
animation: move4 1s linear 0.75s infinite;
}
@keyframes move1 {
0% {
left: -100%;
}
50%,
100% {
left: 100%;
}
}
@keyframes move2 {
0% {
top: -100%;
}
50%,
100% {
top: 100%;
}
}
@keyframes move3 {
0% {
right: -100%;
}
50%,
100% {
right: 100%;
}
}
@keyframes move4 {
0% {
bottom: -100%;
}
50%,
100% {
bottom: 100%;
}
}
</style>
</head>
<body>
<div class="login">
<h2>用户登录</h2>
<form action="http://localhost:3000/login" method="post">
<div class="login_box">
<input type="text" name='name' id='name' required />
<label for="name" >用户名</label>
</div>
<div class="login_box">
<input type="password" name='pwd' id='pwd' required="required">
<label for="pwd">密码</label>
</div>
<button class="submit">
登录
<span></span>
<span></span>
<span></span>
<span></span>
</button>
</form>
</div>
<script>
function drawStar() {
//定义数组,arr存放每个小星星的信息,colour为颜色数组,存几个好看的颜色
var arr = [];
var colours = ["#ffff00", "#66ffff", "#3399ff", "#99ff00", "#ff9900"];
var timeoutList = []; // 计时器列表-用于后续清理计时器
// 创建画布
const canvas = document.createElement('canvas')
document.body.appendChild(canvas)
var ctx = canvas.getContext("2d");
// 让画布自适应窗口大小,这个复制即可
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
// 给画布css样式,固定定位,且阻止用户的鼠标事件
canvas.style.cssText = `
position: fixed;
z-index: 1000;
top: 0;
left: 0;
pointer-events: none;
`;
// 封装绘制一个五角星函数
// x是圆心横坐标,y是圆心纵坐标,其实就是鼠标位置(x ,y)
// r是里面小圆半径 ,l是大圆半径
// rot是初始旋转角度
function star(x, y, r, l, rot) {
ctx.beginPath();
// 循环5次,因为5个点
for (let i = 0; i < 5; i++) {
//先绘制小圆上一个点
ctx.lineTo(
Math.cos(((18 + i * 72 - rot) * Math.PI) / 180) * r + x,
-Math.sin(((18 + i * 72 - rot) * Math.PI) / 180) * r + y
);
//连线到大圆上一个点
ctx.lineTo(
Math.cos(((54 + i * 72 - rot) * Math.PI) / 180) * l + x,
-Math.sin(((54 + i * 72 - rot) * Math.PI) / 180) * l + y
);
}
ctx.closePath();
}
// 绘制一堆星星
function draw() {
//循环数组
for (let i = 0; i < arr.length; i++) {
let temp = arr[i];
//调用绘制一个星星函数
star(temp.x, temp.y, temp.r, temp.r * 3, temp.rot);
//星星颜色
ctx.fillStyle = temp.color;
//星星边框颜色
ctx.strokeStyle = temp.color;
//线宽度
ctx.lineWidth = 0.1;
//角有弧度
ctx.lineJoin = "round";
// 填充
ctx.fill();
// 绘制路径
ctx.stroke();
}
}
//更新动画
function update() {
//循环数组
for (let i = 0; i < arr.length; i++) {
// x坐标+dx移动距离
arr[i].x += arr[i].dx;
// y坐标+dy移动距离
arr[i].y += arr[i].dy;
// 加上旋转角度
arr[i].rot += arr[i].td;
// 半径慢慢减小
arr[i].r -= 0.015;
// 当半径小于0时
if (arr[i].r < 0) {
//删除该星星
arr.splice(i, 1);
}
}
}
// 添加当前位置星星数据
function addStarts(e) {
// 每移动触发一次事件给arr数组添加一个星星
arr.push({
// x是初始横坐标
x: e.clientX,
//y是初始纵坐标
y: e.clientY,
//r是星星里面那个小圆半径,哪来的小圆等会说
r: Math.random() * 0.5 + 1.5,
//运动时旋转的角度
td: Math.random() * 4 - 2,
// X轴移动距离
dx: Math.random() * 2 - 1,
// y轴移动距离
dy: Math.random() * 1 + 1,
// 初始的旋转角度
rot: Math.random() * 90 + 90,
// 颜色
color: colours[Math.floor(Math.random() * colours.length)],
});
}
// 监听屏幕变化事件
window.onresize = resizeCanvas;
// 监听鼠标移动事件
window.addEventListener("mousemove", (e) => {
// 添加星星数据
addStarts(e);
//设置100毫秒内效果
for (let index = 0; index < 200; index++) {
if (index === 0 && timeoutList.length > 0) {
for (const timeoutName of timeoutList) {
clearTimeout(timeoutName)
}
}
timeoutList[index] = setTimeout(() => {
//清屏
ctx.clearRect(0, 0, canvas.width, canvas.height);
//绘制
draw();
//更新
update();
}, index * 20);
}
});
}
drawStar()
</script>
</body>
</html>
服务器的js文件:
// 引入模块
const express = require('express');
// 创建应用对象
let app = express()
// 引入处理请求体的模块
const bodyParser = require('body-parser')
// 获取处理form表单的中间件
const urlencodedParser = bodyParser.urlencoded()
// 创建路由规则
app.get('/login', (request, response) => {
response.sendFile(__dirname + '/resource/login.html')
})
app.post('/login', urlencodedParser, (request, response) => {
// 中间件处理完成后,request.body才会有值
let {name,pwd} = request.body
response.send(`用户名:${name},密码:${pwd}`)
})
// 监听端口启动服务
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
8、使用中间件来实现防盗链:
① 防盗链的含义:
防止外部的网站盗用本网站的资源
② referer请求头:
会自动携带当前网页的
协议+域名+端口号,服务器会根据该字段进行资源是否允许请求的判断
③ 实践:
只允许
127.0.0.1域名的网站显示图片
(1)js文件内容:
const express = require('express');
let app = express()
let path = require('path')
function antiHotlinkingMiddleware(req, res, next) {
let refererVal = req.get('referer')
if (refererVal) {
let url = new URL(refererVal)
if (url.hostname !== '127.0.0.1') {
res.status(404).send()
return
}
}
// 不使用 req.host 的原因:会将资源的请求域名也进行获取,不能准确的判断
next()
}
app.use(antiHotlinkingMiddleware); // 使用全局中间件
// 将静态资源暴露在服务器上
app.use(express.static(path.join(__dirname, './resource')))
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
(2)html文件内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试静态资源的访问</title>
</head>
<body>
<h1>静态资源的访问之我是服务器中的index.html文件</h1>
<img src="http://localhost:3000/cow.png" alt="">
<img src="http://127.0.0.1:3000/cow.png" alt="">
</body>
</html>
五、模版引擎:
1、模版引擎的定义:
模版引擎是分离
用户界面和业务数据的一种技术
2、js的模版引擎 – ejs:
① 官网及中文站:
https://ejs.co/
https://ejs.bootcss.com/
② 安装 ejs:
npm i ejs --save
③ 简单使用 ejs:
js文件:
// 引入 ejs 模块
let ejs = require('ejs')
// 模版字符串
let tpl = `我叫<%= name %>, 今年 <%= age %> 岁`
// 将模版和数据分离渲染
let result = ejs.render(tpl, { name: '张三', age: 20 })
console.log(result)
④ ejs语法:
(1)变量使用: <%= 变量名称 %>
(2)js语法使用: <% js语法 %>
(3)实践:
循环渲染一个无序列表;并且通过判断
isLogin变量的值,来显示不同的内容。
js文件:
// 引入 ejs 模块
const ejs = require('ejs')
// 引入 fs 模块
const fs = require('fs')
// 模版文件
const tplFile = fs.readFileSync('./index.html').toString()
// 数据
const data = {
name: '张三',
age: 20,
arr: ['唐僧', '孙悟空', '猪八戒', '沙和尚'],
isLogin: false
}
// 将模版和数据分离渲染
let result = ejs.render(tplFile, data)
console.log(result)
html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>我叫<%= name %>, 今年 <%= age %> 岁</h2>
<ul>
<% for(var i=0; i<arr.length; i++) { %>
<li><%= arr[i] %></li>
<% } %>
</ul>
<% if(isLogin) { %>
<button>欢迎</button>
<% }else{ %>
<button>登录</button>
<% } %>
</body>
</html>
⑤ express中使用ejs:
(1)设置模版引擎:
app.set('view engine', 'ejs')
(2)设置模版文件存放的位置:
app.set('views', path.join(__dirname, '文件目录名称'))
(3)渲染模版文件:
res.render('模版文件名称', 数据)
(4)具体使用:
模版目录下的模版文件 home.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>我叫<%= name %>, 这是首页</h2>
</body>
</html>
服务器配置js文件:
const express = require('express')
const app = express()
const path = require('path')
// 配置模版引擎
app.set('view engine', 'ejs')
// 配置模版文件的存放目录
app.set('views', path.join(__dirname, 'views'))
app.get('/homePage', (req, res) => {
// 模版文件的名字,数据
res.render('home', { name: '张三' })
})
app.listen(3000, () => {
console.log('3000端口监听中,服务器启动成功')
})

3、js的模版引擎 – pug:
① 官网:
4、php的模版引擎 – twig:
① 官网:
六、应用程序生成器:express-generator
1、官网:
https://www.expressjs.com.cn/starter/generator.html
2、安装:npm i express-generator -g
安装成功后,可以全局使用 express命令

3、初始化项目:
① 创建项目:express -e 12_generator
(1)-e:添加 ejs模版引擎的支持
(2)12_generator:代码安装的文件夹
② 安装依赖:
进入到代码安装的文件夹(12_generator)下,使用 npm i 进行依赖安装
③ 运行项目:npm start
如果想要自动重启项目,将改命令中的 node 改为 nodemon

4、编写文件上传功能:
① 首先新建一个模版文件,将要显示给用户的页面进行代码编写:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>文件上传</h2>
<!-- enctype="multipart/form-data" 编码方式必须加,不然拿到的文件数据只有文件名 -->
<!-- 路径简写:<form action="/users/uploadFile" method="post" enctype="multipart/form-data"> -->
<form action="http://localhost:3000/users/uploadFile" method="post" enctype="multipart/form-data">
<div style="margin-bottom: 20px;">
<label for="">用户名:</label>
<input type="text" name="account" required>
</div>
<div style="margin-bottom: 20px;">
<label for="">请选择文件:</label>
<input type="file" name="file" required>
</div>
<div>
<button type="submit">提交</button>
</div>
</form>
</body>
</html>
② 然后将用户显示页面与模版文件相关联,并且将用户发送的请求进行关联:

var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
// 显示上传文件的用户页面
router.get('/uploadFile', function(req, res, next) {
res.render('uploadFile');
});
// 用户点击上传文件后进行处理的请求
router.post('/uploadFile', function(req, res, next) {
res.send('上传文件之后的处理');
})
module.exports = router;
③ 处理用户发送过来的请求:解析表单数据 formidable
使用enctype="multipart/form-data"的编码方式,必须要进行表单数据的解析,不然就会拿到空对象
(1)安装:
npm install formidable
(2)引入:
var formidable = require('formidable');
(3)在 express 中使用:
获取表单的所有详细信息:

var express = require('express');
var router = express.Router();
// 引入解析表单的模块
var formidable = require('formidable');
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
// 显示上传文件的用户页面
router.get('/uploadFile', function(req, res, next) {
res.render('uploadFile');
});
// 用户点击上传文件后进行处理的请求
router.post('/uploadFile', function(req, res, next) {
// 创建表单对象
const form = formidable({});
// 解析表单数据
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
let {newFilename, mimetype} = files.file
// 文件的新路径,需要存储在数据库。此时暂时存在静态资源服务器的根目录下
let imgUrl = newFilename+mimetype.replace('image/', '.');
res.json({ fields, files, imgUrl });
});
})
module.exports = router;
配置上传文件的保存目录:
const form = formidable({
// 上传文件的临时目录
uploadDir: __dirname + '/../public/images',
});
保持文件后缀:
const form = formidable({
// 保留文件的后缀名
keepExtensions: true,
});
将文件存储在静态资源根目录下,并且进行查看:


5、ejs模版中使用script标签引入资源:
要使用绝对路径,将文件放在静态资源的目录下

七、自用练习项目:
需求:显示列表页面,并且可以对列表进行增、删、改、查。
- 数据存储:本地
json文件(mongooose下载安装与使用中会改为数据库存储) - 页面展示:
ejs模版引擎
1、存储数据:lowdb
① 官网:
② 安装:
npm i lowdb@1.0.0
③ 使用:

var express = require('express');
var router = express.Router();
// 导入lowdb
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
// const adapter = new FileSync('db.json') // 这个是写入数据的文件,如果没有需要新建
const adapter = new FileSync(__dirname+ '/../data/db.json') // 这个是写入数据的文件,更改一下路径
// 初始化lowdb对象
const db = low(adapter)
// 引入唯一id
var nanoId = require('nano-id');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
// 列表请求
router.get('/list', function(req, res, next) {
// 从db.json中获取数据
let data = db.get('users')
res.render('list', {users: data});
});
// 列表删除
router.get('/delete/:id', function(req, res, next) {
// 获取表单数据
if(req.params.id){
// 1、删除数据
db.get('users').remove({id: req.params.id}).write()
}
// 2、返回列表页面
res.redirect('/list')
})
// 表单新增或者修改
router.get('/editForm', function(req, res, next) {
// 获取表单数据
if(req.query.id){
// 1、获取编辑的数据
let user = db.get('users').find({id: req.query.id}).value()
// 2、回显数据
res.render('editForm', {user});
} else {
// 新增
res.render('editForm', {user: {}});
}
});
// 表单提交
router.post('/editForm', function(req, res, next) {
// 使用 lowdb 保存数据
let editId = req.query.id
if(editId) {
// 3、将数据修改
let usersAll = db.get('users').value()
for(let i = 0; i < usersAll.length; i++) {
if(usersAll[i].id === editId) {
let editUser = Object.assign(usersAll[i], req.body)
usersAll[i] = editUser
}
}
db.set('users', usersAll).write()
// 4、返回列表页面
res.redirect('/list')
} else {
// 1、初始化 db.json 文件:存一些默认数据
db.defaults({ users: [] }).write()
// 2、生成唯一id
let id = nanoId()
// 3、将数据存储到 users 字段中
db.get('users')
.unshift({id, ...req.body})
.write()
// 4、返回新增页面
res.redirect('/editForm')
}
});
module.exports = router;
2、数据的唯一id:nano-id
① 官网:
② 安装:
npm i nano-id
③ 使用:
// 导入模块
var nanoId = require('nano-id');
// 生成唯一id
let id = nanoId()
3、项目的模版引擎代码:
需求:做一个可以新增&编辑的表单,将添加的数据进行显示

editForm.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2><%= user.id ? '修改' : '新增' %>数据</h2>
<a href="/">点击回到首页</a>
<form action="<%= user.id ? '/editForm?id='+user.id : '/editForm' %>" method="post">
<div style="margin-bottom: 20px;">
<label for="name">称呼:</label>
<input type="text" id="name" name="name" value="<%= user.name %>">
</div>
<div style="margin-bottom: 20px;">
<label for="age">年龄:</label>
<input type="number" id="age" name="age" value="<%= user.age %>">
</div>
<div style="margin-bottom: 20px;">
<label for="email">联系方式:</label>
<input type="email" id="email" name="email" value="<%= user.email %>">
</div>
<button type="submit">提交</button>
<a href="/list">查看列表</a>
</form>
</body>
</html>
list.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="/">点击回到首页</a>
<table border="1" cellspacing="0">
<tr>
<th>ID</th>
<th>称呼</th>
<th>年龄</th>
<th>联系方式</th>
<th>操作</th>
</tr>
<% for (let item of users) { %>
<tr>
<td><%= item.id%></td>
<td><%= item.name%></td>
<td><%= item.age%></td>
<td><%= item.email%></td>
<td>
<a href="/editForm?id=<%= item.id%>">修改</a> |
<a href="/delete/<%= item.id%>">删除</a>
</td>
</tr>
<% } %>
</table>
</body>
</html>
八、会话控制:
1、cookie:
① 定义:
cookie是http服务器向客户浏览器发送,按照域名以明文(安全性相对低) 的方式 保存在浏览器中的数据(不同的浏览器不共享cookie,不同的域名不共享cookie)。
② cookie的局限:
设置的内容过多会增大报文的体积,影响传输效率
浏览器限制单个cookie保存的数据不能超过4k,且单个域名下的存储数量也有限制
③ 新增cookie:response.cookie('键名', '键值', {配置项})
- 关键配置项如下:

(1)设置cookie的执行流程:
- 执行设置
cookie代码 - 客户端向服务器端发送请求,服务器端会判断当前域名下有没有
cookie,如果没有则告知客户端需要设置cookie(此时请求头中没有Cookie字段,响应头中有Set-Cookie字段) - 客户端设置
cookie - 客户端向服务器端再次发送请求时,当前域名下已经有设置好的
cookie了(此时请求头中有Cookie字段,响应头中有Set-Cookie字段)



(2)设置cookie的完整代码:
const express = require('express');
let app = express()
app.get('/', (request, response) => {
response.end('Home')
})
app.get('/set-cookie', (request, response) => {
response.cookie('name', 'zs') // 不设置cookie的有效期,默认是关闭浏览器,则销毁cookie
response.cookie('name', 'zs', {
maxAge: 30 * 1000 // 设置cookie的有效期为30s,关闭浏览器后,cookie仍然存在,一直到cookie无效
})
response.end('设置成功!')
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
④ 删除cookie:response.clearCookie(键名)
(1)删除cookie的执行流程:
- 执行移除
cookie的代码 - 客户端向服务器端发送请求,服务器告知客户端需要设置
cookie的过期时间 - 客户端向服务器端再次发送请求时,当前域名下已经没有该
cookie了

(2)删除cookie的完整代码:
const express = require('express');
let app = express()
app.get('/', (request, response) => {
response.end('Home')
})
app.get('/delete-cookie', (request, response) => {
response.clearCookie('name')
response.end('删除成功!')
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
⑤ 获取cookie:request.cookies
(1)解析cookie:cookie-parser
(2)安装cookie-parser: npm i cookie-parser
(3)导入并且使用cookie-parser:
var express = require('express')
var cookieParser = require('cookie-parser')
var app = express()
app.use(cookieParser())
(4)获取cookie的完整代码:
const express = require('express');
let app = express()
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/', (request, response) => {
response.end('Home')
})
app.get('/get-cookie', (request, response) => {
let cookieObj = request.cookies
console.log('Cookies: ', cookieObj)
response.send(`获取${cookieObj.name}成功!`)
})
app.get('/set-cookie', (request, response) => {
response.cookie('name', 'zs') // 不设置cookie的有效期,默认是关闭浏览器,则销毁cookie
response.end('设置成功!')
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
2、session:
① 定义:
session是保存在服务器中的数据,主要是保存当前用户的信息。
② session与cookie的联系:session通过cookie传递id
③ 解析session:express-session
由于
session存储在服务器中,为了方便查看,可以安装connect-mongo进行数据库的连接
https://www.npmjs.com/package/express-session
https://www.npmjs.com/package/connect-mongo
④ 安装express-session、 connect-mongo:
npm i express-session connect-mongo
⑤ 导入并且使用express-session、 connect-mongo:
要记得开启数据库服务器!不会开启可以参考文章:Mongodb下载安装与使用(Windows版本)
const express = require('express');
let app = express()
var session = require('express-session')
const MongoStore = require('connect-mongo');
app.use(session({
name: 'sid', // 服务器生成的session的id需要返回给客户端,客户端通过该名称从cookie中进行id的获取,名称默认是connect.sid
secret: 'keyboard cat', // 参与加密的字符串(签名、加盐)
resave: false, // 在每次请求时,是否更新session的过期时间
saveUninitialized: false, // 每次请求,都需要进行session的设置(如果取值是false,则不会对所有用户都进行session的设置)
store: MongoStore.create({ // 数据库的连接配置
mongoUrl: 'mongodb://localhost:27017/session'
}),
cookie: {
httpOnly: true, // 是否允许通过前端JS获取cookie(例如document.cookie)
maxAge: 1000 * 60 * 5 // 设置session的id的过期时间(客户端和服务器),单位是毫秒
}
}))
⑥ 设置session:req.session.键名 = 键值
(1)设置session的执行流程:
- 客户端校验用户信息后,服务器端进行用户信息的校验,校验通过后,会将用户信息进行存储,并生成一个
session_id; - 服务器端将
session_id设置为响应头(cookie)传递给客户端; - 客户端再次请求时,就会自动携带
session_id,服务器会根据session_id确定用户的身份。
(2)设置session的完整代码:
const express = require('express');
let app = express()
var session = require('express-session')
const MongoStore = require('connect-mongo');
app.use(session({
name: 'sid', // 服务器生成的session的id需要返回给客户端,客户端通过该名称从cookie中进行id的获取,名称默认是connect.sid
secret: 'keyboard cat', // 参与加密的字符串(签名、加盐)
resave: false, // 在每次请求时,是否更新session的过期时间
saveUninitialized: false, // 每次请求,都需要进行session的设置(如果取值是false,则不会对所有用户都进行session的设置)
store: MongoStore.create({ // 数据库的连接配置
mongoUrl: 'mongodb://localhost:27017/session'
}),
cookie: {
httpOnly: true, // 是否允许通过前端JS获取cookie(例如document.cookie)
maxAge: 1000 * 60 * 5 // 设置session的id的过期时间(客户端和服务器),单位是毫秒
}
}))
app.get('/', (request, response) => {
response.end('Home')
})
app.get('/set-session', (request, response) => {
request.session.name = 'zs'
request.session.age = '18'
response.end('设置成功!')
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})


⑦ 删除session:request.session.destroy(回调函数)
(1)删除session的完整代码:
const express = require('express');
let app = express()
var session = require('express-session')
const MongoStore = require('connect-mongo');
app.use(session({
name: 'sid', // 服务器生成的session的id需要返回给客户端,客户端通过该名称从cookie中进行id的获取,名称默认是connect.sid
secret: 'keyboard cat', // 参与加密的字符串(签名、加盐)
resave: false, // 在每次请求时,是否更新session的过期时间
saveUninitialized: false, // 每次请求,都需要进行session的设置(如果取值是false,则不会对所有用户都进行session的设置)
store: MongoStore.create({ // 数据库的连接配置
mongoUrl: 'mongodb://localhost:27017/session'
}),
cookie: {
httpOnly: true, // 是否允许通过前端JS获取cookie(例如document.cookie)
maxAge: 1000 * 60 * 5 // 设置session的id的过期时间(客户端和服务器),单位是毫秒
}
}))
app.get('/', (request, response) => {
response.end('Home')
})
app.get('/get-session', (request, response) => {
let sessionObj = request.session
console.log('session: ', sessionObj)
})
app.get('/delete-session', (request, response) => {
request.session.destroy(() => {
response.clearCookie('sid');
response.send('已注销');
})
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})
如果saveUninitialized设置为了true,则会显示下面的结果,即生成一个新的sid

⑧ 获取session:req.session.键名
(1)获取session的完整代码:
const express = require('express');
let app = express()
var session = require('express-session')
const MongoStore = require('connect-mongo');
app.use(session({
name: 'sid', // 服务器生成的session的id需要返回给客户端,客户端通过该名称从cookie中进行id的获取,名称默认是connect.sid
secret: 'keyboard cat', // 参与加密的字符串(签名、加盐)
resave: false, // 在每次请求时,是否更新session的过期时间
saveUninitialized: false, // 每次请求,都需要进行session的设置(如果取值是false,则不会对所有用户都进行session的设置)
store: MongoStore.create({ // 数据库的连接配置
mongoUrl: 'mongodb://localhost:27017/session'
}),
cookie: {
httpOnly: true, // 是否允许通过前端JS获取cookie(例如document.cookie)
maxAge: 1000 * 60 * 5 // 设置session的id的过期时间(客户端和服务器),单位是毫秒
}
}))
app.get('/', (request, response) => {
response.end('Home')
})
app.get('/get-session', (request, response) => {
let sessionObj = request.session
console.log('session: ', sessionObj)
response.send(`获取${sessionObj.name}成功!`)
})
app.listen(3000, () => {
console.log('服务已经启动,3000端口监听中...');
})


3、token:
① 定义:
token是服务器端生成,并且返回给客户端的一串加密字符串,主要是保存当前用户的信息。
② token的作用:
识别用户身份,主要用在移动端
APP
③ token的工作流程:
- 客户端校验用户信息后,服务器端进行用户信息的校验,校验通过后,会将用户信息进行存储,并生成一个
token; - 服务器端将
token一般放在响应体中传递给客户端; - 客户端再次请求时,需要手动将
token添加在请求报文中(一般是放在请求头中),服务器会根据token确定用户的身份。
④ token的特点:
- 服务器端压力更小,因为数据都存储在客户端
- 数据加密,可以避免
CSRF,相对来说更安全 - 服务间可以共享
token,便于增加服务节点,扩展性更强
⑤ 规范token:JWT
(1)定义:
JWT(Json Web Token)是跨域认证的解决方案,可以使token的生成和校验更加规范。
https://www.npmjs.com/package/jsonwebtoken
(2)安装:npm i jsonwebtoken
(3)引入:
var jwt = require('jsonwebtoken');
(4)设置token:jwt.sign(数据,加密字符串,配置项);
var jwt = require('jsonwebtoken');
var token = jwt.sign({
name: 'admin',
age: 18
}, 'secret', {
expiresIn: 60 // 单位是秒
});
console.log(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJhZ2UiOjE4LCJpYXQiOjE3NTA3MzU3MjYsImV4cCI6MTc1MDczNTc4Nn0.aOxL6BB_nyCmQmDXYREbbJcyU9pk2om07HHEl7k4syw
(5)校验token:jwt.verify(token,加密字符串[,配置项,回调函数])
- 常用的配置项如下:

let t = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJhZ2UiOjE4LCJpYXQiOjE3NTA3MzYxNDAsImV4cCI6MTc1MDczNjIwMH0.4bAeFSa-NqRHG0CfYAe-MwD58hBeJSLAYAKuTV_k4R8`
// try {
// const decoded = jwt.verify(t, 'secret');
// // 解密的字符串
// console.log(decoded) // { name: 'admin', age: 18, iat: 1750736140, exp: 1750736200 }
// } catch (err) {
// if (err.name === 'TokenExpiredError') {
// console.error('Token 已于', err.expiredAt, '过期');
// } else {
// throw err;
// }
// }
jwt.verify(t, 'secret', (err, data) => {
if (err) {
console.error('Token 报错');
return
}
const decoded = jwt.verify(t, 'secret');
// 解密的字符串
console.log(decoded) // { name: 'admin', age: 18, iat: 1750736140, exp: 1750736200 }
})
537

被折叠的 条评论
为什么被折叠?



