koa+mongodb学习

1,109 阅读5分钟

起步

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

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

  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": "成功"
}

未完,待续...