You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 17:50:44