DRF接口幂等操作

CACHES = {

'default': {

'BACKEND': 'django_redis.cache.RedisCache',

'LOCATION': 'redis://127.0.0.1:6379/1',

'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'}

}

}

幂等键保留时间(秒),建议比你业务最长处理时间久一点,比如7天

IDEMPOTENCY_TIMEOUT = 60 * 60 * 24 * 7

decorators.py

from django.core.cache import cachefrom django.http import JsonResponsefrom rest_framework.response import Responsefrom functools import wrapsimport hashlibimport jsondef idempotent(timeout=86400): """ DRF视图幂等装饰器 使用方式: @idempotent(timeout=3600) 放在 post/put 方法上 """ def decorator(view_func): @wraps(view_func) def wrapper(self, request, *args, **kwargs): # 1. 获取幂等键(优先Header,其次请求体) idem_key = request.headers.get('Idempotency-Key') or request.data.get('idempotency_key') if not idem_key: # 如果没有幂等键,直接执行(不阻塞,但非幂等) return view_func(self, request, *args, **kwargs) # 2. 生成唯一缓存Key(结合用户ID和路径,防止不同用户冲突) user_id = request.user.id if request.user.is_authenticated else 'anonymous' path = request.path cache_key = f"idem:{user_id}:{path}:{idem_key}" # 3. 尝试原子性占位(利用cache.add的原子性) # 设置占位值 "PROCESSING",防止并发请求同时执行业务 if not cache.add(cache_key, "PROCESSING", timeout=timeout): # 键已存在,说明请求正在处理或已处理完成 cached_result = cache.get(cache_key) if cached_result == "PROCESSING": # 并发冲突:另一个相同请求正在执行中 # 方案A:轮询等待(不推荐,占用连接) # 方案B:直接返回409,让客户端稍后重试(推荐) return Response( {"error": "Duplicate request is being processed, please retry later"}, status=409 # Conflict ) else: # 已处理完成,直接返回之前缓存的响应 return Response(cached_result.get('data'), status=cached_result.get('status')) # 4. 成功占用锁,执行业务逻辑 try: response = view_func(self, request, *args, **kwargs) # 判断响应类型,提取数据和状态码 if isinstance(response, Response): resp_data = response.data resp_status = response.status_code else: # 兼容普通的HttpResponse/JsonResponse resp_data = json.loads(response.content) if hasattr(response, 'content') else {} resp_status = response.status_code # 缓存成功结果(覆盖 "PROCESSING") cache.set(cache_key, {"data": resp_data, "status": resp_status}, timeout=timeout) return response except Exception as e: # 业务执行失败,删除缓存键,释放锁(允许客户端重试) cache.delete(cache_key) raise e # 继续抛出异常让DRF处理 return wrapper return decorator

views.py

from rest_framework.views import APIViewfrom .decorators import idempotentclass OrderAPIView(APIView): @idempotent(timeout=3600) # 该订单创建接口1小时内幂等 def post(self, request): # 假设这里会扣库存、创建订单 order = create_order(request.data) return Response({"order_id": order.id, "status": "created"}, status=201)

相关推荐
廿士1 小时前
python脚本使用相关
python
青 春 记 忆2 小时前
零基础入门python19:Flask账本第一步——应用工厂、蓝图和健康检查
python·flask·后端开发
朦胧之2 小时前
Python 后端核心知识
python
denggun123452 小时前
yield
前端·数据库·python
zx_741484813 小时前
【Python 入门】面向对象基础:类、对象、成员变量与构造方法
开发语言·python
招财小梗4 小时前
沈阳商贸公司AI企业服务优势几何?
大数据·人工智能·python
ZISHU_9875 小时前
用 TLabel 给 SynTouch BioTac 数据做语义标注:从原始信号到结构化标注
开发语言·人工智能·python·数据·机器人触觉
m0_617493946 小时前
SSLError [ASN1: NOT_ENOUGH_DATA] 问题排查与解决指南
python·ssl
whcyhhh6 小时前
头歌实践教学平台:数据科学与大数据技术导论(七上)
大数据·数据库·python