【问题标题】:Creating django objects with a random primary key使用随机主键创建 django 对象
【发布时间】:2011-03-18 17:56:13
【问题描述】:

我正在使用一个 API,它希望我为其 API 的交易生成不透明的“参考 ID”,换句话说,用户无法以任何方式猜测或推断的唯一参考。 (“推断”是正确的英语吗?)

这是我目前一起破解的:

randomRef = randint(0, 99999999999999)
while Transaction.objects.filter(transactionRef = randomRef).count():
    randomRef = randint(0, 99999999999999)

Transaction.objects.create(user=user, transactionRef=randomRef, price=999)

不幸的是,我的数据库目前似乎缺少交易。我已经意识到我的方法不是特别线程安全(比如说我在多个 mod_wsgi apache 线程上运行相同的 django 代码,它们都可能生成相同的 randomRef!)

有没有人有更好的技巧来为我生成随机主键?

【问题讨论】:

    标签: python django random primary-key


    【解决方案1】:

    为什么不直接加密普通的顺序 ID?对于不知道加密密钥的人来说,ID 看起来是随机的。您可以编写一个包装器,在通往 DB 的途中自动解密 ID,并在从 DB 的途中对其进行加密。

    【讨论】:

    • 这是个好主意,谢谢。我考虑过对 userid 和标准 transaction.id 的组合进行哈希处理,但这也不能保证是唯一的。你能推荐一个好的python函数来加密吗?我已经安装了 m2crypto 作为另一个软件包的先决条件,但还没有真正使用它。
    • 对不起@AlexBliskovsky,我是加密货币 n00b。有没有办法将加密输出指定为一个小整数?在概念上是否可行(确保没有冲突,除非我们确保纯文本的域小于加密文本的域)?
    【解决方案2】:

    我根据这个问题创建了一个要点:https://gist.github.com/735861

    按照 Amber 的建议,使用 DES 对私钥进行加密和解密。加密密钥以 base 36 表示,但只要表示是唯一的,任何其他基于字符的表示都可以工作。

    任何需要这种加密私钥表示的模型只需要从代码中显示的模型和管理器继承即可。

    这是代码的核心:

    import struct
    from Crypto.Cipher import DES
    from django.db import models
    
    class EncryptedPKModelManager(models.Manager):
        """Allows models to be identified based on their encrypted_pk value."""
        def get(self, *args, **kwargs):
            encrypted_pk = kwargs.pop('encrypted_pk', None)
            if encrypted_pk:
                kwargs['pk'] = struct.unpack('<Q', self.model.encryption.decrypt(
                    struct.pack('<Q', encrypted_pk)
                ))[0]
            return super(EncryptedPKModelManager, self).get(*args, **kwargs)
    
    
    class EncryptedPKModel(models.Model):
        """Adds encrypted_pk property to children."""
        encryption = DES.new('8charkey') # Change this 8 character secret key
    
        def _encrypted_pk(self):
            return struct.unpack('<Q', self.encryption_obj.encrypt(
                str(struct.pack('<Q', self.pk))
            ))[0]
    
        encrypted_pk = property(_encrypted_pk)
    
        class Meta:
            abstract = True
    

    对于名为transactionTransaction 对象,transaction.encrypted_pk 将返回私钥的加密表示。 Transaction.objects.get(encrypted_pk=some_value) 将根据加密的私钥表示搜索对象。

    需要注意的是,此代码假定仅适用于可以正确表示为长值的私钥。

    【讨论】:

      【解决方案3】:

      jbrendel 创建了一个类,您可以简单地继承它来获取自定义 ID。

      https://github.com/jbrendel/django-randomprimary

      【讨论】:

      • 它似乎不适用于 django 管理员。它不会让我用空白 id 保存新记录,因为它是必填字段;如果我定义一个 id 则不会生成随机的。也许这只适用于以前的 Django 版本?
      • @sherbang 要使其与管理员一起使用,只需修改 if self.id: 部分。如果您仔细查看 cmets,它会显示“# 显然,我们已经知道我们的 ID,因此我们不必在这里 # 做任何特别的事情。”
      【解决方案4】:

      os.urandom(n) 可以“返回适合加密使用的 n 个随机字节的字符串”。只需确保n 足够大,2**(8*n) 远高于您要识别的“唯一”键数量的平方,并且您可以将碰撞风险降至最低如你所愿。例如,如果您认为最终可能会完成 10 亿笔交易(大约 2**30),n=8 可能就足够了(但要谨慎行事,无论如何都要使用更大的 n;-)。

      【讨论】:

        【解决方案5】:

        随机整数不唯一,主键必须唯一。将关键字段设为 char(32) 并尝试这样做:

        from uuid import uuid4 as uuid
        randomRef = uuid().hex
        

        UUIDs 非常擅长提供独特性。

        【讨论】:

        • uuid4 生成的两个 UUID 不保证是唯一的,只有极小概率(但非零)发生冲突。
        • 谢谢,我以前从未听说过 UUID,它们似乎比我目前的 randint 好得多。我还应该通过循环运行以确保 UUID 未被使用吗?
        • 如果您尝试插入非唯一键,您的数据库层将抛出异常。您可以捕获该异常并创建另一个 UUID。虽然当我发布它时我认为这是一个很好的答案,但如果您可以实施它们,Alex Martelli 和 Amber 都给出了更好的建议。
        • 使用长字符字段作为主键听起来不是个好主意。
        【解决方案6】:

        您应该能够将数据库中的 transactionRef 列设置为唯一的。这样,数据库将不允许添加具有相同 transactionRef 值的事务。一种可能性是randomly generate UUIDs——随机 UUID 碰撞的概率非常小。

        【讨论】:

          猜你喜欢
          • 2014-05-05
          • 2023-04-05
          • 2013-04-02
          • 2021-10-30
          • 2015-01-04
          • 2018-03-14
          • 1970-01-01
          • 1970-01-01
          • 2019-05-13
          相关资源
          最近更新 更多