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.
8.9 KiB
8.9 KiB
文件夹服务 Repository 层补全
日期:2026-02-04
类型:功能补全
影响范围:后端 - 文件夹服务
变更概述
补全文件夹服务的 Repository 层实现,新增分享和导出功能的数据访问层。
变更内容
1. 新增 FolderShareRepository
文件:server/app/repositories/folder_share_repository.py
功能:
- 用户分享管理(创建、查询、撤销)
- 链接分享管理(创建、查询、撤销、访问计数)
- 分享列表查询(按文件夹、按用户)
- 支持过期时间和密码保护
核心方法:
- 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、大小、过期时间)
- 任务列表查询(按用户、按状态)
- 过期任务清理
核心方法:
- 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 规范
-
异步编程:
- 所有方法使用
async/await - 使用
AsyncSession进行数据库操作
- 所有方法使用
-
日志记录:
- 使用
app.core.logging.get_logger(__name__) - 关键操作记录日志(创建、更新、删除)
- 使用
-
类型提示:
- 完整的类型注解(UUID、Optional、List)
- 使用 SQLModel 类型
-
枚举类型:
- 使用 IntEnum(MemberRole、ExportStatus)
- 数据库存储 SMALLINT
-
时间戳:
- 使用
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 | 下载链接过期时间 |
已存在的相关迁移文件
-
初始化表结构:
20260127_1931_3a3a2a1417de_initial_schema_初始化所有表结构.py- 创建 folders 表
- 创建 folder_members 表
-
触发器:
20260129_1400_add_folder_triggers.py- updated_at 自动更新触发器
- 路径和层级自动计算触发器
- folder_category 自动继承触发器
- 唯一性索引(名称唯一性)
-
分享和导出表:
20260129_1410_create_folder_shares_and_export_jobs.py- 创建 folder_shares 表
- 创建 folder_export_jobs 表
- 添加索引和注释
-
时间戳修复:
20260129_1600_fix_folder_timestamp_timezone.py- 修改所有时间字段为 TIMESTAMPTZ
-
注释补充:
20260129_1700_add_folder_members_comments.md- 添加 folder_members 表注释
与现有代码的集成
FolderService 已集成
server/app/services/folder_service.py 中已经使用了这两个 Repository:
# 分享功能
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}- 获取导出任务状态
测试建议
单元测试
# 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()
集成测试
# 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 任务处理导出:
# 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. 过期任务清理
需要实现定时任务清理过期导出:
# server/app/tasks/folder_cleanup_tasks.py
@celery_app.task
async def cleanup_expired_exports():
"""清理过期的导出文件"""
# 1. 查询过期任务
# 2. 删除对象存储文件
# 3. 删除数据库记录
3. 分享链接访问
需要实现公开访问接口:
# 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. 返回文件夹内容
相关文档
总结
本次变更补全了文件夹服务的 Repository 层实现,新增了分享和导出功能的数据访问层。所有代码符合项目技术栈规范,与现有 Service 层和 API 层无缝集成。数据库迁移文件已存在,无需额外创建。