要解决的问题

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. 最短路之Dijkstra(单源)HDU 2544

    #include <iostream> using namespace std; ; ][]; ]; int middist; ]; void dijkstra(int n,int m) ...

  2. Spring+JCaptcha验证码使用示例

    1,导入jcaptcha.jar包,这里用的是1.0版本 2,编写captcha-context.xml配置文件(非必须,可在spring配置文件中直接添加): <?xml version=&q ...

  3. 安卓,IOS真机调试

    移动端前端开发真机调试攻略 有线调试: 一.IOS 移动端 (Safari开发者工具) 手机端:设置 → Safari → 高级 → Web 检查器 → 开. mac端:Safari → 偏好设置 → ...

  4. na 残

    题目描述: 对于斐波那锲数列f(0)=0,f(1)=1,....求f(f(n)的值 0<=n<=10^100 给出T组数据,每行一个n 输出n行 f(f(n)) 样例输入: 4 0 1 2 ...

  5. 用vue.js实现购物车功能

    购物车是电商必备的功能,可以让用户一次性购买多个商品,常见的购物车实现方式有如下几种: 1. 用户更新购物车里的商品后,页面自动刷新. 2. 使用局部刷新功能,服务器端返回整个购物车的页面html 3 ...

  6. centos 7下Hadoop 2.7.2 伪分布式安装

    centos 7 下Hadoop 2.7.2 伪分布式安装,安装jdk,免密匙登录,配置mapreduce,配置YARN.详细步骤如下: 1.0 安装JDK 1.1 查看是否安装了openjdk [l ...

  7. Android 实现九宫格、点击图片放大全屏浏览等

    项目GitHub地址https://github.com/tikeyc/TNinePlaceGridView_Android https://github.com/tikeyc/TikeycAndro ...

  8. linux winqq 不能输入中文的解决办法

    wineqq的运行脚本是/usr/share/deepinwine/qqintl/wine-qqintl编辑此脚本,在最开始加入: export XMODIFIERS="@im=fcitx& ...

  9. Linux Device Driver 学习(1)

    Linux Device Driver 学习(1) 一.搭建虚拟机开发环境 1.选择虚拟机VirtualBox,官网下载.deb包安装: VirtualBox Linux 5.1.6 下载fedora ...

  10. Hibernate查询方式汇总

    Hibernate总的来说共有三种查询方式:HQL.QBC和SQL三种.但是细分可以有如下几种: 一.HQL查询方式    这一种我最常用,也是最喜欢用的,因为它写起来灵活直观,而且与所熟悉的SQL的 ...