# 文件夹服务 Repository 层补全 **日期**:2026-02-04 **类型**:功能补全 **影响范围**:后端 - 文件夹服务 --- ## 变更概述 补全文件夹服务的 Repository 层实现,新增分享和导出功能的数据访问层。 --- ## 变更内容 ### 1. 新增 FolderShareRepository **文件**:`server/app/repositories/folder_share_repository.py` **功能**: - 用户分享管理(创建、查询、撤销) - 链接分享管理(创建、查询、撤销、访问计数) - 分享列表查询(按文件夹、按用户) - 支持过期时间和密码保护 **核心方法**: ```python - get_by_id(share_id) - 查询分享记录 - get_user_share_by_folder_and_user(folder_id, user_id) - 查询用户分享 - get_link_share_by_token(token) - 通过 token 查询链接分享 - create_user_share(folder_id, user_id, role, created_by) - 创建用户分享 - create_link_share(folder_id, token, access_level, ...) - 创建链接分享 - revoke_share(share_id) - 撤销分享 - increment_access_count(share_id) - 增加访问次数 - get_folder_shares(folder_id, share_type, include_revoked) - 获取文件夹分享列表 - get_user_shared_folders(user_id, page, page_size) - 获取用户被分享的文件夹 - count_user_shared_folders(user_id) - 统计用户被分享的文件夹数量 ``` ### 2. 新增 FolderExportRepository **文件**:`server/app/repositories/folder_export_repository.py` **功能**: - 导出任务管理(创建、查询、更新状态) - 导出结果设置(文件 URL、大小、过期时间) - 任务列表查询(按用户、按状态) - 过期任务清理 **核心方法**: ```python - get_by_id(job_id) - 查询导出任务 - create(folder_id, user_id, format, ...) - 创建导出任务 - update_status(job_id, status, progress, error_message) - 更新导出状态 - set_result(job_id, file_url, file_size, expires_at) - 设置导出结果 - get_user_jobs(user_id, status, page, page_size) - 获取用户导出任务列表 - count_user_jobs(user_id, status) - 统计用户导出任务数量 - get_expired_jobs(limit) - 获取已过期的导出任务 - delete_job(job_id) - 删除导出任务 - cancel_job(job_id) - 取消导出任务 ``` --- ## 技术规范符合性 ### ✅ 符合 jointo-tech-stack 规范 1. **异步编程**: - 所有方法使用 `async/await` - 使用 `AsyncSession` 进行数据库操作 2. **日志记录**: - 使用 `app.core.logging.get_logger(__name__)` - 关键操作记录日志(创建、更新、删除) 3. **类型提示**: - 完整的类型注解(UUID、Optional、List) - 使用 SQLModel 类型 4. **枚举类型**: - 使用 IntEnum(MemberRole、ExportStatus) - 数据库存储 SMALLINT 5. **时间戳**: - 使用 `datetime.now(timezone.utc)` 生成 UTC 时间 - 符合 ADR 006 规范(TIMESTAMPTZ) --- ## 数据库表关联 ### folder_shares 表 | 字段 | 类型 | 说明 | |------|------|------| | id | UUID | 主键 | | folder_id | UUID | 文件夹 ID(逻辑外键) | | share_type | TEXT | 分享类型(user/link) | | shared_with_user_id | UUID | 被分享用户 ID | | role | SMALLINT | 用户分享角色(1=owner, 2=editor, 3=viewer) | | share_token | TEXT | 分享链接令牌 | | access_level | SMALLINT | 链接访问级别 | | password_hash | TEXT | 链接访问密码哈希 | | expires_at | TIMESTAMPTZ | 链接过期时间 | | access_count | INTEGER | 链接访问次数 | | created_by | UUID | 创建人 ID | | created_at | TIMESTAMPTZ | 创建时间 | | revoked_at | TIMESTAMPTZ | 撤销时间 | ### folder_export_jobs 表 | 字段 | 类型 | 说明 | |------|------|------| | id | UUID | 主键 | | folder_id | UUID | 文件夹 ID(逻辑外键) | | user_id | UUID | 用户 ID(逻辑外键) | | format | TEXT | 导出格式(默认 zip) | | include_subfolders | BOOLEAN | 是否包含子文件夹 | | include_resources | BOOLEAN | 是否包含资源文件 | | export_format | TEXT | 导出格式类型(native/json) | | status | SMALLINT | 导出状态(1-5) | | progress | INTEGER | 导出进度(0-100) | | file_url | TEXT | 导出文件 URL | | file_size | BIGINT | 文件大小(字节) | | estimated_size | BIGINT | 预估文件大小 | | error_message | TEXT | 错误信息 | | created_at | TIMESTAMPTZ | 创建时间 | | started_at | TIMESTAMPTZ | 开始时间 | | completed_at | TIMESTAMPTZ | 完成时间 | | expires_at | TIMESTAMPTZ | 下载链接过期时间 | --- ## 已存在的相关迁移文件 1. **初始化表结构**:`20260127_1931_3a3a2a1417de_initial_schema_初始化所有表结构.py` - 创建 folders 表 - 创建 folder_members 表 2. **触发器**:`20260129_1400_add_folder_triggers.py` - updated_at 自动更新触发器 - 路径和层级自动计算触发器 - folder_category 自动继承触发器 - 唯一性索引(名称唯一性) 3. **分享和导出表**:`20260129_1410_create_folder_shares_and_export_jobs.py` - 创建 folder_shares 表 - 创建 folder_export_jobs 表 - 添加索引和注释 4. **时间戳修复**:`20260129_1600_fix_folder_timestamp_timezone.py` - 修改所有时间字段为 TIMESTAMPTZ 5. **注释补充**:`20260129_1700_add_folder_members_comments.md` - 添加 folder_members 表注释 --- ## 与现有代码的集成 ### FolderService 已集成 `server/app/services/folder_service.py` 中已经使用了这两个 Repository: ```python # 分享功能 async def share_folder_with_users(self, user_id, folder_id, users): from app.repositories.folder_share_repository import FolderShareRepository share_repo = FolderShareRepository(self.session) # ... async def share_folder_with_link(self, user_id, folder_id, link_settings): from app.repositories.folder_share_repository import FolderShareRepository share_repo = FolderShareRepository(self.session) # ... # 导出功能 async def create_export_job(self, user_id, folder_id, export_config): from app.repositories.folder_export_repository import FolderExportRepository export_repo = FolderExportRepository(self.session) # ... async def get_export_job_status(self, user_id, job_id): from app.repositories.folder_export_repository import FolderExportRepository export_repo = FolderExportRepository(self.session) # ... ``` ### API 路由已集成 `server/app/api/v1/folders.py` 中已经定义了相关接口: - `POST /api/v1/folders/{folder_id}/share` - 分享文件夹 - `POST /api/v1/folders/{folder_id}/export` - 创建导出任务 - `GET /api/v1/folders/export/{job_id}` - 获取导出任务状态 --- ## 测试建议 ### 单元测试 ```python # tests/unit/repositories/test_folder_share_repository.py async def test_create_user_share() async def test_create_link_share() async def test_revoke_share() async def test_get_folder_shares() # tests/unit/repositories/test_folder_export_repository.py async def test_create_export_job() async def test_update_status() async def test_set_result() async def test_cancel_job() ``` ### 集成测试 ```python # tests/integration/test_folder_share.py async def test_share_folder_with_user() async def test_share_folder_with_link() async def test_access_shared_folder() # tests/integration/test_folder_export.py async def test_export_folder() async def test_export_job_lifecycle() async def test_expired_job_cleanup() ``` --- ## 后续工作 ### 1. 后台任务处理 需要实现 Celery 任务处理导出: ```python # server/app/tasks/folder_export_tasks.py @celery_app.task async def process_folder_export(job_id: str): """处理文件夹导出任务""" # 1. 获取任务信息 # 2. 收集文件夹内容 # 3. 打包为 ZIP # 4. 上传到对象存储 # 5. 更新任务状态 ``` ### 2. 过期任务清理 需要实现定时任务清理过期导出: ```python # server/app/tasks/folder_cleanup_tasks.py @celery_app.task async def cleanup_expired_exports(): """清理过期的导出文件""" # 1. 查询过期任务 # 2. 删除对象存储文件 # 3. 删除数据库记录 ``` ### 3. 分享链接访问 需要实现公开访问接口: ```python # server/app/api/v1/public/shared_folders.py @router.get("/shared/f/{token}") async def access_shared_folder(token: str, password: Optional[str] = None): """访问分享链接""" # 1. 验证 token # 2. 检查过期时间 # 3. 验证密码(如果有) # 4. 增加访问计数 # 5. 返回文件夹内容 ``` --- ## 相关文档 - [文件夹服务需求文档](../../requirements/backend/04-services/project/folder-service.md) - [ADR 006: 时间戳时区规范](../../architecture/adrs/006-timestamptz-for-event-timestamps.md) - [技术栈规范](../../architecture/tech-stack.md) --- ## 总结 本次变更补全了文件夹服务的 Repository 层实现,新增了分享和导出功能的数据访问层。所有代码符合项目技术栈规范,与现有 Service 层和 API 层无缝集成。数据库迁移文件已存在,无需额外创建。