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)