You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:40:14