# UUID 生成迁移到应用层 **日期**: 2026-01-27 **类型**: 架构变更 **影响**: 数据库迁移、Model 定义 ## 变更概述 将 UUID v7 生成从数据库层迁移到应用层,移除数据库 `gen_uuid_v7()` 函数和所有 `server_default` 设置。 ## 变更原因 ### 架构一致性 与"禁止物理外键"的架构理念保持一致,所有业务逻辑集中在应用层。 ### 技术优势 1. **应用层控制** - UUID 生成逻辑在 Python 代码中,便于测试和 Mock 2. **数据库独立** - 减少对 PostgreSQL 特定功能的依赖,提高可移植性 3. **简化迁移** - 数据库迁移脚本更简洁,无需管理自定义函数 4. **统一管理** - 所有 ID 生成逻辑在 `app/utils/id_generator.py` 统一管理 ## 具体变更 ### 1. 移除数据库函数 **之前**: ```sql -- 迁移脚本中创建函数 CREATE OR REPLACE FUNCTION gen_uuid_v7() RETURNS uuid AS $$ DECLARE unix_ts_ms BIGINT; uuid_bytes BYTEA; BEGIN unix_ts_ms = (EXTRACT(EPOCH FROM CLOCK_TIMESTAMP()) * 1000)::BIGINT; uuid_bytes = SUBSTRING(INT8SEND(unix_ts_ms) FROM 3 FOR 6) || gen_random_bytes(10); RETURN ENCODE(..., 'hex')::UUID; END; $$ LANGUAGE plpgsql VOLATILE; ``` **之后**: ```python # 应用层生成 from app.utils.id_generator import generate_uuid id = generate_uuid() # 使用 uuid_utils 库 ``` ### 2. 移除 server_default **之前**: ```python # 迁移脚本 op.create_table('users', sa.Column('user_id', sa.UUID(), server_default=sa.text('gen_uuid_v7()'), nullable=False), ... ) ``` **之后**: ```python # 迁移脚本 op.create_table('users', sa.Column('user_id', sa.UUID(), nullable=False), # 无 server_default ... ) ``` ### 3. Model 定义 **之前**: ```python class User(SQLModel, table=True): user_id: UUID = Field( sa_column=Column(PG_UUID(as_uuid=True), primary_key=True) # 依赖数据库 server_default ) ``` **之后**: ```python from app.utils.id_generator import generate_uuid class User(SQLModel, table=True): user_id: UUID = Field( sa_column=Column( PG_UUID(as_uuid=True), primary_key=True, default=generate_uuid # ✅ 应用层生成 ) ) ``` ### 4. 移除物理外键 同时移除了所有模型中的 `ForeignKey` 和 `foreign_key` 定义: **之前**: ```python owner_id: UUID = Field(foreign_key="users.user_id", index=True) # 或 owner_id: UUID = Field( sa_column=Column(PG_UUID(as_uuid=True), ForeignKey("users.user_id"), index=True) ) ``` **之后**: ```python owner_id: UUID = Field( sa_column=Column(PG_UUID(as_uuid=True), nullable=False, index=True), description="所有者用户ID - 应用层验证" ) ``` ## 影响的文件 ### Model 文件(移除 ForeignKey) - `server/app/models/ai_job.py` - `server/app/models/ai_quota.py` - `server/app/models/ai_usage_log.py` - `server/app/models/folder.py` - `server/app/models/project.py` ### 迁移脚本(移除 UUID 函数和 server_default) - `server/alembic/versions/20260127_1931_3a3a2a1417de_initial_schema_初始化所有表结构.py` ### 数据库初始化(改用 Alembic) - `server/app/core/database.py` - 移除 `SQLModel.metadata.create_all()`,改用 Alembic 迁移 ## 验证结果 ```sql -- ✅ 无物理外键 SELECT * FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'; -- (0 rows) -- ✅ 无 UUID 生成函数 SELECT proname FROM pg_proc WHERE proname = 'gen_uuid_v7'; -- (0 rows) -- ✅ 所有 UUID 字段无默认值 SELECT table_name, column_name, column_default FROM information_schema.columns WHERE data_type = 'uuid' AND column_default IS NOT NULL; -- (0 rows) ``` ## 迁移指南 ### 新项目 直接使用更新后的迁移脚本和模型定义。 ### 现有项目 1. **清理数据库**: ```bash ./start_docker.sh -c -b # 清理并重建 ``` 2. **验证迁移**: ```bash docker exec jointo-server-app alembic current # 输出: 3a3a2a1417de (head) ``` 3. **验证无外键**: ```bash docker exec jointo-server-postgres psql -U jointoAI -d jointo -c " SELECT table_name, constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'; " # 输出: (0 rows) ``` ## 注意事项 1. **应用层必须验证引用完整性** - 在 Service 层检查关联记录是否存在 2. **所有关联字段必须有索引** - 保证查询性能 3. **UUID 生成器已封装** - 使用 `app/utils/id_generator.py` 的 `generate_uuid()` 4. **测试时可 Mock** - `generate_uuid` 函数便于单元测试 ## 相关文档 - [数据库设计规范](.claude/skills/jointo-tech-stack/references/database.md) - [引用完整性保证](./2026-01-27-user-service-no-foreign-keys.md) - [Alembic 迁移指南](../guides/alembic-migration-guide.md) ## 总结 这次变更将 UUID 生成和外键约束从数据库层完全移除,实现了"应用层控制、数据库层简化"的架构目标,为后续的分库分表和微服务化奠定了基础。