如何为Django模型创建类Stripe风格的数据库ID?
Django实现Stripe风格ID:兼顾可读性与数据库性能
核心实现思路
你的方向完全正确:用UUID类型的私有主键存储ULID(转成UUID格式)保证数据库性能,动态生成带前缀的公开ID对外暴露。下面是可落地的实现方案,同时解决Django内部操作与对外ID的兼容问题:
1. 封装抽象基模型
先写一个抽象模型,把Stripe风格ID的逻辑封装进去,所有需要该ID格式的业务模型都继承它:
import uuid from ulid import ULID from django.db import models class StripeIDModel(models.Model): # 数据库主键:存储ULID转成的UUID,保证索引/关联查询性能 _db_id = models.UUIDField( primary_key=True, default=lambda: uuid.UUID(str(ULID())), editable=False, db_index=True ) # 子类必须重写该属性,设置表的前缀(如usr_、ord_) ID_PREFIX = None objects = models.Manager() class Meta: abstract = True @property def id(self): """对外暴露的Stripe风格ID,动态生成不存储""" if not self.ID_PREFIX: raise NotImplementedError("子类必须定义ID_PREFIX属性") ulid_str = str(ULID.from_uuid(self._db_id)) return f"{self.ID_PREFIX}_{ulid_str}" @classmethod def get_by_public_id(cls, public_id): """通过公开ID查询模型实例""" if not public_id.startswith(f"{cls.ID_PREFIX}_"): raise ValueError("无效的公开ID前缀") try: ulid_str = public_id.split("_")[1] db_id = uuid.UUID(str(ULID.from_str(ulid_str))) except (IndexError, ValueError): raise cls.DoesNotExist(f"{cls.__name__}不存在该公开ID") return cls.objects.get(_db_id=db_id)
2. 自定义管理器简化查询
为了让对外查询更直观(直接用id参数传入公开ID),重写模型管理器:
class StripeIDManager(models.Manager): def get(self, *args, **kwargs): # 自动处理传入的公开ID参数,转成内部主键查询 if "id" in kwargs: public_id = kwargs.pop("id") return self.model.get_by_public_id(public_id) return super().get(*args, **kwargs) # 更新抽象模型的管理器 class StripeIDModel(models.Model): # ... 之前的代码 objects = StripeIDManager()
3. 业务模型示例
继承抽象模型,设置自己的前缀即可:
class User(StripeIDModel): ID_PREFIX = "usr" name = models.CharField(max_length=100) class Order(StripeIDModel): ID_PREFIX = "ord" # 关联字段直接用抽象模型的主键,Django内部自动用UUID做关联查询 user = models.ForeignKey(User, on_delete=models.CASCADE) amount = models.DecimalField(max_digits=10, decimal_places=2)
4. 适配对外场景
序列化(以DRF为例)
让序列化器返回公开ID,而不是内部的_db_id:
from rest_framework import serializers class UserSerializer(serializers.ModelSerializer): id = serializers.CharField(read_only=True) class Meta: model = User fields = ["id", "name"]
Admin后台适配
在Admin界面显示公开ID,方便调试:
from django.contrib import admin class StripeIDAdmin(admin.ModelAdmin): readonly_fields = ["id"] # 把公开ID加到列表展示和详情页 list_display = ["id", "name"] admin.site.register(User, StripeIDAdmin)
关键逻辑说明
- 内部操作:Django的ORM关联查询、索引、事务等所有底层操作,都会使用
_db_id这个UUID主键,完全保证数据库性能,不会因为字符串ID产生性能损耗。 - 对外暴露:
id是动态生成的property,不会存储到数据库,仅在需要对外输出时生成,完全符合Stripe的ID格式要求。 - 查询兼容:通过自定义管理器,对外可以直接用
User.objects.get(id="usr_01HXYZ...")查询,内部自动解析为UUID主键查询,无需额外转换。
更优细节补充
- 依赖
ulid库生成ULID,安装命令:pip install ulid-py - PostgreSQL对UUID类型的支持非常友好,建议搭配使用;如果是MySQL,也能正常存储UUID,性能优于长字符串主键。
- 抽象模型的设计让逻辑复用性极强,新增业务模型只需继承并设置前缀即可。
内容的提问来源于stack exchange,提问作者Kenny Loveall
相关产品推荐
相关产品推荐

