Koa系列-基础功能实现

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

Koa与Express风格相似,不一样在于默认异步解决方案和采用洋葱圈模型的中间件。javascript

Koa没有绑定任何中间件,简单的同时也缺失了不少Web程序基础的功能,如今咱们实现这些基础的功能:
koa架构.pngcss

路由

实现路由功能咱们使用到三个中间件,分别是koa-routerkoa-bodykoa-parameterhtml

  1. 其中koa-router来实现最基础的路由功能,将不一样的url分发到相应的处理函数中;
  2. koa-body对post请求的参数进行处理,将处理结果解析到ctx.request.body中,koa-body也可以处理上传文件,文件会被解析到ctx.request.files中;
  3. koa-parameter对传参进行校验,get请求会对query进行校验,post请求则对body进行校验,校验方法基于parameter

server/index.js中引用中间件:java

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

app.use(require('koa-body')({
  multipart: true,
  formidable: {
      maxFileSize: 200*1024*1024    // 设置上传文件大小最大限制,默认2M
  }
}))

require('koa-parameter')(app)

app.use(require('./api'))

app.listen(3000)

server/api.js中分发请求进行相应的处理:node

const fs = require('fs')
const path = require('path')
const Router = require('koa-router')

const api = new Router({
  prefix: '/api'
})

api.post('/test', ctx => {
  // 使用koa-parameter对参数进行校验
  ctx.verifyParams({
    name: { type: "string", required: true }
  })
  // koa-body会将参数解析到ctx.request.body
  ctx.body = ctx.request.body
})

api.post('/upload', ctx => {
  // 文件在ctx.request.files中以对象的形式保存,若是多个文件的key相同,则value是一个File对象组成的数组,结构{ key: <File|File[]>value }
  Object.keys(ctx.request.files).forEach(key => {
    const file = ctx.request.files[key]
    const reader = fs.createReadStream(file.path)
    const upStream = fs.createWriteStream(path.join(__dirname, '../dist/' + file.name))

    reader.pipe(upStream)
  })
  ctx.body = 'upload success'
})

module.exports = api.routes()

执行命令node ./server/index.js,运行程序,使用postman访问路由,访问/api/test时,若是没有参数name,状态码为422,提示Validation Failed
20201001155137.jpggit

而咱们带上参数name,返回结果为咱们请求的参数
20201001155221.jpggithub

使用postman模拟文件上传,调用/api/upload接口,上传成功显示upload success,咱们项目中的dist文件夹也多出了上传的文件(dist文件夹须要先建立,否则程序会报错)。
20201001155246.jpgweb

为了方便咱们调试程序,咱们使用nodemon启动程序,首先运行yarn run nodemon --dev,而后在package.json中添加命令"dev": "nodemon ./server/index.js",以后咱们启动程序只须要运行yarn run dev便可,若是项目进行了修改,程序自动会自动从新运行。mongodb

页面与资源

咱们使用koa-static来实现静态资源的访问;生成页面通常会使用koa-views+相应的模板引擎的方式来实现,可是我准备使用atr-tempate来生成页面,根据官网的说明咱们使用koa-art-template便可:数据库

const static = require('koa-static')
app.use(static(path.resolve(__dirname, '../dist')))

const render = require('koa-art-template')
render(app, {
  root: path.join(__dirname, 'view'),
  extname: '.art',
  debug: process.env.NODE_ENV !== 'production'
})

新建页面和样式文件,而且添加路由:

<!-- server/view/index.art -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{title}}</title>
</head>
<body>
  <div id="app">
    Hello World
  </div>
  <link rel="stylesheet" href="style.css">
</body>
</html>
/* dist/style.css */
#app {
  color: red;
  font-size: 24px;
}
// server/routes.js
const Router = require('koa-router')
const page = new Router()

page.get('/index',async ctx => {
  await ctx.render('index', { title: 'Hello' })
})

module.exports = page.routes()

链接数据库

数据库用于持久化保存数据,服务端的开发每每离不开数据库的使用。MongoDB 是一个基于分布式文件存储的开源数据库系统。在node.js中咱们能够经过mongoose操做MongoDB数据库。

首先咱们参考菜鸟教程安装好MongoDB数据库,而后为项目添加mongoose

在项目中操做数据库,第一步先链接数据库,新建dbconnect.js文件:

const mongoose = require('mongoose')
const db = require('../config/db')
mongoose.connect(db.dbname, {useNewUrlParser: true, useUnifiedTopology: true}, err => {
    if (err) {
        log.fatal({msg: '[Mongoose] database connect failed!', err})
    } else {
        console.log('[Mongoose] database connect success!')
    }
})
module.exports = mongoose

Mongoose中全部东西都是从SchemaSchema相似于MySQL中的数据结构,Schema约束了MongoDB中每一个集合的字段结构,限制程序随意修改数据库;Model是根据Schema定义的结构编译生成的高级构造函数,Model的实例被称为DocumentModel负责从底层MongoDB数据库中建立和读取文档;关于Mongoose的其余概念,还有SchemaModelDocument三者相关的接口能够查看Mongoose官方文档

在项目中新建model/User.js

const { Schema, model } = require('mongoose')

const UserSchema = new Schema({
  username: { type: String, require: true, unique: true },
  password: { type: String, require: true }
})

module.exports = model('User', UserSchema)

在这个文件中,咱们定义了Schema,而后生成Model导出使用,要操做数据库,咱们只须要使用Model相应的方法便可。

身份验证

Passport是Node.js的身份验证中间件。 Passport极其灵活和模块化,能够绝不费力地放入任何基于Express的Web应用程序中。一套全面的策略支持使用用户名和密码,Facebook,Twitter等进行身份验证。

咱们准备使用jwt进行身份验证,暂时不使用第三方受权登陆,若是想要了解第三方受权登陆或者Passport更多的信息能够阅读官方文档;在项目安装koa-passport和jwt对应的策略passport-jwt,新建auth.js

const keys = require('../config/keys')
const User = require('./model/User')


const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: keys.secretOrkey
}

module.exports = passport => {
  passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
    User.findById(jwt_payload._id).then(user => {
      if (user) {
        done(null, user)
      } else {
        done(null, false)
      }
    })
  }))
}

而后在项目中使用:

const passport = require('koa-passport')
require('./auth')(passport)
app.use(passport.initialize())

新建接口,并使用passport验证身份信息:

api.post('/auth', passport.authenticate('jwt', { session: false }), async ctx => {
  ctx.body = 'auth'
})

结果提示Unauthorized,证实身份验证中间件已经生效。

接下来咱们来实现用户的注册和登陆,完善身份验证的整个流程,注册和登陆咱们会用到bcryptjsonwebtokenbcrypt用于密码的加密和比较,jsonwebtoken用于生成token;新增login和register接口:

api.post('/login', async ctx => {
  ctx.verifyParams({
    username: { type: "string", required: true },
    password: { type: "string", required: true },
  })
  const { username, password } = ctx.request.body
  const user = await User.findOne({ username })
  if (user && bcrypt.compareSync(password, user.password)) {
    const token = jwt.sign({ _id: user._id, username }, keys.secretOrkey, { expiresIn: 3600 })
    ctx.status = 200
    ctx.body = { token: 'Bearer ' + token }
  } else if (user) {
    ctx.status = 500
    ctx.body = { error: '密码错误' }
  } else {
    ctx.status = 500
    ctx.body = { error: '用户名不存在' }
  }
})

api.post('/register', async ctx => {
  const { username, password } = ctx.request.body
  const users = await User.find({ username })
  if (users.length > 0) {
    ctx.status = 500
    ctx.body = { error: '用户名已被占用' }
  } else {
    await User.create({ username, password: bcrypt.hashSync(password, keys.salt) }).then(user => {
      ctx.body = user
    }).catch(err => {
      ctx.status = 500
      ctx.body = { error: err }
    })
  }
})

调用这两个接口获取用户登陆的token,再次访问auth接口,状态码200,正常返回访问信息。
20201004163733.jpg

日志

log4js是Node.js的日志工具,它提供丰富的日志功能,详细的功能的使用能够查看log4js 彻底讲解官方文档,咱们如今只实现最简单的路由访问日志打印功能:

const log4js = require("log4js");
const logger = log4js.getLogger();
logger.level = "info";

app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

访问接口控制台会打印简单的访问日志
20201004171433.jpg
至此一个简单koa项目的基础功能咱们就实现好了,具体代码实现能够查看项目地址

相关文章
相关标签/搜索