You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

5.9 KiB

RFC 133: 文件夹枚举字段重构为 SMALLINT

状态: 实施中
创建时间: 2026-01-23
作者: System
关联文档: folder-service.md


背景

当前 folder_members 表的 role 字段使用 PostgreSQL ENUM 类型存储成员角色。根据项目架构决策和性能考虑,需要将其重构为 SMALLINT 类型,与 folders.folder_category 保持一致。

同时,为未来的文件夹导出功能预留 export_status 枚举设计。

问题

当前实现

# models/folder.py
class MemberRole(str, Enum):
    OWNER = "owner"
    EDITOR = "editor"
    VIEWER = "viewer"

# 数据库列定义
role: MemberRole = Field(
    sa_column=Column(
        SQLEnum(MemberRole, name="member_role"),
        nullable=False,
        default=MemberRole.VIEWER
    )
)

存在的问题

  1. 性能问题: PostgreSQL ENUM 类型在大量查询时性能不如整数类型
  2. 扩展性差: 添加新角色需要 ALTER TYPE,可能导致锁表
  3. 不一致: 项目中 folder_category 已使用 SMALLINT,应保持统一
  4. 迁移复杂: ENUM 类型的迁移比整数类型更复杂

解决方案

设计原则

  1. 数据库层: 使用 SMALLINT 存储枚举值(1-255)
  2. 代码层: 使用 Python IntEnum 提供类型安全
  3. API 层: 保持字符串格式,确保向后兼容
  4. 转换层: Schema 层负责字符串 ↔ 整数转换

枚举值映射

MemberRole(成员角色)

名称 字符串 说明
1 OWNER "owner" 所有者
2 EDITOR "editor" 编辑者
3 VIEWER "viewer" 查看者

ExportStatus(导出状态)

名称 字符串 说明
1 PENDING "pending" 等待处理
2 PROCESSING "processing" 处理中
3 COMPLETED "completed" 已完成
4 FAILED "failed" 失败
5 CANCELLED "cancelled" 已取消

实现细节

1. Model 层(数据库)

from enum import IntEnum

class MemberRole(IntEnum):
    """成员角色枚举"""
    OWNER = 1
    EDITOR = 2
    VIEWER = 3
    
    @classmethod
    def from_string(cls, value: str) -> "MemberRole":
        """从字符串转换"""
        mapping = {"owner": cls.OWNER, "editor": cls.EDITOR, "viewer": cls.VIEWER}
        return mapping.get(value.lower(), cls.VIEWER)
    
    def to_string(self) -> str:
        """转换为字符串"""
        mapping = {self.OWNER: "owner", self.EDITOR: "editor", self.VIEWER: "viewer"}
        return mapping[self]

# 数据库列定义
role: int = Field(
    sa_column=Column(SmallInteger, nullable=False, default=MemberRole.VIEWER)
)

2. Schema 层(API)

from pydantic import field_validator, field_serializer

class MemberRoleEnum(str, Enum):
    """API 层角色枚举(字符串)"""
    OWNER = "owner"
    EDITOR = "editor"
    VIEWER = "viewer"

class FolderMemberCreate(BaseModel):
    role: MemberRoleEnum = Field(default=MemberRoleEnum.VIEWER)
    
    @field_validator('role', mode='before')
    @classmethod
    def convert_role_to_int(cls, v):
        """API 输入:字符串 → 整数"""
        if isinstance(v, str):
            return MemberRole.from_string(v)
        return v

class FolderMemberResponse(BaseModel):
    role: str
    
    @field_serializer('role')
    def serialize_role(self, value: int) -> str:
        """API 输出:整数 → 字符串"""
        return MemberRole(value).to_string()

3. Service 层

# 简化角色映射
role_enum = MemberRole.from_string(role_string)

# 或直接使用整数值
has_permission = await self.repository.check_user_permission(
    user_id, folder_id, MemberRole.VIEWER
)

数据库迁移

由于数据库可以清除重建,迁移脚本将直接创建新表结构:

-- 006_folder_enum_to_smallint.py

-- 删除旧的 ENUM 类型(如果存在)
DROP TYPE IF EXISTS member_role CASCADE;

-- folder_members 表使用 SMALLINT
CREATE TABLE folder_members (
    id TEXT PRIMARY KEY DEFAULT gen_uuid_v7(),
    folder_id TEXT NOT NULL REFERENCES folders(id) ON DELETE CASCADE,
    user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
    role SMALLINT NOT NULL DEFAULT 3 CHECK (role IN (1, 2, 3)),
    -- 1: owner, 2: editor, 3: viewer
    ...
);

-- folder_export_jobs 表使用 SMALLINT
CREATE TABLE folder_export_jobs (
    id TEXT PRIMARY KEY DEFAULT gen_uuid_v7(),
    status SMALLINT NOT NULL DEFAULT 1 CHECK (status IN (1, 2, 3, 4, 5)),
    -- 1: pending, 2: processing, 3: completed, 4: failed, 5: cancelled
    ...
);

优势

  1. 性能提升: SMALLINT 查询和索引性能优于 ENUM
  2. 易于扩展: 添加新值无需 ALTER TYPE,只需更新 CHECK 约束
  3. 一致性: 与 folder_category 等字段保持统一
  4. 向后兼容: API 层仍使用字符串,客户端无感知
  5. 类型安全: Python IntEnum 提供编译时类型检查

风险与缓解

风险 影响 缓解措施
数据迁移失败 数据库可清除重建,无历史数据
API 兼容性问题 Schema 层保持字符串格式
代码逻辑错误 完整的单元测试覆盖

实施步骤

  1. 创建 RFC 文档
  2. 更新 Model 层(IntEnum + SMALLINT)
  3. 更新 Schema 层(转换逻辑)
  4. 更新 Service 层(简化映射)
  5. 创建数据库迁移脚本
  6. 代码验证(getDiagnostics)
  7. 测试验证

相关文档

变更记录

  • 2026-01-23: 初始版本,定义重构方案