搜索
您的当前位置:首页正文

RESTfulAPI批量操作的实现

来源:意榕旅游网
RESTfulAPI批量操作的实现

要解决的问题

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

DELETE /api/resourse//

如果我们要删除100条数据怎么搞?难道要调⽤100次接⼝吗?⽐较容易想到的是下⾯两种⽅案:

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

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

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

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)

因篇幅问题不能全部显示,请点此查看更多更全内容

Top