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
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
)
)
存在的问题
- 性能问题: PostgreSQL ENUM 类型在大量查询时性能不如整数类型
- 扩展性差: 添加新角色需要 ALTER TYPE,可能导致锁表
- 不一致: 项目中
folder_category已使用 SMALLINT,应保持统一 - 迁移复杂: ENUM 类型的迁移比整数类型更复杂
解决方案
设计原则
- 数据库层: 使用 SMALLINT 存储枚举值(1-255)
- 代码层: 使用 Python IntEnum 提供类型安全
- API 层: 保持字符串格式,确保向后兼容
- 转换层: 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
...
);
优势
- 性能提升: SMALLINT 查询和索引性能优于 ENUM
- 易于扩展: 添加新值无需 ALTER TYPE,只需更新 CHECK 约束
- 一致性: 与
folder_category等字段保持统一 - 向后兼容: API 层仍使用字符串,客户端无感知
- 类型安全: Python IntEnum 提供编译时类型检查
风险与缓解
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| 数据迁移失败 | 高 | 数据库可清除重建,无历史数据 |
| API 兼容性问题 | 中 | Schema 层保持字符串格式 |
| 代码逻辑错误 | 中 | 完整的单元测试覆盖 |
实施步骤
- ✅ 创建 RFC 文档
- ⏳ 更新 Model 层(IntEnum + SMALLINT)
- ⏳ 更新 Schema 层(转换逻辑)
- ⏳ 更新 Service 层(简化映射)
- ⏳ 创建数据库迁移脚本
- ⏳ 代码验证(getDiagnostics)
- ⏳ 测试验证
相关文档
变更记录
- 2026-01-23: 初始版本,定义重构方案