Django中如何用自动生成的slug作为文章URL并避免重复冲突
解决slug重复问题的方案
- 首先新增数据库唯一约束
这是兜底的必备逻辑,避免任何上层校验遗漏导致的重复数据入库
修改字段定义为:
post_id = models.SlugField(unique=True, default=get_random_string, null=False, editable=False)
加了unique=True后,一旦出现重复slug,数据库会直接抛出IntegrityError,不会写入重复数据。
- 重写save方法做唯一性校验
在保存对象前循环生成随机slug,直到确认没有重复再写入,同时捕获并发场景下的异常做二次处理,示例代码如下:
import uuid from django.db import models, IntegrityError from django.utils.crypto import get_random_string from django.urls import reverse class Post(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) post_id = models.SlugField(unique=True, null=False, editable=False) def _generate_unique_slug(self, length=12): # 可自定义字符集,比如去掉易混淆的l、I、1、0、O等字符降低用户输入错误率 allowed_chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789' while True: slug = get_random_string(length, allowed_chars) if not Post.objects.filter(post_id=slug).exists(): return slug def save(self, *args, **kwargs): # 仅新建对象时生成slug,后续修改不会变动 if not self.post_id: self.post_id = self._generate_unique_slug() # 捕获极端并发场景下的唯一约束报错,重新生成slug后再保存 try: super().save(*args, **kwargs) except IntegrityError: self.post_id = self._generate_unique_slug() super().save(*args, **kwargs) def get_absolute_url(self): return reverse('postdetails', args=[self.post_id]) def __str__(self): return str(self.id)
这样处理后,基本不会出现slug重复导致的站点报错问题。
更优的实现方案推荐
可以根据你的业务场景选择更适配的方案:
- 语义化slug方案:如果你的文章有标题字段,推荐用标题生成语义化slug,既方便用户记忆,也利于SEO。可以用Django自带的
slugify工具,重复的话自动加数字后缀,示例逻辑:
from django.utils.text import slugify def _generate_unique_slug(self): base_slug = slugify(self.title, allow_unicode=True) slug = base_slug count = 1 while Post.objects.filter(post_id=slug).exists(): slug = f"{base_slug}-{count}" count += 1 return slug
如果需要中文转拼音的slug,可以引入pypinyin库处理标题后再做slugify。
- 短链方案:如果只需要短URL,不需要语义,可将slug长度调整为6~8位,同时去掉易混淆的字符,8位长度的62进制(大小写+数字)组合有超过200万亿种,普通业务场景下几乎不可能出现碰撞,配合唯一约束完全够用。
- 自增ID转短字符方案:如果不需要隐藏文章发布数量,可将主键改为自增整形ID,再将ID转成62进制字符串作为slug,完全不会出现重复问题,URL长度也更短。
内容的提问来源于stack exchange,提问作者onecodeinai
相关产品推荐
相关产品推荐

