要解决的问题

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. 微信开发(一)URL配置

    启用开发模式需要先成为开发者,而且编辑模式和开发模式只能选择一个,进入微信公众平台-开发模式,如下: 需要填写url和token,当时本人填写这个的时候花了好久,我本以为填写个服务器的url就可以了( ...

  2. 爬虫之图片懒加载技术、selenium和PhantomJS

    爬虫之图片懒加载技术.selenium和PhantomJS   图片懒加载 selenium phantomJs 谷歌无头浏览器 一.图片懒加载 什么是图片懒加载? 案例分析:抓取站长素材http:/ ...

  3. php—cURL库基本用法总结

    作用 用来连接客户端和服务器端,实从互联网上获取资源 常用接口 curl_init(): 初始化curl curl_close: 结束curl,释放资源 curl_setopt: 设置curl的属性 ...

  4. Problem D. What a Beautiful Lake dp

    Problem D. What a Beautiful Lake Description Weiming Lake, also named "Un-named Lake", is ...

  5. C#中 添加 删除 查找Xml中子节点

    //添加xml节点    private void AddXml(string image, string title)     {        XmlDocument xmlDoc = new X ...

  6. chrome浏览器之网络面板

    这篇指导向你展示怎样检测网络张状况或者在chrome开发工具的网络面板中尽可能的优化网页. 排列的或受阻的请求 症状:同时发出六个请求.之后有一系列的请求排队或受阻.一旦最先的六个请求中有一个响应结束 ...

  7. Web开发者应掌握的12个Firebug技巧

    来源: 廖煜嵘 相信很多从事Web开发工作的开发者都听说和使用过Firebug,但可能大部分人还不知道,其实它是一个在网页设计方面功能相当强大的编辑器,它 可以对HTML.DOM.CSS.HTTP和J ...

  8. uvm_void 寂静的空宇

    空也是一种存在.   ——<三体> 文件: $UVM_HOME/src/base/uvm_misc.svh   virtual class uvm_void; endclass   在静寂 ...

  9. IP-XACT IP IEEE交换格式

    1 What is  IP-XACT?      IP-XACT is an XML format that defines and describes electronic components a ...

  10. freebsd安装ports

    /etc/portsnap.conf 里面更改 SERVERNAME=portsnap.hshh.org portsnap的命令比较少 fetch 获取数据 extract 释放全部ports upd ...