koa+mongodb學習

B1an發表於2019-07-31

起步

Node 和 Mongodb 安裝

網上有很多node和mongodb的安裝教程,這裡就不一一描述了,可以隨便在網上教程,按照教程一步一步操作就好了.

構建專案

  1. 定位到專案目錄
  2. 建立專案資料夾或者手動建立
mkdir admin-server
複製程式碼
  1. 開啟 admin-server
cd admin-server
複製程式碼
  1. 安裝 koa
git init -y
npm install koa -S
複製程式碼
  1. 建立主程式入口 app.js
touch app.js
複製程式碼
  1. 建立初始服務
// 引入koa
const Koa = require('koa')
const app = new Koa()

// 啟動服務
// 監聽3000埠
app.listen(3000, () => {
    console.log('[Koa] Server is starting at port 3000!')
})
複製程式碼

程式碼連線 mongodb

菜鳥教程有相關的 mongodb教程 可以查詢 mongodb 的相關基礎操作

推薦使用 mongodb 視覺化工具 Robo 3T

連線 mondodb 前,需要啟動mongodb服務

  1. 進入 mongodb 安裝目錄啟動 mongod
// 我的本地路徑
cd /usr/local/mongodb/bin
suod mongod
複製程式碼
  1. 專案目錄 admin-server 下建立 database
  2. 安裝 mongoose
npm install mongoose -S
複製程式碼
  1. database 建立 index.js
// /admin/database/index.js

// 1. 引入mongoose庫
const mongoose = require('mongoose')
// 2. 資料庫地址
const DB_ADDRESS = 'mongodb://localhost:27017/admin-server'
//3. 連線資料庫
mongoose.connect(DB_ADDRESS, {useNewUrlParser: true}, err => {
  if (err) {
    console.log('[Mongoose] database connect failed!')
  } else {
    console.log('[Mongoose] database connect success!')
  }
})

module.exports = mongoose
複製程式碼
  1. 修改入口檔案 app.js 配置
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

const router = require('./api')
const mongoose = require('./database')

app.listen(3000, () => {
  console.log(`[Koa]Server is starting at port 3000`)
})
複製程式碼

至此,專案的起步工作都已完成!

開發

路由(介面開發)

  1. 安裝 koa-router
npm install koa-router -S
複製程式碼
  1. 在專案根目錄下建立 api 資料夾,並在檔案下建立 modules 資料夾和路由出口模組 index.js

koa+mongodb學習
3. 在 app.js 上掛載路由

// app.js
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')

// 引入路由
const router = require('./api')
const mongoose = require('./database')

const app = new Koa()

app.use(bodyParser())

// 掛載路由
app.use(router.routes())
  .use(router.allowedMethods())

app.listen(3000, () => {
  console.log(`[Koa]Server is starting at port 3000`)
})
複製程式碼
  1. 編寫路由出口檔案 /api/index.js
// 引入元件
const Router = require('koa-router')
// 引入路由模組
const userRouter = require('./modules/user')
// 例項化
const router = new Router()

// 註冊路由
router.use('/user', userRouter.routes(), userRouter.allowedMethods()) 
// 匯出路由
module.exports = router
複製程式碼
  1. 編寫具體路由 /api/modules/user.js /api/modules/todo.js
const Router = require('koa-router')
// 例項化路由
const router = new Router()

// 註冊get方法
router.get('/login', async (ctx, next) => {
  ctx.body = {
    code: 1,
    msg: 'success'
  }
})

// 註冊post方法
router.post('/register', async (ctx, next) => {
  ctx.body = {
    code: 1,
    msg: 'success'
  }
})

module.exports = router

複製程式碼
const Router = require('koa-router')
const mongoose = require('mongoose')
const Todo = require('../../database/schema/Todo')

const router = new Router()

router.post('/save', async (ctx, next) => {
  const req = ctx.request.body
  const todoItem = {
    userId: req.userId,
    content: req.content,
    status: req.status
  }
  const todo = new Todo(todoItem)
  const result = todo.save()
  
  if (result) {
    ctx.body = {
      code: 1,
      msg: 'success'
    }
  } else {
    ctx.body = {
      code: 0,
      msg: 'failed'
    }
  }
})

router.post('/update', async (ctx, next) => {
  const req = ctx.request.body
  const res = await Todo.updateOne({
    _id: mongoose.Types.ObjectId(req._id)
  }, {
    status: req.status === '0' ? 1 : 0
  })
  if (res.nModified === 1) {
    ctx.body = {
      code: 1,
      msg: 'success'
    }
  } else {
    ctx.body = {
      code: 0,
      msg: 'failed'
    }
  }
})

module.exports = router
複製程式碼
  1. 修改 /api/index.js
const Router = require('koa-router')

const userRouter = require('./modules/user')
// 引入 todo
const todoRouter = require('./modules/todo')

const router = new Router()

router.use('/user', userRouter.routes(), userRouter.allowedMethods()) 
// 掛載 todo
router.use('/todo', todoRouter.routes(), todoRouter.allowedMethods()) 

module.exports = router
複製程式碼

koa-bodyparser 中介軟體不支援 form-data 型別,因此post型別是 form-date 時使用ctx.request.body獲取的值為空,可以使用 x-www-form-urlencoded 傳送 post 引數,或者使用 koa-body 中介軟體代替

jwt鑑權

jwt是JSON Web Token的簡稱,是目前最流行的跨域身份驗證解決方案,基本流程是前通過介面登入成功後,拿到後臺返回 token 儲存在本地,在請求其他需要鑑權介面時將 token 放入請求頭 Authorization 欄位中,後臺判斷 token 是否過期,過期則返回 401 或者其他在操作提示.

  1. 安裝 koa-jwtjsonwebtoken
npm install koa-jwt jsonwebtoken -S
複製程式碼
  1. 專案根目錄建立 /utils/token.js
// /utils/token.js
// 引入jsonwebtoken
const JWT = require('jsonwebtoken')

// 金鑰
const JWT_SECRET = 'token'

// 登入請求是通過獲取使用者的 username 和 _id 生成 token 方法
exports.createToken = (data, expiresIn = '1h') => {
  const { username, _id } = data
  let opt = {
    username,
    _id
  }
  // 過期時間
  const exp = { expiresIn }
  return JWT.sign(opt, JWT_SECRET, exp)
}

// 使用者請求其他需要鑑權介面是解析 header,返回 authorization
// 請求頭 authorization 攜帶 token 時 需拼接 Bearer 格式如: `Bearer ${token}`,否則會報錯
// 因此,解析token時需要對 authorization 欄位做處理
exports.parseHader = ctx => {
  if (!ctx || !ctx.request || !ctx.request.header || !ctx.request.header.authorization) return null
  return ctx.request.header.authorization
}

// 解析 token
exports.decodeToken = token => {
  return JWT.decode(token)
}

exports.JWT_SECRET = JWT_SECRET
複製程式碼
  1. 修改登入介面
// /api/modules/user.js
const Router = require('koa-router')

// 引入 createToken
const { createToken } = require('../../utils/token')
router.get('/login', async (ctx, next) => {
  const req = ctx.request.body
  const user = await User.findOne({
    username: req.username,
    password: req.password
  })
  if (user) {
    let token = createToken(user)

    ctx.body = {
      code: 1,
      msg: '登入成功',
      data: {
        token
      }
    }
  } else {
    ctx.body = {
      code: 0,
      msg: '使用者名稱或密碼不正確'
    }
  }
})
複製程式碼

完成以上步驟後,重啟服務,測試登入介面就會發現,我們已經拿到需要的 token

koa+mongodb學習

  1. 修改 /app.js ,統一攔截 token,設定不需要攔截的路由
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
// 引入 jwt
const jwt = require('koa-jwt')
// 引入金鑰
const { JWT_SECRET } = require('./utils/token')

const router = require('./api')
const mongoose = require('./database')

const app = new Koa()

app.use(bodyParser())

// jwt 攔截錯誤處理,被 jwt 攔截後會返回 401
app.use((ctx, next) => {
  return next().catch(err => {
    if (err.status === 401) {
      ctx.status = 401
      ctx.body = {
        code: 401,
        mag: '暫無許可權'
      }
    } else {
      throw err
    }
  })
})

// 掛載 jwt 中介軟體,並設定不需要攔截的路由
app.use(
  jwt({ secret: JWT_SECRET})
    .unless({
      path: [
        /^\/user\/login/,
        /^\/user\/register/,
      ]
    })
)

app.use(router.routes())
  .use(router.allowedMethods())
  
app.listen(3000, () => {
  console.log(`[Koa]Server is starting at port 3000`)
})
複製程式碼

掛載 jwt 中介軟體需要放在掛載路由之前

  1. 驗證 token

/utils/token.js 新增 token 驗證中介軟體

exports.verify = () => {
  return async (ctx, next) => {
    let token = this.parseHader(ctx)
    try {
      let decode = JWT.verify(token, JWT_SECRET)
      let { _id } = decode
      if (_id) {
        ctx.status = 200
        await next()
      }
    } catch (err) {
      // 容錯 過濾掉 koa-jwt 中 unless 設定的路由
      if (token == null) {
        await next()
      } else {
        ctx.body = {
          code: 401,
          msg: 'token 驗證錯誤'
        }
      }
    }
  }
}
複製程式碼

驗證通過要設定 status = 200 ,否則無法返回正確資料

/app.js 引入 token 驗證中介軟體,並使用

const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const jwt = require('koa-jwt')
const { JWT_SECRET, verify } = require('./utils/token')

const router = require('./api')
const mongoose = require('./database')

const app = new Koa()

app.use(bodyParser())

app.use((ctx, next) => {
  return next().catch(err => {
    console.log(err)
    if (err.status === 401) {
      ctx.status = 401
      ctx.body = {
        code: 401,
        mag: '暫無許可權'
      }
    } else {
      throw err
    }
  })
})

app.use(
  jwt({ secret: JWT_SECRET})
    .unless({
      path: [
        /^\/user\/login/,
        /^\/user\/register/,
      ]
    })
)

app.use(verify())

app.use(router.routes())
  .use(router.allowedMethods())
  
  
  

app.listen(3000, () => {
  console.log(`[Koa]Server is starting at port 3000`)
})
複製程式碼
  1. 使用 token 資訊

修改 /api/modules/todo.js,增加獲取使用者 todo 列表介面

router.post('/list', async (ctx, next) => {
  const token = parseHader(ctx)
  const tokenDecoded = decodeToken(token)
  const { _id } = tokenDecoded
  const todoList = await Todo.find({
    userId: _id
  })
  if (todoList) {
    ctx.body = {
      code: 1,
      data: todoList,
      msg: '成功'
    }
  } else {
    ctx.body = {
      code: 0,
      msg: '失敗'
    }
  }
})
複製程式碼
// 測試介面結果
{
    "code": 1,
    "data": [
        {
            "content": "測試todo-1",
            "_id": "5d43b0670f4dc43336b3ea38",
            "userId": "5d43ac2f854fb42f399e142e",
            "status": 0,
            "createdAt": "2019-08-02T03:39:19.305Z",
            "updatedAt": "2019-08-02T03:39:19.305Z",
            "__v": 0
        },
        {
            "content": "測試todo-2",
            "_id": "5d43b11d6685db340dc882d1",
            "userId": "5d43ac2f854fb42f399e142e",
            "status": 0,
            "createdAt": "2019-08-02T03:42:21.865Z",
            "updatedAt": "2019-08-02T03:42:21.865Z",
            "__v": 0
        }
    ],
    "msg": "成功"
}
複製程式碼

未完,待續...

相關文章