【Node】之那些社区常用框架:express、koa、egg 该怎么上手?

3,315 阅读9分钟

写这篇文章是由于我之前有接触过一段时间的Node,之前做前端一直是在用别人的接口,当试着自己写接口,从另一个角度看待前端和后端后,感觉蛮有意思。最近离职了,时间比较充裕就把这三个框架都过一遍,毕竟都说前端的尽头是全栈,哈哈哈,整个学习过程,让我对于服务器、浏览器的理解加深了,也便于自己对项目的全局理解。

本篇文章不过多介绍关于这几个框架的理论知识,我一贯认为:能上手写尽量不啰嗦,关于你想了解更多,这里奉献上几个框架的官网地址expresskoaegg

如果你想了解原生node的一些使用操作,请点击这里

一、Node 框架之 express

上手简单,自带路由,新手可以使用express找感觉

下载:

npm i express -D

下载完成后,我们来看看具体的使用:

1. 自带路由

const express = require('express');

let server = express();
server.listen("8080");

// send 方法用来向服务器发送内容
server.get('/a', (req, res, next) => {
  res.send('qq');
});

启动服务:

 node server.js

浏览器端:

WeChate73299709ad8810167775314b0c4a598.png

2. 同一个请求可以进行多次处理 next()

需要手动调用next

const express = require('express');

let server = express();
server.listen("8080");

// 自带路由
server.get('/a', (req, res, next) => {
  console.log('a');
  // 根据情况,手动调用 next 即可向后继续调用
  next();
});
server.get('/a', (req,res,next) => {
  console.log("b");
})

启动服务,控制台正常打印:

WeChatabeaf3cd82f7f4e1f684f719020fc909.png


3. 支持 get 、post 、 use的请求

支持 get post 的请求,如果不确定什么请求,可以用use

get('url',fn);

post('url',fn);

use('url',fn);

// 另一种传参方式,会拦截所有的请求,一起进行处理

get(fn);

post(fn);

use(fn);

4. 处理文件

在处理数据时,大部分需要使用中间件,这个就做个简述,中间件:顾名思义就是帮我们处理一些小的操作或者数据解析之类的小玩意,在后面我们用到时候会具体介绍。

1.html 文件

WeChat3a613b769d477a603e73a49a4cc1c69e.png

server.js

const express = require('express');

let server = express();
server.listen("8080");

// 自带路由
server.get('/a', (req, res, next) => {
  res.send('aaa');
});
server.get('/b', (req,res,next) => {
   res.send('bbb');
})

// 处理文件
server.use(express.static('./static/'));

启动服务器,查看资源已经被拿到了:

WeChat0442df9c9f6ee8f3e345b22f9c33fe5c.png


get 请求的数据处理

const express = require('express');

let server = express();
server.listen("8080");


server.get('/a', (req, res, next) => {
  console.log(req.query);
  res.send('aaa');
});

启动服务器,打开浏览器端:

WeChate3fd90e451674a77e9b31e54385b106c.png

打开控制台,请求的数据已经被解析:

WeChat8199c7bcdabba58790db4b2c6528e3d3.png


post 请求处理数据

下载中间件:

npm i body-parser -D

示例表单:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form action="http://localhost:8080/reg" method="post">
    <input type="text" name='user' value="">
    <input type="password" name='pass' value="">
    <input type="submit" name='' value="注册">

  </form>
</body>
</html>

处理js:

const express = require('express');
const body = require('body-parser');

let server = express();
server.listen("8080");

server.use(body.urlencoded())

server.post('/reg', (req, res) => {
  console.log(req.body);
});

启动服务,打开浏览器输入信息:

WeChat865915e40ca036be7e8165f384e687da.png

打开控制台,数据已经被解析:

WeChat14a943f3c3474078c902787563b5265e.png


模拟实现body-parser

如果不考虑文件上传,我们来模拟一下这个body-parser,看一下它具体的实现原理是怎样的:

const express = require('express');
const querstring = require('querystringify');

let server = express();
server.listen("8080");

server.use((req, res, next) => {
  let arr = [];
  req.on('data', buffer => {
    arr.push(buffer);
  })

  req.on('end', () => {
    let post =querstring.parse(Buffer.concat(arr).toString());
    req.body = post;
    next();
  })
});

server.post('/reg', (req, res) => {
  console.log(req.body);
});

页面仍然使用这个提交页面:

WeChat28123a47ba7ae93f81a3b59d10ad909b.png

查看控制台的解析,成功!!!:

WeChata0eb35579ff29d6b74cee20c2c6a161a.png


再进一步封装:

body-parser.js

const querstring = require('querystringify');
module.exports = {
  urlencoded() {
    return (req, res, next) => {
      let arr = [];
      req.on('data', buffer => {
        arr.push(buffer);
      })

      req.on('end', () => {
        let post = querstring.parse(Buffer.concat(arr).toString());
        req.body = post;
        next();
      })
    };
  }
}

使用页面:

const express = require('express');
const body = require('./static/body-parser');

let server = express();
server.listen("8080");

server.use(body.urlencoded());

server.post('/reg', (req, res) => {
  console.log(req.body);
});

启动服务器测试,效果一样,封装成功!:

WeChataef0a773163c16d23dbd6d0bd949b39c.png


处理文件上传 multer

npm i multer  -D

1.html 上传页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form action="http://localhost:8080/reg" method="post" enctype="multipart/form-data">

    <input type="file" name='f1' value="">
    <input type="submit" name='' value="上传">

  </form>
</body>
</html>

处理数据:

const express = require('express');
const multer = require('multer');

let server = express();
server.listen("8080");

// 找一个位置来存上传后的文件
let obj = multer({ dest: './static/upload' })

// 这里可以限制文件类型、大小
server.use(obj.any());

server.post('/reg', (req, res) => {
  console.log(req.files);
  res.end('upload successed');
});

启动服务,浏览器上传文件:

WeChat2aeb5ab38539263890528d66c53f0dd6.png

查看控制台输出:

WeChat4f3591f1c1c8c163c468e4f9832ec141.png

浏览器处理完的成功提示:

WeChat650275be9ee36a362975ef560774504f.png

查看上传完成的文件:

WeChat6da51c5b4eeed32c1857b6618f61c421.png


cookie 不跨域的

子域名可以访问主域名

只能向上,不能向下

domain:


5. 处理cookie

先去下载一个中间件:

cnpm i cookie-parser -D

普通的存取cookie:

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

let server = express();
server.listen("8080");

server.use(cookieParser());

server.get('/a', (req, res) => {
  // 存储cookie,可根据情况配置参数
  res.cookie('age', 18, {
    // domain:'a.com',
    //path:'/',
    maxAge:14*86400*1000
  });

  // 读取cookie
  console.log(req.cookies);
  res.send('ok');
})

重启服务,我们在浏览器看到已经存好了,且过期时间也设置上了:

WeChat66c057eb3b833f7678b4862180a65ddd.png

控制台看一下获取cookie:

WeChat9b36c8e533cc438e85b3c57aa97ee9e6.png


带签名的cookie

我们知道cookie 是非常不安全的,在浏览器里是可以像这样手动随意修改的,那么有没有一个办法去限制修改呢,那就要说签名这个话题了。

WeChatee10f31f35ac2fa784c421c4090ee701.png

如何来设置签名:

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

let server = express();
server.listen("8080");

// welmxxfoiweorkpwk9834534nsd8j8u 这个签名是你自己编写的,别人不知道的
server.use(cookieParser(
  "welmxxfoiweorkpwk9834534nsd8j8u"
));

server.get('/a', (req, res) => {
  // 存储cookie,可根据情况配置参数
  res.cookie('age', 18, {
    maxAge: 14 * 86400 * 1000,
    // httpOnly:true,  // 只能服务器修改,前台修改失效
    // secure: true,   // 只认https的请求
    signed:true     // 是否是签名的
  });

  // 读取cookie
  console.log("普通的cookie",req.cookies);
  console.log("签名的cookie",req.signedCookies);
  res.send('ok');
})

看一下浏览器端:

WeChatcfec971f1da3c5c73542216097f6ef44.png

我们看到我们的值被存储为一堆我们不认识的编码,那么它到底是什么?

s%3A18.Vbc4AX1PPW%2FeDBZexBICvp3lNB9Xx8qpE5Gm9UROIu0

我们来解析一下:

s代表我们的值是签名过的

通过 decodeURIComponent('%3A') ,'%3A 解析出来是 : 冒号

Vbc4AX1PPW%2FeDBZexBICvp3lNB9Xx8qpE5Gm9UROIu0 这是使用我们之前给服务器的welmxxfoiweorkpwk9834534nsd8j8u 这个编码又重新编译的值。


此时我们再看下控制台输出的结果,也是可以正常打印出来的:

WeChat2adc1bd18dbc1565a247a1813e1b334e.png

我们再去浏览器修改一下,重新刷新浏览器看到值被还原了,也就是没有修改成功,且控制台提示我们这个值不被认可:

WeChat75866b52189729ec86d4ae7498e11186.png

此时我们的目的已经达到了,签名也在一定程度上提高了cookie 的安全性。


6.处理session

session一般用来放用户登录信息,具体的信息存储在服务器,是不加密的:

const express = require('express');
const cookieSession= require('cookie-session');

let server = express();
server.listen("8080");

server.use(cookieSession({
  keys: ['sdfgherwr23sds', 'oi884jxkcoifpgd', '2kcoer043kclcu'],  // 循环密钥用于生成签名
  maxAge:20*60*10000 // 一般设置20分钟
}))

server.get('/a', (req, res) => {
  if (!req.session['view']) {
    req.session['view'] = 1;
  } else {
    req.session['view']++;
  }
  console.log(req.session['view']);
  res.send(`欢迎你第${req.session['view']}次访问!`);
})

启动服务器,看到浏览器已经把我的session 存上了,且我们发现除了session ,还多了一个session.sig,这个就是使用我们的循环密钥生成的签名。

WeChata9487b9d6d86972ddf1f3acee097f947.png

我们尝试来存一些敏感数据:

const express = require('express');
const cookieSession= require('cookie-session');

let server = express();
server.listen("8080");

server.use(cookieSession({
  keys: ['sdfgherwr23sds', 'oi884jxkcoifpgd', '2kcoer043kclcu'],  // 循环密钥
  maxAge:20*60*10000 // 一般设置20分钟
}))

server.get('/a', (req, res) => {
  if (!req.session['view']) {
    req.session['view'] = 1;
  } else {
    req.session['view']++;
  }
  req.session['amount'] = 199;
  console.log(req.session['view']);
  res.send(`欢迎你第${req.session['view']}次访问!`);
})

我们看到前台并不会新增字段,因为它只会存储一个sessionid,用来识别身份,所以比较安全:

WeChat49c3b02549111734f911facda3b4be1c.png

express 就介绍到这里,下面我们来看一下express 的升级版 koa


二、 Node框架之Koa

koaexpress 同一个团队开发的,本身区别不是很大,思想有些区别,然后就是写法上面的不同 koa@v1 / v2/ v3

express 基于回调的思想

koa: 利用promise 的思想

    v1: generator
    v2: 过渡版本,generator && async / await
    v3: async / await

下载(koa 不带路由,所以需要再下载一个路由):

cnpm i koa -D

cnpm i koa-router -D  

来个示例:

const koa = require('koa');
const Router = require('koa-router');

let server = new koa();
server.listen(8080);

// 路由需要自己new
let router = new Router();
//ctx 里包含 req & res
router.get('/a', async (ctx, next) => {
  ctx.body = 'qqq';
});

// 将路由添加到 koa 上
server.use(router.routes());

启动服务器,看一下浏览器是否已经有内容:

截屏2023-01-09 15.48.01.png


嵌套路由

我们在上面知道koa 的路由是需要单独下载的,那么把路由抽离出来的目的其实也是为了方便路由在项目中的使用,这一部分就来分析一下多层嵌套路由的使用:

const koa = require('koa');
const Router = require('koa-router');

let server = new koa();
server.listen(8080);

//主路由
let router = new Router();

// cart
let cartRouter = new Router();
cartRouter.get('/b', async ctx => {
   ctx.body = 'cart的b';
})
// 一级路由
router.use('/cart', cartRouter.routes());


// user
let userRouter = new Router();
userRouter.get('/', async ctx => {
  ctx.body = 'user根目录';
});
// 一级路由
router.use('/user', userRouter.routes());


//user - company
let company = new Router();
company.get('/a', async ctx => {
  ctx.body = '企业的a';
});
// 二级路由
userRouter.use('/company',company.routes());


//user - admin
let admin = new Router();
admin.get('/a', async ctx => {
  ctx.body = '管理员的a';
});
// 二级路由
userRouter.use('/admin',admin.routes());


// 将路由添加到koa
server.use(router.routes());

下面就是我们示例中嵌套层级,如果是实际开放项目中,路由可以单独抽离一个文件来管理,看起来就不会这么凌乱:

一级路由 二级路由 请求接口 最终路由
user company a user/company/a
admin a user/admin/a
cart b cart/b

路由带参

const Koa = require('koa');
const Router = require('koa-router');

let server = new Koa();
server.listen(8080);

let router = new Router();

router.get('/news/:id', async ctx => {
  let { id } = ctx.params;
  ctx.body = '新闻' + id;

})

router.get('/news/:id/:id2/:id3', async ctx => {
  let { id, id2, id3 } = ctx.params;
  ctx.body = `新闻 ${id}-${id2}-${id3}`;
})

// 将路由添加到koa
server.use(router.routes());

启动服务,查看浏览器如何传参:

当有一个参数时:

截屏2023-01-09 20.11.43.png

当有三个参数时:

截屏2023-01-09 20.11.56.png


当传参方式用 的形式,用query来取参:

const Koa = require('koa');
const Router = require('koa-router');

let server = new Koa();
server.listen(8080);

let router = new Router();

router.get('/news', async ctx => {
  let { id } = ctx.query;
  ctx.body = id;

})

server.use(router.routes());

截屏2023-01-09 20.20.11.png


server.context

相当于ctx 的proptype,在server.context 挂的参数是可以在ctx 上取的,适用于挂一些全局使用的内容

const Koa = require('koa');
const Router = require('koa-router');

let server = new Koa();
server.listen(8080);

server.context.qq = 232323;

let router = new Router();

router.get('/news', async ctx => {
  ctx.body = ctx.qq;

})

server.use(router.routes());

截屏2023-01-09 20.27.49.png


ctx.throw

用于抛错

...
router.get('/login',async ctx=>{

if(!ctx.query.user|| !ctx.query.pass){
    ctx.throw(400,'user and password is required')
}esle{
     // 正常的逻辑
}

})

截屏2023-01-09 20.53.00.png


ctx.assert

...
router.get('/login',async ctx=>{

 ctx.assert(ctx.query.user,400,'username is required');
 ctx.assert(ctx.query.pass,400,'password is required')

   // 正常的逻辑
})

ctx.state 状态码

...
router.get('/login',async ctx=>{
 ctx.state=404;
})

截屏2023-01-09 20.59.16.png


ctx.redirect 重定向

...
router.get('/login',async ctx=>{
 ctx.redirect='/news';
})

截屏2023-01-09 21.01.00.png


koa-static

访问静态资源

基本使用:

创建一个static 文件,存放静态资源;

截屏2023-01-10 12.20.41.png

当前有一个1.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>

</head>
<body>


  <div class='box'>
   gjktyurtxvfgerte34535645647
  </div>
</body>
</html>

server.js

const Koa = require('koa');
const Router = require('koa-router');
const static = require('koa-static');

let server = new Koa();
server.listen(8080);


server.use(static('./static'))

启动服务去请求静态资源,资源被成功拿到:

截屏2023-01-10 12.22.55.png

当然我们koa 的static 是可以设置缓存时间的,在一定程度上减轻服务器的压力,下面我们一起来试试吧:

maxage 设置缓存时间

server.use(static('./static', {
  maxage: 84600 * 1000 * 10,
}));

image.png


index 设置默认文件

server.use(static('./static', {
  index:'1.html'
}));

WeChat076fbe222fcae998a4828bb644fea77e.png


可根据文件类型设置不同缓存时间:

const Koa = require('koa');
const Router = require('koa-router');
const static = require('koa-static');

let server = new Koa();
server.listen(8080);


let staticRouter = new Router();

staticRouter.all(/(\.jpg|\.png|\.gif)$/i, static('./static', {
 maxage: 86400 * 1000 * 60
}));

staticRouter.all(/(\.css)$/i, static('./static', {
 maxage: 86400 * 1000 * 20
}));

staticRouter.all(/(\.html|\.hml|\.shtml)$/i, static('./static', {
 maxage: 86400 * 1000 * 10
}));

staticRouter.all('',static('./static', {
 maxage: 86400 * 1000 * 30
}));

server.use(staticRouter.routes());

WeChat3b4d83d411d457029a8c774c3d36d690.png

WeChat2e2b1173a24de70ff0840bdba4d0d003.png


cookie

koa 是自带cookie的,所以我们就可以直接用

设置cookie:

const Koa = require('koa');


let server = new Koa();
server.listen(8080);

server.keys = ['2323rfdf3434', '23sds32rfex', 'sd4ds4434'];

server.use(async ctx => {
 ctx.cookies.set('user', '222s', {
   maxAge: 86400 * 1000 * 7,
   signed:true
 });
 ctx.body = '232';

})

image.png

获取cookie:

const Koa = require('koa');

let server = new Koa();
server.listen(8080);

server.keys = ['2323rfdf3434', '23sds32rfex', 'sd4ds4434'];

server.use(async ctx => {
ctx.cookies.get('user', {
  signed:true
});
ctx.body = '232';

})

截屏2023-01-10 15.50.47.png

koa-session

const Koa = require('koa');
const session = require('koa-session');

let server = new Koa();
server.listen(8080);

server.keys = ['2323rfdf3434', '23sds32rfex', 'sd4ds4434'];

server.use(session({
 maxAge: 86400 * 1000, // 有效期
 renew: true // 自动续期
},server));

server.use(async ctx => {
 if (!ctx.session['view']) {
   ctx.session['view'] = 0;
 }
 ctx.session['view']++;
 ctx.body = `欢迎你第${ctx.session.view}次来访`;
})

WeChate0804bbc3ce8acce20a51828f955a851.png


三、Node框架之Egg

Egg.js是阿里旗下的一个基于nodejskoa2的企业级应用框架,基于es6es7 和nodejs。之前项目有用到egg 来搭建一个简单的服务,下面就来分享一下我的使用感受。

特性

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。而 Egg 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强。

所以吧,大概就是这么个关系:

1636468970239.jpg

----施主别走,加个关注,我还在持续补充中

Egg.js官网

Egg.js文档


可能会有人问了:现在egg 都已经可以替代 koaexpress了,那为啥还要学旧的呢?

我其实是想说,框架出现的目的,是使我们可以更便捷的写代码,去实现业务的需求,所以学习哪个框架他们是不关心的,对我们个人来说,当你知道一个框架的演变过程后,你会对它有更深的理解,不同框架对于同一问题处理方式不同,不同的处理方式之间又有哪些差别,是思维的扩展,是解决问题的方式的延伸。