编写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是验证码的标签 每请求一次都会返回不同的验证码