Django创建PurchasedKey对象时,如何从Software的JSONField中取出并移除激活密钥
Django软件激活密钥存储与购买分配方案优化
一、替代JSONField的更优存储方案
用JSONField存批量密钥不是最佳实践,推荐单独创建Key模型来管理每个激活密钥,理由如下:
- 符合数据库设计范式,避免嵌套数据操作的繁琐
- 方便快速查询、统计可用/已售出密钥数量
- 更容易实现并发安全,防止同一密钥被重复分配
优化后的模型代码:
from django.db import models from django.contrib.auth.models import User class Software(models.Model): name = models.CharField(max_length=20) class Key(models.Model): SOFTWARE_KEY_STATUS = ( ('available', '可用'), ('sold', '已售出'), ) software = models.ForeignKey(Software, on_delete=models.CASCADE, related_name='keys') activation_key = models.CharField(max_length=100, unique=True) status = models.CharField(max_length=20, choices=SOFTWARE_KEY_STATUS, default='available') class PurchasedKey(models.Model): purchased_by = models.ForeignKey(User, on_delete=models.CASCADE) software = models.ForeignKey(Software, on_delete=models.CASCADE) activation_key = models.OneToOneField(Key, on_delete=models.CASCADE) purchased_at = models.DateTimeField(auto_now_add=True)
二、并发安全的密钥购买实现逻辑
为避免高并发场景下重复分配密钥,使用Django事务+select_for_update锁行机制:
from django.db import transaction from .models import Software, Key, PurchasedKey def purchase_key(user_id, software_id): try: with transaction.atomic(): # 锁定可用密钥行,防止并发修改 available_key = Key.objects.select_for_update().filter( software_id=software_id, status='available' ).first() if not available_key: raise ValueError("该软件暂无可用激活密钥") # 创建购买记录 purchased_key = PurchasedKey.objects.create( purchased_by_id=user_id, software_id=software_id, activation_key=available_key ) # 更新密钥状态为已售出 available_key.status = 'sold' available_key.save() return purchased_key.activation_key.activation_key except Exception as e: raise e
三、若坚持使用JSONField的实现方式(不推荐)
如果必须保留原JSONField设计,需注意并发控制,用原子更新避免竞态条件:
首先修正PurchasedKey的字段:
class PurchasedKey(models.Model): purchased_by=models.ForeignKey(User, on_delete=models.CASCADE) software=models.ForeignKey(Software, on_delete=models.CASCADE) activation_key=models.CharField(max_length=100) # 存储单个密钥的字符串字段
然后实现分配逻辑:
from django.db import transaction, F from django.db.models.functions import ArrayRemove from .models import Software, PurchasedKey def purchase_key_with_json(user_id, software_id): try: with transaction.atomic(): # 锁定软件对象,防止并发修改可用密钥列表 software = Software.objects.select_for_update().get(id=software_id) if not software.available_keys: raise ValueError("该软件暂无可用激活密钥") # 取出第一个可用密钥 activation_key = software.available_keys[0] # 创建购买记录 PurchasedKey.objects.create( purchased_by_id=user_id, software=software, activation_key=activation_key ) # 原子更新,从JSON数组中移除已分配的密钥 Software.objects.filter(id=software_id).update( available_keys=ArrayRemove(F('available_keys'), activation_key) ) return activation_key except Exception as e: raise e
注:JSONField方案存在局限性,比如部分数据库对
ArrayRemove函数支持有限,统计密钥数量需要解析JSON,并发控制复杂度更高。
内容的提问来源于stack exchange,提问作者HimDek
相关产品推荐
相关产品推荐

