60

关于使用Koa2的点点滴滴

 5 years ago
source link: http://feg.netease.com/archives/684.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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要麻烦,但也是因为这样,可以只保留自己业务需要的功能,其余不需要就可以放弃掉,性能上会有所提升。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK