1 Express框架介绍与安装
1.1 Express 安装
生成配置文件package.json
npm init --yes
安装 Express 框架,就是使用 npm 的命令。
npm install express --save
yarn add express --save
初次使用
const express = require('express')
//实例化express
const app = express()
const port = 3000 //端口
//配置路由
app.get('/', (req, res) => {
res.send('Hello World!')
})
//监听端口
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
2 Express中的路由
路由(Routing)是由一个 URI(或者叫路径)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何响应客户端对某个网站节点的访问
Express中的路由配置非常简单
const express = require('express')
const app=express()
app.get("/",(req,res)=>{
res.send("路由配置完成")
})
app.listen(3000)
配置post路由
app.post("/doLogin",(req,res)=>{ //post:增加数据
console.log("执行登录")
res.send("执行登录")
})
配置put路由
app.put("/editUser",(req,res)=>{ //put:主要用于修改数据
console.log("修改用户")
res.send("修改用户")
})
配置delete路由
app.delete("/editUser",(req,res)=>{ //put:主要用于修改数据
console.log("修改用户")
res.send("修改用户")
})
动态路由的配置路由的时候要注意顺序
动态路由在下面
app.get("/article/add",(req,res)=>{
res.send("article add")
})
app.get("/article/:id",(req,res)=>{
var id=req.params["id"] //获取动态路由
res.send("动态路由"+id)
})
路由get中的参数
//get 传值 http://localhost:3000/product?id=123&cid=123
app.get("/product",(req,res)=>{
let query = req.query //获取get传值
console.log(query)
res.send("product-"+query.id)
})
3 express 工程化
Express:是比较轻量级的框架,不自带许多工程化目录,需要自己配置
如下配置:新建routes目录
将模块分开为三大模块,并在入口文件引入
//引入外部模块
const admin = require("./routes/admin")
const api = require("./routes/api")
const index = require("./routes/index")
//挂载外部模块
app.use("/admin", admin)
app.use("/app", api)
app.use("/", index)
举例:当admin模块中还有很多模块就再次抽离
admin.js挂载子模块
const express = require("express")
const router = express.Router()
//引入模块
const user = require("./admin/user")
const nav = require("./admin/nav")
router.get("/", (req, res) => {
res.send("后台管理中心")
})
//挂载模块
router.use("/user", user)
router.use("/nav", nav)
module.exports = router
子模块则延续父模块路由,如:/admin/user
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.get("/del", (req, res) => {
res.send("删除用户")
})
module.exports = router
3.1 Express生成器(express-generator)
https://www.expressjs.com.cn/starter/generator.html
通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。
安装成功后
可使用express 查看相关用法
express -h
Usage: express [options] [dir]
Options:
-h, --help 输出使用方法
--version 输出版本号
-e, --ejs 添加对 ejs 模板引擎的支持
--hbs 添加对 handlebars 模板引擎的支持
--pug 添加对 pug 模板引擎的支持
-H, --hogan 添加对 hogan.js 模板引擎的支持
--no-view 创建不带视图引擎的项目
-v, --view <engine> 添加对视图引擎(view) <engine> 的支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认是 jade 模板引擎)
-c, --css <engine> 添加样式表引擎 <engine> 的支持 (less|stylus|compass|sass) (默认是普通的 css 文件)
--git 添加 .gitignore
-f, --force 强制在非空目录下创建
生成项目
#view = 模板引擎
express--view = ejs 项目名
yarn install
npm install
4 Express使用Ejs模板引擎
4.1 安装
npm install ejs --save
yarn add ejs --save
4.2 Ejs的使用
const express = require("express")
//引入ejs
const ejs = require("ejs");
const app = express()
app.get("/", (req, res) => {
//render渲染模板
res.render("index.ejs", {
//传入数据
})
})
app.listen(3000)
4.2.1 ejs传输数据
app.get("/", (req, res) => {
//对象假数据
let userinfo = {
username: "Ting",
age: "20"
}
// 标签假数据
let article = "<h3>我是标价数据</h3>"
res.render("index.ejs", {
//传入数据
userinfo: userinfo,
article: article
})
})
4.2.2 模板渲染数据
<h2>我是一个ejs模板</h2>
//%= 解析普通文本 %-可解析html
<p>我叫<%=userinfo.username%>年龄<%=userinfo.age%></p>
<p><%-article%></p>
4.3 条件判断语句
<%if(flag==true){%>
<strong>flag=true</strong>
<%}%>
<%if(score>=60){%>
<p>及格</p>
<%}else{%>
不及格
<%}%>
4.4 循环语句
<ul>
<%for(let i=0;i<list.length;i++){%>
<li><%=list[i]%></li>
<%}%>
</ul>
<br>
<ul>
<%for(let i=0;i<newsList.length;i++){%>
<li><%=newsList[i].title%></li>
<%}%>
</ul>
引入公共文件入引入foot.ejs
<%-include('foot.html')%>
4.5 配置模板引擎后缀
//配置模板引擎后缀为:html
app.engine("html",ejs.__express);
//view目录下的ejs
app.set("view engine","ejs");
4.6 静态文件
app.use(express.static("static"))
5 封装Express路由
尝试模拟封装类似express的路由
express配置路由方式如下
app.get("/", function (req, res) {
res.send('hello world')
})
那么需要一个app函数来调用
let app=function(req,res){
// console.log('调用app方法')
if(G['/login']){
G['/login'](req,res); //执行方法
}
}
当不同请求时调用不同路径方法,需要注册不同方法
let G={};//保存注册的路由
//静态方法
app.get=function(str,cb){
//注册方法
G[str]=cb;
}
那么初版完成了注册和配置调用
5.1 封装route
将上面代码封装到route文件里,并暴露app方法
const url= require("url");
let G={};
let app=function(req,res){
// console.log('调用app方法')
let pathname=url.parse(req.url).pathname;
if(G[pathname]){
G[pathname](req,res); //执行方法
}else{
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
app.get=function(str,cb){
//注册方法
G[str]=cb;
}
module.exports=app;
5.2 引入http模块
引入route模块和http模块并配置路由
const http = require("http");
const app=require('./module/route')
//注册web服务
http.createServer(app).listen(3000);
//配置路由
app.get('/',function(req,res){
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('首页');
})
5.3 加入post
当我们post的时候可能会和get请求路径一样
那么我们定义G时可以分开定义不同方法
const url = require("url");
let server = () => {
let G = {};
//把get 和 post分开
G._get={};
G._post={};
let app = function (req, res) {
//扩展res的方法
changeRes(res);
let pathname = url.parse(req.url).pathname;
//获取请求类型
let method=req.method.toLowerCase();
if (G['_'+method][pathname]) {
if(method=="get"){
G['_'+method][pathname](req, res); //执行方法
}else if(method=="post"){
//post 获取post的数据 把它绑定到req.body
let postData = '';
req.on('data', (chunk) => {
postData += chunk;
})
req.on('end', () => {
req.body = postData;
G['_'+method][pathname](req, res); //执行方法
})
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
app.get = function (str, cb) {
//注册方法
G._get[str] = cb;
}
app.post = function (str, cb) {
//注册方法
G._post[str] = cb;
}
return app;
}
module.exports = server();
5.4 添加静态页配置
配置静态web服务目录
const fs = require('fs');
const path = require('path');
const url = require('url');
//根据后缀名获取文件类型
function getFileMime(extname) {
var data = fs.readFileSync('./data/mime.json'); //同步方法
let mimeObj = JSON.parse(data.toString());
return mimeObj[extname];
}
//静态web服务的方法
function initStatic(req, res, staticPath) {
//1、获取地址
let pathname = url.parse(req.url).pathname;
let extname = path.extname(pathname);
//2、通过fs模块读取文件
if (extname) {//如果有后缀名用静态web服务处理
try {
let data = fs.readFileSync('./' + staticPath + pathname);
if (data) {
let mime = getFileMime(extname);
res.writeHead(200, { 'Content-Type': '' + mime + ';charset="utf-8"' });
res.end(data);
}
} catch (error) {
console.log(error);
}
}
}
let server = () => {
let G = {
_get: {},
_post: {},
staticPath: 'static' //,默认静态web目录
};
let app = function (req, res) {
//扩展res的方法
changeRes(res);
//配置静态web服务
initStatic(req, res, G.staticPath);
let pathname = url.parse(req.url).pathname;
//获取请求类型
let method = req.method.toLowerCase();
let extname = path.extname(pathname);
if (!extname) { //如果有后缀名用静态web处理
if (G['_' + method][pathname]) {
if (method == "get") {
G['_' + method][pathname](req, res); //执行方法
} else {
//post 获取post的数据 把它绑定到req.body
let postData = '';
req.on('data', (chunk) => {
postData += chunk;
})
req.on('end', () => {
req.body = postData;
G['_' + method][pathname](req, res); //执行方法
})
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end('页面不存在');
}
}
}
//get请求
app.get = function (str, cb) {
//注册方法
G._get[str] = cb;
}
//post请求
app.post = function (str, cb) {
//注册方法
G._post[str] = cb;
}
//配置静态web服务目录
app.static = function (staticPath) {
G.staticPath = staticPath;
}
return app;
}
module.exports = server();
调用配置
app.static("public")
6 Express中间件
Express的中间件,用来实现各种功能,对于同一个网络请求,可能同时有多个匹配的中间件,一般顺序执行。而 next() 则是把执行控制权,从上一个中间件,转移到下一个中间件的函数。
6.1 应用级中间件
express通过app.use来配置应用级中间件
调用next方法,匹配下一个中间件
//应用级中间件
app.use((req, res, next) => {
console.log("添加中间件")
//需要匹配下面中间件就要添加next回调
next()
})
6.2 路由级中间件
路由级中间件用的比较少
一般情况下,路由匹配到一个就不会向下匹配了
app.get("/article/add",(req,res)=>{
res.send("article add")
})
app.get("/article/:id",(req,res)=>{
var id=req.params["id"] //获取动态路由
res.send("动态路由"+id)
})
加入next()回调就会向下匹配
app.get("/article/add",(req,res,next)=>{
res.send("article add")
next()
})
app.get("/article/:id",(req,res)=>{
var id=req.params["id"] //获取动态路由
res.send("动态路由"+id)
})
6.3 错误处理中间件
和应用级中间件使用差不多,用于错误处理
app.use((req, res, next) => {
res.status(404).send("404")
})
6.4 内置中间件
如:静态文件目录匹配中间件
app.use(express.static("static"))
6.5 第三方中间件
第三方中间件各种各样,学express就是需要学习各种中间件
如:配置body-parse中间件获取post提交数据
- 安装第三方中间件
npm install body-parser --save
yarn add body-parser --save
- 配置中间件
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
- 使用body
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
7 Express使用Cookie和Session
7.1 Cookie 的使用
Express 中要使用 Cookie 的话,我们需要使用 cookie-parser 模块来实现
7.1.1 安装
npm instlal cookie-parser --save
yarn add cookie-parser --save
7.1.2 引入并配置
const cookieParser = require('cookie-parser');
//配置中间件
app.use(cookieParser());
//设置cookie(key, value, {过期时间})
res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true});
//获取cookie
req.cookie.name
7.1.3 设置参数
//设置cookie(key, value, {过期时间})
res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true});
参数 | 解释 |
---|---|
domain | 域名 name=value:键值对,可以设置要保存的 Key/Value,注意这里的 name不能和其他属性项的名字一样 |
Expires | 过期时间(秒),在设置的某个时间点后该 Cookie 就会失效,如expires=Wednesday, 09-Nov-99 23:12:40 GMT |
maxAge | 最大失效时间(毫秒),设置在多少后失效 |
secure | 当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效 |
Path | 表示 cookie 影响到的路路径,如 path=/。如果路径不能匹配时,浏览器则不发送这个 Cookie |
httpOnly | 是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生 |
singed | 表示是否签名 cookie, 设为 true 会对这个 cookie 签名,这样就需要用res.signedCookies 而不是 res.cookies 访问它。被篡改的签名 cookie 会被服务器拒绝,并且 cookie 值会重置为它的原始值 |
设置cookie 的几种方法
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
删除cookie
//重置为空
res.cookie('rememberme', '', { expires: new Date(0)});
res.cookie('username','zhangsan',{domain:'.ccc.com',maxAge:0,httpOnly:true});
7.1.4 加密Cookie
配置中间件的时候需要传参
var cookieParser = require('cookie-parser');
app.use(cookieParser('123456'));
设置cookie 的时候配置signed 属性
res.cookie('userinfo','hahaha',{
domain:'.ccc.com',
maxAge:900000,
httpOnly:true,
signed:true //加密
});
signedCookies 调用设置的cookie
console.log(req.signedCookies);
7.2 Session的使用
当浏览器访问服务器并发送第一次请求时,服务器端会创建一个 session对象,生成一个类似于 key,value的键值对, 然后将 key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带 key(cookie),找到对应的 session(value)。
7.2.1 安装
安装 express-session
npm install express-session --save
yarn add express-session --save
配置中间件
app.use(session({
secret: 'keyboard cat', //生成dession签名(随意写)
resave: true, //强制保存session
saveUninitialized: true //强制将为初始化的session存储
cookie: {
maxAge: 1000*60,
secure: false //true表示只有https才能访问cookie
}
}))
7.2.2 设置参数
参数 | 解释 |
---|---|
secret | 一个 String 类型的字符串,作为服务器端生成 session 的签名。 |
name | 返回客户端的 key 的名称,默认为connect.sid,也可以自己设置。 |
resave | 强制保存 session 即使它并没有变化,。默认为 false。 don’t save session if unmodified |
saveUninitialized | 强制将未初始化的 session 存储。当新建了一个 session 且未设定属性或值时,它就处于未初始化状态。在设定一个 cookie 前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。(默认:true)。建议手动添加。 |
cookie | 设置返回到前端 key 的属性,默认值为{ path: ‘/’, httpOnly: true, secure: false,maxAge: null }。 |
rolling | 在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false) |
7.2.3 常用方法
//设置 session
req.session.username='张三';
//销毁 session
req.session.destroy(function(err) {})
//获取session
req.session.username
//重新设置 cookie 的过期时间
req.session.cookie.maxAge=0;
7.2.4 Session 保存到数据库
保存到mongodb数据库,保存到其他数据库操作类似
https://www.npmjs.com/package/connect-redis
https://www.npmjs.com/package/connect-mysql
安装connect-mongo 模块
npm i connect-mongo --save
yarn add connect-mongo --save
引入
const MongoStore = require('connect-mongo')(session);
配置中间件
app.use(session({
secret: 'this is session', //服务器端生成 session 的签名
name:"itying", //修改session对应cookie的名称
resave: false, //强制保存 session 即使它并没有变化
saveUninitialized: true, //强制将未初始化的 session 存储
cookie: {
maxAge:1000*60*30,
secure: false // true 表示只有https协议才能访问cookie
},
rolling:true, //在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)
//配置保存数据库
store: new MongoStore({
url: 'mongodb://127.0.0.1:27017/shop',
touchAfter: 24 * 3600 // 不管发出了多少请求 在24小时内只更新一次session, 除非你改变了这个session
})
}))
8 Express结合multer上传文件
Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。它是写在 busboy 之上非常高效。
注意: Multer 不会处理任何非 multipart/form-data 类型的表单数据。
8.1安装
npm install --save multer
借助multer模块上传文件
var multer = require('multer')
//配置
var storage = multer.diskStorage({
//文件保存路径 注意路径必须存在
destination: function (req, file, cb) {
cb(null, 'public/upload/')
},
//修改文件名称
filename: function (req, file, cb) {
//获取后缀名
let fileFormat = (file.originalname).split(".");
//拼接后缀名
cb(null,Date.now() + "." + fileFormat[fileFormat.length - 1]);
}
})
const upload = multer({ storage: storage })
8.2 封装上传模块
新建model目录->tools.js文件封装
const multer = require('multer');
const path = require('path');
const sd = require('silly-datetime');
const mkdirp = require('mkdirp')
let tools={
multer(){
var storage = multer.diskStorage({
//配置上传的目录
destination: async (req, file, cb) => {
//1、获取当前日期 20200703
let day=sd.format(new Date(), 'YYYYMMDD');
// static/upload/20200703
let dir=path.join("static/upload",day)
//2、按照日期生成图片存储目录 mkdirp是一个异步方法
await mkdirp(dir)
//上传之前目录必须存在
cb(null, dir)
},
//修改上传后的文件名
filename: (req, file, cb)=> {
//1、获取后缀名
let extname= path.extname(file.originalname);
//2、根据时间戳生成文件名
cb(null, Date.now()+extname)
}
})
var upload = multer({ storage: storage })
return upload;
},
md5(){
}
}
module.exports=tools