koa使用教程,快速打通前后端壁垒

基础使用

使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库

Koa 是洋葱模型,这重点在于中间件的设计。 express采用callback来处理异步, koa v1采用generator,koa v2 采用async/await。

安装

需要安装 koa

js 复制代码
npm install koa
js 复制代码
const Koa = require('koa');
const app = new Koa();

// 响应
app.use((ctx,next) => {
  console.log(111)
  ctx.body = 'Hello Koa';
});

app.listen(3000);

监听路由

安装路由

js 复制代码
npm istall koa-router
js 复制代码
const Koa = require('koa');
const Router = require("koa-router")
const app = new Koa();
const router = new Router();
// 响应
router.get("/list",(ctx,next)=>{
  ctx.body=['1111','2222']
})

app.use(router.routes())
app.listen(3000);

浏览器请求:http://localhost:3000/list 就会返回一个数组

把get改成post,然后在浏览器继续请求: http://localhost:3000/list

js 复制代码
router.post("/list",(ctx,next)=>{
  ctx.body=['1111','2222']
})

此时浏览器发送的是get请求,而koa监听的是post请求,就会发现找不到这个请求

allowedMethods方法

给响应头添加允许的访问方式

js 复制代码
app.use(router.routes()).use(router.allowedMethods());

再次访问就会提示方法错误

get\post\del\put请求

Koa-router 请求方式: get 、 put 、 post 、 patch 、 delete 、 del ,而使用方法就是 router.方式() ,比如 router.get() 和 router.post()

js 复制代码
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 success',
  }
})

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

router.put("/list/:id",(ctx,next)=>{
  ctx.body={
    ok:1,
    info:'update success',
  }
})
router.del("/list/:id",(ctx,next)=>{
  ctx.body={
    ok:1,
    info:'del success',
  }
})
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

可以通过 获取参数

js 复制代码
ctx.params 

路由拆分

当路由比较多时,都放在一个文件里就比较复杂,这个时候可以将不同的路由放到不同的文件里,进行一个拆分,然后在监听路由级组件

路由级组件

在入口文件 导入不同路径的路由并注册路由组件,监听不同路径的路由

js 复制代码
const Koa = require('koa');
const Router = require("koa-router")
const app = new Koa();
const router = new Router();
const listRouter = require('./routes/list');
const userRouter = require('./routes/user');

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

// 应用级组件
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

访问时,就会根据不同的路由,找到对应的内容

新建路由内容

在routes 文件夹下新建 list和user文件

js 复制代码
const Router = require("koa-router")
const router = new Router();


router.post("/",(ctx,next)=>{
    ctx.body={
      ok:1,
      info:'add success',
    }
  })
  
  router.get("/",(ctx,next)=>{
    ctx.body=['111','222']
  })
  
  router.put("/:id",(ctx,next)=>{
    ctx.body={
      ok:1,
      info:'update success',
    }
  })
  router.del("/:id",(ctx,next)=>{
    ctx.body={
      ok:1,
      info:'del success',
    }
  })

  module.exports = router

在入口页已经监听到不同的路由了,所以在user和list文件里就可以直接使用 /

统一加前缀

在使用时,都会加一个 /api 辨别是接口还是页面

js 复制代码
const Koa = require('koa');
const Router = require("koa-router")
const app = new Koa();
const router = new Router();
const listRouter = require('./routes/list');
const userRouter = require('./routes/user');

// 前缀
router.prefix('/api');
// 注册路由级组件
router.use("/list", listRouter.routes(), listRouter.allowedMethods());
router.use("/user", userRouter.routes(), userRouter.allowedMethods());

// 应用级组件
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

静态资源

需要安装koa-static

js 复制代码
npm install koa-static

新建public文件夹和html文件

在入口文件引入koa-static

js 复制代码
const Koa = require('koa');
const Router = require("koa-router")
const static = require("koa-static");
const app = new Koa();

// 应用级组件
app.use(static(__dirname + '/public'));

app.listen(3000);

http://localhost:3000/center.html

请求参数

get请求

get请求可以直接使用 ctx.query 获取

post请求

post请求需要使用 ctx.request.body 获取请求的参数,但是直接使用是获取不到的

post请求需要添加插件

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

js 复制代码
npm i koa-bodyparser

在入口引入

js 复制代码
const Koa = require('koa');
const static = require("koa-static");
const router = require('./routes/index')
const app = new Koa();
const bodyParser = require('koa-bodyparser');

// 应用级组件
app.use(bodyParser());
app.use(static(__dirname + '/public'));
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

ejs模版

使用 ejs 模版和 koa-views

js 复制代码
npm install ejs koa-views

引入路径和模版引擎

在入口页 配置好路径和模版引擎

js 复制代码
const Koa = require('koa');
const path = require('path');
const static = require("koa-static");
const router = require('./routes/index')
const app = new Koa();
const views = require('koa-views');

const bodyParser = require('koa-bodyparser');

// 应用级组件
app.use(bodyParser());
app.use(static(path.join(__dirname ,'/public')))

// 配置路径和模版引擎  
app.use(views(path.join(__dirname , '/views'),{extension:"ejs"}));

app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

模版路径就配置到 views文件夹下,通过ejs引擎处理

创建模版

新建views文件夹,在他下面创建 home.ejs 文件

js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>home模版</title>
</head>
<body>
    <h1>home模版页面</h1>
</body>
</html>

创建路由 引入模版

js 复制代码
const Router = require("koa-router")
const router = new Router();
  
  router.get("/",async (ctx,next)=>{
    // render 会自动找 views 文件夹下的 home.ejs
    await ctx.render("home")
  })
  

module.exports = router

模版需要异步引入,否则会报404

而且在引入模版时 需要使用 ctx.render,他会自动去 views文件夹下匹配

传入参数

在路由下,传参给ejs模板

js 复制代码
const Router = require("koa-router")
const router = new Router();
  
  router.get("/",async (ctx,next)=>{
    // render 会自动找 views 文件夹下的 home.ejs
    await ctx.render("home",{
      title:"home的页面",
      username:"董员外"})
  })
  

module.exports = router
js 复制代码
<!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>
    <h1>home模版页面</h1>
    <div>欢迎<%= username%> 回来玩</div>
</body>
</html>

cookie&session

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

  • ctx.cookies.get(name, [options]) 读取上下文请求中的cookie
  • ctx.cookies.set(name, value, [options]) 在上下文中写入cookie
js 复制代码
// 设置cookies
ctx.cookies.set("token",'this is a token')

// 获取
ctx.cookies.get("token")

session

koa-session-minimal 是一个用于koa2 的session中间件,提供存储介质的读写接口 。设置过期时间等操作

添加这个库

js 复制代码
npm i koa-session-minimal

在入口页引入并配置中间件

js 复制代码
const session = require('koa-session-minimal');

// 配置session
app.use(session({
    key: 'dyySessionId',
    cookie:{maxAge: 1000*60}
}))

同时也能设置拦截器,当符合规则时继续向下走

js 复制代码
// 配置session,设置过期时间
app.use(session({
    key: 'dyySessionId',
    cookie:{
        maxAge: 1000*60*60 // 1小时后过期
    }
}))
// 拦截器
app.use(async (ctx, next) => {
    //排除login相关的路由和接口
    if (ctx.url.includes("login")) {
        await next()
        return
    }

  // 没有session让用户重新登录
    if (ctx.session.user) {
        //重新设置sesssion,过期时间从新计时
        ctx.session.mydate = Date.now()
        await next()
    } else {
        ctx.redirect("/login")
    }
})

Token(令牌)

  • 访问资源接口(API)时所需要的资源凭证

  • 简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)

  • 特点:

    • 服务端无状态化、可扩展性好
    • 支持移动端设备
    • 安全
    • 支持跨程序调用
  • token 的身份验证流程:

jwt

JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。

  • 前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  • 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
  • 后端将JWT Token字符串作为登录成功的结果返回给前端。
  • 前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  • 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  • 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

安装 jsonwebtoken

js 复制代码
npm i jsonwebtoken

为了方便使用,进行jwt封装一些方法

新建util文件

util/JWT.js

js 复制代码
const jwt = require('jsonwebtoken');

const secret = 'dyy'; //加密的秘钥

const JWT = {
    generate(value,exprires){
        console.log(value,exprires)
        return jwt.sign(value,secret,{expiresIn:exprires})
    },
    verify(token){
        try{
            console.log(token)
            return jwt.verify(token,secret)
        }catch(e){
            return false
        }
    }
}

module.exports = JWT

jwt.sign是创建一个token

token由3部分组成,分别 加密数据、秘钥、过期时间

js 复制代码
jwt.sign(value,secret,{expiresIn:exprires})

在路由处验证

js 复制代码
const JWT = require("../util/JWT.JS")

// jwt验证
const token  = JWT.generate({name:'dongyuanwai'},'10s')
console.log("立即验证",JWT.verify(token))

setTimeout(()=>{
    console.log("9s后验证",JWT.verify(token))
},9000)

setTimeout(()=>{
    console.log("12s后验证",JWT.verify(token))
},12000)

模拟登录鉴权

在login登录页调用登录接口

// login.ejs

html 复制代码
<!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>
    <h1>login登录页</h1>
    <h2>username:dongyuanwai</h2>
    <h2>password:123456</h2>
    <button id="rightLogin">点击登录</button>
    <div style="width: 100%; border: 2px solid red;margin: 20px 0;"></div>
    <button id="errorLogin"> 输入用户名密码错误------点击登录</button>

    <script>
        document.getElementById('rightLogin').onclick = function(){
            const data = {
                username: 'dongyuanwai',
                password: '123456'
            }
            fetch("/login", {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
                })
                .then(response => {
                    console.log("成功",response)
                })
        }
        // 错误登录
        document.getElementById('errorLogin').onclick = function(){
            const data = {
                username: 'hai',
                password: '1111'
            }
            fetch("/login", {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
                })
                .then(response => {
                    console.log("成功",response)
                })
        }
    </script>
</body>
</html>

在login后台路由监听登录接口

生成 token 把token返回给前端

js 复制代码
const Router = require("koa-router")
const JWT = require("../util/JWT.js")
const router = new Router();
  
  router.get("/",async (ctx,next)=>{
    // render 会自动找 views 文件夹下的 home.ejs
    await ctx.render("login",{
      title:"login的页面",
    })
  })
  router.post("/",async (ctx,next)=>{
    // render 会自动找 views 文件夹下的 home.ejs
    const {username,password} = ctx.request.body
    console.log("🚀 ~ router.post ~ username,password:", username,password)
    if(username==="dongyuanwai"&&password==="123456"){
      // 设置header
      const token = JWT.generate({
        username,
        id:1111,
      },"10s")
      ctx.set("Authorization",token)
      
      ctx.body={
        ok:1,
        info:'login success',
      }
    }else{
      ctx.body={
        ok:0,
        info:'login fail',
      }
    }
  })

module.exports = router

登录的账户名密码正确,header就会有Authorization

axios

用axios改造,在前端引入axios,设置拦截器,在请求前加入请求头带上token,然后在响应后存入token

html 复制代码
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.7/dist/axios.min.js"></script>
<script>
    // 拦截器
    axios.interceptors.request.use(function (config) {
        // 请求发出前 执行的方法
        const token = localStorage.getItem('token');
        console.log("🚀 ~ token:", token)
        config.headers.Authorization = `Bearer ${token}`
        return config;
    }, function (error) {
        // Do something with request error
        return Promise.reject(error);
    });

    
    axios.interceptors.response.use(function (response) {
        // 请求成功后 第一个调用的方法
        // 在此处 获取token
        const {authorization} = response.headers;
        if(authorization){
            localStorage.setItem('token', authorization)

        }
        return response;
    }, function (error) {
        // Any status codes that falls outside the range of 2xx cause this function to trigger
        // Do something with response error
        return Promise.reject(error);
    });
  axios.post("/login", data).then(res => {
      console.log("成功", res)
      if (res.data.ok === 1) {
          location.href = '/home'
          // alert("跳转-=-=")
      }
  })
</script>

在后端的登陆接口处,如果账号密码正确 则设置token

js 复制代码
if(username==="dongyuanwai"&&password==="123456"){
      // 设置header
      const token = JWT.generate({
        username,
        id:1111,
      },"10s")
     // 设置请求的 Authorization 头部用于后续的身份验证
      ctx.set("Authorization",token)
      
      ctx.body={
        ok:1,
        info:'login success',
      }
    }else{
      ctx.body={
        ok:0,
        info:'login fail',
      }
    }

在入口文件 添加拦截器

前端接口请求时,先走这一步,如果token没过期则更新token给接口返回数据

如果token过期了则就返回401

js 复制代码
app.use(async (ctx, next) => {
    //排除login相关的路由和接口
    if (ctx.url.includes("login")) {
        await next()
        return
    }
     // Authorization会变成authorization
    const token = ctx.headers['authorization']?.split(' ')[1];
    console.log("token:",token)
    if(token){
        const payload = JWT.verify(token);
        console.log("🚀 ~ app.use ~ payload:", payload)
        if(payload){
            const newToken = JWT.generate({
                id:payload.id,
                username:payload.username
            },'30s')
            ctx.set("Authorization",newToken)
            await next()
        }else{
            ctx.status = 401;
            ctx.body = {
                errCodea:-1,
                errMsg:'token失效'
            }
        }
    }else{
        await next()
    }
})

在其他页面 比如 home.ejs页监听返回数据

发送接口请求时带着token 让后端验证,如果过期了,则把token移除,并且跳转到登录页

html 复制代码
<script>
    // 拦截器
    axios.interceptors.request.use(function (config) {
        // 请求发出前 执行的方法
        const token = localStorage.getItem('token');
        config.headers.Authorization = `Bearer ${token}`
        return config;
    }, function (error) {
        // Do something with request error
        return Promise.reject(error);
    });


    axios.interceptors.response.use(function (response) {
        // 请求成功后 第一个调用的方法
        // 在此处 获取token
        const {authorization} = response.headers;
        if(authorization){
            localStorage.setItem('token', authorization)
        }
        return response;
    }, function (error) {
        console.log("error",error)
        if(error.response.status === 401){
            localStorage.removeItem("token")
            location.href='/login'
        }
        return Promise.reject(error);
    });

    axios.get('/home/list').then(res=>{
        console.log("🚀 ~ res:", res)
    })
相关推荐
中微子11 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上102426 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y42 分钟前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript
拾光拾趣录1 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟1 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan1 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构
写不出来就跑路1 小时前
基于 Vue 3 的智能聊天界面实现:从 UI 到流式响应全解析
前端·vue.js·ui