Koa


2019-2-14 Koa

Koa

Koa 入门

npm install--save koa
1
//引入 Koa 
const koa=require('koa'); 
const app=new koa();
//配置中间件 (可以先当做路由) 
app.use( async (ctx)=>{ 
  ctx.body='hello koa2' 
})
//监听端口 
app.listen(3000);
1
2
3
4
5
6
7
8
9

Koa 路由

npm install --save koa-router
1
const Koa = require('koa'); 
const router = require('koa-router')(); //注意:引入的方式 
const app = new Koa(); 
router.get('/', function (ctx, next) { 
  ctx.body="Hello koa"; 
})
router.get('/news',(ctx,next)=>{ 
  ctx.body="新闻 page" 
});
app.use(router.routes()); //作用:启动路由 
app.use(router.allowedMethods()); // 作用: 这是官方文档的推荐用法,我们可以 看到 router.allowedMethods()用在了路由匹配 router.routes()之后,所以在当所有 路由中间件最后调用.此时根据 ctx.status 设置 response 响应头 
app.listen(3000,()=>{ 
  console.log('starting at port 3000'); 
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

get 传值

在 koa2 中 GET 传值通过 request 接收,但是接收的方法有两种:query 和 querystring。

  • query:返回的是格式化好的参数对象。
  • querystring:返回的是请求字符串。
const Koa = require('koa'); 
const Router = require('koa-router');
const app = new Koa(); 
const router = new Router(); 
router.get('/', function (ctx, next) { 
  ctx.body="Hello koa"; 
}) 
router.get('/newscontent',(ctx,next)=>{
  let url =ctx.url; //从 request 中获取 GET 请求 
  let request =ctx.request; 
  let req_query = request.query; 
  let req_querystring = request.querystring; //从上下文中直接获取 
  let ctx_query = ctx.query; 
  let ctx_querystring = ctx.querystring; 
  ctx.body={ 
    url, 
    req_query, 
    req_querystring, 
    ctx_query, 
    ctx_querystring 
  }
}); 
app.use(router.routes()); //作用:启动路由 
app.use(router.allowedMethods()); //作用: 当请求出错时的处理逻辑 
app.listen(3000,()=>{ 
  console.log('starting at port 3000'); 
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

动态路由

//请求方式 http://域名/product/123 
router.get('/product/:aid',async (ctx)=>{ 
  console.log(ctx.params); //{ aid: '123' } //获取动态路由的数据 
  ctx.body='这是商品页面'; 
});
1
2
3
4
5

post

原生 Nodejs 获取 post 提交数据

functionparsePostData(ctx){ 
  returnnewPromise((resolve,reject)=>{ 
    try{ 
      letpostdata=""; 
      ctx.req.on('data',(data)=>{ 
        postdata+=data 
      }) 
      ctx.req.on("end",function(){ 
        resolve(postdata); 
      }) 
    }catch(error){ 
      reject(error); 
    } 
  }); 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Koa 中 koa-bodyparser 中间件的使用

npm install--savekoa-bodyparser
1
var Koa=require('koa'); 
var bodyParser=require('koa-bodyparser'); 
var app=newKoa(); 
app.use(bodyParser());
app.use(async ctx=>{ 
  ctx.body=ctx.request.body; // 获取 post 提交的数据
});
1
2
3
4
5
6
7

中间件

通俗的讲:中间件就是匹配路由之前或者匹配路由完成做的一系列的操作,我们就可以 把它叫做中间件。

在express中间件(Middleware)是一个函数,它可以访问请求对象(requestobject(req)) , 响应对象(responseobject(res)), 和 web 应用中处理请求-响应循环流程中的中间件,一 般被命名为 next 的变量。在 Koa 中中间件和 express 有点类似。

中间件的功能包括:

  • 执行任何代码。
  • 修改请求和响应对象。
  • 终结请求-响应循环。
  • 调用堆栈中的下一个中间件。

如果我的 get、post 回调函数中,没有 next 参数,那么就匹配上第一个路由,就不会往下匹 配了。如果想往下匹配的话,那么需要写 next()

应用级中间件

const Koa = require('koa'); 
const Router = require('koa-router');
const app = new Koa(); 
const router = new Router(); 
app.use(async (ctx,next)=>{ 
  console.log(new Date()); 
  await next(); 
}) 
router.get('/', function (ctx, next) { 
  ctx.body="Hello koa"; 
}) 
router.get('/news',(ctx,next)=>{ 
  ctx.body="新闻页面" 
});
app.use(router.routes()); //作用:启动路由 
app.use(router.allowedMethods()); //作用: 当请求出错时的处理逻辑 
app.listen(3000,()=>{ 
  console.log('starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

路由中间件

router.get('/', async(ctx, next)=>{ 
  console.log(1) 
  next() 
}) 
router.get('/', function (ctx) { 
  ctx.body="Hello koa"; 
})
1
2
3
4
5
6
7

错误处理中间

app.use(async (ctx,next)=> { 
  next(); 
  if(ctx.status==404){ 
    ctx.status = 404; 
    ctx.body="这是一个 404 页面"
  } 
});
1
2
3
4
5
6
7

第三方中间件

// 静态资源中间件
const static = require('koa-static'); 
const staticPath = './static'; 
app.use(static( 
  path.join( __dirname, staticPath) 
))
//
const bodyParser = require('koa-bodyparser'); 
app.use(bodyParser());
1
2
3
4
5
6
7
8
9

Koa 中间件的执行顺序

Koa 的中间件和 Express 不同,Koa 选择了洋葱圈模型。 koa.jpg

1、Koa 中设置 Cookie 的值

ctx.cookies.set(name,value,[options])
1

通过options 设置 cookie name 的value:

options名称 options值
maxAge 一个数字表示从 Date.now() 得到的毫秒数
expires cookie 过期的Date
path cookie 路径, 默认是'/'
domain cookie 域名
secure 安全 cookie 默认 false,设置成 true 表示 只有 https 可以访问
httpOnly 是否只是服务器可访问 cookie, 默认是true
overwrite 一个布尔值,表示是否覆盖以前设置的同名 的 cookie(默认是 false). 如果是 true, 在同 一个请求中设置相同名称的所有 Cookie(不 管路径或域)是否在设置此 Cookie 时从 Set-Cookie 标头中过滤掉。

2、Koa 中获取 Cookie 的值

ctx.cookies.get('name');
1

3、Koa 中设置中文 Cookie

console.log(new Buffer('hello, world!').toString('base64'));// 转换成base64字符 串:aGVsbG8sIHdvcmxkIQ==
console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString());// 还原base 64字符串:hello, world!
1
2

Session

session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 session 保存在服务器上。

Session 的工作流程

当浏览器访问服务器并发送第一次请求时,服务器端会创建一个session对象,生成一个类似于key,value的键值对, 然后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie),找到对应的session(value)。 客户的信息都保存在session中

koa-session

1.安装 express-session

npm install koa-session --save
1

2.引入express-session

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

3.设置官方文档提供的中间件

app.keys = ['some secret hurr'];
const CONFIG = {
  key: 'koa:sess', //cookie key (default is koa:sess)
  maxAge: 86400000, // cookie 的过期时间 maxAge in ms (default is 1 days)
  overwrite: true, //是否可以 overwrite (默认 default true)
  httpOnly: true, //cookie 是否只有服务器端可以访问 httpOnly or not (default true)
  signed: true, //签名默认 true
  rolling: false, //在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)
  renew: false, //(boolean) renew session when session is nearly expired,
};
app.use(session(CONFIG, app));
1
2
3
4
5
6
7
8
9
10
11

4.使用

ctx.session.username = "张三";  // 设置值 
ctx.session.username  // 获取值
1
2
  1. cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
  2. cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗 考虑到安全应当使用 session。
  3. session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能 考虑到减轻服务器性能方面,应当使用 COOKIE。
  4. 单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。

koa-static 静态资源中间件

const Koa = require('koa')
const static = require('koa-static')
const app = new Koa()
app.use(require('koa-static')(__dirname + '/public'))
app.use(static('views')) // 静态资源中间件
1
2
3
4
5

koa2-cors 跨域

const Koa = require('koa')
const cors = require('koa2-cors') // 跨域

const app = new Koa()
app.use(cors())
1
2
3
4
5

koa-json

const Koa = require('koa')
const json = require('koa-json')

const app = new Koa()
app.use(json())
1
2
3
4
5

koa-logger

const Koa = require('koa')
const logger = require('koa-logger')

const app = new Koa()
app.use(logger())

app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
1
2
3
4
5
6
7
8
9
10
11
12

koa-jwt token中间件

const Koa = require('koa')
const jwt = require('koa-jwt')

const app = new Koa()
// jwt token中间件
app.use(jwt({
  secret: 'mds'
}).unless({path: [/^\/mds\/login/]}))
1
2
3
4
5
6
7
8

验证中间件

aap.js

const Koa = require('koa')

const errorHandle = require('./utils/errorHandle')
const sendHandle = require('./utils/sendHandle')

const app = new Koa()

// 验证中间件
app.use(sendHandle())
app.use(errorHandle())
1
2
3
4
5
6
7
8
9
10

sendHandle.js

const sendHandle = () => {
  // 处理请求成功方法
  const render = ctx => {
    return (data, msg = '请求成功') => {
      ctx.set('Content-Type', 'application/json');
      ctx.body = {
        code: '200',
        data,
        msg
      }
    }
  }

  // 处理请求失败方法
  const renderError = ctx => {
    return (code, msg = '请求失败') => {
      ctx.set('Content-Type', 'application/json');
      ctx.body = {
        code,
        data: null,
        msg
      }
    }
  }

  return async (ctx, next) => {
    ctx.send = render(ctx);
    ctx.sendError = renderError(ctx);
    await next();
  }
}

module.exports = sendHandle;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

errorHandle.js

const jwt = require('jsonwebtoken')
const verify = require('util').promisify(jwt.verify)

const errorHandle = function () {
  return async (ctx, next) => {
    try {
      const token = ctx.header.authorization
      if (token) {
        let payload
        try {
          payload = await verify(token.split(' ')[1], 'mds')
          ctx.user = payload
        } catch (err) {
          console.log('token verify fail:', err)
        }
      }
      await next()
    } catch (err) {
      if (err.status === 401) {
        ctx.status = 401
        ctx.sendError('401', '页面登录已失效,请重新登录')
      } else {
        ctx.status = 404
        ctx.body = {
          code: 404,
          msg: '404'
        }
      }
    }
  }
}

module.exports = errorHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

sequelize

Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),。它当前支持MySQL,、MariaDB、SQLite、PostgreSQL、Sql Server 数据库。

对象-关系映射(Object/Relation Mapping,简称ORM)
ORM方法论基于三个核心原则:

  • 简单性:以最基本的形式建模数据。
  • 传达性:数据库结构被任何人都能理解的语言文档化。
  • 精确性:基于数据模型创建正确标准化了的结构。

ORM包括以下四部分:

  • 一个对持久类对象进行CRUD操作的API;
  • 一个语言或API用来规定与类和类属性相关的查询;
  • 一个规定mapping metadata的工具;
  • 一种技术可以让ORM的实现同事务对象一起进行dirty checking, lazy association fetching以及其他的优化操作。

sequelize 连接mysql

const Sequelize = require('sequelize');

const sequelize = new Sequelize('mds_node','root','root',{
  host:'localhost',
  port: 3306,
  dialect:'mysql',
  define: {
    timestamps: true
  },
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
})

module.exports = sequelize;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

sequelize-auto 生成models

auto.js

var SequelizeAuto = require('sequelize-auto')
var auto = new SequelizeAuto('mds', 'root', 'root', {
    host: 'localhost',
    dialect: 'mysql',
    directory: '../models',
    port: '3306',
    additional: {
      timestamps: false,
      underscored:true,
    }
  }
)
auto.run(function (err) {
  if (err) throw err;
  console.log(auto.tables); // table list
  console.log(auto.foreignKeys); // foreign key list
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

生成model关系模型

createModelsExport.js

let fs = require('fs')
let files = fs.readdirSync('../models')
let models = []
// 解析名称做成驼峰命名法
files.forEach(item => {
  if (item != 'index.js') {
    let names = item.split('.')[0].split('_')
    let name = ''
    for (let i = 0; i < names.length; i++) {
      name += names[i].substring(0,1).toUpperCase() + names[i].substring(1)
    }
    models.push({name: name, path: './' + item})
  }
})
// 文件内容模板
const template = `
const  Sequelize = require('sequelize');
const sequelize = require('../db/dbconfig')
// 数据库模型名称及路径
const models =${JSON.stringify(models, null, 4)}
// 数据模型输出
models.forEach(item => {
    module.exports[item.name] = require(item.path)(sequelize, Sequelize)
})
`
fs.writeFile("../models/index.js", template, function () {
  console.log('创建成功')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

sequelize同步模型到数据库

const sequelize = require('../db/connectDB')
const User = sequelize.import('./user')
const Role = sequelize.import('./role')

// 模型之间的关系
User.belongsToMany(Role, {through: 'sys_user_roles', as:'UserRoles'});
Role.belongsToMany(User, {through: 'sys_user_roles', as:'UserRoles'});

// 同步模型到数据库
sequelize.sync()

exports.User = User
exports.Role = Role
1
2
3
4
5
6
7
8
9
10
11
12
13

login生成token

controllers层
jsonwebtoken生成token 由三部分组成
bcrypt.hashSync加密

const UserModel = require('../models').User
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const salt = bcrypt.genSaltSync(10);

module.exports = login = async (ctx)=>{
  const user = ctx.request.body
  const userSigned = await UserModel.findOne({
    where: {
      username: user.username
    }
  })
  if (!userSigned) {
    ctx.body = {
      code:500,
      msg:'用户不存在'
    }
    return
  } else {
    const hashPwd = bcrypt.hashSync(user.password, salt);
    console.log(hashPwd) // 加密密码
    if (!bcrypt.compareSync(user.password, userSigned.password)) {
      ctx.body = {
        code: 1000,
        msg: '密码不正确'
      }
      return
    } else {
      ctx.user = {
        userId: userSigned.userId,
        username: userSigned.username
      }
      // jsonwebtoken生成token  由三部分组成
      const token = jwt.sign({
        exp: Math.floor(Date.now() / 1000) + 60 * 30,
        userId: userSigned.userId,
        username: userSigned.username
      },'mds')
      ctx.body = {
        code: 0,
        msg: 'success',
        list: [userSigned],
        Authorization: token
      }
    }
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

基本sql语句

sql orm
select findAll,findOne,findById,findOrCreate,findAndCountAll
delete destroy
update update
insert create
SELECT foo, bar ... Model.findAll({attributes: ['foo', 'bar']});
SELECT foo, bar AS baz ... Model.findAll({attributes: ['foo', ['bar', 'baz']]});
SELECT COUNT(hats) AS no_hats ... Model.findAll({attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]});
SELECT id, foo, bar, quz ... Model.findAll({attributes: {exclude: ['baz'] }});

WHERE

sql orm
SELECT * FROM post WHERE authorId = 12 AND status = 'active' Post.findAll({where: { authorId: 2,status: 'active'}});

Operators

Post.update({
  updatedAt: null,
}, {
  where: {
    deletedAt: {
      $ne: null
    }
  }
});
// UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL;

Post.findAll({
  where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6)
});
// SELECT * FROM post WHERE char_length(status) = 6;

{
  rank: {
    $or: {
      $lt: 1000,
      $eq: null
    }
  }
}
// rank < 1000 OR rank IS NULL

{
  createdAt: {
    $lt: new Date(),
    $gt: new Date(new Date() - 24 * 60 * 60 * 1000)
  }
}
// createdAt < [timestamp] AND createdAt > [timestamp]

{
  $or: [
    {
      title: {
        $like: 'Boat%'
      }
    },
    {
      description: {
        $like: '%boat%'
      }
    }
  ]
}
// title LIKE 'Boat%' OR description LIKE '%boat%'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
op define
$and: {a: 5} AND (a = 5)
$or: [{a: 5}, {a: 6}] (a = 5 OR a = 6)
$gt: 6, > 6
$gte: 6, >= 6
$lt: 10, < 10
$lte: 10, <= 10
$ne: 20, != 20
$between: [6, 10], BETWEEN 6 AND 10
$notBetween: [11, 15], NOT BETWEEN 11 AND 15
$in: [1, 2], IN [1, 2]
$notIn: [1, 2], NOT IN [1, 2]
$like: '%hat', LIKE '%hat'
$notLike: '%hat' NOT LIKE '%hat'
$iLike: '%hat' ILIKE '%hat' (case insensitive) (PG only)
$notILike: '%hat' NOT ILIKE '%hat' (PG only)
$like: { $any: ['cat', 'hat']} LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike
$overlap: [1, 2] && [1, 2] (PG array overlap operator)
$contains: [1, 2] @> [1, 2] (PG array contains operator)
$contained: [1, 2] <@ [1, 2] (PG array contained by operator)
$any: [2,3] ANY ARRAY[2, 3]::INTEGER (PG only)
$col: 'user.organization_id' "user"."organization_id", with dialect specific column identifiers, PG in this example --$col取表的字段

tabel need primarykey:如果没有,Sequlize会自己加个自增主键,可能引起错误

findOrCreate:查到一个,查不到就新建

models.BrandReview.findOrCreate({
      where: {
          mem_id: mem_id,
          brand_id: brand_id
      },
      defaults: {
          score: score //新建的数据
      }
  })
 //返回值为数组,[json,created] 第一位是查询或创建的数据,第二位标识是否新建
1
2
3
4
5
6
7
8
9
10

update:返回值为数据,[2],数字代码改动记录数 destroy:返回数字,代表删除记录数

使用原生sql

官网中代码例子

//1
sequelize.query('SELECT 1', {
  logging: console.log,
  
  plain: false,
 
  raw: false,
 
  type: Sequelize.QueryTypes.SELECT
})
 
//2
sequelize
  .query('SELECT * FROM projects', { raw: true })
  .then(projects => {
    console.log(projects)
  })
//3
sequelize.query('SELECT * FROM projects WHERE status = ?',
  { replacements: ['active'], type: sequelize.QueryTypes.SELECT }
).then(projects => {
  console.log(projects)
})
//4
sequelize.query('SELECT * FROM projects WHERE status = :status ',
  { replacements: { status: 'active' }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
  console.log(projects)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • sequelize中提供了query函数,用于直接操作原生语句

  • 该函数将返回两个参数 - 结果数组和包含元数据的对象,对于mysql将是返一对象的两个引用。

  • query函数的第二个参数,是一个对象,对象里面几个常用参数进行说明。

    1. pain:如果plain为真,那么sequelize只返回第一个 记录结果集。如果为false,则返回所有记录。
    2. type:正在执行的查询的类型(也可以is hi更新哦,具体哪些去看官网api)。查询类型影响返回结果之前的格式化方式。
    3. raw:查询对类型是否有模型定义,如果您的查询没有模型定义,请将此设置为true。
    4. logging: console.log记录查询的函数 是否会为发送的每个SQL查询调用 到服务器。
  • 对于查找条件where后面的字段

    1. 如果传递数组,?将按它们在数组中出现的顺序进行替换。
    2. 如果传递了一个对象,:key则将替换该对象中的键。如果对象包含查询中未找到的键,会抛出查询异常。
  • 对于替换where后面的变量,也可以使用in关键字从数组匹配,也可以使用通配符like%等 代码如下:

//in关键字使用官网例子
sequelize.query('SELECT * FROM projects WHERE status IN(:status) ',
  { replacements: { status: ['active', 'inactive'] }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
  console.log(projects)
})
//like通配符关键字使用官网例子
sequelize.query('SELECT * FROM users WHERE name LIKE :search_name ',
  { replacements: { search_name: 'ben%'  }, type: sequelize.QueryTypes.SELECT }
).then(projects => {
  console.log(projects)
})
1
2
3
4
5
6
7
8
9
10
11
12
  • 查询的时候还可以直接传递model,如果传递模型,则返回的数据将是该模型的实例,上面其它字段也可以在这使用
sequelize
  .query('SELECT * FROM projects', {
    model: Projects,
    mapToModel: true // 如果有任何映射字段,则在这里传递true
  })
  .then(projects => {
    // Each record will now be an instance of Project
  })
1
2
3
4
5
6
7
8
let sqlRank = `SELECT userspk.avatar AS user_avatar, 
        userspk.gender AS user_gender, 
        userspk.nickname AS user_nickname,
        a.id AS pk_record_id,
        a.user_id, 
        a.answer_record, 
        a.pk_type, 
         MAX(score) AS score, 
        a.create_time
        FROM (select * from pkrecord  order by score desc,create_time asc) as a 
        INNER JOIN userspk AS userspk 
        ON a.user_id = userspk.user_id
        WHERE a.status = 1 
        AND a.pk_type = 'noreal' 
        AND a.subject_id = :subject_id
        GROUP BY user_id
        ORDER BY a.score DESC 
        LIMIT 3;`
let pkRankResult= await ctx.main.query(sqlRank,  {
    replacements: {
        subject_id: subject_id,
    },
    type: Sequelize.QueryTypes.SELECT }
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Last Updated: 2019-8-30 6:26:45 PM