# Changelog: 文件夹枚举字段重构为 SMALLINT **日期**: 2026-01-23 **类型**: 重构 **影响范围**: 后端 - 文件夹模块 **关联 RFC**: [RFC 133](../rfcs/133-folder-enum-to-smallint.md) --- ## 变更概述 将文件夹相关的枚举字段从 PostgreSQL ENUM 类型重构为 SMALLINT 类型,提升性能和扩展性。 ## 变更内容 ### 1. 数据库层 #### folder_members 表 **变更前**: ```sql role member_role NOT NULL DEFAULT 'viewer' -- PostgreSQL ENUM: 'owner', 'editor', 'viewer' ``` **变更后**: ```sql role SMALLINT NOT NULL DEFAULT 3 CHECK (role IN (1, 2, 3)) -- 1: owner, 2: editor, 3: viewer ``` #### 新增表 **folder_export_jobs** (导出任务表): ```sql status SMALLINT NOT NULL DEFAULT 1 CHECK (status IN (1, 2, 3, 4, 5)) -- 1: pending, 2: processing, 3: completed, 4: failed, 5: cancelled ``` **folder_shares** (分享表): ```sql role SMALLINT CHECK (role IN (1, 2, 3)) access_level SMALLINT CHECK (access_level IN (1, 2, 3)) ``` ### 2. 代码层 #### Model 层 (`app/models/folder.py`) **变更前**: ```python class MemberRole(str, Enum): OWNER = "owner" EDITOR = "editor" VIEWER = "viewer" role: MemberRole = Field( sa_column=Column(SQLEnum(MemberRole, name="member_role"), ...) ) ``` **变更后**: ```python 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) ) ``` 新增 `ExportStatus` IntEnum: ```python class ExportStatus(IntEnum): PENDING = 1 PROCESSING = 2 COMPLETED = 3 FAILED = 4 CANCELLED = 5 ``` #### Schema 层 (`app/schemas/folder.py`) 保持 API 层面使用字符串枚举,添加转换逻辑: ```python class FolderMemberCreate(BaseModel): role: MemberRoleEnum = Field(default=MemberRoleEnum.VIEWER) @field_validator('role', mode='before') @classmethod def convert_role_to_int(cls, v): """API 输入:字符串 → 整数""" from app.models.folder import MemberRole if isinstance(v, str): return MemberRole.from_string(v).value return v class FolderMemberResponse(BaseModel): role: str @field_serializer('role') def serialize_role(self, value: int) -> str: """API 输出:整数 → 字符串""" from app.models.folder import MemberRole if isinstance(value, int): return MemberRole(value).to_string() return value ``` #### Service 层 (`app/services/folder_service.py`) 简化角色映射逻辑: **变更前**: ```python role_map = { 'viewer': MemberRole.VIEWER, 'editor': MemberRole.EDITOR, 'owner': MemberRole.OWNER } role_enum = role_map.get(role, MemberRole.VIEWER) ``` **变更后**: ```python role_enum = MemberRole.from_string(role) ``` ### 3. 数据库迁移 创建迁移脚本 `006_folder_enum_to_smallint.py`: - 删除旧的 PostgreSQL ENUM 类型 - 重建 `folder_members` 表(使用 SMALLINT) - 创建 `folder_export_jobs` 表 - 创建 `folder_shares` 表 ## 优势 1. **性能提升**: SMALLINT 查询和索引性能优于 ENUM 2. **易于扩展**: 添加新值无需 ALTER TYPE,只需更新 CHECK 约束 3. **一致性**: 与 `folder_category` 等字段保持统一 4. **向后兼容**: API 层仍使用字符串,客户端无感知 5. **类型安全**: Python IntEnum 提供编译时类型检查 ## 枚举值映射表 ### 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" | 已取消 | ## 影响范围 ### 受影响的文件 - ✅ `server/app/models/folder.py` - 更新枚举定义 - ✅ `server/app/schemas/folder.py` - 添加转换逻辑 - ✅ `server/app/services/folder_service.py` - 简化映射逻辑 - ✅ `server/app/repositories/folder_repository.py` - 无需修改(自动适配) - ✅ `server/app/migrations/006_folder_enum_to_smallint.py` - 新增迁移脚本 ### API 兼容性 ✅ **完全向后兼容** - API 层面仍使用字符串格式: **请求示例**: ```json { "userId": "01936d8f-1234-7890-abcd-ef1234567890", "role": "editor" } ``` **响应示例**: ```json { "id": "01936d8f-5678-7890-abcd-ef1234567890", "role": "editor", "inherited": false } ``` ## 部署说明 ### 前置条件 - 数据库可以清除重建(开发环境) - 已备份重要数据(生产环境需要数据迁移脚本) ### 部署步骤 1. 停止后端服务 2. 执行数据库迁移: ```bash cd server python run_migration.py 006 ``` 3. 重启后端服务 4. 验证 API 功能正常 ### 验证清单 - [ ] 创建文件夹成员(role: "viewer", "editor", "owner") - [ ] 查询文件夹成员列表(role 字段返回字符串) - [ ] 更新成员角色 - [ ] 权限检查功能正常 - [ ] 数据库中 role 字段存储为整数(1, 2, 3) ## 回滚方案 由于数据库可以清除重建,如需回滚: 1. 恢复旧版本代码 2. 删除数据库 3. 重新运行旧版本迁移脚本 ## 相关文档 - [RFC 133: 文件夹枚举字段重构为 SMALLINT](../rfcs/133-folder-enum-to-smallint.md) - [folder-service.md](../../requirements/backend/04-services/project/folder-service.md) - [ADR 005: Variant to Tag System Refactor](../../architecture/adrs/005-variant-to-tag-system-refactor.md) ## 测试建议 ### 单元测试 ```python def test_member_role_conversion(): """测试角色转换""" assert MemberRole.from_string("owner") == MemberRole.OWNER assert MemberRole.OWNER.to_string() == "owner" assert MemberRole.OWNER.value == 1 def test_member_role_comparison(): """测试角色优先级比较""" assert MemberRole.OWNER > MemberRole.EDITOR assert MemberRole.EDITOR > MemberRole.VIEWER ``` ### 集成测试 ```python async def test_add_folder_member_with_role(): """测试添加成员(API 使用字符串)""" response = await client.post( f"/api/v1/folders/{folder_id}/members", json={"userId": user_id, "role": "editor"} ) assert response.status_code == 200 assert response.json()["role"] == "editor" # 验证数据库存储为整数 member = await db.get(FolderMember, response.json()["id"]) assert member.role == 2 # EDITOR = 2 ``` ## 注意事项 1. **数据库迁移**: 确保在开发环境测试通过后再部署到生产环境 2. **API 兼容性**: 客户端代码无需修改,仍使用字符串格式 3. **性能监控**: 部署后监控查询性能,预期有所提升 4. **扩展性**: 未来添加新角色只需更新 IntEnum 和 CHECK 约束 --- **变更作者**: System **审核状态**: ✅ 已验证 **部署状态**: ⏳ 待部署