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)

相关推荐
2401_885885046 小时前
芬兰语音通知接口怎样对接?语音提醒接入流程
android·前端·python·php·语音识别
Dev _6 小时前
Python解释器安装在系统级目录导致命令行用户‌没有权限‌向该目录写入文件
开发语言·python
Dev _7 小时前
PyCharm旧进程仍在后台运行,导致新实例无法启动或连接
ide·python·pycharm
quantdash_cc8 小时前
如何检查滚动收益率计算中的窗口长度不足?——从数学定义到 Pandas 实现
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
砚底藏山河8 小时前
python量化:实时行情WebSocket断线重连状态机
java·python·websocket·金融
CodeGL8 小时前
16、Python Web开发
开发语言·前端·python
加点盐9 小时前
别再用 configparser 了:给本地配置加上加密、校验和版本迁移
python
小嘉丶学长9 小时前
Day08 · 字符串(二)
服务器·开发语言·python·学习·1024程序员节
BD_Marathon9 小时前
【Python】列表推导式
python
2501_948122639 小时前
Qs 跨端三方库仓颉适配全流程
python·react native·django·tornado