做有态度的前端团队

网易FEG前端团队

关于使用Koa2的点点滴滴

Koa是基于Nodejs的一个后端框架,算是比较常用的,核心思想就是中间件,Koa实现底层逻辑,其余的就需要自己实现,包括:session、数据库操作、文件上传、路由、模板、静态资源访问等

前言

一直以来都是使用thinkjs这个框架,集成很多功能,基本上就只需要负责写逻辑就OK了,所以想尝试一下Koa这种需要动手逐个实现功能的,类似也有express

这里并不打算写新手教程,可自行百度,只是写使用过程中遇到的一些疑惑

下面代码基于ES6、7

1、安装

官网推荐使用Nodejs >= 7.6的版本,小于这个版本需要采用babel方式曲线救国

实际使用发现,用了babel方式,可以解决koa本身的兼容问题,但koa的部分插件还是有问题,所以还是别采用这种方式了

2、配置文件

很多时候,我们需要有本地配置、测试配置、正式配置,如果每次发布都要注释掉其它两个环境的配置,就很容易出错

可以结合package.json中配置脚本命令,启动时候带不同参数来区分

  "scripts": {
    "start": "set NODE_ENV=test && node ./app.js",
    "dev": "set NODE_ENV=dev && node ./app.js",
    "release": "set NODE_ENV=release && node ./app.js"
  }

思路为设置环境变量NODE_ENV的值,然后启动项目,上面对应的命令为:npm run (scripts的key)

然后在代码中根据环境变量的值,引用对应的配置文件

    //根据不同的NODE_ENV,输出不同的配置对象
    const env = process.env.NODE_ENV.replace(/\s+/,"");

    let config = require('./'+(env||"test"));

3、登陆态(上)

Koa本身是没有实现session这块,需要用插件才能实现

可以使用插件koa-session2,默认使用它,会将session内容保存在内存中,这样会有一个坏处,开发的时候需要经常重启服务,内存就被清空,意味着你需要重新登陆一次,所以一般不会存在内存中

  • 本地与测试环境:采用文件形式保存
  • 正式环境:采用数据库或者缓存方式保存,考虑到多台机器部署

koa-session2并没有提供上述的保存方式,只提供了参数可选,具体方式需要自行实现,虽然也有对应的插件,不过使用时候发现并不好使,还是自己实现,其实很简单,覆盖三个函数即可:

    //插件提供的基类
    const { Store } = require("koa-session2");
    //自定义一个类继承它
    class FileStore extends Store {
        //构造函数,照抄即可
        constructor() {
            super();
        }
        //读取session内容的方法,sid为每个session的key,一串加密串,读取出session内容,返回
        async get(sid, ctx) {}
        //设置session内容的方法,需要将session对象的内容保存起来
        async set(session, { sid =  this.getID(24)} = {}, ctx) { }
        //session过期或者删除时候,需要如何清除
        async destroy(sid, ctx) {}
    }

实现完之后,需要配置session的保存参数store为上面实现的new FileStore(),这样就可以在所有的controller中读取和设置属性ctx.session

4、登陆态(下)

登陆态一般会在登陆成功后、注册完进行设置,那如何判断是否登陆了?然后没有登陆跳转到登陆页面;最简单就是在每个controller的action中判断 ctx.session是否为空,但每个都写,很头疼

常见的方式是:自定义一个中间件,这样用户的每一个请求都会经过它,由它去判断

    let loginMiddle = async (ctx, next) => {
    
        //获取用户请求的URL
        let url = ctx.request.url;
    
        //login路径为不需要登陆态,因为它是登陆方法,执行next则跳到controller中
        if(url.indexOf("/login") > -1)return await next();
        //假设登陆态是设置了user属性
        let user = ctx.session.user;
    
        //没有登陆态
        if(!user){
            //跳转到登陆页
            return ctx.response.redirect('/login');
        }
        //正常执行controller
        await next();
    };

只需要使用koa的时候,加载这个中间件就可以,需要注意:加载顺序需要在路由之前

登陆态是在用户浏览器设置一个加密的sessionid的cookie,而页面的地址与后台地址不同域名的话,则会出现种植失败,需要添加跨域允许种cookie才行,使用插件koa-cors,使用时候,设置参数credentials : true即可

5、防止CSRF漏洞

CSRF算是比较普遍的漏洞了,koa也有插件可以实现,不过使用起来不那么方便,直接将token种在页面中,对于后台只实现接口,没有页面渲染的不友好,所以可以自行实现个简单版的

在登录成功的action中,设置一个用于校验的token的cookie

    //定义用户的token值,这里简单用时间戳,应该是用加密的md5值
    let token = new Date() - 0;
    //将token设置到session中,用于未来校验
    ctx.session._csrf = token;
    //将token种在用户的cookie中
    ctx.cookies.set(
        '_csrf',token,{
            path:'/',
            httpOnly:false,  // 是否只用于http请求中获取
            overwrite:true  // 是否允许重写
        }
    );

可以在上面的第四步中的登陆态中间件加入校验,这样就不需要每个action都去校验一次

    let loginMiddle = async (ctx, next) => {
        
        //此处省略判断session登陆态
        //...

        //获取请求中的token
        let csrfToken = ctx.request.method.toLowerCase() == "get" ? ctx.request.query["_csrf"] : ctx.request.body["_csrf"];
        //没有传,提示token错误
        if(!csrfToken)return ctx.body = "token错误";
        //与session中的token对比
        if(csrfToken != ctx.session._csrf) return ctx.body = "token错误";

        //正常执行controller
        await next();
    };

6、jsonp

koa有很多jsonp的插件,大部分是直接全局替换,即返回的内容直接就是jsonp方式,连普通的返回都没了

使用koa-response-jsonp插件,可以按需要自行使用jsonp方式,在controller的action中,使用ctx.jsonp(object),请求参数固定为callback=xxx

PS:当然你可以自行实现这个中间件,需要校验参数中的一些合法字符即可

7、数据校验

很多时候后台需要校验前端传送过来的数据,校验是否合法,可以避免后续操作很多问题,写在action中的话,会显得有些长,而action中更应该专注于逻辑

数据校验可以使用parameter插件,内置了基本的规则,比如:数字、字符串、邮箱等

    //demo,简单的三条规则
    mobile : {type : 'id',required:false}
    email : {type : 'email',required:false}
    nickname : {type : 'string',required: true,min:1}

校验可以写在router路由中,比如下面这个,在调用具体action前,先校验数据

    router.get('/get_order', async (ctx)=>{
        //校验规则
        let rule = {
            openid : {type : 'string',required: false,min:6,allowEmpty:true},
            code : {type : 'string',required:true,min:6}
        };
        //校验数据
        let ret = validateUtil.validate(rule,ctx.request.query);
        //校验失败,返回错误信息,不执行具体action
        if(ret.status == false)return ctx.body = ret;
        //校验完毕,执行后续操作
        await controller.get_order(ctx);
    });

8、设置404

很奇怪,koa是连这个也没有,需要自己去实现,不麻烦,就是所有的路由规则都没有命中,就是404了

    //设定404页面,即所有路由都没命中
    router.get('*', async (ctx, next) => {
        ctx.status = 404;
        await ctx.render('error/404');
    });

9、日志

一般都用Log4js来做日志记录操作,本地开发的时候还好好的,直到用上了pm2来启动,以及采用了多线程方式,日志就哑火了,一个都没记录了

Log4js的配置,需要加上这个:

  • "pm2": true
  • "pm2InstanceVar": 'INSTANCE_ID'

以及需要再pm2.json文件加上:

  • "instances": 0
  • "instance_var": "INSTANCE_ID"

10、其余常用的插件

  • koa-bodyparser:主要用于获取post请求的参数
  • koa-helmet:安全插件,防止SQL注入与攻击等
  • koa-router:路由插件
  • koa-static:静态资源处理,用户显示css、img、js等
  • koa-views:模板渲染,需要搭配ejs插件使用
  • axios:请求第三方接口的插件

后话

Koa很多事情都需要自行去处理解决,虽然有插件能帮忙完成,但根据自身需要,还是需要手动去实现一些功能,总体来说,起步上手,会比什么都搭好的thinkjs要麻烦,但也是因为这样,可以只保留自己业务需要的功能,其余不需要就可以放弃掉,性能上会有所提升。

手机阅读请扫描下方二维码:

已有 2 条评论

  1. Eason

    ali-53.gif

  2. rosen

    ali-53.gif

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif