diff --git a/models/base.py b/models/base.py index edb84ad..eb5c8e1 100644 --- a/models/base.py +++ b/models/base.py @@ -1,7 +1,65 @@ -import uuid -from sqlalchemy.orm import declarative_base +import hashlib +from sqlalchemy import Column, String +from sqlalchemy.orm import declarative_base, declared_attr +import ulid Base = declarative_base() -def gen_uuid() -> str: - return str(uuid.uuid4()) \ No newline at end of file +# Base62 字符集 +ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + +def encode_base62(num: int) -> str: + """将一个整数编码为 Base62 字符串""" + if num == 0: + return ALPHABET[0] + arr = [] + base = len(ALPHABET) + while num: + num, rem = divmod(num, base) + arr.append(ALPHABET[rem]) + return "".join(reversed(arr)) + + +def encode_tablename_to_prefix(tablename: str, length: int = 3) -> str: + """ + 将表名 encode 成固定长度的前缀,使用 MD5 拿到表名的唯一哈希值,转为整数后用 Base62 截取前 N 位 + """ + # 计算表名的 MD5 哈希 + hasher = hashlib.md5(tablename.encode("utf-8")) + hex_digest = hasher.hexdigest() + + # 将十六进制哈希转为大整数 + num = int(hex_digest, 16) + + # 转为 Base62 字符串 + b62_str = encode_base62(num) + + # 截取固定长度(例如 3 位)作为该表的专属代码 + return b62_str[:length] + + +def gen_convex_payload() -> str: + """生成 128位 ULID 转 Base62 的趋势递增 Payload""" + u = ulid.new() + return encode_base62(u.int) + + +class AutoID: + """ + 全自动 Convex 风格 ID 注入器 + 不再需要手动定义任何 prefix,全自动根据 __tablename__ 编码生成 + """ + @declared_attr + def id(cls): + # 运行时动态获取子类的 __tablename__ + tablename = cls.__tablename__ + + # 将表名编码为固定前缀(例如 "users" -> "7X1") + table_prefix = encode_tablename_to_prefix(tablename, length=3) + + # 核心工厂函数:拼接【编码后的表前缀】和【递增Payload】 + def id_factory(): + return f"{table_prefix}_{gen_convex_payload()}" + + return Column(String(32), primary_key=True, default=id_factory) \ No newline at end of file diff --git a/models/message.py b/models/message.py new file mode 100644 index 0000000..1eead8e --- /dev/null +++ b/models/message.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, DateTime, String, ForeignKey +from sqlalchemy.sql import func +from .base import Base, AutoID + +class Message(Base, AutoID): + __tablename__ = "messages" + + user_id = Column(String, ForeignKey("users.id"), index=True) + content = Column(String) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) \ No newline at end of file diff --git a/models/user.py b/models/user.py index e3331fe..e9ed6bc 100644 --- a/models/user.py +++ b/models/user.py @@ -1,13 +1,11 @@ from sqlalchemy import Column, DateTime, String from sqlalchemy.sql import func -from .base import Base, gen_uuid +from .base import AutoID, Base -class User(Base): +class User(Base, AutoID): __tablename__ = "users" - id = Column(String, primary_key=True, default=gen_uuid) - google_id = Column(String, unique=True, index=True) email = Column(String, unique=True, index=True) name = Column(String) diff --git a/pyproject.toml b/pyproject.toml index db42da5..16e8ccf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,5 +9,6 @@ dependencies = [ "httpx>=0.28.1", "python-jose>=3.5.0", "sqlalchemy>=2.0.49", + "ulid-py>=1.1.0", "uvicorn>=0.47.0", ] diff --git a/uv.lock b/uv.lock index 9d6cbf7..3871b0f 100644 --- a/uv.lock +++ b/uv.lock @@ -50,6 +50,7 @@ dependencies = [ { name = "httpx" }, { name = "python-jose" }, { name = "sqlalchemy" }, + { name = "ulid-py" }, { name = "uvicorn" }, ] @@ -59,6 +60,7 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, { name = "python-jose", specifier = ">=3.5.0" }, { name = "sqlalchemy", specifier = ">=2.0.49" }, + { name = "ulid-py", specifier = ">=1.1.0" }, { name = "uvicorn", specifier = ">=0.47.0" }, ] @@ -339,6 +341,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "ulid-py" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/53/d14a8ec344048e21431821cb49e9a6722384f982b889c2dd449428dbdcc1/ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0", size = 22514, upload-time = "2020-09-15T15:35:09.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/7c/a12c879fe6c2b136a718c142115ff99397fbf62b4929d970d58ae386d55f/ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987", size = 25753, upload-time = "2020-09-15T15:35:08.075Z" }, +] + [[package]] name = "uvicorn" version = "0.47.0"