如何优雅地在Django项目里生成不重复的ID?

前言

本来标题是想叫"生成不重复的四位数"的,不过单纯数字有点局限,推广一下变成不重复 ID 吧~

这个功能是在做下面图片里这个小项目时遇到的,有点像微信的面对面建群,生成一个随机且不重复的密码,其他人输入这个密码就能加入教室。

实现这个功能有不少方法,本文简单记录一下。

不依赖第三方库

首先单纯基于 Django ORM 来实现这个功能

先定义一个模型

python 复制代码
from django.db import models

class MyModel(models.Model):
    unique_code = models.CharField(max_length=4, unique=True)

单任务

最简单粗暴的方法,写一个死循环

python 复制代码
import random

def generate_unique_code():
    while True:
        code = str(random.randint(1000, 9999))
        if not MyModel.objects.filter(unique_code=code).exists():
            return code

这个实现在单线程测试环境下肯定是没问题的,不过这个操作并不是原子化的,并发环境下可能会生成重复的数字。

考虑并发

高并发情况下,可以使用数据库事务或乐观锁。

python 复制代码
from django.db import transaction, IntegrityError

def create_instance():
	  # 尝试次数
  	retry = 10
    for _ in range(retry):
        code = generate_unique_code()
        try:
            with transaction.atomic():
                instance = MyModel(unique_code=code)
                instance.save()
            return instance
        except IntegrityError:
            # 如果出现唯一性冲突,重新尝试
            continue
    raise Exception("无法生成唯一的四位数")

预先生成

前面两种方法都要频繁读取数据库,性能比较差。

还可以用空间换时间的方式,因为只是四位数,0000-9999 这个范围的数字也不多,预先把这一万行存入数据库,加个 available 字段

当需要生成唯一 ID 的时候,就先筛选 available == True 的数据,然后随机抽取一个;并且把这个字段设置为 False

大概思路就是这样

使用第三方库

在这个项目里,我搭配使用了这三个库(这也是我写这篇文章的主要目的,记录一下这几个库)

  • shortuuid
  • hashids
  • django-autoslug

shortuuid

shortuuid 是一个轻量级的库,可以生成比较短的 UUID

使用这个库来实现这个功能的话很简单

python 复制代码
import shortuuid
shortuuid.ShortUUID(alphabet="0123456789").random(length=4)

不过这个项目中,我并没有使用这个库来做这个

事实上,这个库顾名思义有个 uuid,自然是用来做与 python 内置 UUID 有关工作

我用这个库把 Client 模型的 ID 简化到 7 位

python 复制代码
class Client(ModelExt):
    client_id = models.UUIDField(default=uuid.uuid4, editable=False)
    client_key = models.CharField(max_length=100, default=uuid.uuid4)
    user = models.OneToOneField(
        User, on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True, unique=True,
    )
    consumer_name = models.CharField(max_length=100, null=True, blank=True)
    is_online = models.BooleanField(default=False)

    def short_client_id(self):
        short = shortuuid.encode(self.client_id)
        return short[:7]

使用 shortuuid.encode 方法可以把 32 位的 UUID 变成 22 位,并且还能使用 decode 方法复原

hashids

这个是将数字转为短字符串的库

虽然名字里带个 hash ,但这个库的编码是可逆的

python 复制代码
import hashids
h = hashids.Hashids()
h.encode(123, 456)
# Out[15]: 'X68fkp'
h.decode('X68fkp')
# Out[16]: (123, 456)

我用来根据时间戳生成教室名称

python 复制代码
def get_timestamp_hashid():
    hashids = Hashids(salt='hahaha salt lala')
    t = timezone.now().timestamp()
    result = tuple(map(int, str(t).split('.')))
    return hashids.encode(*result)

因为 encode 方法接收的是数字(也可以是包含数字的 tuple),所以这里把时间戳的整数部分和小数部分转换为 tuple 然后传入 hashids

django-autoslug

这个库用于生成基于字段的唯一 slug,同时可以自定义生成逻辑。

我就是用这个库来实现生成唯一的教室密码功能(但并不是很推荐这种方式)

python 复制代码
from autoslug import AutoSlugField


def populate_classroom_number(instance):
    return str(random.randint(1000, 9999))
  
class ClassroomIdiom(ModelExt):
    name = models.CharField(max_length=100)
    number = AutoSlugField(populate_from=populate_classroom_number, unique=True)

这个库的原理很简单,根据用户定义的规则生成 slug,然后检查数据库是否重复,遇到重复的话就在后面追加数字,这样有可能导致生成出来的数字超过 4 位数

最好的还是我前面说的 不依赖第三方库 的第三种方式。

这里使用 django-autoslug 单纯是为了偷懒,把复杂的判断逻辑交给第三方库,毕竟这只是个玩具项目。

小结

在生成唯一 ID 这件事上,Django 和其他后端框架没啥不同的,思路都是类似的,只不过可以借助 Python 生态偷懒一下...

相关推荐
恸流失6 小时前
DJango项目
后端·python·django
编程大全15 小时前
41道Django高频题整理(附答案背诵版)
数据库·django·sqlite
网安小张18 小时前
解锁FastAPI与MongoDB聚合管道的性能奥秘
数据库·python·django
KENYCHEN奉孝19 小时前
Pandas和Django的示例Demo
python·django·pandas
老胖闲聊20 小时前
Python Django完整教程与代码示例
数据库·python·django
noravinsc20 小时前
django paramiko 跳转登录
后端·python·django
践行见远20 小时前
django之请求处理过程分析
数据库·django·sqlite
声声codeGrandMaster20 小时前
Django之表格上传
后端·python·django
菌菌的快乐生活1 天前
网站静态文件加速-Django项目静态文件存储到腾讯云COS存储提升网络请求速度
django·cos存储
noravinsc1 天前
django ssh登录 并执行命令
django·sqlite·ssh