如何为Django的ProductKey模型实现过期自动标记功能?
嘿,我来帮你搞定这个密钥过期自动标记的问题!针对你现在的ProductKey模型,这里有几种实用的方案,你可以根据自己的需求选:
其实很多时候,我们不需要把expired存在数据库里——直接通过valid_to和当前时间对比就能实时得到状态,这样还能避免数据不一致的问题。你可以给模型加个属性方法:
from django.utils import timezone from django.db import models class ProductKey(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, unique=False) key = models.CharField(max_length=14) valid_from = models.DateTimeField(default=timezone.now) valid_to = models.DateTimeField() # 如果你不需要持久化expired字段,可以直接删掉它,用下面的属性替代 # expired = models.BooleanField(default=False) @property def expired(self): return timezone.now() > self.valid_to
用的时候直接像访问字段一样用product_key.expired就行,比如验证密钥时:
if product_key.expired: # 密钥已过期的逻辑
优点:零维护成本,永远不会出现“数据库里expired是False但实际已经过期”的情况;缺点:不能用这个属性做数据库查询过滤(比如ProductKey.objects.filter(expired=True)),如果有这种需求就看下面的方案。
如果必须把expired存在数据库里(比如要经常用它做查询过滤),可以用定时任务定期检查并更新过期的密钥。比如用Django常用的Celery来实现:
第一步:写定时任务
在你的app下新建tasks.py:
from celery import shared_task from django.utils import timezone from .models import ProductKey @shared_task def mark_expired_keys(): # 找出所有已经过期但还没标记的密钥,批量更新 expired_keys = ProductKey.objects.filter( valid_to__lt=timezone.now(), expired=False ) expired_keys.update(expired=True)
第二步:设置任务执行频率
在Celery的配置文件(比如celery.py)里添加定时规则,比如每天凌晨跑一次:
from celery.schedules import crontab app.conf.beat_schedule = { 'daily-mark-expired-keys': { 'task': '你的app名称.tasks.mark_expired_keys', 'schedule': crontab(hour=0, minute=0), # 每天0点执行 }, }
优点:性能好,批量更新对数据库压力小;缺点:有延迟(比如密钥凌晨1点过期,要等到当天0点才会被标记),如果需要实时性,可以把执行间隔改短(比如每小时一次)。
如果你的应用里用户验证密钥的频率不低,也可以在每次验证的时候顺便检查并更新过期状态,这样用户看到的永远是最新的:
def verify_product_key(request): key = request.POST.get('key') try: product_key = ProductKey.objects.get(key=key) # 先检查是否过期,没标记的话立即更新 if timezone.now() > product_key.valid_to and not product_key.expired: product_key.expired = True product_key.save() if product_key.expired: return HttpResponse("抱歉,这个密钥已经过期了") # 验证通过,创建对应应用的逻辑 # ... return HttpResponse("验证成功!") except ProductKey.DoesNotExist: return HttpResponse("无效的密钥")
优点:实现简单,实时性强;缺点:只有用户验证时才会更新,要是有密钥过期但没人验证,数据库里的expired字段还是False——不过这种情况一般不影响业务,因为只有用户用的时候才需要知道状态。
如果需要每次查询ProductKey时都保证expired字段是最新的,可以自定义模型管理器:
from django.db import models from django.utils import timezone class ProductKeyManager(models.Manager): def get_queryset(self): # 先更新所有过期未标记的密钥,再返回查询集 self.model.objects.filter(valid_to__lt=timezone.now(), expired=False).update(expired=True) return super().get_queryset() class ProductKey(models.Model): # ... 现有字段 ... objects = ProductKeyManager() # 替换默认的管理器
这样不管你用ProductKey.objects.all()还是filter(),都会先更新过期的密钥。注意:如果你的应用数据量很大,每次查询都跑这个更新可能会影响性能,小体量应用可以用。
内容的提问来源于stack exchange,提问作者rector

