# 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: '/' })
:"/"以及它的子路由都可以访问usernameres.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都可以访问usernameres.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;
}