要解决的问题

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. redis开发小结

    随着缓存在web服务中用的越来越广泛,redis可以说成为了目前最流行的NoSQL数据库!redis与memcached最大的不同在于redis支持更多的数据类型,包括string.hash.list ...

  2. C/S 和 B/S 架构

    浏览器/服务器结构.它是C/S架构的一种改进,可以说属于三层C/S架构. 比较大的差别1.结构 C/S是两层架构,由客户端和服务器组成,而B/S是三层架构,由浏览器,WEB服务器和数据库服务器组成. ...

  3. 利用Common-Fileupload上传文件图片

    一,介绍 common-fileupload是appache的开源组件,基于该组件可以轻松实现文件上传的功能,strust框架的文件上传功能也是基于该组件. 二,使用 1,导入两个jar包:commo ...

  4. web基础笔记

    浏览器渲染页面的过程 浏览器渲染页面前需要先构建 DOM 和 CSSOM 树.因此,我们需要确保尽快将 HTML 和 CSS 都提供给浏览器. 参考:https://developers.google ...

  5. [已读]JavaScript高级程序设计(第2版)

    经典红皮书~~

  6. ubuntu触摸板关闭开启

    sudo rmmod psmouse          #用来禁用触摸板 sudo modprobe psmouse     #用来启用触摸板

  7. 如何在spring环境中做单元测试

    在测试类的上方加入以下注解 @RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:spring.xm ...

  8. 头部和信号栏一个颜色appcloud

    <header id="header" > <ul > <li class="active" onclick="api. ...

  9. oop典型应用,代码。

    遍历获得一个实体类的所有属性名,以及该类的所有属性的值.//先定义一个类: public class User{ public string name { get; set; } public str ...

  10. 浅析ES6中的iterator

    1.iterator迭代器必须保证其遍历终止条件可控,否则会形成死循环demo: //会用到iterator接口的场合 //1.for...of循环 //2. ...解构表达式 const obj = ...