要解决的问题

RESTful API对于批量操作存在一定的缺陷。例如资源的删除接口:

DELETE /api/resourse/<id>/

如果我们要删除100条数据怎么搞?难道要调用100次接口吗?

比较容易想到的是下面两种方案:

  1. 用逗号分割放进url里:/api/resource/1,2,3...
  2. 将需要删除的资源的id放到请求体里面

对于方案1,由于浏览器对url的长度存在限制,如果操作的资源过多就无法实现。

对于方案2,这种处理方式存在一定的风险,因为根据RPC标准文档,DELETE的请求体在语义上没有意义,一些网关、代理、防火墙在收到DELETE请求后,会把请求的body直接剥离掉。

所以我参考https://www.npmjs.com/package/restful-api,将批量处理的操作名称和数据全部放到请求体里,统一使用POST请求发送:

POST /api/resource/batch/
Body: {
"method": "create",
"data": [ { "name": "Mr.Bean" }, { "name": "Chaplin" }, { "name": "Jim Carrey" } ]
} POST /api/resource/batch/
Body: {
"method": "update",
"data": { "1": { "name": "Mr.Bean" }, "2": { "name": "Chaplin" } }
} POST /api/resource/batch/
Body: {
"method": "delete",
"data": [1, 2, 3]
}

Python实现

环境:python3.6.5, django2.2, djangorestframework==3.9.4

GenericViewSet中加入了一些自定义的分发逻辑,将相应的Batch View放在Mixin里实现可重用。

class BatchGenericViewSet(GenericViewSet):
batch_method_names = ('create', 'update', 'delete')
def batch_method_not_allowed(self, request, *args, **kwargs):
method = request.batch_method
raise exceptions.MethodNotAllowed(method, detail=f'Batch Method {method.upper()} not allowed.') def initialize_request(self, request, *args, **kwargs):
request = super().initialize_request(request, *args, **kwargs)
# 将batch_method从请求体中提取出来,方便后面使用 
batch_method = request.data.get('method', None)
if batch_method is not None:
request.batch_method = batch_method.lower()
else:
request.batch_method = None
return request def dispatch(self, request, *args, **kwargs):
       self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers
try:
self.initial(request, *args, **kwargs)
# 首先识别batch_method并进行分发
if request.batch_method in self.batch_method_names:
method_name = 'batch_' + request.batch_method.lower()
handler = getattr(self, method_name, self.batch_method_not_allowed)
elif request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response

下面是Mixin,因为懒所以放在了一个里面:

class BatchMixin:

    def batch_create(self, request, *args, **kwargs):
"""
Create a batch of model instance request body like this:
{
"method": "create",
"data": [
{
"name": "Mr.Liu",
"age": 27
},
{
"name": "Chaplin",
"age": 88
}
]
}
"""
data = request.data.get('data', None)
if not isinstance(data, list):
raise exceptions.ValidationError({'data': 'Data must be a list.'})
serializer = self.get_serializer(data=data, many=True)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def batch_update(self, request, *args, **kwargs):
"""
Update a batch of model instance request body like this:
{
"method": "update",
"data": {
1: { "name": "Mr.Liu" },
2: { "name": "Jim Carrey" }
}
}
"""
data = request.data.get('data', None)
if not isinstance(data, dict):
raise exceptions.ValidationError({'data': 'Data must be a object.'})
ids = [int(id) for id in data]
queryset = self.get_queryset().filter(id__in=ids)
results = []
for obj in queryset:
serializer = self.get_serializer(obj, data=data[str(obj.id)], partial=True)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
self.perform_update(serializer)
results.append(serializer.data)
return Response(results) def batch_delete(self, request, *args, **kwargs):
"""
Delete a batch of model instance request body like this:
{
"method": "delete",
"data": [1, 2]
}
"""
data = request.data.get('data', None)
if not isinstance(data, list):
raise exceptions.ValidationError({'data': 'Data must be a list.'})
queryset = self.get_queryset().filter(id__in=data)
with transaction.atomic():
self.perform_destroy(queryset)
return Response(status=status.HTTP_204_NO_CONTENT)

这样实现对于restframework框架的ModelPermission权限判定会出现问题,因为所有请求都是通过POST实现的,默认情况下无法对Model的增、删、改权限进行有效的判断。稍微修改下DjangoModelPermissions就可以了:

class BatchModelPermissions(DjangoModelPermissions):
batch_method_map = {
'create': 'POST',
'update': 'PATCH',
'delete': 'DELETE'
} def has_permission(self, request, view):
if getattr(view, '_ignore_model_permissions', False):
return True if not request.user or (
not request.user.is_authenticated and self.authenticated_users_only):
return False queryset = self._queryset(view)
# 这里,这里
batch_method = getattr(request, 'batch_method', None)
if batch_method is not None:
perms = self.get_required_permissions(self.batch_method_map[batch_method], queryset.model)
else:
perms = self.get_required_permissions(request.method, queryset.model) return request.user.has_perms(perms)

参考:https://www.npmjs.com/package/restful-api

RESTful API批量操作的实现的更多相关文章

  1. 虚拟研讨会:如何设计好的RESTful API?

    http://www.infoq.com/cn/articles/how-to-design-a-good-restful-api/ REST架构风格最初由Roy T. Fielding(HTTP/1 ...

  2. Python自动化开发 - RESTful API

    本节内容 1.  RESTful 简介 2.  RESTful 设计指南 3.  Django REST Framework 最佳实践 4.  理论拓展与开放平台 5.  API文档化与测试 一  R ...

  3. 虚拟研讨会:如何设计好的RESTful API(转)

    原文:虚拟研讨会:如何设计好的RESTful API? REST架构风格最初由Roy T. Fielding(HTTP/1.1协议专家组负责人)在其2000年的博士学位论文中提出.HTTP就是该架构风 ...

  4. 人人都是 API 设计师:我对 RESTful API、GraphQL、RPC API 的思考

    原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. 有一段时间没怎么写文章了,今天提笔写一 ...

  5. 理解RESTful API

    近日妹子向我求助RESTful API到底是个什么东西.原因是她们公司一个新启动的项目因为RESTful API起了争执.服务端同学坚持要用RESTful API,而前端同学则认为服务端用RESTfu ...

  6. (转载) RESTful API 设计指南

    作者: 阮一峰 日期: 2014年5月22日 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......). 因此,必须有一种统一的机制 ...

  7. Node.js实现RESTful api,express or koa?

    文章导读: 一.what's RESTful API 二.Express RESTful API 三.KOA RESTful API 四.express还是koa? 五.参考资料 一.what's R ...

  8. Restful Api 最佳实践

    Web APIs has become an very important topic in the last year. We at M-Way Solutions are working ever ...

  9. 基于轻量型Web服务器Raspkate的RESTful API的实现

    在上一篇文章中,我们已经了解了Raspkate这一轻量型Web服务器,今天,我们再一起了解下如何基于Raspkate实现简单的RESTful API. 模块 首先让我们了解一下"模块&quo ...

随机推荐

  1. TabBar背景颜色设置

    // 第一种方式 // [[UITabBar appearance] setBarTintColor:[UIColor blackColor]]; // [UITabBar appearance].t ...

  2. bzoj2740 串 && bzoj2176 strange string(最小表示法模板)

    https://konnyakuxzy.github.io/BZPRO/JudgeOnline/2740.html 题解讲的很清楚了 (好像等于的情况应该归入case2而不是case1?并不确定) 具 ...

  3. 如何在Linux上升级java

    首先使用rpm -qa|grep gcj命令查找安装信息 卸载老版java: rpm -e <检索到软件名> 下载最新java JDK: 自行到oracle官网下载相应的版本,放到linu ...

  4. js 学习网站

    1.  Mozilla Developer Network(MDN) 这里你可以找到官方的完整的javascript参考,还有许多有用的指导,教程以及文章,从基本javascript使用到最佳实践以及 ...

  5. VUE注意事项(建项目)

    1>删除空格影响的:删除掉框中的代码 2>不需要新建,直接打开APP.vue,在此文件上进行修改,(注意:index.html最好不要进行修改) 3>修改APP.vue为自己需要的页 ...

  6. Linux-软件安装(一) —— jdk/tomact 安装(普通安装)

    Linux-软件安装(一) -- jdk/tomact 安装(普通安装) 1. 可使用 FinalShell 上传至 Linux 服务器 2. 解压 cd /usr/local #解压命令 tar - ...

  7. 2017 清北学堂 Day 6终极考试报告

    预计分数: 100+70+70 = 240 实际假分数 : 40+80+70= 190  in cena(好吧不得不承认这个分数,,,,,,=.=) 实际真分数 : 100+80+100 = 280 ...

  8. 【学习笔记】八:浏览器对象模型BOM

    1.window对象 window是BOM的核心,它既是JS访问浏览器的一个接口,又是ES规定的Global对象. 1)全局作用域对象 a.所有在全局作用域中声明的变量.函数都会成为window对象的 ...

  9. node.js0-5初级者

    伴着<妈是心中的茉莉花> 这里,我用的sublime记事本,所以用的运行方法是终端.(后来发现git 可以省去cd切换目录). 安装node.js  官网说的很清楚. 这里我们可以在js文 ...

  10. css3 省略号

    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; 也无需给元素设置固定宽度!