# Express

# 1】基本使用

const express = require('express')
const app = express()
app.listen(3000)//异步最后执行
app.get('/', (req, res) => {
  res.send(`你好Express
  <button onclick="handle(1)" >注册</button>
  <button onclick="handle(2)" >登陆</button>
  <script>
  function handle(type){
    switch(type){
      case 1:
      window.location.href='http://localhost:3000/register';
      break;
      case 2:
      window.location.href='http://localhost:3000/login';
    }
  }
  </script>
  `)
})
app.get('/login', (req, res) => {
  res.send('登录页面')
})
app.get('/register', (req, res) => {
  res.send('注册页面')
})

//动态路由http://localhost:3000/article/add
app.get('/article/:id', (req, res) => {
  res.send('动态路由' + req.params.id)
  //动态路由add
})

//参数查询http://localhost:3000/product?id=1&cid=2
app.get('/product', (req, res) => {
  res.send('商品页面' + JSON.stringify(req.query))
  //商品页面{"id":"1","cid":"2"}
})

如果动态路由和静态路由冲突,会根据代码路由配置顺序去匹配:(配置动态路由需要注意顺序)

app.get('/article/add', (req, res) => {
  res.send('文章页面')
})
app.get('/article/:id', (req, res) => {
  res.send('动态路由' + req.params.id)
})

//此时,访问http://localhost:3000/article/add
//文章页面
app.get('/article/:id', (req, res) => {
  res.send('动态路由' + req.params.id)
})
app.get('/article/add', (req, res) => {
  res.send('文章页面')
})

//此时,访问http://localhost:3000/article/add
//动态路由add

# 2】使用ejs模板引擎

/*
1、安装npm install ejs --save

2、配置注册app.set("view engine","ejs")

3、使用(默认加载模板引擎的文件夹是根目录下的views)

4、指定模板文件夹位置,默认模板位置是views文件夹
app.set('views',__dirname,'/views')//保存在当前文件目录下的views里

文件目录:
|-node_modules
|-views(默认的模板引擎文件夹)
		|-index.ejs		
|-app.js
|-package.json
*/

const express = require('express')
const app = express()

//配置模板引擎
app.set("view engine","ejs")

app.get("/ejs",(req,res)=>{
  res.render('index', {})
})

//监听端口,开启服务
app.listen(3000)

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>我是ejs模板引擎<h2>
</body>
</html>

# 3】静态文件托管

/*
1、配置静态web目录中间件
app.use(express.static('static'))

文件目录:
|-node_modules
|-static
	  |-css
	  	 |-base.css
|-views(默认的模板引擎文件夹)
		|-index.ejs		
|-app.js
|-package.json
*/
const express = require('express')
const app = express()

//配置静态web目录中间件
app.use(express.static('static'))(默认)
//app.use('/pub', express.static('static'));

//也可以配置多个静态目录
app.use(express.static('public'))//此时public目录下的文件也可访问

//配置模板引擎
app.set("view engine","ejs")

app.get("/ejs",(req,res)=>{
  res.render('index', {})
})

//监听端口,开启服务
app.listen(3000)

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>
  <link rel="stylesheet" href="/css/base.css">
  <!--引入静态css文件-->
</head>
<body>
  <h2>我是ejs模板引擎<h2>
</body>
</html>

此时访问http://localhost:3000/css/base.css和http://localhost:3000/ejs即可查看css引入成功的效果。

# 虚拟静态目录设置:

app.use('/public',express.static('static'));
//static是真实在服务器上存在的目录,public是虚拟的,在地址栏访问public路径服务器就会指向static目录
/*静态文件目录:
|-node_modules
|-static
	  |-css
	  	 |-base.css
|-views(默认的模板引擎文件夹)
		|-index.ejs		
|-app.js
|-package.json

访问http://localhost:3000/public/css/base.css

区别默认:
app.use(express.static('static'));//(默认)
访问http://localhost:3000/css/base.css

*/

# 4】中间件

通俗的讲:中间件就是匹配路由之前或者匹配路由完成做的一系列的操作。中间件中如果想往下匹配的话,那就必须写next(),(执行中间件修改了req或者res,从而到达效果)

# 1、内置中间件:

app.use('/public',express.static('static'));

# 2、应用级中间件(用于权限判断)

app.use((req,res,next)=>{/*匹配任何路由*/
  console.log(new Date())
  
  next();/*表示匹配完成这个中间件以后程序继续向下执行*/
})

# 3、路由级中间件(用的比较少)

//动态路由http://localhost:3000/article/add
app.get('/article/add', (req, res,next) => {
  console.log('执行增加新闻')
  next()//继续向下匹配
})

app.get('/article/:id', (req, res) => {
  res.send('动态路由' + req.params.id)//动态路由add
})

# 4、错误处理中间件

//很多路由...

app.use((req, res, next) => {
  //匹配到最后匹配不到返回404
  res.status(404).send('404')
})

# 5、第三方中间件

例如:body-parser中间件(用于接收表单post数据)

npm i body-parser --save

执行中间件修改了req,从而到达效果

const bodyParser = require('body-parse')
const app = express()

//表示解析 application/x-www-form-urlencoded(form表单)
//如果有上传file,需要解析multipart/form-data
app.use(bodyParser.urlencoded({ extended:false }))

//表示解析 application/json
app.use(bodyParser.json())

# 5】cookie-parser

我们使用第三方中间件cookie-parser

npm i cookie-parser --save

const cookieParser = require('cookie-parser')
const app = express()

app.use(cookieParser())

app.get('/login', (req, res) => {
  // 设置cookie(maxAge单位ms)
  res.cookie('username', 'zhangsan', { maxAge: 1000 * 60 * 60 })
  res.send('登录页面')
})
app.get('/register', (req, res) => {
  // 获取cookie
  res.send('注册页面' + req.cookies.username || '')
})

/*cookie的加密:

1、配置中间件的时候需要传入加密的参数(这是是通过xiaoduoai字符串进行对cookie进行加密)
app.use(cookieParser("xiaoduoai"))

2、设置cookie时开启加密
res.cookie('username', 'zhangsan', { maxAge: 1000 * 60 * 60, signed:true })

3、req.signedCookies获取加密cookie(通过xiaoduoai字符串解密cookie中的值)
const username = req.signedCookies.username
(req.cookies.username是不能获取到加密cookie的)

*/

cookie(name: string, val: string, options: CookieOptions)

CookieOptions:

  • maxAge?: number;设置多少毫秒后cookie过期
  • signed?: boolean;是否加密
  • expires?: Date;设置具体的cookie过期时间点
  • httpOnly?: boolean;默认false,如果为true前端的JS脚本就无法获取cookie,只有后端才能操作cookie
  • path?: string;设置哪些路由路径可以访问我们设置的当前cookie,
    • res.cookie('username', 'zhangsan', { path: '/' }):"/"以及它的子路由都可以访问username
    • res.cookie('age', '20', { path: '/info' }):"/info"以及它的子路由都可以访问age。但是"/"或"/xx"没法访问age
  • domain?: string;默认是当前域名(多个子域名之间共享cookie
    • res.cookie('username', 'zhangsan', { domain:".xiaoduoai.com" }):此时dev.xiaoduoai.com和prod.xiaoduoai.com都可以访问username
    • res.cookie('username', 'zhangsan'):此时默认只有设置cookie时的域名才能访问username。
  • secure?: boolean;默认为false,如果为true,cookie在HTTP中无效,在HTTPS中才有效。
  • encode?: (val: string) => string;
  • sameSite?: boolean | 'lax' | 'strict' | 'none';

# 6】express-session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。

我们使用第三方中间件express-session

npm i express-session --save

const session = require("express-session")
const app = express()

//注册中间件
app.use(session({
    secret: "this is session secret",//用于服务器端生成session的签名
    name: "SID",//设置session的名字(返给客户端的cookie的键名)
    resave: false,//强制保存session即使它并没有变化
    saveUninitialized: true,//强制将未初始化的session存储
    cookie: { //(session是基于cookie的,生成SID记录客户端状态)
        maxAge: 1000*60,//单位ms,设置session的过期时间
        secure:false //true表示只有https协议才能访问session
    }
}))

//设置session: req.session.username = '张三'
//获取session: req.session.username
app.get('/login', (req, res) => {
  // 设置session
  req.session.age = 100000
  res.send('登录页面')
})

app.get('/register', (req, res) => {
  //获取session
  res.send('注册页面' + (req.session.age || ''))// 100000
})

//退出登录(删除session)
app.get('/loginOut',(req,res)=>{
  //方法一、设置session的过期时间为0
  req.session.cookie.maxAge = 0//(弊端:但是所有的session都过期了)
  
  //方法二、销毁指定session
  req.session.username=''
    
  //方法三、req.session.destroy方法
  req.session.destroy(err=>{
      console.log(err)
  })
    
  res.send("退出登录")
})

SessionOptions:

  • secret: string | string[];用于服务器端生成session的签名
  • name?: string;设置session的名字(返给客户端的cookie的键名)
  • resave?: boolean;强制保存session即使它并没有变化
  • store?: Store;分布式session,保存在redies中
  • rolling?: boolean;在每次请求获取cookie时都重新设置cookie,这将重置cookie过期时间(默认false)

# 7】负载均衡保存Session

# 分布式架构中配置session
# 将nodejs程序发布到三台服务器上,再配置一个nginx服务器(做负载均衡)
# 可能会造成session信息丢失,所以把信息保存到mongo或redis中
	  	  |-----北京nodejs (设置session) --->  ______________
nginx------|-----上海nodejs (获取session) <--- | mongo/redis |
		  |-----深圳nodejs                     |_____________| 
		  |----- ...                                 

负载均衡配置Session到mongoDB:

//1.需要安装express-session和connect-mongo模块 
//2.引入模块使用
var session = require('express-session')
const MongoStore = require('connect-mongo')(session)

//3.配置express-session中间件
app.use(session({
    secret:'foo',
    //...
    store:new MongoStore(options)
}))

下面是实际的配置情况:

app.use(session({
  secret: "this is session secret",//用于服务器端生成session的签名
  name: "SID",//设置session的名字(返给客户端的cookie的键名)
  resave: false,//强制保存session即使它并没有变化
  saveUninitialized: true,//强制将未初始化的session存储
  cookie: { //(session是基于cookie的)
    maxAge: 1000 * 60,//单位ms,设置session的过期时间
    secure: false //true表示只有https协议才能访问session
  },
    store: new MongoStore({
        url: 'mongodb://127.0.0.1:27017/shop',
        touchAfter: 24 * 3600//不管发出了多少请求,在24小时内只更新一次session,除非你改变了session
    })
    //配置分布式session(当前url下的shop数据库)运行了该程序之后我们就可以到shop数据库中得sessions表中查看登录的session信息。
}))

# 8】Express路由模块化

express.Router()来模块化开发路由接口

// routes/login.js
const express = require('express')
const router = express.Router()

// 使用路由中间件
router.use(function timeLog (req, res, next) {
  console.log('Time: ', Date.now())
  next()
})
// 登录接口(/login)
router.get('/', function (req, res) {
  res.send('login')
})
// 接口(/login/doLogin)
router.get('/doLogin', function (req, res) {
  res.send('doLogin')
})

module.exports = router
//-----------------------------------------------------------
// app.js
const express = require('express')
const app = express()
//导入路由模块
const login = require('./routes/login')
const admin = require('./routes/admin')
const index = require('./routes/index')
const api = require('./routes/api')


app.listen(3000)//异步最后执行

//挂载配置路由模块
app.use('/admin', admin)

app.use('/api', api)

app.use('/', index)

app.use('/login', login)

app.use((req, res, next) => {
  res.status(404).send('404')
})

模块化目录:

|-routes
	|-admin(用户模块)
	|   |-login.js(用户登录)
	|   |-nav.js(用户导航)
	|   |-user.js(用户管理)
	|   |-index.js(用户->去挂载子路由)
	|   
	|-api
	|   |-index.js(接口->去挂载子路由)
	|   
	|-index.js(首页->去挂载子路由)
|-app.js

# 9】Express脚手架

npm i -g express-generator

安装了之后就可以使用express命令

express --view=ejs rayhomie_project

# 10】Multer图片上传

npm i multer --save

<!-- 前端上传组件 想要上传图片必须在form上加enctype="multipart/form-data" -->
<form action="/profile" method="post" enctype="multipart/form-data">
    <input type="file" name="avatar" />
</form>

也可以将一下配置,封装在一个工具类中进行使用:

// 后端接口
const express = require('express')
const multer = require('multer')
const path = require('path')
const mkdirp = require('mkdirp')

/*方式一:指定配置
const upload = multer({
    dest: 'static/upload',// 配置图片上传的路径(上传之前目录必须存在)
})
*/

// 方式二:指定配置
const storage = multer.diskStorage({
  destination:async (req, file, cb) => {// 配置上传目录
      // 按照日期分类存储图片
      const dateName = getCurrentDate()
      // 拼接目录(使用path.join帮我们处理更好)
      const dir = path.join('static/upload', dateName)
      // 必须要先创建好目录才行,mkdirp是一个异步方法(返回的是一个promise)。
      await mkdirp(dir)
      cb(null, dir)
  },
  filename: (req, file, cb) => {// 配置文件名
      const extname = (file.originalname).split('.')[1] //获取后缀名
      cb(null, `${Date.now()}.${extname}`)
  }
})
const upload = multer({ storage: storage })

const app = express()
// 单文件上传(upload.single)
// avater对应input上面的name
app.post('/profile', upload.single('avatar'), (req, res, next) => {
    console.log(req.file) // 获取上传的图片信息 
})
 
// 多文件上传(upload.fields)
app.post('/photos/upload', upload.array('photos', 12), (req, res, next) => {
    res.send({
        body:req.body,
        files:req.files
    })
})

app.post('/cool-profile', upload.fields([
    { name: 'avatar', maxCount: 1 },
    { name: 'gallery', maxCount: 8 }
]), (req, res, next) => {
    res.send({
        body:req.body,
        files:req.files
    })
})

//获取当前时间,例如:20210203
function writeCurrentDate() {
	var now = new Date();
	var year = now.getFullYear(); //得到年份
	var month = now.getMonth();//得到月份
	var date = now.getDate();//得到日期
	var week;
	month = month + 1;
	if (month < 10) month = "0" + month;
	if (date < 10) date = "0" + date;
	var time = "";
	return time = year +  month + date;
  }
Last Updated: 8/1/2021, 1:43:20 PM