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項目的基礎功能咱們就實現好了,具體代碼實現能夠查看項目地址

相關文章
相關標籤/搜索