前言
原因: 前几天基于Django做了TodoList案例之后, 有前端同学拿着我的Django后端API访问,给我一顿喷,说我的API设计的怎么这么乱,然后就走了, 走的时候就留下了一句: 都2023年了,你居然还不知道RestFul
🚀 所以我下去之后立马看了看什么是RestFul
在前后端分离开发模式中, 对于接口的请求方式与路径,每个后端开发人员都有自己独特的定义方式与风格,这就导致了你对接不同的后端,就要适应不同风格的Api
那么是否存在一种统一的定义方式,能够被广大的开发人员所接受呢? —— RestFul, 这也就是我们今天的主题
RestFul
RestFul Api只是一种接口风格, 即使你不遵守这个风格, 你的代码逻辑, 前后端联调也是能够正常完成的(上面我个人的案例就能够证明)
下面我放一组RestFul风格的API, 和我之前的做一下对比,相信你们就会一目了然
请求方法 | 请求路径 | 含义 |
GET | /books | 查询所有的图书信息 |
POST | /books | 添加图书 |
GET | /books/1 | 查询id为1的图书详情 |
PUT | /books/1 | 更新id为1的图书信息 |
DELETE | /books/1 | 删除id为1的图书 |
请求方法 | 请求路径 | 含义 |
GET | /list | 查询所有任务信息 |
POST | /create | 创建任务 |
POST | /update | 更新任务信息 |
DELETE | /delete | 删除任务信息 |
看完这两张表之后我相信大家更喜欢也更乐意去调用第一种风格的Api
这里我列举几点RestFul Api的风格
1、资源作为网址,不能有动词
上面我的api风格就全是动词, create, update,delete, 但是反之RestFul采用名词, books, 而且这里的名词一般多数使用复数形式
2、搭配合理的HTTP动词
- GET 从服务器中取出资源
- POST 在服务器中新建一个资源
- PUT 更新服务器资源
- DELETE 从服务器中删除资源
除了这几个我们耳熟能详的HTTP动词,还有三个不常用的,借此机会,我们也要了解一下
- PATCH 在服务器更新资源
- HEAD 获取资源的元数据
- OPTIONS 获取信息, 关于资源的哪些属性是客户端可以改变的
3、过滤信息
Api也应该主动提供参数,过滤返回的结果, 下面是一些常见的参数
?limit=10 指定返回记录数量
?pageSize=10&pageNum=1 指定访问第几页,以及每页的数量
?orderBy=age&order=asc 指定返回结果按照哪个属性排序,以及排序的顺序
4、状态码
200 OK - [GET: 服务器成功返回用户请求的数据
201 CREATED -[POST/PUT/PATCHJ: 用户新建或修改数据成功。
202 Accepted -[]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT -[DELETE]:用户制除数据成功。
400 INVALID REQUEST-[POST/PUT/PATCH]: 用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized-[l:表示用户没有权限 (令牌、用户名、密码错误)。403 Forbidden -[ 表示用户得到授权 (与401错误相对),但是访问是被禁止的。
404 NOT FOUND-[]: 用户发出的请求针对的是不存在的记录,服务器没有进行操作.
406 Not Acceptable - [GET]: 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GEI: 用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity -[POST/PUT/PATCH]当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR-[: 服务器发生错误,用户将无法判断发出的请求是否成功
5、返回值
返回值应该是JSON格式的数据,除了DELETE方法可以没有返回体,其他的都应该有合适的返回体。
6、错误处理
如果状态码是4xx, 服务器应该向用户返回错误信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{
error: "has no token"
}
实践
知道了什么事Restful,就要动手实践一下,把我们之前写过的todoList案例中的接口改造成RestFul风格。
以下是五个要写的基本接口
todoList/views.py
"""
get /todos
post /todos
get /todos/pk
post /todos/pk
delete /todos/pk
"""
引入基础包以及定义基础返回内容
from django.http import JsonResponse, HttpResponse
from .models import TodoList
from django.views import View
import json
BaseResponseData = {
'code': 200,
'message': '',
'data': None
}
获取所有的todo列表信息和 增加todo的接口
class TodoListView(View):
def get(self, request):
todos = TodoList.objects.filter(is_delete=0)
todo_list = []
for todo in todos:
todo_dict = {
'id': todo.id,
'title': todo.title,
'completed': todo.completed,
'create_time': todo.create_time,
'update_time': todo.update_time,
}
todo_list.append(todo_dict)
return JsonResponse(todo_list, safe=False)
def post(self, request):
print(request)
post_dict = json.loads( request.body )
todo = TodoList.objects.create(title=post_dict.get("title"))
response_dict = {
'code': 200,
'message': "创建成功",
'todo': {
'id': todo.id,
'title': todo.title,
'completed': todo.completed,
'create_time': todo.create_time,
'update_time': todo.update_time,
}
}
return JsonResponse(response_dict, safe=False)
获取、删除、修改单个todo信息的接口
class TodoDetailView(View):
def get(self, request, pk):
try:
todo = TodoList.objects.get(pk=pk, is_delete=0)
except TodoList.DoesNotExist:
return JsonResponse({"message": "任务不存在"}, status=404)
todo_dict = {
'id': todo.id,
'title': todo.title,
'completed': todo.completed,
'create_time': todo.create_time,
'update_time': todo.update_time,
}
return JsonResponse(todo_dict, safe=False)
def put(self, request, pk):
try:
todo = TodoList.objects.get(pk=pk, is_delete=0)
except TodoList.DoesNotExist:
return JsonResponse({"message": "任务不存在"}, status=404)
todo.completed = 1 if todo.completed == 0 else 0
todo.save()
response_dict = {
'code': 200,
'message': "任务更新成功",
'todo': {
'id': todo.id,
'title': todo.title,
'completed': todo.completed,
'create_time': todo.create_time,
'update_time': todo.update_time,
}
}
return JsonResponse(response_dict, safe=False)
def delete(self, request, pk):
try:
todo = TodoList.objects.get(pk=pk, is_delete=0)
except TodoList.DoesNotExist:
return JsonResponse({"message": "任务不存在"}, status=404)
todo.is_delete = 1
todo.save()
response_dict ={
'code': 200,
'message': "删除成功"
}
return JsonResponse(response_dict, safe=False)
todoList/urls.py
from django.urls import path
from todoList import def_views as todo_views
from todoList import views
urlpatterns = [
# 未使用ResfFul风格对应的url
path("create", todo_views.create_todo_view),
path('update', todo_views.update_todo_view),
path("delete", todo_views.delete_todo_view),
path('list', todo_views.todo_list_view),
# RestFul风格的url, 对比来看更加简洁,优雅
path("todos", views.TodoListView.as_view()),
path("todos/<int:pk>", views.TodoDetailView.as_view()),
]
到这里为止,todoList代码就改造完成了。
和之前是有些不一样的,细心的话可以注意到这里写的是类视图,之前写的是函数视图,这里大概列出他们之间有什么区别:
1、类视图中可以写方法,比如get,put,delete对应的就是调用Api时的HTTP动词; 而在方法视图中则是需要通过reques.method去拿到访问此APi当前的HTTP动词是什么,再去判断使用什么样的方法,就会出现很多的if-else
2、类视图中的方法第一个参数是self,可以调用自己内部的其他属性和函数
3、类视图需要继承一个基础的视图类,这里使用的是 from django.views import View,这里剧透一下,之后我们还会学习很多诸如此类的基础视图类
这里我放一段方法视图的代码,大家可以观察一下他们的区别
def delete_todo_view(request):
if request.method == 'DELETE':
id = request.GET.get('id')
TodoList.objects.filter(pk=id).update(is_delete=1)
BaseResponseData['message'] = '任务删除成功'
else:
BaseResponseData['message'] = '方法错误'
return HttpResponse(json.dumps(BaseResponseData))
从本篇文章之后的所有的Api都会使用RestFul风格的类视图去编写。