Express

作者: hellomyshadow | 来源:发表于2019-04-21 10:41 被阅读0次

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

相关文章

网友评论

      本文标题:Express

      本文链接:https://www.haomeiwen.com/subject/duudgqtx.html