express学习小结

Express

express中文网连接: https://www.expressjs.com.cn/

1.基于node平台的web开发框架

  • 特性
    • 提供了简便的路由定义方式
    • 对获取的http请求参数进行了简化处理,将参数做成了请求对象的属性
    • 对模板引擎支持程度高
    • 提供了中间件机制控制http请求
    • 有大量第三方中间件对功能进行扩展

2.使用步驟

// 引入express框架
const express = require('express')
// 创建网站服务器
const app = express()
app.get('/',(req,res)=>{
	// send方法内部检测响应内容的类型 自动设置http状态码 自动设置响应的内容类型及编码
  res.send() // 可以传递json对象
})
app.listen(3000)

3.核心概念:中间件

  • 中间件就是一堆方法,接收请求,可以对请求作出响应,也可以将请求继续交给下一个中间件处理

  • 中间件由express提供,负责拦截请求,请求处理函数由开发人员自己提供

  • 默认情况下,从上到下匹配中间件,一旦匹配成功,终止匹配,next方法决定是否允许请求向下走

  • 一般分为应用层中间件、路由中间件、内置中间件、错误处理中间件、第三方中间件

    -------------------------应用层中间件-------------------------
    app.use((req,res,next)=>{
    console.log(‘所有请求先过我这里’);
    next(); // 必须要向后放行,否则后面的代码不会执行
    })
    -------------------------路由中间件-------------------------
    // 创建路由对象
    var router = express.router()
    router.use(’/user’,(req,res,next)) => {
    console.log(‘匹配的地址’:‘req.originalurl’)
    },(req,res) => {
    res.send(‘请登录’)
    })
    app.use(’/’,router)
    ---------------引申,利用路由中间件创建二级路由----------------
    home.js
    const express = require(‘express’)
    const home = express.Router() // 创建路由对象
    home.get(’/index’,()=>{ // home路由下创建二级路由
    // 路由为/home/index
    res.send(‘home页面’)
    })
    module.exports = home;

    app.js
    const home = require(’./home’)
    // 路由和请求路径匹配
    app.use(’/home’,home)
    ----------------错误处理中间件(有四个参数)----------------------
    // 集中处理错误的地方
    app.use((err,req,res,next)=>{
    res.sendStatus(err.httpStatusCode).json(err);
    })
    // 一般错误处理放到最后,匹配不到路径就返回404,注意下Koa是放到最前
    app.use(function(req,res){
    res.status(404).send(“您查找的页面不存在”);
    });
    -----------------引申,抛错误方式-------------------------------
    throw new Error(‘程序发生未知错误’) //同步方式,throw出node的内置错误对象
    fs.readFile(’./…’,(err,data)=>{
    if(err!=null) next(err) //异步方式,把错误对象传到next()方法中
    })
    try{ //异步方式同步写法,使用async await语法糖
    await User.find()
    }catch(err){
    next(err);
    }…
    -------------------内置中间件---------------------------------
    // 4.x版本开始,express.static()成了唯一内置中间件
    app.use(express.static(path.join(__dirname,‘public’)[,options])
    app.use(’/static’,express.static(path.join(__dirname,‘public’)) // 写第一个参数,还可以增加虚拟路径
    options配置项:
    var options = {
    dotfiles: ‘ignore’, //是否对外输出文件名以点(.)开头的文件
    etag: false, //启用或禁用 etag 生成
    extensions: [‘htm’, ‘html’], //用于设置后备文件扩展名
    index: false, //发送目录索引文件
    maxAge: ‘1d’, //设置 Cache-Control 头的 max-age 属性
    redirect: false,//当路径名是目录时重定向到结尾的’/’
    setHeaders: function (res, path, stat) { //设置随文件一起提供的HTTP头的函数
    res.set(‘x-timestamp’, Date.now());
    }
    }
    --------------------第三方中间件------------------------------
    例如下文中的body-parser中间件

4.express路由

app.methods(path,callback) // express路由方法
// methods方法有get、post、put、head、delete、options、trace、copy、lock、mkcol、move、purge、propfind、proppatch、unlock、report、mkactivity、checkout、merge、m-search、notify、subscribe、unsubscribe、patch、search、connect
// path包含三种 1.完整字符串路径  2.字符串模式路径  3.正则表达式路径 /^home/ 
-----------------------------------------------------
app.get('/user/:id',callback)  //动态路由

路由请求参数获取

a.获取get请求参数

app.get(...,(req,res)=>{log(req.query)})  //输出一个对象 {name:'张三',age:18}
req.query直接把url地址中query的部分转换为对象格式输出

b.获取post请求参数

-----------1.第三方模块 body-parser---------------------
// 下载安装 引入模块
const bodyParser = require('body-parser');
// 配置body-parser模块,解析json格式数据
const jsonparser = bodyParser.json();
app.use(jsonparser);
// 或者 解析x-www-form-urlencoded格式数据,false表示使用express内置的模块解析
urlendcodedparser = bodyParser.urlencoded({ extended:false });
// 拿到参数,如果没有使用app.use(..),那么传入请求中使用
app.post('/user',urlendcodedparser,(req,res) => {
	console.log(req.body)  //输出一个对象 {name:'张三',age:18}
})

第三方模块的本质其实是内部返回了一个类似function(req,res,next){....;next();}的函数

------------2. 第三方模块 connect-multiparty------------------
// 多用于文件上传,但也可以访问到post请求的数据,尽量不使用这个
npm i connect-multiparty
cosnt multipaty = require('connect-multiparty');
const multipartyMiddleware = multiparty();
app.post('/',multipartMiddleware,function(req,res){
    console.log(req.body);
});
------------3. 第三方模块 formidable--------------------------
// 用于接收post传来的formdata
(前端要么new Formdata()然后append('file',file.files[0]),要么设置enctype='multipart/form-data')
(前端预览文件? 
方式1:
input的change事件const readr = new FileReader();
reader.readAsDataURL(this.files[0]);
reader.addEventListener('load',()=>{ img.src = reader.result; })
方式2:
img.src = window.URL.createObjectURL(this.files[0])
方式3:
img.src = 后端传过来文件上传到服务器上的临时地址
监听load事件完毕,再把img设置为display=''
)
const formidable = require("formidable");
const fS = require('fs');
const path = require("path");

app.post('/upload', (req,res) => { 
  const form = new Formidable.IncomingForm();  //创建解析对象
  form.uploadDir = Path.join(__dirname, './Public/');  //设置上传路径
  form.keepExtensions = true; // 保存文件后缀名
  // 解析表单对象,fields对象为字段名,files对象为上传的文件
  form.parse(req,function (err, fields, files) {
    if (err) throw err;
    const FilePath = files.Content.path;  //Content为formdata append文件的对应字段
    const NewPath = path.join(Path.dirname(FilePath), files.Content.name);
    fS.rename(FilePath, NewPath, function (err) {
      if (err) throw err;
      let msg = {
        errno: 0,
        data:['http://localhost:8080/' + files.Content.name]
      };
      res.json(msg);  //返回前台json数据,包含一个errno和data(包含文件的地址)
    });
  });
});

c.路由参数 (restful风格+动态路由)

url: localhost:3000/find/123
app.get('/find/:id',(req,res)=>{
  log(req.params) // {id:123}获取路由参数
})

d.重定向

res.redirect('/')

e.cookie

// 存储量小 一般4k 正常情况不加密,实际使用需要加密
npm i cookie-parser
const cookieParser=require("cookie-parser");
app.use(cookieParser());
// 设置cookie
res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true}); //参数:名称,值,{配置信息}
参数说明:
	domain: 域名 
	name=value:键值对,可以设置要保存的 Key/Value,注意这里的 name 不能和其他属性项的名字一样
 	Expires: 过期时间(秒),在设置的某个时间点后该 Cookie 就会失效
	maxAge: 最大失效时间(毫秒),设置在多少后失效 。
	secure: 值为true时,cookie在HTTP中是无效,在HTTPS中才有效 。
 	Path: 表示在哪个路由下可以访问到cookie。
 	httpOnly:微软对COOKIE 做的扩展。如果设置了“httpOnly”属性,则通过脚本无法读取到COOKIE信息
 	singed:是否签名cookie,设为true会对cookie签名,需要用 res.signedCookies而不是 res.cookies访问cookie;且被篡改的签名cookie会被服务器拒绝,会重置为它的原始值
// 获取cookie
req.cookie.name //得到'zhangsan'
------------------------多个二级域名共享cookie-------------------------
// 实现相应路由下多个二级路由的cookie共享
domain: "abc.com"  abc.com这个顶级域名下的二级域名都可以访问
------------------------cookie加密-------------------------------------
// 1.设置cookie时进行加密,设置 signed:true 获取是使用req.signedCookies.name
// 原理:signed设置为true后,底层会将cookie的值与“secret”进行hmac加密
// 2.直接对cookie值加密,使用node的crypto模块
*****************md5摘要算法(加签名,防篡改)***************
定义md5.js
const crypto=require('crypto');
module.exports={
    MD5_SUFFIX:'FIFJOSDSXMJVRO039292MKK3J5NO2J',   // 提高安全性的做法,加入随机字符串
    md5:function(str){    // str为需要加密的字符串,utf-8避免出现中文加密不一致情况    
        return crypto.createHash('md5').update(str,'utf-8').digest('hex');
    }
}
****************使用****************
const crypto =require('./md5')
const str = '123456';
console.log(crypto.md5(str+crypto.MD5_SUFFIX));

****************封装一个AES对称加密解密模块****************
/* 写在前面:
createCipheriv方法接受三个参数:
algorithm用于指定加密算法,如aes-128-ecb、aes-128-cbc等;
key是用于加密的密钥;
iv参数可选,用于指定加密时所用的向量
注意这里的密钥必须是8/16/32位,如果加密算法是128,则对应的密钥是16位,如果加密算法是256,则对应的密钥是32位
例如:ase-128-cbc 加密算法要求key和iv长度都为16
*/
const crypto = require('crypto');
const secretkey = Buffer.from('123456789abcdefg', 'utf8'); //设置唯一(公共)秘钥
const serectIv = Buffer.from('abcdefg123456789', 'utf8');  // 16位
// AES对称加密函数
function encrypt(data, key = secretkey, iv = serectIv) {
  const cipher = crypto.createCipheriv('aes-128-cbc', key, iv); //使用aes-128-cbc加密
  let enc = cipher.update(data, 'utf8', 'hex'); //编码方式从utf-8转为hex
  return (enc += cipher.final('hex'));
}
// AES对称解密函数
function decrypt(data, key = secretkey, iv = serectIv) {
  const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);//使用aes-128-cbc解密
  let dec = decipher.update(data, 'hex', 'utf8');//编码方式从hex转为utf-8
  return dec += decipher.final('utf8');
}
module.exports = { encrypt, decrypt }

调用的时候直接导入文件,然后encrypt(data)  decrypt(data)

f.session

npm i express-session
const session=require("express-session");
// 配置session中间件
app.use(session({
	secret: 'secret key', // 用来加密信息的参数
	resave: false,
	saveUninitialized: false,
	cookie: (
		'name',
		'value',
		{
		maxAge: 24 * 60 * 60 * 1000, //设置客户端cookie有效期限
		secure: false,
		resave:false
		}
	)
}));
session(options)的配置主要有
	name - cookie的名字(原属性名为 key)(默认:’connect.sid’)
	store - session存储实例
	secret - 用它来对session cookie签名,防止篡改
	genid - 生成新sessionID的函数 (默认使用uid2库)
	rolling - 在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
	resave - 强制保存session即使它并没有变化 (默认: true, 建议设为:false)
	proxy - 当设置了secure cookies(通过”x-forwarded-proto” header )时信任反向代理。当设定为true时,”x-forwarded-proto” header 将被使用。当设定为false时,所有headers将被忽略。当该属性没有被设定时,将使用Express的trust proxy。
	saveUninitialized - 强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。在设定一个cookie前,对于登陆验证减轻服务端存储压力,权限控制是有帮助的(默认:true)
    unset - 控制req.session是否取消(例如通过 delete,或者将它的值设置为null)。这可以使session保持存储状态但忽略修改或删除的请求(默认:keep)

//设置session
app.use('/login',function(req,res){
    req.session.userinfo.name='lisi';
    res.send("登陆成功!");
});
//获取session
app.use('/',function(req,res){
    if(req.session.userinfo){
        res.send('welcome' + req.session.userinfo.name + '!');
    }else{
        res.send("请登陆");
    }
});
//重新设置cookie的过期时间 单位ms
req.session.cookie.maxAge=1000;	
// 删除session
app.use('/logout',function(req,res){
	req.session.destroy(function () {
		res.clearCookie('connect.sid');// 删除cookie
		res.redirect('/admin/login');// 重定向到用户登录页面
		req.app.locals.userInfo = null;// 清除公共信息
	}
});
------------------------引申 express-mysql-session----------------------
// 将session上传存储到mysql数据库
const express=require("express");
const mysql=require("mysql");
const cors=require("cors");   // 处理跨域
const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);
const options = {  
  	host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'root',
    database: 'session_store' 
 }
const sessionConnection = mysql.createConnection(options); // 数据库连接
const sessionStore = new MySQLStore({  
  	expiration: 1000*60*60*3,
    createDatabaseTable: true,  // 创建表
    schema: {  // 表规则
        tableName: 'session',   // 表名
        columnNames: {      // 列
            session_id: 'session_id',
            expires: 'expires',
            data: 'data'
        }
    }
},sessionConnection);
const app = express();
app.use(session({
    key: 'sessione_name', // 自行设置的签名
    secret: '123456',      //密匙
    store: sessionStore,    //存储管理器***
    resave: false,
    saveUninitialized: false,
    cookie:('name', 'value',{ maxAge: 12*60*1000,
                              secure: false,
                              name: "seName",
                              resave: false}))
}));
app.use(cors());
.....后面存取session是一样的操作

扩展1:express中的模板引擎(了解)

ejs https://ejs.bootcss.com/

express-art-template

需要同时下载 art-template express-art-template

使用方式

// 当渲染后缀名为art的模板文件时,使用express-art-template模板引擎
app.engine('art',require('express-art-template'))
// 设置模板存放目录
app.set('views',path.join(__dirname,'views'); //第一个参数固定,views
// 设置默认模板后缀,自动帮我们拼接
app.set('view engine','art');

// 配置完之后的好处:使用express提供的rend()方法
app.get('/',(req,res)=>{
  res.rend('index')  // 渲染模板
})

app.locals对象

  • 将变量设置到app.locals对象下,所有的模板中都可以获取到,公共数据存取很方便

    // app.locals对象下添加一个自定义users属性,属性的值可以自己任意设置
    app.locals.users = [
    {name:‘张三’,age:18}
    {name:‘李思’,age:40}
    ]
    // 模板中使用时,可以直接写users拿到 app.locals对象下添加的users属性值
    {{each users}}

  • {{KaTeX parse error: Expected 'EOF', got '}' at position 11: value.name}̲}---{{value.age}}

  • {{/each}}

    res.app.locals 通过res也可以设置和获取app.locals对象

扩展2:Express脚手架

npm i express-generator -g
// 创建一个名称为app的express应用并使用ejs模板引擎
express --view=ejs app
目录结构基本大同小异

扩展3:项目技能

1.密码加密bcrypt

哈希加密,单程加密方式,加入随机字符串增加密码被破解的难度
bcrypt依赖环境 1.Python2.x 2.node-gyp (-g) 3.windows-build-tools (--global --production)
// 导入bcrypt
const bcrypt = require('bcrypt');
// 返回生成的随机字符串
const salt = await bcrypt.genSalt(10);
// 对密码进行加密,返回值是加密后的密码,参数是密码和随机字符串
const result = await bcrypt.hash('123456', salt);
// 密码比对 返回值true或false
let isValid = await bcrypt.compare(明文密码, 加密密码); 

2.登录拦截

// 写在需要拦截的路由之前或者所有路由之前,搭配session使用
// 拦截请求 判断用户登录状态
app.use('/admin', require('./middleware/guard'));

const guard = (req, res, next) => {
	if (req.url != '/login' && !req.session.username) {
		res.redirect('/admin/login');
	} else {
		// 如果用户是登录状态 并且是一个普通用户
		if (req.session.role == 'normal') {
			return res.redirect('/home/')
		}
		// 用户是登录状态 将请求放行
		next();
	}
}

3.Joi验证器,node服务端好用的验证工具

// 定义时写在model的构造函数js文件中
// 引入joi模块
const Joi = require('joi');
// 验证器
const validateUser = user => {
// 定义对象的验证规则
const schema = {
username: Joi.string().min(2).max(12).required().error(new Error('用户名不符合验证规则')),
email: Joi.string().email().required().error(new Error('邮箱格式不符合要求')),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required().error(new Error('密码格式不符合要求')),
role: Joi.string().valid('normal', 'admin').required().error(new Error('角色值非法')),
state: Joi.number().valid(0, 1).required().error(new Error('状态值非法'))
};
// 实施验证
return Joi.validate(user, schema);
}

// 使用时  引入集合构造函数和验证器
const { User, validateUser } = require('../../model/user');
// 使用验证
try {
	await validateUser(req.body)
}catch (e) {
	// 验证没有通过
	return next(JSON.stringify({path: '/admin/user-edit', message: e.message}))
}

4.数据分页

  • 分页功能核心要素:当前页,总页数(向上取整)

    和mongoose/mongodb配合的示例

    let current = req.body.current || 1 // 当前页码
    let pagesize = req.body.pagesize //每页条数
    let count = await User.countDocuments({查询条件}) // 查询数据总数
    let total = Math.ceil(count / pagesize) // 计算总页数
    // 数据开始查询位置=(current-1)*pagesize
    let start = (current-1)*pagesize
    User.find({查询条件}).limit(pagesize).skip(start)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值