如何在Django中生成唯一的slug,避免竞态条件?

后端开发 2026-07-09

我正在使用Django REST Framework为一个博客站点创建一个简单的API。我希望每篇博客文章都拥有一个唯一的、易读的slug,将在URL中使用,就像 /api/posts/my-first-post 那样。下面是我当前的实现:

# models.py

class Post(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(unique=True, blank=True, max_length=255)
    body = models.TextField()

    def save(self, *args, **kwargs) -> None:
        if not self.slug:
            import string, random
            self.slug = slugify(self.title)
            while Post.objects.filter(slug=self.slug).exists():
                suffix = '-' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=5))
                self.slug = slugify(self.title + suffix)
        super().save(*args, **kwargs)
# views.py

class PostList(generics.ListCreateAPIView):
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    lookup_field = 'slug'


    def perform_create(self, serializer):
        return serializer.save(author=self.request.user)

但显然 Post.objects.filter(slug=self.slug).exists() 会产生竞态条件,因为它是在应用层工作,而不是在数据库层工作,因此如果两个具有相同slug的帖子在同一时刻保存,可能会抛出一个 IntegrityError。我不确定该如何修复。

解决方案

我觉得可能找到了一个解决办法,采用EAFP风格,就像 @jonrsharpe 所建议的那样:

    def save(self, *args, **kwargs) -> None:
        if self.slug:
            return super().save(*args, **kwargs)
        for _ in range(10):
            try:
                self.slug = f'{slugify(self.title)[:246]}-{uuid.uuid4().hex[:8]}'
                return super().save(*args, **kwargs)
            except IntegrityError:
                self.slug = None
        raise RuntimeError(f'Could not generate unique slug for post {self.title}')

我们让它先失败,然后再重试,直到成功。不过这也可能会有问题。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章