Express
1. Express是一个基于NodeJs平台的后台开发框架;
1. 极简、开放、灵活的web应用开发框架,帮助开发各种Web和移动设备应用;
2. Express不对NodeJs已有的特性进行二次抽象,只是在它之上扩展了Web应用所需的功能;
3. Express提供了丰富的HTTP快捷方法和任意排列组合的Connect中间件。
2. Express的使用:npm install express --save
1. 引入:const express = require('express'),let app = new express()
2. 配置get请求的路由
app.get('/', (req, res) => { ------> 配置路由
res.send('Hello Express') ----> 响应内容
})
3. 配置post请求的路由
app.post('/dologin', (req, res) => { ... })
4. 设置监听的Ip和端口号:app.listen(3000, 'ip'),默认Ip是127.0.0.1
5. node命令运行该js文件,它也就是项目的入口文件。
3. 传递参数
1. 配置动态路由
app.get('/news/:id', (req, res) => {
let {id} = req.params ---> 获取id参数
}) --> 访问http://localhost:3000/news/123
2. get传值
app.get('/news', (req, res) => {
let {id} = req.query ---> 获取id参数
}) --> 访问http://localhost:3000/news?id=123
4. 其他API
1. 重定向:res.redirect('/index') -->重定向到路由'/index'
2. 获取访问的路由:req.url ---> 如'/login'
3. 响应一段JS代码,弹出alert(),并重定向页面路由:
res.send("<script>alert('登录失败!');location.href='/login'</script>")
express与ejs
1. Express中引入ejs模板引擎:app.set('view engine', 'ejs')
2. 使用ejs模板:res.render('模板名', { key:value })
1. 模板名不需要指定路径,默认使用的路径:项目根目录/views/模板名.ejs
app.get('/news', (req, res) => {
res.render('index', {newsId: 123}) -->默认使用的模板:views/index.ejs
})
2. 指定模板的目录:app.set('views', __dirname + '/static'),修改为static目录;
3. 使用path模块拼接路径:require('path').join(__dirname, 'static')
3. 全局变量
1. 如果多个模板都需要接收同一变量,每个模板对应的路由都必须传递该参数,而全局变量只需要
设置一次即可,所有模板都可以接收全局变量;
2. 设置全局变量:app.locals['全局变量名'] = '123'
3. 在第一个需要接收全局变量的模板被渲染之前,设置全局变量uname的值:
app.post('/dologin', (req, res) => {
app.locals['uname'] = '123' -->在任何模板中都可以接收变量uname:<%= uname %>
......
})
4. app.locals['变量名']是真正的全局,req.app.locals['变量名']表示请求的全局。
静态服务
1. 静态服务/静态路由:用于提供静态资源服务,包括css文件、js文件、图片、供下载的文件...
2. 以css文件为例,在模板中直接使用<link />引入是无效的
app.get('/login', (req, res) => {
res.render('login') ----> views/login.ejs
})
1. login.ejs中引入css文件:<link rel="stylesheet" href="css/login.css" />
2. css文件的真实获取方式:http://localhost:3000/css/login.css
3. 很明显,"/css/login.css"的路由是不存在,所以获取css文件失败。
2. 在express中,利用 express.static 中间件托管静态文件
1. app.use(express.static('public')) --> 给public目录下的文件提供静态web服务
2. 在public目录下创建有效的css/login.css
http://localhost:3000/css/login.css --> 查找路径:public/css/login.css
3. 同理,login.ejs中的<img />加载的图片资源也必须提供静态服务
<img src="img/a.png" /> =>http://localhost:3000/img/a.png =>public/img/a.png
3. 注意:href="css/login.css" 与 href="/css/login.css" 并不完全相同
1. '/'、'/login'、'/user'都属于一级路由,而'/index/login'属于二级路由;
2. 在一级路由中渲染的ejs模板中,"css/login.css" 与 "/css/login.css" 是相同的
http://localhost:3000/css/login.css ==> public/css/login.css
3. 对于二级路由,"css/login.css" 与 "/css/login.css"的参考路由不同,也就导致了资源
路径出错,从而无法加载资源;
4. 在二级路由 '/index/login' 中渲染login.ejs模板时:
href="css/login.css" -----> http://localhost:3000/index/css/login.css ------
--> public/index/css/login.css --> public目录下不存在 index目录,所以访问失败
href="/css/login.css" --> http://localhost:3000/css/login.css --> 访问成功
5. 原因:href="css/login.css"相对于当前路由,而href="/css/login.css"相对于根路由。
5. 设置虚拟目录,映射真实的资源路径:app.use('/static', express.static('public'))
1. app.use(express.static('public')) -->匹配的是根路由,而所有路由又都开始于根路由
2. app.use('/static', express.static('public')) 匹配的路由是 '/static'
3. href="/css/login.css" 不能在访问了,需要使用 href="/static/css/login.css"
http://localhost:3000/static/css/login.css ==> public/css/login.css
6. 如果静态资源存在多个目录,可以配置多个 express.static 中间件
app.use(express.static('public')) ---> public目录
app.use(express.static('static')) ---> static目录
app.use('/static', express.static('public')) --> 虚拟路径
1. 访问静态资源时,express会依次匹配静态资源目录,如果都找不到,才会抛出404
7. href和src都是可以跨域的,可以直接指定静态资源的完整地址,包括域名和路径。
中间件
1. Express本身非常简洁,它完全是由路由和中间件构成的web开发框架,其应用就是在调用各种中间件
2. 简单来说,中间件就是在匹配路由之前和之后所做的一系列操作;
3. 类型:应用级中间件、路由级中间件、错误处理中间件、内置中间件、第三方中间件
1. 应用级中间件:匹配任何路由
app.use((req, res, next) => { ... })
1. 路由默认只匹配一次,一旦匹配成功,则不再继续向下匹配;
2. 调用 next(),让路由继续向下匹配,直到匹配正确的路由。
http://localhost:3000/news
app.use((req, res, next) => { ---> 可用作权限判断
......
next(); --> 继续向下匹配
})
app.get('/news', (req, res) => { ---> 匹配成功
res.send('匹配news路由')
})
4. 默认匹配所有路由,也可以指定只匹配某个路由
app.use('/news', (req, res, next) => { --> 只匹配 /news
......
next();
})
5. 中间件和路由的排列顺序不能颠倒,否则一旦匹配成功,将不再继续执行。
2. 路由级中间件:匹配多个路由
http://localhost:3000/news
app.get('/news', (req, res, next) => {
console.log('路由中间件news')
next(); ---> 不执行 res.send() 返回数据,让路由继续向下执行
})
app.get('/news', (req, res) => {
res.send('匹配news路由')
})
3. 错误处理中间件
1. 写在所有路由的后面,当上面的所有路由都不匹配时,则返回404
app.use((req, res) => { ---> 匹配所有路由
res.status(404).send('404页面')
})
4. 内置中间件:如 express.static(),用于托管静态资源
app.use('/static', express.static('./static'))
第三方中间件
1. body-parser:用于获取POST提交的数据,npm install body-parser --save
1. let parser = require('body-parser')
2. 配置 body-parser 中间件
//parse application/x-www-form-urlcoded
app.use(parser.urlencoded({extended:false}))
app.use(parser.json()) ---> parse application/json
app.post('/login', (req, res) => {
let body = req.body ---> 获取post提交的数据
})
2. 但是,如果表单中涉及文件上传,body-parser模块就无法处理了,需要使用multiparty模块
1. npm install multiparty --save
2. multiparty可以设置上传文件的保存路径、限制文件的大小...
3. 对于涉及文件上传的表单,必须设置<form enctype="multipart/form-data">
let multiparty = require('multiparty')
let form = new multiparty.Form()
form.uploadDir = 'upload' ----> 设置上传文件的保存目录,项目根目录/upload
form.parse(req, (err, fields, files) => { ---> 处理 req 中提交的表单数据
// -->fields是处理后的表单数据,files是文件上传成功后返回的信息
})
4. 使用虚拟目录为 upload 目录配置静态服务:
app.use('/upload', express.static('upload'))
5. 访问 upload 目录中上传的图片文件:http://localhost:3000/upload/a.jpg
3. multiparty模块的问题
1. 如果表单中没有选择上传的图片,即<input type="file" />是空的,multiparty模块仍会在
上传目录中生成一个临时文件;
2. 上传成功后,files中的originalFilename属性表示上传文件的原始名称,path属性表示上传
文件在服务器端存储的路径,为了防止文件名重复,文件的名称已经被重新编码了;
3. 如果表<input type="file" />是空的,那么 files.originalFilename='',由此判断文件
是否上传,如果没有上传,则根据 files.path 删除临时文件。
Cookie
1. NodeJs操作Cookie的中间件:npm install cookie-parser --save
2. 配置 Cookie 中间件
let cookieParser = require('cookie-parser')
app.use(cookieParser())
3. 设置cookie
app.get('/set', (req, res) => {
res.cookie('username', 'Mack', { maxAge:90000, httpOnly:true })
res.send('set Cookie')
})
1. 参数1和参数2分别是cookie的键-值,参数3是对cookie的配置;
2. maxAge:表示过期时间(s),最大失效时间
3. httpOnly:默认为false,表示不允许客户端的脚本访问,只能在nodeJs服务的操作
4. 获取cookie:一旦访问过 /set 路由,就可以在其他路由中获取 cookie
app.get('/news', (req, res) => {
let username = req.cookies.username; -->{ username: 'Mack' }
res.send(`get Cookie: ${username}`);
})
4. Cookie的更多设置
res.cookie('uname', 'Node', {expires:new Date(Date.now()+9000), httpOnly:true})
1. expires:也是设置Cookie的过期时间,表示从当前时间开始,到 expires 设置的时间过期
res.cookie('uname', 'Node', {domain:'.exa.com', path: '/admin', secure: true})
1. secure:设置为true,表示cookie在 HTTP 中是无效的,仅在 HTTPS 中才有效;
2. path:指定cookie匹配的路由,如果访问的路由不匹配,浏览器不会发送cookie
3. domain:域名,让所有子域名/二级域名共享同一个cookie信息,如domain:'.exa.com'的
子域名www.exa.com、aaa.exa.com ...
5. cookie的签名/加密
app.use(cookieParser('sign')) --> 传入一个随机字符串,用于cookie的加密
res.cookie('uname', [1,2,3], {maxAge:60000, signed:true})
1. cookie默认是明文存储的,signed用于签名cookie,设置为true 会对cookie签名/加密;
2. res.signedCookies:获取签名后的cookie
3. 被篡改的签名cookie会被服务器拒绝,且cookie会被重置为初始值。
Session
1. session是另一种记录客户端状态的机制,与cookie不同的是,session保存在服务器端;
1. 当客户端第一次访问服务器时,session可以保存客户端的登录状态,当客户访问其他路由时,
判断客户的登录状态,作出响应提示;
2. 工作机制:客户端第一次向服务器发送请求 --> 服务器创建一个类似键-值的session对象,
然后将key设置在cookie中,返回给客户端 --> 客户端下次访问时会携带cookie,根据cookie中
的key找到对应的session
2. 与cookie不同,session没有过期时间,浏览器一旦关闭,session就失效了;
3. session还可以和Redis等数据库结合,实现持久化,就算服务器宕机,也不会丢失用户状态;
4. express-session:nodeJs操作session的中间件
1. npm install express-session --save
2. 配置中间件
const session = require('express-session')
app.use(session({
secret:'anything', --> 服务器生成session的签名,可以是任意字符串,用于加密
//name: '' --------> 返回给客户端的cookie名称,默认为connect.sid
resave: false, --> 强制保存session,即使没有变化,默认值为true
saveUninitialized:true, -->强制将未初始化的session存储,默认为true
cookie:{ secure:false } -->设置cookie,maxAge、path、domain...
})) --> 一旦关闭浏览器,session就失效了,相关的cookie也会失效
2. 设置/获取session
app.get('/', (req, res) => {
if(req.session.userInfo) { ---> 获取session
res.send('welcome back: ', req.session.userInfo)
} else {
res.send('logout')
}
})
app.get('/login', (req, res) => {
req.session.userInfo = {uname:'Mack', pwd:'123'} ---> 设置session
res.send('login success')
})
3. 销毁session,如切换登陆、退出登录时,要主动销毁session
req.session.cookie.maxAge=0 --> 方式1:把cookie的过期时间设置为0ms后,立即过期
req.session.destroy(err => {}) ---> 方式2:调用session的销毁方法
5. 负载均衡配置Session
1. 负载均衡:后台配置多个服务器,执行同一套node代码,通过Nginx服务器动态地把不同地区的
用户请求转发给不同的服务器处理,或者把用户请求转发给同一地区压力比较小的服务器;
2. session默认保存在服务器的缓存文件中,而后台配置了负载均衡之后,同一用户访问的服务器
就可能发生变化,那么原本的session信息也就不存在了,但多个服务器会共享数据库,所以需要把
session保存到数据库中;
3. 把session保存在MongoDB数据库中:express-session和connect-mongo两大模块
let session = require('express-session')
const MongoStore = require('connect-mongo')(session)
4. 配置中间件
app.use(session({secret:'1234', resave:false, saveUninitialized:true,
cookie: { maxAge:1000*60*30 }, --> 在浏览器未关闭的情况下,30min后过期
rooling:true, -->默认false,每次请求时强行设置cookie,这将会重置cookie过期时间
store:new MongoStore({
url: 'mongodb://127.0.0.1:27017/student', --->数据库地址
touchAfter: 24*3600 --> 不管多少次请求,24小时内只更新一次session,除非在
}) ------------------------------------------> session数据上更改了某些内容
}))
5. db.sessions.find(); --> 查询MongoDB中保存的session
express模块化
1. express.Router:路由模块化,进而实现项目的模块化,便于协作开发与维护;
2. 创建入口文件app.js:
const express = require('express'); let app = express();
1. 引入路由模块的文件
let index = require('./routes/index')
let user = require('./routes/user')
2. 应用路由模块
app.use('/', index) --->当访问 http://localhost:3000 时,进入index.js去匹配路由
app.use('/user', user) --> http://localhost:3000/user --> user.js
app.listen(3000)
2. 以index.js为例:const express = require('express');
1. 创建路由对象:let router = express.Router();
2. 引入index模块的子路由模块login.js、home.js
let login = require('./index/login'); let home = require('./index/home')
3. 配置匹配的路由
router.use('/', login) --> http://localhost:3000 --> 进入login.js去匹配
router.use('/home', home) --> http://localhost:3000/home --> home.js
4. 暴露模块的路由对象:module.exports = router
3. 以home.js为例
const express = require('express'); let router = express.Router();
1. 响应处理请求
router.get('/', (req, res) => { -->处理响应http://localhost:3000/home
res.send('index-->home')
})
router.get('/item', (req, res) => { -->响应http://localhost:3000/home/item
res.send('index-->home-->item')
})
2. 暴露子模块的路由对象:module.exports = router
4. 使用ejs模板
1. 在入口文件app.js中配置ejs引擎和静态服务:
app.set('view engine', 'ejs'); app.use(express.static('public'));
2. 在login.js中使用
router.get('/', (req, res) => {
res.render('login') ---> 默认使用的模板为views/login.ejs
})
5. express脚手架:express-generator
网友评论