在DRF中实现课程购买优惠券的使用次数与有效期限制
DRF优惠券系统的有效性限制实现方案
要实现基于使用人数(如前50名用户)和时间段(如未来一周)的优惠券有效性限制,你可以从以下几个核心环节入手:
一、数据库模型设计
首先需要在模型中存储优惠券的限制规则和使用状态:
from django.db import models from django.utils import timezone from django.contrib.auth.models import User class Coupon(models.Model): code = models.CharField(max_length=50, unique=True) max_usage = models.PositiveIntegerField(default=0, help_text="允许使用的最大用户数,0表示无限制") used_count = models.PositiveIntegerField(default=0, verbose_name="已使用次数") valid_from = models.DateTimeField(default=timezone.now, verbose_name="生效时间") valid_until = models.DateTimeField(verbose_name="失效时间") is_active = models.BooleanField(default=True, verbose_name="是否激活") def __str__(self): return self.code # 记录用户与优惠券的关联,防止重复使用 class CouponUsage(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='coupon_usages') coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE) used_at = models.DateTimeField(default=timezone.now) class Meta: unique_together = ('user', 'coupon') # 确保同一用户无法重复使用同一优惠券
二、优惠券有效性验证
在序列化器中实现验证逻辑,检查优惠券是否满足时间段、使用人数限制,以及用户是否已使用过:
from rest_framework import serializers from .models import Coupon from django.utils import timezone class CouponApplySerializer(serializers.Serializer): code = serializers.CharField(max_length=50) def validate_code(self, value): try: coupon = Coupon.objects.get(code=value, is_active=True) except Coupon.DoesNotExist: raise serializers.ValidationError("无效的优惠券码") # 验证时间段有效性 now = timezone.now() if now < coupon.valid_from or now > coupon.valid_until: raise serializers.ValidationError("优惠券不在有效期内") # 验证使用人数限制 if coupon.max_usage > 0 and coupon.used_count >= coupon.max_usage: raise serializers.ValidationError("优惠券已被抢光") # 验证当前用户未使用过该优惠券 user = self.context['request'].user if user.coupon_usages.filter(coupon=coupon).exists(): raise serializers.ValidationError("你已使用过该优惠券") return coupon
三、视图层处理逻辑
在视图中处理优惠券申请请求,验证通过后更新使用次数并记录使用记录:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .serializers import CouponApplySerializer from .models import CouponUsage from django.db import transaction class ApplyCouponView(APIView): def post(self, request): serializer = CouponApplySerializer(data=request.data, context={'request': request}) if serializer.is_valid(): coupon = serializer.validated_data['code'] # 原子更新使用次数,避免并发超卖 with transaction.atomic(): # 加锁防止并发修改 locked_coupon = Coupon.objects.select_for_update().get(pk=coupon.pk) if locked_coupon.used_count >= locked_coupon.max_usage and locked_coupon.max_usage > 0: return Response({"error": "优惠券已被抢光"}, status=status.HTTP_400_BAD_REQUEST) locked_coupon.used_count += 1 locked_coupon.save() # 记录用户使用记录 CouponUsage.objects.create(user=request.user, coupon=coupon) return Response({"message": "优惠券使用成功", "coupon_code": coupon.code}, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
四、关键注意事项
- 并发安全:使用
select_for_update或F()表达式确保优惠券使用次数的更新是原子操作,避免多个用户同时操作导致超卖。 - 时间准确性:依赖Django的
timezone.now()确保时间验证基于服务器时区,避免客户端时间误差。 - 状态管理:通过
is_active字段可以快速禁用优惠券,无需删除数据。
参考资料
- Django官方文档:模型字段定义、数据库事务、F表达式
- DRF官方文档:序列化器验证、API视图类
内容的提问来源于stack exchange,提问作者Kartikey
相关产品推荐
相关产品推荐

