Restful API设计和koa实操任务

785 阅读7分钟

编写Restful API

  • Representational State Transfer翻译过来是'表现层状态变化',它是一种互联网软件的架构原则。因为符合REST风格的WEB API设计,就称为Restful API
  • Restful是目前最流行的API规范,适用于web接口规范的设计。让接口易读,且含义清晰。

1. URL设计

1.1 动词 + 宾语

它的核心思想就是客户端发出的数据操作指令都是【动词 + 宾语】的结构,比如GET/articles这个命令,GET是动词,/acticles是宾语

  • GET: 读取资源
  • POST: 新建资源
  • PUT: 更新资源
  • PATCH:资源部分数据更新
  • DELETE:删除资源

正确的例子

  • GET/zoos:列出所有动物园
  • POST/zoos: 新建一个动物园
  • GET/zoos/ID: 获取某个指定动物园的信息
  • PUT/zoos/ID: 更新某个指定动物园的信息(获取该动物园的全部信息)
  • PATCH/zoos/ID: 更新某个指定动物园的信息(获取该动物园的部分信息)
  • DELETE/zoos/ID: 删除某个动物园
  • GET/zoos/ID/animals: 列出某个指定动物园的所有动物
  • DELETE/zoos/ID/animals/ID: 删除某个指定动物园的指定动物

1.2 动词的覆盖

有些客户端只能使用GET和POST两种方法。服务器必须接受POST模拟其他三个方法(PUT、PATCH、DELETE)
这时,客户端发出的HTTP请求,要加上X-HTTP-Method-Override属性,告诉服务器应该使用哪一个动词来覆盖POST方法

1.3 宾语必须是一个名词

就是API的url,是HTTP动词作用的对象,所有应该是名词。例如/books这个URL就是正确的,而下面的URL不是名词,都是错误的写法。

错误示范

GET /getAllUsers?name=jl
POST /createUser
POST /deleteUser

1.4 复数名词

URL是名词,那么是使用复数还是单数?

没有统一的规定,但是我们通常操作的数据多数是一个集合,比如GET /books,所以我们就是用复数。

统一规范,建议都是用复数URL,比如获取id=2的书 GET /books/2 要好于GET /book/2

1.5 避免出现多级的URL

有时候我们要操作的资源可能是有多个层级,因此很容易写多级URL,比如获取某个坐着某种分类的书

GET /authorship/2/categories/2获取作者id为2分类为2的文章

这种URL不利于拓展,语义也不清晰。

更好的方式就是除了第一级,其他级别都是通过查询字符串来表达。

GET /authors/2?categories=2

2. 过滤信息(Filtering)

状态码如果记录数量很多,服务器不可能将它们都返回给用户。API应该提供参数,过滤返回结果

下面是一些常见的参数

  • ?limit=10: 指定返回的记录数量
  • ?offset=10: 指定返回记录的开始位置
  • ?page=2&per_page=100: 指定第几页,以及每页的记录数
  • ?sortby=name&order=asc: 指定返回结果按照片哪个属性排序,以及排序顺序
  • ?animal_type_id=1: 指定筛选条件

2.1 状态码必须精确

客户端的请求,服务端都必须响应,包含HTTP状态码和数据。

HTTP状态码就是一个三位数,分成五个类别。

  • 1xx: 相关信息
  • 2xx: 操作成功
  • 3xx: 重定向
  • 4xx: 客户端错误
  • 5xx: 服务器错误

2.2 2xx状态码

200状态码表示操作成功,但是不同的方法可以返回更精确的状态码

  • GET:200
  • POST:201 Created
  • PUT:200 OK
  • PATCH:200 OK
  • DELETE:204 No Content

2.3 4xx状态码

4xx状态码表示客户端错误,主要有下面几种

  • 400 Bad Request: 服务器不理解客户端的请求,未做任何处理
  • 401 Unauthorized: 用户未提供身份验证凭据,或者没有通过身份验证
  • 403 Forbidden: 用户通过了身份验证,但是不具有访问资源所需的权限
  • 404 Not Found:所请求的资源不存在或不可用
  • 405 Method Not Allowed:用户已经通过身份验证,但是所用的HTTP方法不在他的权限之内
  • 410 Gone:所请求的资源已从这个地址转移,不再可用
  • 415 Unprocessable Media Type:客户端要求的返回格式不支持。比如,API只能返回JSON格式,但是客户端要求返回XML格式
  • 422 Unprocessable Entity: 客户端上传的附件无法处理,导致请求失败
  • 429 Too Many Requests: 客户端的请求次数超过限制

2.4 5xx状态码

5xx状态码表示服务端错误。一般来说,API不会向用户透露路服务器的详细信息,所以只要两个状态码

  • 500 Internal Server Error: 客户端请求有效,服务器处理时发生了意外
  • 503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态

3. 服务器响应

3.1 不要返回纯文本

API返回的数据格式,不应该是纯文本,而应该是一个JSON对象,因为这样才能返回标准的结构化数据。所以,服务器回应的HTTP头的Content-Type属性要设为application/json

客户端请求时,也要明确告诉服务器,可以接受JSON格式,即请求的HTTP头的ACCEPT属性也要设成application/json

3.2 发生错误的时候,不要返回200状态码

koa实操任务

koa跨域

1.下载并引入koa2-cors

2.app.use(cors())

const koa = require('koa');
const cors = require('koa2-cors');
const app = new koa();
app.use(cors());

koa上传文件

1.下载并引用koa-multer

2.创建一个配置 来规定文件保存路径以及修改文件名称

用multer.diskStorate({第一个对象destination写文件保存路径},{第二个对象修改文件名称})

具体用法参考文章 github.com/expressjs/m…

3.加载配置

const upload = multer({storage:配置名})

4.post请求接口里第二个参数upload.single('前端传来的文件名')

single为单文件情况下 具体参考上面链接

// 文件上传
const multer = require('koa-multer');
//配置 磁盘存储
const storage = multer.diskStorage({
  //文件保存路径
  destination: function (req, file, cb) {
    cb(null, 'public/images/')
  },
  //修改文件名称
  filename: function (req, file, cb) {
    var fileFormat = (file.originalname).split(".");  //以点分割成数组,数组的最后一项就是后缀名
    cb(null, Date.now() + "." + fileFormat[fileFormat.length - 1]);
  }
})
//加载配置
const upload = multer({ storage: storage });
router.post('/upload', upload.single('avatar'), (ctx, next) => {
  // ctx.req.file  是avatar文件的信息
  // ctx.req.body 文本域数据 如果存在
  //  ctx.body = '上传成功';
  console.log(ctx.req.file.filename);
  ctx.body = {
    ok: 1
  }
})

表单验证

1.下载并引入koa-bouncer

const bouncer = require('koa-bouncer');
app.use(bouncer.middleware());

2.看代码注释查看具体使用方法

router.post('/', async (ctx, next) => {
  // ctx.request.body
  // uname  pw1  pw2
  try {
    ctx.validateBody('uname')
      .required('用户名是必须的') //只要求有uname字段
      .isString() //确保输入的字段是字符串或者可以转换成字符串
      .trim() //清除空格
      .isLength(6, 12, '用户名必须是6~12位')

    ctx.validateBody('email')
      .optional()
      .isString()
      .trim()
      .isEmail('非法的邮箱格式')

    ctx.validateBody('pwd1')
      .required('密码是必填项')
      .isString()
      .isLength(6, 16, '密码必须是6~16位字符')

    ctx.validateBody('pwd2')
      .required('密码必须必填项')
      .isString()
      .eq(ctx.vals.pwd1, '两次密码不一致')

    // 校验数据库是否存在相同值
    // ctx.validateBody('uname')
    // .check(await db.findUserByUname(ctx.vals.uname),'Username taken')
    // ctx.validateBody('uname').check('tom', '用户名已存在')

    //如果代码执行到这里,校验通过
    // 校验器会用净化后的值填充·ctx.vals·对象
    console.log(ctx.vals);
    ctx.body = {
      ok: 1
    }
  } catch (error) {
    // 校验异常特别判断
    if (error instanceof bouncer.ValidationError) {
      ctx.status = 400
      ctx.body = '校验失败:' + error.message;
      return;
    }
    throw error
  }
})

图形验证码

1.下载并引入trek-captcha

2.写验证码接口 captcha来获取随机验证码

const captcha = require('trek-captcha');
router.get('/captcha', async (ctx, next) => {
  const { token, buffer } = await captcha({ size: 4 });
  // //token的作用 前端输入完验证码与此时的token做对比
  ctx.body = buffer;
})

3.前端如果想换其他随机验证码可以让请求数据的src加上时间Date now()

changeCaptcha() {
  this.$refs.captcha.src = '/users/captcha?r=' + Date.now();
},

this.$refs.captcha是验证码的标签 每请求一次都会返回不同的验证码

文章参考自 路飞学城-小马哥老师