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)
    })
相关推荐
fs哆哆2 分钟前
ExcelVBA运用Excel的【条件格式】(二)
linux·运维·服务器·前端·excel
安冬的码畜日常16 分钟前
【CSS in Depth 2精译】2.5 无单位的数值与行高
前端·css
ilisi_17 分钟前
导航栏样式,盒子模型
前端·javascript·css
吉吉安26 分钟前
grid布局下的展开/收缩过渡效果【vue/已验证可正常运行】
前端·javascript·vue.js
梦凡尘31 分钟前
Vue3 对跳转 同一路由传入不同参数的页面分别进行缓存
前端·javascript·vue.js
攒了一袋星辰31 分钟前
Webpack安装以及快速入门
前端·webpack·node.js
吃饱很舒服1 小时前
kotlin distinctBy 使用
android·java·开发语言·前端·kotlin
勤劳兔码农1 小时前
从IE到Edge:微软浏览器的演变与未来展望
前端·microsoft·edge
web守墓人1 小时前
【前端】解决element-ui两层dialog嵌套,遮罩层消失的问题。
前端·ui
茶卡盐佑星_1 小时前
vue如何解决跨域?原理?
前端·javascript·vue.js