当前位置: 首页 > 工具软件 > koa-xml-body > 使用案例 >

koa

颜云瀚
2023-12-01

restful API

为什么叫REST

  1. Representational State Transfer
  2. Representational: 数据的表现形式 (JSON, XML …)
  3. State: 当前状态或者数据
  4. Transfer: 数据传输

6个限制

  1. 客户-服务(Client-Server)

    • 关注点分离
    • 服务端专注数据存储, 提升了简单性
    • 客户端专注用户界面, 提升了可移植性
    1. 无状态(Stateless)
    • 所有用户会话信息都保存在客户端
    • 每次请求必须包括所有信息, 不能依赖上下文信息
    • 服务端不保存会话信息, 提升了简单性, 可靠性, 可见性
    1. 缓存(Cache)
    • 所有服务端响应都要被标为可缓存或不可缓存
    • 减少前后端交互, 提升了性能
    1. 统一接口(Uniform Interface)
    • 接口设计尽可能统一通用, 提升了简单性, 可见性
    • 接口与实现解耦, 使前后端可以独立开发迭代
    1. 分层系统(Layered System)
    • 每次只知道相邻的一层, 后面隐藏的就不知道了
    • 客户端不知道是和代理还是和真正的服务器通信
    1. 按需代码(Code-On-Demand可选)
    • 客户端可以下载运行服务端传来的代码(比如JS)
    • 通过减少一些功能, 简化了客户端

统一接口的限制

  1. 资源的标识
    • 资源是任何可以命名的事物,比如用户, 评论等
    • 每个资源可以通过URI被唯一的标识
      a. https://api.github.com/users
      b. https://api.github.com/users/lewi617
  2. 通过表述来操作资源
    • 表述就是Representational, 比如 JSON, XML …
    • 客户端不能直接操作(比如SQL)服务端资源
    • 客户端应该通过表述 (比如JSON)来操作资源
  3. 自描述消息
    • 每个消息(请求或响应)必须提供足够的信息让接受者理解
    • 媒体(application/json application/xml)
    • HTTP方法: GET(查), POST(增), DELETE(删)
    • 是否缓存(Cache-Control)
  4. 超媒体作为应用状态引擎
    • 超媒体: 带文字的连接
    • 应用状态: 一个网页
    • 引擎: 驱动, 跳转
    • 合起来: 点击链接跳转到另外一个网页

什么是restful API

符合REST架构风格的API

  1. 具体:

    1. 基本的URI: 如: https://api.github.com/users
    2. 标准的HTTP方法, 如 GET(查), POST(增), DELETE(删), PUT, PATCH
    3. 传输的数据媒体类型, 如: JSON, XML
  2. 现实举例:

    1. GET/users # 获取user列表
    2. GET/users/12 # 查看某个具体的user
    3. POST/users # 新建一个user
    4. PUT/users/12 # 更新user12
    5. DELETE/users/12 # 删除user12
  3. 请求设计规范

    1. URI使用名词,尽量用复数,如/users
    2. URI使用嵌套表示关联关系, 如: users/12/repos/5
    3. 使用正确的http方法, 如 GET(查), POST(增), DELETE(删), PUT, PATCH
    4. 不符合CRUD的情况: POST/action/子资源
  4. 响应设计规范

    1. 查询
    2. 分页
    3. 字段过滤
    4. 状态码
    5. 错误处理
  5. 安全
    HTTPS 鉴权 限流

  6. 开发者友好

         文档   超媒体
    

koa

一些命令行:

cd /d/code 切换到文件夹

mkdir xxx 创建文件夹

code xxx vsCode打开文件夹

搭建koa程序

npm init: 生成package.json文件, 管理依赖

npm i koa --save 安装koa

编写index.js --> 执行node index.js启动程序

const Koa = require('koa')  // 1.引入koa

const app = new Koa() //1.创建一个应用程序

app.use(ctx => {  //2.中间件
  ctx.body = 'hello world 111'
})

app.listen(3000, () => console.log('程序已启动'))  // 1.启动应用程序 监听端口

npm i nodemon --save-dev // 修改文件后程序自动启动 只在开发阶段使用

由于nodemon 不是全局安装的所以使用时需要修改脚本如下:

"scripts": {

    "start": "nodemon index.js"

  },
  
 执行npm start 启动程序

编写koa中间件及洋葱模型

中间件:

const Koa = require('koa')

const app = new Koa()

app.use(async (ctx, next) => {
  await next() //第一个中间件只有调用了next,下面的中间件才能执行,否则下面的不执行
  ctx.body = 'hello world 111'
})

app.use(ctx => console.log(2)) //第二个中间件

app.listen(3000, () => console.log('程序已启动'))

洋葱模型

const Koa = require('koa')

const app = new Koa()

//第一个中间件
app.use(async (ctx, next) => {
  console.log(1)
  await next() 
  console.log(2)
  ctx.body = 'hello world 111'
})

 //第二个中间件
app.use(async (ctx, next) => {
	console.log(3)
	await next()
	console.log(4)
})

 //第三个中间件
app.use(async (ctx) => {
	console.log(5)
}) 

app.listen(3000, () => console.log('程序已启动'))

// 1 3 5 4 2

路由

路由的本质是中间件

const Koa = require('koa')

const app = new Koa()

//仿路由中间件
app.use( async ctx => {
  if (ctx.url ==='/') { //处理不同的url
    ctx.body = '主页'
  } else if (ctx.url === '/users') {
    if(ctx.method === 'GET') {  //处理不同的http方法
      ctx.body = '用户列表'
    } else if (ctx.method === 'POST') {
    	ctx.body = '创建用户'
    } else {
    	 ctx.status = 405
    }
  } else if (ctx.url.match(/\/users\/\w+/)) {  //解析路由URL上的参数
  	const userId = ctx.url.match(/\/users\/(\w+)/)[1]
  	ctx.body = `这是用户${userId}`
  } else {
    ctx.status = 404
  }
})

app.listen(3000, () => console.log('程序已启动'))

\w: 代表一个字母或者数字
\w+: 代表多个字母或者数字
正则表达式会返回一个数组, 数组的第一项是整个的URL,如果给其中的某一项加上括号,则会返回匹配里面的正则表达式作为数组的第二项

koa-router实现路由

npm i koa-router --save 安装koa-router

基本实现:

const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router

router.get('/', ctx => {
  ctx.body = '主页'
})

router.get('/users', ctx => {
  ctx.body = '用户列表'
})

router.post('/users', ctx => {
  ctx.body = '创建用户'
})

router.get('/users/:id', ctx=>{
  ctx.body = `这是用户${ctx.params.id}`  //获取路由中的参数
})

app.use(router.routes()) //注册中间件
app.listen(3000, () => console.log('程序已启动'))

使用前缀:

const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
const userRouter = new Router({prefix: '/users'}) //实例化router 前缀

router.get('/', ctx => {
  ctx.body = '主页'
})

userRouter.get('/', ctx => {
  ctx.body = '用户列表'
})

userRouter.post('/', ctx => {
  ctx.body = '创建用户'
})

userRouter.get('/:id', ctx=>{
  ctx.body = `这是用户${ctx.params.id}`
})

app.use(router.routes()) //注册中间件
app.use(userRouter.routes()) //注册中间件

app.listen(3000, () => console.log('程序已启动'))

多中间件(仿用户鉴权):

const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
const userRouter = new Router({prefix: '/users'}) //实例化router 前缀

const auth = async (ctx, next) => {  //仿用户鉴权
  if (ctx.url !== '/users') {
  	ctx.throw(401)
  }
  await next()
}

router.get('/', ctx => {
  ctx.body = '主页'
})

userRouter.get('/', auth, ctx => {
  ctx.body = '用户列表'
})

userRouter.post('/', auth, ctx => {
  ctx.body = '创建用户'
})

userRouter.get('/:id', auth, ctx=>{
  ctx.body = `这是用户${ctx.params.id}`
})

app.use(router.routes()) //注册中间件
app.use(userRouter.routes()) //注册中间件

app.listen(3000, () => console.log('程序已启动'))

http options的作用

面试常问,考察对HTTP及跨域理解的深度

帮助理解koa-router的allowedMethods作用

作用:

检测服务器所支持的请求方法 (截图)
cors中的预检请求 (某个网站只有某些请求可以跨域, 可以通过options请求来预检)

allowedMethods作用:

响应options方法,告诉它所支持的请求方法(响应头的Heads中看)
响应的返回405(不允许, 比如: 如果/users这个url没有delete这个请求方法, 如果去请求就会报这个错)和501(没实现, 比如用link方法去请求会返回501, 因为koa不支持)


const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
const userRouter = new Router({prefix: '/users'}) //实例化router 前缀

const auth = async (ctx, next) => {  //仿用户鉴权
  if (ctx.url !== '/users') {
  	ctx.throw(401)
  }
  await next()
}

router.get('/', ctx => {
  ctx.body = '主页'
})

userRouter.get('/', auth, ctx => {
  ctx.body = '用户列表'
})

userRouter.post('/', auth, ctx => {
  ctx.body = '创建用户'
})

userRouter.get('/:id', auth, ctx=>{
  ctx.body = `这是用户${ctx.params.id}`
})

app.use(router.routes()) //注册中间件
app.use(userRouter.routes()) //注册中间件
app.use(userRouter.allowedMethods()) //注册中间件 主要作用是所有的接口都可以用options发送请求

app.listen(3000, () => console.log('程序已启动'))

获取HTTP请求参数

query string, 如: ?q=keyword
router parama, 如: /users/:id
body, 如: {name: "李磊"}
Head, 如: accept, cookie

发送HTTP响应:

发送status, 如: 200/400等
发送body, 如:  {name: "李磊"}
发送Head, 如: allow content-type

异常状况有哪些

运行时错误, 都返回500
逻辑错误:
	404: 找不到
	412: 先决条件失败
	422: 无法处理的实体, 参数格式不对

获取http请求参数

  1. ?q=keyword --> ctx.query

  2. /users/:id --> ctx.params
    userRouter.get(’/:id’, auth, ctx=>{
    ctx.body = db[ctx.params.id * 1]
    })

  3. 获取body需要安装插件koa-bodyparser (npm i koa-bodyparser --save)
    const bodyparser = require(‘koa-bodyparser’) //作用是解析请求体

    app.use(bodyparser()) //注册
    
    ctx.request.body
    
    如:
    userRouter.put('/:id', ctx=>{
      db[ctx.params.id * 1] = ctx.request.body
      ctx.body = ctx.request.body
    })
    
  4. ctx.header.xxx

发送HTTP响应

  1. 发送status
    ctx.status = 204
  2. 发送body
    ctx.body = {name: ‘李磊’}
  3. 发送header
    userRouter.get(’/’, ctx => {
    ctx.set(‘Allow’, ‘GET, POST’) //设置响应头
    ctx.body = [{name: ‘aa’}, {name: ‘bb’}]
    })

合理的目录结构

新建app文件夹, 将index.js文件放进去, 修改pacage.json

"scripts": {
    "start": "nodemon app"
  },

koa自带的错误处理

412错误:

  findById(ctx) {
      if (ctx.params.id * 1 >= db.length ) {
        ctx.throw(412, '先决条件失败: id大于等于数组长度')
      }
      ctx.body = `这是用户${ctx.params.id}`
   }

500错误:

  find(ctx) {
      a * b 
      ctx.body = [{name: 'aa'}, {name: 'bb'}]
    }

自己编写错误中间件

const Koa = require('koa')
const bodyparser = require('koa-bodyparser') //作用是解析请求体
const routing = require('./routes')
const app = new Koa()

// 404错误不走中间件,会提前抛出

app.use(async (ctx, next) => { //自定义错误中间件
  try {
    await next ()
  } catch (err) {
    // 6.err信息截图
    // 运行时报错时, err中没有status和statusCode
    ctx.status = err.status || err.statusCode || 500
    ctx.body = {
      // restful api 希望返回格式为json
      message: err.message
    }
  }
})


app.use(bodyparser()) //注册
routing(app) //批量注册路由
app.listen(3000, () => console.log('程序已启动'))

使用koa-json-error进行错误处理

npm i koa-json-error --save

index.js引入并注册:

const error = require('koa-json-error')

app.use(error({
	// 生产环境不返回堆栈信息
  postFormat: (e, { stack, ...rest }) => process.env.NODE_ENV === 'production' ? rest : { stack, ...rest }

}));

此处需要设置环境变量:
 "scripts": {
    "start": "cross-env NODE_ENV=production node app",    //启动 npm start
    "dev": "nodemon app"   //npm run dev
  }, 
由于Windows不能直接设置环境变量, 需要安装依赖cross-env实现跨平台设置环境变量
npm i cross-env --save-dev

使用koa-parameter校验参数

npm i koa-parameter --save

index.js中引入注册:

const parameter = require('koa-parameter')

app.use(error({
  postFormat:(e, {stack, ...rest}) => process.env.NODE_ENV === 'production' ? rest : {stack, ...rest}
}))
app.use(bodyparser()) //注册
app.use(parameter(app)) //由于是用来校验请求体的所以放这里
routing(app) //批量注册路由
app.listen(3000, () => console.log('程序已启动'))

controller users.js文件中使用:

  create(ctx) {
      ctx.verifyParams({ //如果不满足条件返回422
        name: {type: 'string', required: true},
        age: {type: 'number', required: false}
      })
      ctx.body = {name: 'ccc'}
    }

//controller users.文件

class UsersCtl {
    find(ctx) {
      ctx.body = [{name: 'aa'}, {name: 'bb'}]
    }
    findById(ctx) {
      if (ctx.params.id * 1 >= db.length ) {
        ctx.throw(412, '先决条件失败: id大于等于数组长度')
      } //手动添加校验
      ctx.body = `这是用户${ctx.params.id}`
    }
    create(ctx) { 
      ctx.verifyParams({
        name: {type: 'string', required: true}
      }) // 利用中间件校验
      ctx.body = {name: 'ccc'}
    }
    update(ctx) {
      if (ctx.params.id * 1 >= db.length ) {
        ctx.throw(412, '先决条件失败: id大于等于数组长度')
      }
      ctx.verifyParams({
        name: {type: 'string', required: true}
      })
      ctx.body = `这是用户${ctx.params.id}`
    }
    delete(ctx) {
      if (ctx.params.id * 1 >= db.length ) {
        ctx.throw(412, '先决条件失败: id大于等于数组长度')
      }
      ctx.status = 204
    }
}

module.exports = new UsersCtl()

NoSQL

分类

列存储(Hbase) 图存储(FlockDB)

文档存储(MongoDB): 按文档存储实际上存储格式是json, 一段json就是一个文档,也可以把一段文档当做是表格中的一行

对象存储(db4o) Key-value存储(Redis): 用于缓存, 如消息存储

XML存储(BsaeX)

优点:

MongoDB

优点

使用

云存储: MongoDB Atlas --> 注册

安装mongoose依赖 npm i mongoose --save

引入注册使用:

const mongoose = require('mongoose') //引入
const { connectionStr } = require('./config')  //存放配置文件

mongoose.connect( connectionStr, () => console.log('mongoose 已连接') ) //连接mongoose数据库
mongoose.connection.on('error', console.error ) //打印错误信息

设计用户模块Schema

models users.js

const mongoose = require('mongoose')

const { Schema, model } = mongoose

const userSchema = new Schema({
  name: {type: String, required: true}
})

module.exports = model('User', userSchema)   // User表示一个集合

session

用户认证与授权

认证: 你是谁

授权: 什么能做什么不能做

劣势(见截图13):

cookie具有不可跨域性, 设置cookie时,不仅要设置sessionId 还要设置一个domain的变量(表示cookie生效的域名, 默认情况下只有这些域名下才可以使用cookie)

JWT

构成:

三者都是经过base64编码过的

签名是对头部和有效载荷部分进行签名

  • node 中使用JWT
    npm i jsonwebtoken 安装依赖jsonwebtoken --> 签名 --> 验证
  • 使用node命令行体验:
    node 回车进入node模式
    jwt = require(‘jsonwebtoken’) 回车
    token= jwt.sign({name: “XXX”}, “secret”) 回车生成token
    jwt.decode(token) //解码 只是一般的解码,容易被篡改
    jwt.verify(token, ‘secret’) //
  • 实现登录并获取token

编写koa中间件实现用户认证与授权

认证: 验证token,并获取用户信息

授权: 使用中间件保护接口

用koa-jwt实现用户认证与授权

npm i koa-jwt --save 安装

用中间件保护接口

使用中间件获取用户信息

使用koa-body中间间获取上传的文件

安装koa-body替换koa-bodyparser, 因为后者只支持json和form,不支持文件这种格式

// const bodyparser = require('koa-bodyparser') //作用是解析请求体, 只能解析json和form,不能解析文件
const koaBody = require('koa-body') //作用是解析请求体  能解析json form 文件

// app.use(bodyparser()) //注册 解析请求体
app.use(koaBody({
  multipart: true, //代表启用文件格式
  formidable: {
    uploadDir: path.join(__dirname, '/public/uploads'), //文件上传路径
    keepExtensions: true  //保存文件拓展名
  }
})) //注册 解析请求体

cotroller:
  // 上传文件
  upload (ctx) {
    console.log(ctx.request);
    console.log(ctx.request.files);
    const file = ctx.request.files.file
    ctx.body = {
      path: file.path
    }
  }
}

router:

const { upload } = require('../controllers/home')
router.post('/upload', upload)

使用koa-static中间件生成图片链接

npm i koa-static --save

const koaStatic = require('koa-static')

// 生成图片链接
app.use(koaStatic(path.join(__dirname, 'public')))

修改上传文件的controller:
  // 上传文件
  upload (ctx) {
    const file = ctx.request.files.file
    const basename = path.basename(file.path)
    ctx.body = {
      url: `${ctx.origin}/uploads/${basename}` //ctx.origin 获取到的是localhost
    }
  }
}

public下面的任何文件都可以通过它生成http链接
 类似资料: