koa框架


koa是由Express原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的Web框架。使用koa编写web应用,通过组合不同的generator,可以免除重复繁琐的 回调函数嵌套 ,并极大地提升错误处理的效率。koa不在内核方法中绑定任何中间件,它仅仅提供了-个轻量优雅的函数库,使得编写Web应用变得得心应手。

koa和express的区别

  • Koa采用洋葱模型
    通常都会说Koa是洋葱模型,这重点在于中间件的设计。但是按照上面的分析,会发现Express也是类似的,不同的是Express中间件机制使用了Callback 实现,这样如果出现异步则可能会使你在执行顺序上感到困惑,因此如果我们想做接口耗时统计、错误处理Koa的这种中间件模式处理起来更方便些。最后一点响应机制也很重要,
    Koa不是立即响应,是整个中间件处理完成在最外层进行了响应,而Express则是立即响应。

  • Koa更轻量
    koa不提供内置的中间件;
    koa不提供路由,而是把路由这个库分离出来了(koa/router)

  • Context对象
    koa增加了一个Context的对象,作为这次请求的上下文对象(在koa2中作为中间件的第一个参数传入)。同时Context上也挂载了Request和Response两个对象。Express类似, 这两个对象都提供了大量的便捷方法辅助开发这样的话对于在保存一些公有的参 数的话变得更加合情合理。

  • 异步流程控制
    express采用callback来处理异步,koa采用async/await。
    async/await使用同步的写法来处理异步,明显好于callback和promise,

  • 中间件模型
    express基于connect中间件,线性模型;
    koa中间件采用洋葱模型(对于每个中间件,在完成了-些事情后,可以非常优雅的将控制权传递给下一个中间件,并能够等待它完成,当后续的中间件完成处理后,控制权又回到了自己)

在这里插入图片描述
代码示例:

同步代码

同步方法没有什么区别:

  • 01-express-同步.js
const express = require("express")
const app = express()

app.use((req, res, next) => {
    console.log("111111")
    next()
    console.log("333333")
    res.send("hello world")
})

app.use((req, res, next) => {
    // 同步操作
    console.log("22222")
})

app.listen(3000)

运行输出

111111
22222
333333
  • 01-koa-同步 .js
const Koa = require("koa")
const app = new Koa()

app.use((ctx, next) => {
    console.log("111111")
    next()
    console.log("333333")
    ctx.body("hello world")
})

app.use((ctx, next) => {
    // 同步操作
    console.log("22222")
})

app.listen(3000)

运行输出:

111111
22222
333333

异步代码

next()表示可以执行下一个中间件,当下一个中间件执行完成之后,如果上一个中间件没有执行完,再返回上一个中间件继续执行。

01-express-异步.js

const express = require("express")
const app = express()

app.use(async (req, res, next) => {
    console.log("111111")
    await next()
    console.log("444444")
    res.send("hello world")
})

app.use(async (req, res, next) => {

    console.log("22222")
     // 异步操作
    await delay(1000)
    console.log("33333")
})

function delay(time) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve,time)
    })
}

app.listen(3000)

运行输出:

111111
22222
444444
33333

由于next()返回的不是promise对象因此await不起作用,所以输出不会像我们所想输出

  • 01-koa-异步.js
const Koa = require("koa")
const app = new Koa()

app.use((ctx, next) => {
    console.log("111111")
    next()
    console.log("444444")
})

app.use((ctx, next) => {
    console.log("22222")
    // 异步操作
    delay(1000)
    console.log("33333")
})

function delay(time) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve,time)
    })
}
app.listen(3000)

运行输出:

111111
22222
33333
444444

koa洋葱模型,正常执行。

koa下载安装

  • npm init
  • npm i koa
  • Koa基本框架
const Koa = require("koa")

const app = new Koa()

// ctx=context 相当于res和req的合并
app.use((ctx, next) => {
    
})

app.listen(3000)

koa的基本使用

  • ctx
    ctx.req//node原生的request对象
    ctx.res//node原生的response对象
    cts.request//koa封装好的的request对象
    cts.response//koa封装好的的response对象
  • 向返回数据到前端页面
    ctx.response.body = 返回的内容
       // 返回代码片段
    ctx.response.body = "<b>hello world</b>"
    // 返回json数据
    ctx.response.body = { name: "yang", age: 18 }

但是后面返回的会覆盖前面返回的

  • 获取请求路径
    ctx.request.path
 console.log(ctx.request.path)
  • 注: ctx.request.path可以简写成 ctx.path
    ctx的多数方法都可以省去request 和 response。

koa的路由

koa中没有路由,需要下载另一个模块

  • 下载:npm i koa-router
  • 使用:
const Koa = require("koa")
const Router = require("koa-router")

const app = new Koa()
const router = new Router()

router.get("/list",(ctx, next) => {
   ctx.body = ["111","222","333"]
})

// 注册路由
app.use(router.routes())
// 当请求方法错误时(比如需要get请求发成了post请求),提示相应错误
app.use(router.allowedMethods())

app.listen(3000)
  • 路由实现增删改查
const Koa = require("koa")
const Router = require("koa-router")

const app = new Koa()
const router = new Router()

router.post("/list",(ctx, next) => {
    ctx.body = {
        ok: 1,
        info:"add list success"
   }
})

router.get("/list",(ctx, next) => {
   ctx.body = ["111","222","333"]
})

router.put("/list/:id", (ctx, next) => {
    console.log(ctx.params)
    ctx.body = {
        ok: 1,
        info:"put list success"
    }
})

router.del("/list/:id", (ctx, next) => {
    ctx.body = {
        ok: 1,
        info:"del list success"
    }
})

// 注册路由
app.use(router.routes())
// 当请求方法错误时(比如需要get请求发成了post请求),提示相应错误
app.use(router.allowedMethods())

app.listen(3000)
  • 路由文件拆分,紫萼在一根文件中太乱了,不同的请求应该放在不同的路由文件中,然后进行引入。
    在这里插入图片描述
    index.js:
const Koa = require("koa")
const Router = require("koa-router")

const app = new Koa()
const router = new Router()
const userRouter = require("./routes/user")
const listRouter = require("./routes/list")

// 先注册路由级组件
router.use("/user", userRouter.routes(),userRouter.allowedMethods())
router.use("/list",listRouter.routes(),listRouter.allowedMethods())

// 再注册应用级组件
app.use(router.routes())
// 当请求方法错误时(比如需要get请求发成了post请求),提示相应错误
app.use(router.allowedMethods())

app.listen(3000)

user.js

const Router = require("koa-router")

const router = new Router()

router.post("/",(ctx, next) => {
    ctx.body = {
        ok: 1,
        info:"add user success"
   }
})

router.get("/",(ctx, next) => {
   ctx.body = ["111","222","333"]
})

router.put("/:id", (ctx, next) => {
    console.log(ctx.params)
    ctx.body = {
        ok: 1,
        info:"put user success"
    }
})

router.del("/:id", (ctx, next) => {
    ctx.body = {
        ok: 1,
        info:"del user success"
    }
})

module.exports = router

list.js

const Router = require("koa-router")

const router = new Router()

router.post("/",(ctx, next) => {
    ctx.body = {
        ok: 1,
        info:"add list success"
   }
})

router.get("/",(ctx, next) => {
   ctx.body = ["111","222","333"]
})

router.put("/:id", (ctx, next) => {
    console.log(ctx.params)
    ctx.body = {
        ok: 1,
        info:"put list success"
    }
})

router.del("/:id", (ctx, next) => {
    ctx.body = {
        ok: 1,
        info:"del list success"
    }
})

module.exports = router
  • 也可以将 index.js中的 注册路由级组件 提出来
    user.js和list.js不用修改,再router下新建index.js

在这里插入图片描述
index.js

const Koa = require("koa")
const app = new Koa()
const router = require("./routes")

// 再注册应用级中间件
app.use(router.routes())
// 当请求方法错误时(比如需要get请求发成了post请求),提示相应错误
app.use(router.allowedMethods())

app.listen(3000)

router/index.js:

const Router = require("koa-router")
const router = new Router()
const userRouter = require("./user")
const listRouter = require("./list")
// 统一加前缀
router.prefix("/api")

// 先注册路由级中间件
router.use("/user", userRouter.routes(),userRouter.allowedMethods())
router.use("/list", listRouter.routes(), listRouter.allowedMethods())

module.exports = router
  • router统一加前缀
// 统一加前缀
router.prefix("/api")
  • 路由的重定向
router.use("/home", homeRouter.routes(), homeRouter.allowedMethods())
router.redirect("/","/home")//重定向

表示当访问更目录时会自动成定向到/home.

koa静态资源

  • 安装模块: npm i koa-static
  • 引入模块,配置静态资源:
    index.js:
const Koa = require("koa")
const app = new Koa()
const static = require("koa-static")
const path= require("path")

// 先注册路由级中间件
const router = require("./routes")

// 再注册应用级中间件
app.use(router.routes())
app.use(static(path.join(__dirname,"public")))
// 当请求方法错误时(比如需要get请求发成了post请求),提示相应错误
app.use(router.allowedMethods())

app.listen(3000)
  • 例子:
    在这里插入图片描述
    center.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- href="css/center.css" 路径前面不要加前缀路径,因为在index.js中配置好了,静态资源会自动去public文件夹下寻找-->
    <link rel="stylesheet" href="/css/center.css"/>
</head>
<body>
    <div>
        center
    </div>
</body>
</html>

center.css

div{
    background-color: brown;
}

运行访问:http://localhost:3000/center.html
在这里插入图片描述

koa获取请求参数

get请求参数获取

在koa中,获取GET请求数据源头是koa中request对象中的query方法或querystring方法。
query返回是 格式化好的参数对象,
querystring返回的是请求字符串,
由于ctx对request的API有 直接引用的方式,所以获取GET请求数据有两个途径:

  • 从上下文中直接获取:
    请求对象ctx.query,返回如{ a:1, b:2 }
    请求字符串ctx.querystring,返回如a=1&b=2
  • 是从上下文的request对象中获取:
    请求对象ctx.request.query,返回如{ a:1, b:2 }
    请求字符串ctx.request.querystring,返回如a=1&b=2

post请求参数获取

对于POST请求的处理,koa-bodyparser中间件可以把koa上下文的formData数据解析到ctx.request.body

  • 配置注册模块
const bodyParser = require('koa-bodyparser')
//使用ctx.body解析中间件
app.use(bodyParser())
  • 获取
    ctx.request.body(不能简写)

输出:eg:

{ username: 'bbb', password: '123456' }

koa的ejs模板

  • 需要安装两个模块
    npm install --save koa-views
    npm install --save ejs
  • 模板配置
const views = require("koa-views")
// 配置模板引擎,将ejs模板文件放在views文件夹下
app.use(views(path.join(__dirname,"views"),{extension:"ejs"}))
  • 模板使用
    /home请求
const Router = require("koa-router")

const router = new Router()

router.get("/", async (ctx, next) => {
    // 是异步的,需要等待模板解析完成之后再返回页面
    await ctx.render("home",{username:"yang"})// 自动去views下找home.ejs(入口index.js中配置了)
})

module.exports = router

home.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Home</h1>
    <h2>欢迎<%=username%>回来</h2>
</body>
</html>

输出:
在这里插入图片描述

koa- 登录鉴权

cookie和session

koa-cookie

koa提供了从上下文直接读取、写入cookie的方法

  • ctx.cookies.get(name, [options])读取上下文请求(前端)中的cookie
  • ctx.cookies.set(name, value, [options])在上下文中写入cookie

koa-session

  • 需要下载模块koa-session-minimal
    npm i koa-session-minimal
  • 模块配置
    index.js:
    拦截
const Koa = require("koa")
const app = new Koa()
const static = require("koa-static")
const path = require("path")
const bodyParser = require('koa-bodyparser')
const views = require("koa-views")
const session = require("koa-session-minimal")

// 先注册路由级中间件
const router = require("./routes")

//使用ctx.body解析中间件,获取post参数
app.use(bodyParser())
// 配置静态资源
app.use(static(path.join(__dirname,"public")))
// 配置模板引擎
app.use(views(path.join(__dirname, "views"), { extension: "ejs" }))
// session配置
app.use(session({
    // 设置钥匙
    key: "yangsessionId",
    cookie:{
        maxAge:1000*60
    }
}))

// session判断拦截
app.use(async (ctx, next) => {
    if (ctx.url.includes("login")) {
        await next()
        return 
    }
    if (ctx.session.user) {
        ctx.session.date = Date.now()
        await next()
    }else{
        ctx.redirect("/login")
    }
})


// 再注册应用级中间件
app.use(router.routes())
app.use(router.allowedMethods())



app.listen(3000)

登陆时设置session

router.post("/login", (ctx) => {
    console.log(ctx.request.body)
    const { username, password } = ctx.request.body
    if (username === 'yang' && password === "123456") {

        // 登陆成功设置session,给session赋值
        ctx.session.user = {
            username:"yang"
        }
        ctx.body = {
            ok: 1,
        }
    } else {
        ctx.body = {
            ok: 0,
        }
    }
    
})

JWT

  • JWT模块
    util/JWT.js:
var jwt = require("jsonwebtoken")
const secret ='yang-secret'

const JWT = {
    // 加密
    generate(value,expires) {
        return jwt.sign(value,secret,{expiresIn:expires})
    },
    // 解密
    verify(token) {
        try {
            return jwt.verify(token, secret)
        } catch (error) {
            return false
        }
    }
}


module.exports = JWT
  • 登陆成功设置token
router.post("/login", (ctx) => {
    console.log(ctx.request.body)
    const { username, password } = ctx.request.body
    if (username === 'yang' && password === "123456") {

        // 登陆成功设置token,插入到header中
        const token = JWT.generate({
            _id: "123456",
            username:"yang"
        }, "10s")
        // 将token返回在header中
        ctx.set("Authorization",token)

        ctx.body = {
            ok: 1,
        }
    } else {
        ctx.body = {
            ok: 0,
        }
    }
    
})
  • login.ejs
    登录成功将token设置在前端页面上:(axios拦截器)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script type='text/javascript'>
        // 拦截器
        // 请求发出前执行的方法
      axios.interceptors.request.use(function (config) {
          console.log("请求发出前执行的方法")
          return config;
        }, function (error) {
         
          return Promise.reject(error);
      });
        // 请求成功之后第一个调用的方法
      axios.interceptors.response.use(function (response) {
            console.log("请求成功之后第一个调用的方法")
            const {authorization} = response.headers
            authorization && localStorage.setItem("token",authorization)
            return response;
          }, function (error) {
            
            return Promise.reject(error);
      });
    </script>
</head>
<body>
    <h1>登录界面</h1>
    <div>
        <div>用户名:<input id="username" /></div>
        <div>密码:<input type="password" id="password" /></div>
        <div><button id="login">登录</button></div>
    </div>

    <script type='text/javascript'>
        var login = document.querySelector("#login");
        var username = document.querySelector("#username");
        var password = document.querySelector("#password");

        login.onclick = () => {
          axios.post("/user/login",{
            username: username.value,
            password: password.value
          }).then(res=>{
            if(res.data.ok==1){
              console.log(res.data)
              // 存储token(拉杰其直接进行了存储)
              location.href="/"
            }else{
                console.log(res)
                alert("用户名密码错误")
            }
          })
      };

    </script>
</body>
</html>
  • index.js

响应请求之前先判断token是否过期

const Koa = require("koa")
const app = new Koa()
const static = require("koa-static")
const path = require("path")
const bodyParser = require('koa-bodyparser')
const views = require("koa-views")
const session = require("koa-session-minimal")
const JWT = require("./utils/jwt")

// 先注册路由级中间件
const router = require("./routes")

//使用ctx.body解析中间件,获取post参数
app.use(bodyParser())
// 配置静态资源
app.use(static(path.join(__dirname,"public")))
// 配置模板引擎
app.use(views(path.join(__dirname, "views"), { extension: "ejs" }))

// token判断拦截
app.use(async (ctx, next) => {
    if (ctx.url.includes("login")) {
        await next()
        return
    }

    const token = ctx.headers["authorization"]?.split(" ")[1]
    if (token) {
        const payload = JWT.verify(token)
        if (payload) {
            // 重新计算token的过期时间
            const newToken = JWT.generate({
                _id: payload._id,
                username: payload.username
            }, "1d")
            // 将token返回在header中
            ctx.set("Authorization",token)
            
            await next()
        } else {
            ctx.status = 401,
            ctx.body = {errCode:-1,errInfo:"token过期"}
        }
    } else {
        await next()
    }
})

// 再注册应用级中间件
app.use(router.routes())
app.use(router.allowedMethods())



app.listen(3000)
  • home.ejs:
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script type='text/javascript'>
        // 拦截器
        // 请求发出前执行的方法
      axios.interceptors.request.use(function (config) {
          console.log("请求发出前执行的方法")
          // 将token给后端
          const token = localStorage.getItem("token")
          // config是请求对象,可以传递给后端,Bearer 是规范
          config.headers.Authorization = `Bearer ${token}`
          return config;
        }, function (error) {
         
          return Promise.reject(error);
      });
        // 请求成功之后第一个调用的方法
      axios.interceptors.response.use(function (response) {
            console.log("请求成功之后第一个调用的方法")
            const {authorization} = response.headers
            authorization && localStorage.setItem("token",authorization)
            return response;

            if(response.data.ok==0){
              location.href="/login"
            }
          }, function (error) {
            console.log("请求成功之后第一个调用的方法err")
            console.log(error.response.status)
            if(error.response.status===401){
              localStorage.removeItem("token")
              location.href="/login"
            }
            return Promise.reject(error);
      });
      </script>
      <style>
        td img{
          width: 80px;
          height: 80px;
        }
      </style>
  </head>
  <body>
    <div>
      <h1>后台管理系统
        <button id="exit">退出登录</button>
        </h1>
      <br />
      <table border="1">
        <thead>
          <tr>
            <td>id</td>
            <td>用户</td>
            <td>年龄</td>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>
    <script type="text/javascript">
      var exit = document.querySelector("#exit");


      axios.get("/home/list").then((res) => {
           res = res.data
            console.log(res);
            var tbody = document.querySelector("tbody");
            // map映射
            tbody.innerHTML = res
              .map(
                (item) => `
              <tr>
                <td>${item._id}</td>
                <td>${item.username}</td>
                <td>${item.age}</td>
              </tr>
              `
              )
              .join("");
        })


      exit.onclick = ()=>{
        // 直接移除前端的token
       localStorage.removeItem("token") 
        location.href="/login"
     
      }
    </script>
  </body>
</html>

koa 文件上传

  • 安装 multer 和@koa/multer
    npm install --save @koa/multer multer
    用法和Express的multer一样。

  • 模块配置

const multer = require("@koa/multer")
// 文件存储地址
const upload = multer({dest:"public/uploads"})

// upload.single("avatar"):接收文件
router.post("/upload", upload.single("avatar"),(ctx) => {
    console.log(ctx.request.body,ctx.file)
    ctx.body={ok:1}
})

koa 操作MongoDB

  • 下载模块:npm i mongoose
  • 启动MongoDB
  • 在vscode中配置mongoDB
    在这里插入图片描述
    db.config.js:
// 连接数据库
const mongoose = require("mongoose")

mongoose.connect("mongodb://127.0.0.1:27017/yang_project")
// 插入集合和数据,yang_project会自动创建

  • index.js引入
// 链接数据库
require("./config/db.config")
  • 向数据库中存数据
// upload.single("avatar"):接收文件
router.post("/upload", upload.single("avatar"),async (ctx) => {
    console.log(ctx.request.body, ctx.file)
    
    const { username, password, age } = ctx.request.body
    const avatar = ctx.file ? `/uploads/${ctx.file.filename}` : `/uploads/9f774d77e9072c2912efb7528249db42`
    
    // 利用UserModel模型进行存储操作 UserModel.create
    await UserModel.create({
        username,
        age,
        password,
        avatar
    })


    ctx.body={ok:1}
})

代码获取:
链接:https://pan.baidu.com/s/1rzovC9h5Nl3lc-XXvRdptw?pwd=xtfx
提取码:xtfx

  • 12
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Koa Router是一个用于处理路由的中间件,它是基于Koa框架的一个插件。它提供了一种简洁的方式来定义和处理路由,使得在Koa应用中管理和处理不同的请求变得更加容易。 使用Koa Router,你可以通过定义路由和相应的处理函数来实现不同URL路径的映射。它支持常见的HTTP方法,如GET、POST、PUT、DELETE等,并且可以处理动态路由和参数。 以下是Koa Router的一些主要特性和用法: 1. 定义路由:你可以使用Koa Router的`router.get()`、`router.post()`等方法来定义不同HTTP方法的路由。例如,`router.get('/users', getUsers)`表示处理GET请求的/users路径。 2. 处理函数:你可以为每个路由定义一个处理函数,用于处理请求并返回响应。处理函数可以是一个普通的异步函数,也可以是一个Koa中间件。 3. 动态路由:Koa Router支持动态路由,你可以在路由路径中使用参数来匹配不同的URL。例如,`router.get('/users/:id', getUserById)`表示匹配/users/后面的任意字符串,并将其作为参数传递给getUserById函数。 4. 路由嵌套:你可以使用Koa Router实现路由的嵌套,将相关的路由组织在一起。这样可以更好地组织和管理路由,提高代码的可读性和可维护性。 5. 中间件支持:Koa Router本身也是一个Koa中间件,你可以将其作为其他中间件的一部分来使用。这样可以在路由处理之前或之后执行一些额外的逻辑,如身份验证、错误处理等。 总的来说,Koa Router提供了一种简单而灵活的方式来处理路由,使得构建和管理Koa应用的路由变得更加方便和可扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值