nodejs项目实战教程15—Express
1. Express操作路由(get、post、put、delete、多级目录、动态路由、get传值)
1.1 安装Express
npm install express --save
或者
cnpm i express --save
详细文档请前往 https://www.npmjs.com/package/express
创建express文件夹,npm init --yse
生成package.json文件,然后安装Express,创建app.js文件。
1.2 get用于显示数据
const express = require('express')
const app = express()
// get用户显示数据
app.get('/', (req, res) => {
res.send('Hello Express')
})
app.get('/register', (req, res) => {
res.send('注册登录页面')
})
app.get('/article', (req, res) => {
res.send('新闻页面')
})
app.listen(3000)
1.3 post用于增加数据
app.post('/doLogin', (req, res) => {
console.log('执行登录')
res.send('执行登录')
})
post请求在浏览器中无法通过地址直接请求,可以通过postman请求查看
1.4 put主要用于修改数据
app.put('/editUser', (req, res) => {
console.log('修改用户')
res.send('修改用户')
})
1.5 delete主要用于删除数据
app.delete('/deleteUser', (req, res) => {
console.log('执行删除')
res.send('执行删除')
})
1.6 路由配置多级目录
app.get('/admin/user/add', (req, res) => {
res.send('admin user add')
})
app.get('/admin/user/edit', (req, res) => {
res.send('admin user edit')
})
1.7 动态路由
// 动态路由
app.get('/article/:id', (req, res) => {
let id = req.params['id']
res.send('动态路由' + id)
})
注意:配置路由的时候要注意顺序,动态路由如果放在前面就会优先执行,比如/article/add想生效,就需要放在/article/:id之前
app.get('/article/add', (req, res) => {
res.send('article add')
})
app.get('/article/:id', (req, res) => {
let id = req.params['id']
res.send('动态路由' + id)
})
app.get('/article/:id', (req, res) => {
let id = req.params['id']
res.send('动态路由' + id)
})
app.get('/article/add', (req, res) => {
res.send('article add')
})
1.8 获取get传值
app.get('/product', (req, res) => {
let query = req.query
console.log(query)
res.send('product-' + query.id)
})
2. Express 使用ejs
2.1 安装ejs
npm install ejs --save
或者
cnpm i ejs --save
ejs使用教程详情查看https://www.npmjs.com/package/ejs
创建项目express02,npm init --yse
生成package.json,cnpm i express --save
下载express,cnpm i ejs --save
下载ejs,创建app.js文件
2.2 使用ejs
(1)配置模板引擎
app.set('view engine', 'ejs')
(2)使用(默认加载模板引擎的文件夹是views)
res.render('index',{
})
(3)实例,app.js写入:
/**
* 1、安装 cnpm i ejs --save
*
* 2、app.set('view engine','ejs')
*
* 3、使用(默认加载模板引擎的文件夹是views)
* res.render('index',{
*
* })
*/
const express = require('express')
const app = express()
// 配置模板引擎
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
res.render('index', {
title: '123',
})
// res.send('Hello Express')
})
app.listen(3000)
以上代码表示端口3000的主页默认读取views/index.ejs的文件,并传递参数title。接着创建views文件夹
index.ejs代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是一个ejs模板引擎</h2>
<p><%=title%></p>
</body>
</html>
运行node app.js
2.3 在ejs中使用javascript
app.js:
/**
* 1、安装 cnpm i ejs --save
*
* 2、app.set('view engine','ejs')
*
* 3、使用(默认加载模板引擎的文件夹是views)
* res.render('index',{
*
* })
*/
const express = require('express')
const app = express()
// 配置模板引擎
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
res.render('index', {
title: '123',
})
// res.send('Hello Express')
})
app.get('/news', (req, res) => {
let userInfo = {
username: '常山赵子龙',
age: 30,
}
let article = '<h3>我是一个h3标题<h3>'
let list = [1111, 2222, 3333]
res.render('news', {
userInfo: userInfo,
article: article,
flag: true,
score: 60,
list: list,
})
})
app.listen(3000)
views/news.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>新闻模板页面</h2>
<p><%=userInfo.username%>--<%=userInfo.age%></p>
<p><%-article%></p>
<%if(flag===true){%>
<strong>flag === true</strong>
<%}%> <%if(score>=60){%>
<p>及格</p>
<%}else{%>
<p>不及格</p>
<%}%>
<h2>循环遍历</h2>
<ul>
<%for(let i = 0;i < list.length;i++){%>
<li><%=list[i]%></li>
<%}%>
</ul>
</body>
</html>
运行node app.js
总结下语法:
(1)直接使用变量,<%=变量%>
(2)使用javascript时,条件语句需要<%{%>和<%}%>配合使用
2.4 在ejs中引用其他ejs文件作为公共模块
views/footer.ejs:
<footer>
<h1>公共的底部</h1>
</footer>
在views/index.ejs中引用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>我是一个ejs模板引擎</h2>
<p><%=title%></p>
<%- include('footer.ejs')%>
</body>
</html>
views/news.ejs中同理:
2.5 修改ejs为html
很多时候,处于习惯,我们希望将ejs修改为html文件,ejs支持这样的配置。
关键语句:
const ejs = require('ejs')
app.engine('html', ejs.__express)
app.set('view engine', 'html')
app.js:
const express = require('express')
const ejs = require('ejs')
const app = express()
// 配置模板引擎为html
app.engine('html', ejs.__express)
app.set('view engine', 'html')
// 配置静态资源目录
app.use(express.static('static'))
app.get('/', (req, res) => {
res.render('index', {
title: '123',
})
// res.send('Hello Express')
})
app.get('/news', (req, res) => {
let userInfo = {
username: '常山赵子龙',
age: 30,
}
let article = '<h3>我是一个h3标题<h3>'
let list = [1111, 2222, 3333]
res.render('news', {
userInfo: userInfo,
article: article,
flag: true,
score: 60,
list: list,
})
})
app.listen(3000)
将原先的ejs文件修改为html文件,并在引用的文件处修改后缀即可
2.6 静态资源配置(静态文件托管)
app.js中配置静态资源文件为static:
// 配置静态资源目录
app.use(express.static('static'))
在static下创建css/base.css文件:
h1 {
background-color: red;
}
在需要的html中引入
3. 中间件
3.1 什么是中间件
中间件就是匹配路由之前或者匹配路由完成做的一系列的操作。中间件中如果想往下 匹配的话,那么需要写 next()。
3.2 中间件的功能
(1)执行任何代码。
(2)修改请求和响应对象。
(3)终结请求-响应循环。
(4)调用堆栈中的下一个中间件。
3.3 Express中间件的种类
3.3.1 应用级中间件(通常用于权限判断)
app.use((req, res, next) => {
console.log(new Date())
next()
})
3.3.2 路由级中间件(用的比较少)
app.get('/news/add', (req, res, next) => {
// res.send('执行增加新闻')
console.log('执行增加新闻')
next()
})
app.get('/news/:id', (req, res) => {
res.send('新闻动态路由')
})
执行next()会继续向下匹配
3.3.3 错误处理中间件
app.use((req, res, next) => {
res.status(404).send('404')
})
匹配路由后如果都没有的话,页面就返回404
3.3.4 内置中间件
app.use(express.static('static'))
其实就是配置静态路由,配置完成后就可以展示static文件夹下的文件
3.3.5 第三方中间件
通常用户获取post请求提交的数据,需要借助body-parser插件,详细文档可以查看https://www.npmjs.com/package/body-parser
获取post传过来的数据
1、cnpm install body-parser --save
2、const bodyParser = require(‘body-parser’)
3、配置中间件
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
4、接收post数据
app.js:
/**
* 获取post传过来的数据
* 1、cnpm install body-parser --save
* 2、const bodyParser = require('body-parser')
* 3、配置中间件
* app.use(bodyParser.urlencoded({ extended: false }))
* app.use(bodyParser.json())
* 4、接收post数据
*/
const express = require('express')
const ejs = require('ejs')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
// 配置模板引擎为html
app.engine('html', ejs.__express)
app.set('view engine', 'html')
// 配置静态资源目录
app.use(express.static('static'))
app.get('/', (req, res) => {
res.send('首页')
})
app.get('/login', (req, res) => {
// req.query 获取get
res.render('login', {})
})
app.post('/doLogin', (req, res) => {
let body = req.body
console.log(body)
res.send('执行提交' + body.username)
})
app.listen(3000)
views/login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>post提交数据</h2>
<form action="/doLogin" method="post">
用户名:<input type="text" name="username" /><br />
密码:<input type="password" name="password" /><br />
<input type="submit" value="提交" />
</form>
</body>
</html>
点击提交
4. Cookie
4.1 Cookie简介
(1)cookie是存储在访问者电脑中的变量,可以让我们在使用同一个浏览器访问同一个域名的时候共享数据。
(2)http是无状态协议。简单来说,就是你浏览了一个页面,之后跳转到相同网站的另一个页面时,服务器无法认识到这是同一个浏览器在访问同一个网站。就是说,每次访问都是没有关系的。
(3)cookie就是一个很简单的想法:当访问一个页面时,服务器在下行http报文中,命令浏览器存储一个字符串;浏览器再访问同一个域时,将把这个字符串携带到上行的http请求中(换句话说,就是在res中存储)。因此第一次访问服务器,不可能携带cookie,必须是服务器得到这次请求,才能在下次请求中携带cookie信息(换句话说,就是在后面的req中获取)。此后每一次浏览器往这个服务器发出请求,都会携带这个cookie,直至失效。
4.2 Cookie特点
(1)保存在浏览器本地
(2)默认cookie不加密,所有用户都可以看到
(3)用户可以删除、禁用、篡改、加密
(4)cookie可用于攻击
(5)cookie存储量很小,未来会被localStorage替代 。
4.3 Cookie使用方式
4.3.1 基础使用方式
在Express中使用cookie的话,要借助cookie-parser中间件,详情可以查看https://www.npmjs.com/package/cookie-parser
(1)安装
npm install cookie-parser --save
或者
cnpm i cookie-parser --save
(2)引入和设置中间件(注意设置中间件要在app申明后使用)
const cookieParser = require('cookie-parser');
app.use(cookieParser());
(3) 设置cookie
res.cookie("username",'zhangsan',{maxAge: 1000*60*60});
(4)获取cookie
req.cookies.username
(5)删除cookie
res.cookie('rememberme', '', { expires: new Date(0)});
res.cookie('username','zhangsan',{domain:'.ccc.com',maxAge:0,httpOnly:true});
实例:
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())
app.get('/', (req, res) => {
res.cookie('username', 'zhangsan', {
maxAge: 1000 * 60 * 60,
})
res.send('Hello World!')
})
app.get('/article', (req, res) => {
let username = req.cookies.username
console.log(username)
res.send('新闻页面--' + username)
})
app.get('/user', (req, res) => {
let username = req.cookies.username
console.log(username)
res.send('用户页面--' + username)
})
app.listen(3000)
4.3.2 cookie属性说明
res.cookie('username', 'zhangsan', {
maxAge: 1000 * 60 * 60,
})
函数中的第三个选项就是cookie的属性,总共有9个可选参数,如下:
(1)maxAge:设置多少毫秒后失效。经常使用。
(2)signed:设置是否加密(或者说表示是否是签名cookie),设置为true时就需要使用res.signedCookies而不是res.cookies访问。被篡改的签名cookie会被服务器拒绝,并且重置为初始值。经常使用。
(3)expires:过期时间(秒),设置在某个具体的时间点后失效,如 expires=Wednesday, 09-Nov-99 23:12:40 GMT。但是一般不用这个属性。
(4)httpOnly:如果设置为true,则无法通过JavaScript读取,本意是为了防止XSS攻击。默认不做设置。
(5)path:表示cookie影响的路径,如果路径不匹配,则浏览器不发送这个cookie。
(6)domain:设置共享cookie的二级域名。经常使用。
(7)secure:当设置为true时,cookie在https请求中才有效。
encode和sameSite不重要,不用记。
hosts文件可以用来修改电脑访问的域名地址
4.3.3 加密cookie
在实际的项目中,我们需要对cookie进行加密,否则任何人都可以直接修改cookie。加密cookie的使用方式如下:
(1)配置中间件的时候需要传递密钥,可以随便写
const cookieParser = require('cookie-parser')
app.use(cookieParser('sheldon'))
(2)设置cookie时配置signed属性
app.get('/', (req, res) => {
res.cookie('username', 'zhangsan', {
maxAge: 1000 * 60 * 60,
// path: '/article',
// domain: '.sheldon.com',
signed: true,
})
res.send('Hello World!')
})
(3)获取cookie使用req.signedCookies
app.get('/product', (req, res) => {
// 获取加密后的cookie
let username = req.signedCookies.username
console.log(username)
res.send('product--' + username)
})
完整实例:
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express()
// 设置加密的密钥为sheldon,可以随意替换成别的
app.use(cookieParser('sheldon'))
app.get('/', (req, res) => {
res.cookie('username', 'zhangsan', {
maxAge: 1000 * 60 * 60,
// path: '/article',
// domain: '.sheldon.com',
signed: true,
})
res.send('Hello World!')
})
// app.get('/article', (req, res) => {
// let username = req.cookies.username
// console.log(username)
// res.send('新闻页面--' + username)
// })
// app.get('/user', (req, res) => {
// let username = req.cookies.username
// console.log(username)
// res.send('用户页面--' + username)
// })
app.get('/product', (req, res) => {
// 获取加密后的cookie
let username = req.signedCookies.username
console.log(username)
res.send('product--' + username)
})
app.listen(3000)
如果直接修改cookie的值,刷新页面后将无法识别,取值可能为undefined或者false。
5. Session
5.1 session简介
session也是一种记录客户状态的方式,不同于cookie,session将将信息保存在服务器中,所以当访问量较多时,就会比较占用服务器的性能。单个cookie保存数据不能超过4k,很多浏览器最多只能保存大约20个cookie。但是session没有这方面的限制。
5.2 session的运作原理
session虽然不同于cookie,但是却是基于cookie运作的。当浏览器访问服务器并发送第一次请求时,服务端会创建一个session对象,生成一个类似于key——value的键值对,然后将cookie(key)返回到客户端。浏览器下次访问时,就可以携带cookie(key)找到对应的session(value)。
5.3 使用方式
更多可以查看https://www.npmjs.com/package/express-session
5.3.1 基础使用方式
(1)下载express-session
npm install express-session --save
或者
cnpm i express-session --save
(2)引入
const session = require('express-session')
(3)设置中间件
app.use(
session({
// 服务器端生成session的签名,随便写
secret: 'keyboard cat',
// 强制保存session,即使session没有变化
resave: false,
// 强制将未初始化的session存储
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60,
secure: false,
},
})
)
(4)设置session
app.get('/login', (req, res) => {
// 设置session
req.session.username = '张三'
res.send('登录')
})
(5)获取session
app.get('/', (req, res) => {
// 获取session
if (req.session.username) {
res.send(req.session.username + '已登录')
} else {
res.send('未登录')
}
})
完整实例:
const express = require('express')
const session = require('express-session')
const app = express()
// 配置session的中间件
app.use(
session({
// 服务器端生成session的签名,随便写
secret: 'keyboard cat',
// 强制保存session,即使session没有变化
resave: false,
// 强制将未初始化的session存储
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60,
secure: false,
},
})
)
app.get('/', (req, res) => {
// 获取session
if (req.session.username) {
res.send(req.session.username + '已登录')
} else {
res.send('未登录')
}
})
app.get('/login', (req, res) => {
// 设置session
req.session.username = '张三'
res.send('登录')
})
app.listen(80)
直接访问80端口
进入登录页面后,session设置完成
再次访问80,获取到session中的数据
5.3.2 session的参数
app.use(
session({
// 服务器端生成session的签名,随便写
secret: 'keyboard cat',
// 修改session对应的cookie名称,默认为connect.sid
name: 'sheldon',
// 强制保存session,即使session没有变化
resave: false,
// 强制将未初始化的session存储
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60,
secure: false,
},
// 每次请求时强制重置cookie的过期时间
rolling: true,
})
)
(1)secret:一个 String 类型的字符串,作为服务器端生成 session 的签名。
(2)name:返回客户端的 key 的名称,默认为 connect.sid,也可以自己设置。
(3)resave:强制保存 session 即使它并没有变化,。默认为 false。 don’t save session if unmodified。
(4)saveUninitialized:强制将未初始化的 session 存储。当新建了一个 session 且未设定属性或值时,它就 处于未初始化状态。在设定一个 cookie 前,这对于登陆验证,减轻服务端存储压力,权 限控制是有帮助的。(默认:true)。建议手动添加。
(5)cookie:设置返回到前端 key 的属性。
(6)rolling:在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)。
5.3.3 删除session
方法一:设置session的过期时间为0(会把所有的session都销毁)
req.session.cookie.maxAge=0
方法二:销毁指定session
req.session.username = ''
方法三:destroy方法,也会删除所有session,并且自带回调函数
req.session.destroy((err) => {})
完整示例:
const express = require('express')
const session = require('express-session')
const app = express()
// 配置session的中间件
app.use(
session({
// 服务器端生成session的签名,随便写
secret: 'keyboard cat',
// 修改session对应的cookie名称,默认为connect.sid
name: 'sheldon',
// 强制保存session,即使session没有变化
resave: false,
// 强制将未初始化的session存储
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60,
secure: false,
},
// 每次请求时强制重置cookie的过期时间
rolling: true,
})
)
app.get('/', (req, res) => {
// 获取session
if (req.session.username || req.session.age) {
res.send(req.session.username + '--' + req.session.age + '--已登录')
} else {
res.send('未登录')
}
})
app.get('/login', (req, res) => {
// 设置session
req.session.username = '张三'
req.session.age = 20
res.send('执行登录')
})
// 销毁session
app.get('/loginOut', (req, res) => {
// 方法一:设置session的过期时间为0(会把所有的session都销毁)
// req.session.cookie.maxAge=0
// 方法二:销毁指定session
// req.session.username = ''
// 方法三:
req.session.destroy((err) => {})
res.send('退出登录1')
})
app.listen(80)
6. 多服务负载均衡,session保存到数据库
6.1 为什么需要将session保存到数据库中
如上图所示,在实际项目中,我们大多会采用nginx,多服务负载均衡的方式减轻服务器压力,但是会有一个问题,就是有可能我们之前是在杭州的服务器设置了session,但是下次访问相同网址时如果时通过其他地方(比如深圳)的服务器就无法获取到之前在杭州服务器上设置的session。
所以在实际项目中,通常会将session存放在mongodb中,这样就可以多个服务器共同使用了。
6.2 使用方式
更多可以查看https://www.npmjs.com/package/connect-mongo
(1)下载connect-mongo
npm install connect-mongo --save
或者
cnpm i connect-mongo --save
以前的版本只需要下载这个,但是最近的版本可能因为某些原因缺包,需要手动下载依赖项mongo,否则会无法运行
cnpm i mongo --save
(2)引入模块
const MongoStore = require('connect-mongo')
(3)配置中间件
// 配置session的中间件
app.use(
session({
// 服务器端生成session的签名,随便写
secret: 'keyboard cat',
// 修改session对应的cookie名称,默认为connect.sid
name: 'sheldon',
// 强制保存session,即使session没有变化
resave: false,
// 强制将未初始化的session存储
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60,
secure: false,
},
// 每次请求时强制重置cookie的过期时间
rolling: true,
store: MongoStore.create({
// admin是账号,123456是密码,如果mongodb没有设置账号密码可以省略
mongoUrl: 'mongodb://admin:123456@localhost/admin',
// 无论发起都少次请求,24小时内只更新一次session,除非session发生了变化
touchAfter: 24 * 3600,
}),
})
)
完整实例:
```javascript
const express = require('express')
const session = require('express-session')
const MongoStore = require('connect-mongo')
const app = express()
// 配置session的中间件
app.use(
session({
// 服务器端生成session的签名,随便写
secret: 'keyboard cat',
// 修改session对应的cookie名称,默认为connect.sid
name: 'sheldon',
// 强制保存session,即使session没有变化
resave: false,
// 强制将未初始化的session存储
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60,
secure: false,
},
// 每次请求时强制重置cookie的过期时间
rolling: true,
store: MongoStore.create({
// admin是账号,123456是密码,如果mongodb没有设置账号密码可以省略
mongoUrl: 'mongodb://admin:123456@localhost/admin',
// 无论发起都少次请求,24小时内只更新一次session,除非session发生了变化
touchAfter: 24 * 3600,
}),
})
)
app.get('/', (req, res) => {
// 获取session
if (req.session.username || req.session.age) {
res.send(req.session.username + '--' + req.session.age + '--已登录')
} else {
res.send('未登录')
}
})
app.get('/login', (req, res) => {
// 设置session
req.session.username = '张三'
req.session.age = 20
res.send('执行登录')
})
// 销毁session
app.get('/loginOut', (req, res) => {
// 方法一:设置session的过期时间为0(会把所有的session都销毁)
// req.session.cookie.maxAge=0
// 方法二:销毁指定session
// req.session.username = ''
// 方法三:
req.session.destroy((err) => {})
res.send('退出登录1')
})
app.listen(80)
执行nodemon app.js,打开cmd,查看对应的session表,此时还没有数据
访问80端口,会将未初始化的session强制存储
访问login页面,会刷新设置的session
此时再访问80端口,就可以获取到设置的session数据了。
7. 路由模块化
之前的项目中,我们都把路由放到app.js文件中,但是在实际的大型项目开发中,多人开发,因此需要将功能相似的路由抽离出去,实现模块化。
7.1 准备模块代码
(1)前期准备,将express05中的代码复制到新建的express09中
views/login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>post提交数据</h2>
<form action="/doLogin" method="post">
用户名:<input type="text" name="username" /><br />
密码:<input type="password" name="password" /><br />
<input type="submit" value="提交" />
</form>
</body>
</html>
app.js:
/**
* 获取post传过来的数据
* 1、cnpm install body-parser --save
* 2、const bodyParser = require('body-parser')
* 3、配置中间件
* app.use(bodyParser.urlencoded({ extended: false }))
* app.use(bodyParser.json())
* 4、接收post数据
*/
const express = require('express')
const ejs = require('ejs')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
// 配置模板引擎为html
app.engine('html', ejs.__express)
app.set('view engine', 'html')
// 配置静态资源目录
app.use(express.static('static'))
app.get('/', (req, res) => {
res.send('首页')
})
app.get('/login', (req, res) => {
// req.query 获取get
res.render('login', {})
})
app.post('/doLogin', (req, res) => {
let body = req.body
console.log(body)
res.send('执行提交' + body.username)
})
app.listen(3000)
(3)package.json:
{
"name": "express05",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.1",
"ejs": "^3.1.6",
"express": "^4.17.2"
}
}
7.2 实现模块化
7.2.1 单个模块示例
详细API可以查看https://expressjs.com/en/guide/routing.html,连接末尾有对应的示例。
(1)新建routes文件夹,用于存放不同模块的路由文件。创建login.js,用于登录有关的路由。
routes/login.js:
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
// req.query 获取get
res.render('login', {})
})
router.post('/doLogin', (req, res) => {
let body = req.body
console.log(body)
res.send('执行提交' + body.username)
})
module.exports = router
(2)在app.js中引入并挂载login模块
(3)修改原来的提交路由
执行nodemon app.js,打开浏览器:
输入用户名、密码后,点击提交:
7.2.2 多模块多级路由模块化
(1)创建路由模块化文件夹routes,完成一级路由模块化
其中admin.js作为二级路由示例,图中标注代码引入二级路由模块,可以先注释,稍后在(2)中进行说明。
admin.js:
const express = require('express')
const router = express.Router()
// 引入外部模块
const login = require('../admin/login')
const nav = require('../admin/nav')
const user = require('../admin/user')
router.get('/', (req, res) => {
res.send('后台管理中心')
})
// 配置外部路由模块
router.use('/login', login)
router.use('/nav', nav)
router.use('/user', user)
module.exports = router
api.js:
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
res.send('api接口')
})
module.exports = router
index.js:
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
res.send('首页')
})
module.exports = router
以上三个模块路由在app.js中引入使用:
执行nodemon app.js
:
(2)创建admin文件夹,作为admin模块下二级模块路由文件夹,完成对应的二级路由模块化
login.js:
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
res.send('用户登录页面')
})
router.post('/doLogin', (req, res) => {
res.send('执行登录')
})
module.exports = router
nav.js:
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
res.send('导航列表')
})
router.get('/add', (req, res) => {
res.send('增加导航')
})
router.get('/edit', (req, res) => {
res.send('修改导航')
})
router.post('/doAdd', (req, res) => {
res.send('执行增加')
})
router.post('/doEdit', (req, res) => {
res.send('执行修改')
})
module.exports = router
user.js:
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
res.send('用户列表')
})
router.get('/add', (req, res) => {
res.send('增加用户')
})
router.get('/edit', (req, res) => {
res.send('修改用户')
})
router.post('/doAdd', (req, res) => {
res.send('执行增加')
})
router.post('/doEdit', (req, res) => {
res.send('执行修改')
})
module.exports = router
再返回admin.js,引入和配置以上模块:
保存后执行nodemon app.js
二级路由页面:
三级路由的post请求(postman展示)和get页面(浏览器展示):
user模块三级路由类似。
7.3 Express应用程序生成器
通过应用生成器工具 express-generator 可以快速创建一个应用的骨架,其实就是一个脚手架工具。
Express应用程序生成器中文网地址:https://www.expressjs.com.cn/starter/generator.html
使用方式:
(1)全局下载:
npx express-generator
或者
cnpm install -g express-generator
(2)创建项目骨架:
express --view=ejs express10
生成项目express10
生成了一个express项目,并且已经配置好了路由提供修改。
app.js:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
routes/index.js:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
routes/users.js:
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
可以看到app.js中引入了routes文件夹中的index和users两个模块,但是app.js本身也作为一个模块输出,并且被bin/www文件所引用。所以,运行项目需要执行两个步骤。
第一步,下载依赖:
cnpm i
第二步,运行:
node bin/www
或者
nodemon app.js
注意:有时会因为nodemon版本过低而导致无法运行express-generator生成的项目,此时只需要重新全局下载下载更新下nodemon依赖即可。
cnpm i -g nodemon
8 结合multer上传文件和模块化封装
8.1 回顾如何获取post请求传来的数据
(1)复制项目demo09,重命名为demo11,删除node_modules重新下载。
(2)创建views/admin/nav/add.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>增加导航</title>
</head>
<body>
<form action="/admin/nav/doAdd" method="post">
标题:<input type="text" name="title" id="title" />
<br />
<br />
<input type="file" name="pic" id="pic" />
<br />
<br />
描述:<textarea name="desc" id="desc" cols="30" rows="10"></textarea>
<br />
<br />
<input type="submit" value="提交" />
</form>
</body>
</html>
(3)添加路由和对应的doAdd请求,用于查看提交的数据
(3)打开对应路由,填写提交
8.2 结合multer实现文件上传
以上可以获取到图片名称,但是不能真正实现文件的上传功能,这时候就需要multer模块实现文件上传,详情可查看https://www.npmjs.com/package/multer
(1)安装multer
cnpm i multer --save
(2)在form表单中加入enctype=“multipart/form-data”
(3)在对应的请求文件中配置multer
admin/nav.js:
const express = require('express')
const multer = require('multer')
const upload = multer({ dest: 'static/upload' }) // 上传之前目录必须存在
const router = express.Router()
router.get('/', (req, res) => {
res.send('导航列表')
})
router.get('/add', (req, res) => {
// res.send('增加导航')
res.render('admin/nav/add')
})
router.get('/edit', (req, res) => {
res.send('修改导航')
})
// 这里的pic是form表单对应的图片id
router.post('/doAdd', upload.single('pic'), (req, res) => {
// 获取表单传过来的数据
// let body = req.body
// res.send(body)
res.send({
body: req.body,
file: req.file,
})
})
router.post('/doEdit', (req, res) => {
res.send('执行修改')
})
module.exports = router
(3)上传文件
可以看到,文件虽然上传成功,但是上传的文件名称后缀名消失并且名称也发生了改变。明显不符合需求。
(4)自定义文件名
查看multer在npmjs.com上的对应说明
关键代码:
const path = require('path')
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// 配置上传的目录
cb(null, 'static/upload')
},
// 修改上传后的文件名
filename: function (req, file, cb) {
// 1.获取后缀名
let extname = path.extname(file.originalname)
// 根据时间戳生成文件名
cb(null, Date.now() + extname)
},
})
const upload = multer({ storage: storage })
运行:
(5)封装文件上传模块
创建model/tools.js,将上传文件的代码剪切到这里:
const multer = require('multer')
const path = require('path')
let tools = {
multer() {
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// 配置上传的目录
cb(null, 'static/upload')
},
// 修改上传后的文件名
filename: function (req, file, cb) {
// 1.获取后缀名
let extname = path.extname(file.originalname)
// 根据时间戳生成文件名
cb(null, Date.now() + extname)
},
})
const upload = multer({ storage: storage })
return upload
},
}
module.exports = tools
在admin/nav.js中引入tools模块,并进行使用
8.3 按照日期生成上传文件目录
(1)安装 silly-datetime 依赖,用于生成生成当前日期字符串
cnpm i silly-datetime --save
(2)安装 mkdirp 依赖,用于生成文件夹
cnpm i mkdirp --save
(2)在model/tools.js中引入并使用
const multer = require('multer')
const path = require('path')
const sd = require('silly-datetime')
const mkdirp = require('mkdirp')
let tools = {
multer() {
const storage = multer.diskStorage({
// 配置上传的目录
destination: async (req, file, cb) => {
// 1、获取当前日期
let day = sd.format(new Date(), 'YYYYMMDD')
let dir = path.join('static/upload', day)
// 2、根据日期生成当前存储目录,mkdirp是一个异步方法,所以使用async和await
await mkdirp(dir)
cb(null, dir)
},
// 修改上传后的文件名
filename: function (req, file, cb) {
// 1.获取后缀名
let extname = path.extname(file.originalname)
// 2、根据时间戳生成文件名
cb(null, Date.now() + extname)
},
})
const upload = multer({ storage: storage })
return upload
},
}
module.exports = tools
8.4 多文件上传
(1)复制admin/nav/add.html到admin/user,并且添加一个文件上传入口
(2)在admin/user.js中引入tools模块,并使用fields进行多文件上传。
上传函数可通过npm上的multer文档查看,单文件上传使用single,多文件上传且id相同时使用array,id不同时使用fields
运行: