基础使用

使用 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
cookie
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/[email protected]/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)
})