koa2笔记

2017-1-8    分类: koa

之前一直使用express觉得还可以,不久express团队又出了一个koa,也出现了1,2版本,目前学习最好使用koa2,使用了es6语法。直接上干货,使用过express上手搭建一个项目其实很简单。

首先:

npm install koa-generator -g  //全局安装这个就可以使用koa命令了

在你需要创建koa项目的目录下输入以下命令

koa2 -e koa2   //此时就会出现一个koa2的目录,并且里面会有一些初始化的文件
如果想使用koa1的话就
koa -e 目录名称

进入到koa2目录下,接着安装需要的依赖模块

npm install

启动koa

node ./bin/www

最后打开浏览器输入:http://localhost:3000/ ,就可以看到我们网站建立了。

提示:如果简单使用node命令启动koa 2报错可以尝试使用

node --harmony xx.js

来启动我们的koa项目,因为koa有时候会需要es5的一些属性,带有--harmony参数才能够启用部分es5的功能。

要么升级node.js环境到7.6以上

--------------------------------------

--------------------------------------

 

创建一个页面

创建首页http://127.0.0.1/
 router.get('/', function (ctx, next) { //首页
     ctx.body = 'Hello World!';
 })

推荐此方法创建页面:
创建内页并且带2个参数 http://127.0.0.1/www/27/28

 router.get('www/:id/:age', async function (ctx, next) {
   ctx.state = {
      title:'ffff'+ctx.params.id
   };
   await ctx.render('index', {
   });
 })

 

 get传参

//page1页面传参
router.get('page1/:id/:age', async function (ctx, next) {
   ctx.state = {
      title:'ffff'+ctx.params.id
   };
   await ctx.render('index', {
   });
})

地址:http://xxxx.com/page1/参数1/参数2

 

//首页传参
router.get(':id/:age', async function (ctx, next) {
   ctx.state = {
      title:'ffff'+ctx.params.id
   };
   await ctx.render('index', {
   });
})

地址:http://xxxx.com/参数1/参数2

 

 

连缀

 router //这里是4个路由
 .get('/', function (ctx, next) {
    ctx.body = 'Hello World!';
 })
 .post('/users', function (ctx, next) {
    // ...
 })
 .put('/users/:id', function (ctx, next) {
    // ...
 })
 .del('/users/:id', function (ctx, next) {
    // ...
 });

 

前缀

 const router = new Router({
    prefix: '/users'
 });

 router.get('/', ...); //实际的响应路径是:/users
 router.get('/:id', ...); //实际的响应路径是:/users/:id

 

 

为一个路由命名

router.get('home',':haha/:vv', function (ctx, next) { //为首页的路由命名为home
 ctx.body = 'Hello World!';
 router.url('home', 3,'age') //第一次参数填写路由名称,第二个参数开始填写自定义参数,输出:/3/age
})

 

路由使用中间件

 router.use(session(), authorize()); //所有路由都使用这两个中间件
 router.use('/users', userAuth()); //只有跳转到/users页面时使用 这个中间件
 router.use(['/users', '/admin'], userAuth()); //只有这两个页面可以使用
 app.use(router.routes()); //上面是设置,此步骤是启动

 

页面跳转

 ctx.redirect('http://localhost:3000/www/123/456')

 

 

路由参数验证

要访问该地址之前http://127.0.0.1/www/参数1/参数2,先验证参数1,2,在决定是否在跳转

router
 .param('ff', function (ff, ctx, next) {  //参数1的验证逻辑
      console.log(ff+'<----------------');
      return next();
 })
 .param('vv', function (vv, ctx, next) {  //参数2的验证逻辑
      console.log(vv+'<----------------');
      if (!vv) return ctx.status = 404;
      return next();
 })
 .get('www/:ff/:vv', async function (ctx, next) {
   // ctx.state = {
     // title:'shouye1111'
   // };

     await ctx.render('index', {
       title:'shouye1111'+ctx.params.ff
     });
})

 

一个路由业务逻辑分离成多个路由

假如我们在写后台的业务逻辑时,往往需要很多判断,我们不可能把所有代码都写进一个路由中,时间长了代码不容易维护,这时候我们需要把后台的业务逻辑同时分离归类成多个文件,编辑文件app.js

const admin = require('./routes/admin/admin_login'); //后台登陆页面
const admin_default = require('./routes/admin/admin_default'); //后台登陆页面后的业务逻辑
.
.
.
router.use('/admin', admin.routes(), admin.allowedMethods());
router.use('/admin', admin_default.routes(), admin_default.allowedMethods());

 

------------以上是路由的使用--------------------
post和get

后台获取客户端get发来的数据
ctx.params     //获取到的是以路由 router.get('/:name') 形式   ,http://xxx/123

ctx.query   //获取到的是url这样传递的数据,http://xxx/1.php?wo='chen'&age='123'

 

后台获取客户端post发来的数据
ctx.request.body

 

cookies的设置和获取
ctx.cookies.set('name', 'value', {httpOnly:false}) //设置 {httpOnly:false},表示可以通过客户端获取cookies的值
signed: 是否要做签名
expires: cookie 有效期时间
path: cookie 的路径,默认为 /'
domain: cookie 的域
secure: false 表示 cookie 通过 HTTP 协议发送,true 表示 cookie 通过 HTTPS 发送。
httpOnly: true 表示 cookie 只能通过 HTTP 协议发送

例如:

ctx.cookies.set(
 'state',
 'hello world',
 {
   domain: 'localhost', // 写cookie所在的域名
   path: '/', // 写cookie所在的路径
   maxAge: 10 * 60 * 1000, // cookie有效时长
   expires: new Date('2017-07-15'), // cookie失效时间
   httpOnly: false, // 是否只用于http请求中获取
   overwrite: false // 是否允许重写
 }
)

 

ctx.cookies.get(name, [options]) //获取

 

session使用

 npm install koa2-cookie-session --save
import session from "koa2-cookie-session"; //一定要用这种方式加载
var router = require('koa-router')();
router.use(session({
  key: "SESSIONID", //default "koa:sid"
  expires:3, //default 7
  path:"/" //default "/"
}));

router.get('home',':haha/:vv',async function (ctx, next) {
  ctx.session.user = {name: "myname更新"}; //设置
});

ctx.session.user //获取

 

生成log日志文件

下载:log.zip

1, 解压后把文件复制到根目录下

2,修改app.js

const logUtil = require('./utils/log_util');   //添加
.
.
.
// logger
app.use(async (ctx, next) => {
 //响应开始时间
  const start = new Date();
  //响应间隔时间
  var ms;
  try {
  //开始进入到下一个中间件
    await next();

    ms = new Date() - start;
 //记录响应日志
   logUtil.logResponse(ctx, ms);

  } catch (error) {

   ms = new Date() - start;
 //记录异常日志
   logUtil.logError(ctx, error, ms);
 }
});

3,安装log4js模块

npm install log4js --save

 

 

 自定义404页面
(是当用户乱输入url时,没有相应的路由匹配的时候才跳转404页面)
编辑文件:app.js

//编辑以下代码
app.use(async (ctx, next) => {
 if (ctx.status === 404){
    ctx.state={
       title:'404页面'
    };
    await ctx.render('error', {  //error为你的错误页面
    });
 }
});


module.exports = app;

 

接着在views/error.ejs,创建错误页面,即可。

 

 

时间戳

这里推荐使用一款模块插件

安装:

npm install moment

 

加载调用:

var moment = require('moment');
moment().format();

 

案例:

moment().format('YYYY-M-DD kk:mm:ss');  //2017-01-01 01:01:01

文档:http://momentjs.com/docs/#/displaying/

 

 

上传

安装:

npm install koa-body@2 --save

前端:创建页面views/upload.ejs

<!doctype html>
<html>
<body>
<form action="/upload" enctype="multipart/form-data" method="post">
 <input type="text" name="username" placeholder="username"><br>
 <input type="text" name="title" placeholder="title of file"><br>
 <input type="file" name="uploads" multiple="multiple"><br>
 <button type="submit">Upload</button>
</form>
</body>
</html>

 

后台:routes/index.js

router.get('/haha',async function (ctx,next) {
  await ctx.render('upload', {});
});




router.post('/upload',
  koaBody({
    multipart: true,
    formidable: {
      uploadDir: __dirname + '/uploads'
    }
  }),
  async function (ctx,next) {
     console.log(ctx.request.body.fields);
     // => {username: ""} - if empty

     console.log(ctx.request.body.files);
     //console.log( (((ctx.request.body.files)["uploads"])).path );
     // console.log( (((((ctx.request.body.files)["uploads"])).name).split('.'))[(((((ctx.request.body.files)["uploads"])).name).split('.')).length-1] );
 
     ctx.body = JSON.stringify(ctx.request.body, null, 2)
     await new Promise((a,b)=>{
     //var pic_route=(((ctx.request.body.files)["uploads"])).path;

    for(let i=0;i<((ctx.request.body.files)["uploads"]).length;i++){
      fs.renameSync( ((((ctx.request.body.files)["uploads"])))[i].path , ((((ctx.request.body.files)["uploads"])))[i].path+'.'+((((((ctx.request.body.files)["uploads"])))[i].name).split('.'))[((((((ctx.request.body.files)["uploads"])))[i].name).split('.')).length-1]); //重命名
    }

    a();
  })
 }
)

要注意提交action的路径和图片上传到服务器的路径

访问http://localhost:3000/haha

 

 

文本编辑器wangeditor2

http://www.kancloud.cn/wangfupeng/wangeditor2/113964

https://github.com/wangfupeng1988/wangEditor

加载必要插件:

https://github.com/wangfupeng1988/wangEditor/tree/master/test/plupload/lib/plupload

<!--引入wangEditor.css-->
<link rel="stylesheet" type="text/css" href="../dist/css/wangEditor.min.css">

<div id="div1">
 <p>请输入内容...</p>
</div>
<!--引入jquery和wangEditor.js-->   <!--注意:javascript必须放在body最后,否则可能会出现问题-->
<script type="text/javascript" src="../dist/js/lib/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="lib/plupload/plupload.full.min.js"></script>
<script type="text/javascript" src="../dist/js/wangEditor.min.js"></script>
<script type="text/javascript"> 
function printLog(title, info) {
 window.console && console.log(title, info);
}

// ------- 配置上传的初始化事件 -------
function uploadInit () {
 // this 即 editor 对象
 var editor = this;
 // 编辑器中,触发选择图片的按钮的id
 var btnId = editor.customUploadBtnId;
 // 编辑器中,触发选择图片的按钮的父元素的id
 var containerId = editor.customUploadContainerId;

 //实例化一个上传对象
 var uploader = new plupload.Uploader({
 browse_button: btnId, // 选择文件的按钮的id
 url: '/admin/upload', // 服务器端的上传地址
 flash_swf_url: '/plupload/lib/plupload/Moxie.swf',
 sliverlight_xap_url: '/plupload/lib/plupload/Moxie.xap',
 filters: {
 mime_types: [
 //只允许上传图片文件 (注意,extensions中,逗号后面不要加空格)
 { title: "图片文件", extensions: "jpg,gif,png,bmp" }
 ]
 }
 });

 //存储所有图片的url地址
 var urls = [];

 //初始化
 uploader.init();

 //绑定文件添加到队列的事件
 uploader.bind('FilesAdded', function (uploader, files) {
 //显示添加进来的文件名
 $.each(files, function(key, value){
 printLog('添加文件' + value.id);
 });

 // 文件添加之后,开始执行上传
 uploader.start();
 });

 //单个文件上传之后
 uploader.bind('FileUploaded', function (uploader, file, responseObject) {
 //注意,要从服务器返回图片的url地址,否则上传的图片无法显示在编辑器中
 var url = responseObject.response;
 //先将url地址存储来,待所有图片都上传完了,再统一处理
 urls.push(url);

 printLog('一个图片上传完成,返回的url是' + url);
 });

 //全部文件上传时候
 uploader.bind('UploadComplete', function (uploader, files) {
 printLog('所有图片上传完成');

 // 用 try catch 兼容IE低版本的异常情况
 try {
 //打印出所有图片的url地址
 console.log(urls)
 $.each(urls, function (key, value) {
 printLog('即将插入图片' + value);

 // 插入到编辑器中
 //editor.command(null, 'insertHtml', '<img src="' + value + '" style="max-width:100%;"/>');
 editor.$txt.append('<img src="' + value + '" style="max-width:100%;"/>');
 });
 } catch (ex) {
 // 此处可不写代码
 } finally {
 //清空url数组
 urls = [];

 // 隐藏进度条
 editor.hideUploadProgress();
 }
 });

 // 上传进度条
 uploader.bind('UploadProgress', function (uploader, file) {
 // 显示进度条
 editor.showUploadProgress(file.percent);
 });
}


// ------- 创建编辑器 -------
var editor = new wangEditor('div1');
editor.config.customUpload = true; // 配置自定义上传的开关
editor.config.customUploadInit = uploadInit; // 配置上传事件,uploadInit方法已经在上面定义了
editor.create();
</script>

 

admin/upload页面

//编辑器中的上传,支持单、多文件上传
router.post('/upload',
 koaBody({
 multipart: true,
 formidable: {
 uploadDir:path.join(process.cwd(), '/public/images')
 }
 }),
 async function (ctx,next) {

 //console.log(ctx.request.body.fields);
 // => {username: ""} - if empty
 //console.log( ((((ctx.request.body.files["file"])).name).split('.'))[(((((ctx.request.body.files["file"])).name).split('.')).length-1)] );
 //console.log( (((ctx.request.body.files)["uploads"])).path );
 // console.log( (((((ctx.request.body.files)["uploads"])).name).split('.'))[(((((ctx.request.body.files)["uploads"])).name).split('.')).length-1] );
 /* => {uploads: [
 {
 "size": 748831,
 "path": "/tmp/f7777b4269bf6e64518f96248537c0ab.png",
 "name": "some-image.png",
 "type": "image/png",
 "mtime": "2014-06-17T11:08:52.816Z"
 },
 {
 "size": 379749,
 "path": "/tmp/83b8cf0524529482d2f8b5d0852f49bf.jpeg",
 "name": "nodejs_rulz.jpeg",
 "type": "image/jpeg",
 "mtime": "2014-06-17T11:08:52.830Z"
 }
 ]}
 */
 // ctx.body = JSON.stringify(ctx.request.body, null, 2)

 await new Promise((a,b)=>{
    cox.body='http://www.xxx.com/1.jpg'
 })
 }
)

 

验证码

https://www.xgllseo.com/?p=5425

 

加密

var crypto = require('crypto');  //node.js内置
module.exports = function(pwd) {
  var hash = crypto.createHash('sha256').update(pwd).digest('base64');
  return hash;
};

 

 

手机页面

使用pc端或者手机端访问可以智能判断切换页面,实现方法有2种:

1,在客户端顶部添加js来判断pc、手机端来实现域名跳转,来访问不同页面。好处就是让服务器减轻负担,坏处就是域名发生跳转。

2,在后台判断来实现加载pc、手机端模版,好处是域名始终不变。坏处就是,稍微给服务器添加一些负担。

npm install koa2-useragent --save

//编辑app.js

const Koa = require('koa');
.
.
.
import userAgent from 'koa2-useragent';
app.use(userAgent());

 

//路由

router.get('/', async function (ctx, next) {
    console.log(ctx.userAgent);
    .
    .
    .
    if(ctx.userAgent.isMobile){  //客户端不同,应用不同模板
         await ctx.render('mobile_index.ejs', {
         });
    }else{
         await ctx.render('home_index', {
         });
    }
})

https://www.npmjs.com/package/koa2-useragent

 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
纯koa2模块介绍
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
以上的教程是基于koa-generator脚手架进行开发,安装的同时也大量安装了一些附属依赖模块让开发的更简单。koa-generator其实也就是以koa2为基础,依赖大量的模块进行组装,如果单纯的使用koa2来进行一步步开发又是如何的呢?

 

首先安装

npm install koa@next   //保证安装koa是2.x版本

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

搭建http

//1.js
const Koa = require('koa');
const app = new Koa();

app.listen(3000);

执行node 1.js

打开http://localhost:3000/ ,会看到"Not Found",那是因为没有像客户端发送任何数据。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

向客户端发送数据

const Koa = require('koa');
const app = new Koa();

app.use((ctx)=>{
    ctx.response.body = '你好';
});

app.listen(3000);

打开页面 http://localhost:3000/ ,可以看到显示内容“你好”,如果没有指定发送给客户端的数据类型默认是:text/plain

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

指定发送客户端的数据类型

const Koa = require('koa');
const app = new Koa();

app.use((ctx)=>{
    ctx.response.type = 'html';
    ctx.response.body = '<html><head><title>你好</title></head><body><p>这里</p></body></html>';
});

app.listen(3000);

其他常见类型有:xml、json、text

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

koa2原生路由

app.use((ctx)=>{
    if(ctx.request.path == '/'){  //首页
        ctx.response.type = 'html';
        ctx.response.body = '<html><head><title>你好</title></head><body><p>首页</p></body></html>';
    }
    if(ctx.request.path == '/about'){   // http://localhost:3000/about
        ctx.response.type = 'html';
        ctx.response.body = '<html><head><title>你好</title></head><body><p>关于我们</p></body></html>';
    }
});

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

静态资源

//在二级目录public里面存放着css.css
const Koa = require('koa');
const app = new Koa();


app.use(require('koa-static')(__dirname + '/public'));
app.listen(3000);

访问http://127.0.0.1:3000/css.css ,可以直接访问到内容

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

302自动跳转、重定向

const Koa = require('koa');
const app = new Koa();


app.use((ctx)=>{
    if(ctx.request.path == '/'){
        ctx.response.redirect('/about')  //访问首页时,会自动跳转到/about页面
        ctx.response.type = 'html';
        ctx.response.body = '<html><head><title>你好</title></head><body><p>首页</p></body></html>';
    }
    if(ctx.request.path == '/about'){
        ctx.response.type = 'html';
        ctx.response.body = '<html><head><title>你好</title></head><body><p>关于我们</p><form method="get" action="/ser"><input type="text" name="xxx1"><input type="submit" value="提交"></form></body></html>';
    }

});

app.listen(3000);

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

中间件,在客户端中将数据传递给后台接收时,的中间过程都会经历一层层的中间处理后才能将数据传递到后台。

app.use() //就是专门加载中间件的,new require('koa')().use

const Koa = require('koa');
const app = new Koa();

// 以下执行顺序是同步执行
const one = (ctx, next) => {
    console.log('>> one');
    next();
    console.log('<< one');
}

const two = (ctx, next) => {
    console.log('>> two');
    next();
    console.log('<< two');
}

const three = (ctx, next) => {
    console.log('>> three');
    next();
    console.log('<< three');
}

app.use(one);
app.use(two);
app.use(three);

app.listen(3000);
-----------------
结果:
>> one
>> two
>> three
<< three
<< two
<< one

 

const Koa = require('koa');
const app = new Koa();


const one = (ctx, next) => {
 console.log('>> one');
 next();
 console.log('<< one');
}

const two = (ctx, next) => {
 console.log('>> two');
                        //next()  删除
 console.log('<< two');
}

const three = (ctx, next) => {
 console.log('>> three');
 next();
 console.log('<< three');
}

app.use(one);
app.use(two);
app.use(three);

app.listen(3000);
-----------------
结果:
>> one
>> two
<< two
<< one

所以说,要保证所有的中间件能运行的到,就要使用next()进行串联

 

中间件,异步执行

const one =async (ctx, next) => {
   await console.log('>> one1');
    next();
    await console.log('<< one2');
}

const two =async (ctx, next) => {
    await console.log('>> two3');
    next();
    await console.log('<< two4');
}

const three =async (ctx, next) => {
    await console.log('>> three5');
    next();
    await console.log('<< three6');
}

执行结果完全打乱

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

合并中间件,如果觉得app.use()使用的频率过高,可以使用koa-compose

npm install koa-compose

案例:

const middlewares = compose([one, two,three]);
app.use(middlewares);

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

500 错误

const main = ctx => {
 ctx.throw(500);
};

 

404错误

const main = ctx => {
 ctx.response.status = 404;   //就相当于ctx.throw(404)
 ctx.response.body = 'Page Not Found';
};

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

错误的统一处理

为了方便处理错误,最好使用try...catch将其捕获。但是,为每个中间件都写try...catch太麻烦,我们可以让最外层的中间件,负责所有中间件的错误处理。

const handler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {  //报错执行此处
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.body = {
      message: err.message
    };
  }
};

const main = ctx => {
  ctx.throw(500);
};

app.use(handler);
app.use(main);

类似效果,对错误后的处理

const main = ctx => {
 ctx.throw(500);
};

app.on('error', (err, ctx) =>
 console.error('server error', err);
);

 

注意:同时使用try...catch 和 app.on('error',fn),监听error事件不会被触发,除非释放错误事件,例如:

const handler = async (ctx, next) => {
 try {
   await next();
 } catch (err) {
   ctx.response.status = err.statusCode || err.status || 500;
   ctx.response.type = 'html';
   ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
   ctx.app.emit('error', err, ctx);  //释放错误app.on('error',fn)才能被执行
 }
};

const main = ctx => {
   ctx.throw(500);
};

app.on('error', function(err) {
   console.log('logging error ', err.message);
   console.log(err);
});

 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cookies

const main = function(ctx) {
 ctx.cookies.set('view', '187'); //设置
 ctx.response.body = ctx.cookies.get('view'); //取值
}