Files
disknext/models/README.md
于小丘 b12aad4e73 feat: Enhance file management and user features
- Add file deduplication mechanism based on PhysicalFile reference counting.
- Implement chunked upload support for large files with resumable uploads.
- Update sharing page to automatically render README and preview content.
- Integrate Redis for caching and token storage (optional).
- Refactor project structure to include new models for download tasks, nodes, and tasks.
- Introduce user filtering parameters for admin user management.
- Add CORS middleware for handling cross-origin requests.
- Improve error messages for authentication failures.
- Update user model to include two-factor authentication key management.
- Enhance API documentation and response models for clarity.
- Implement admin checks for user management and permissions.
2026-01-13 15:29:52 +08:00

1275 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Models 数据库模型文档
本目录包含 DiskNext Server 的所有数据库模型定义,基于 SQLModel 框架实现。
## 目录结构
```
models/
├── base/ # 基础模型类
│ ├── __init__.py # 导出 SQLModelBase
│ └── sqlmodel_base.py # SQLModelBase 基类(自定义元类,支持联表继承)
├── mixin/ # Mixin 模块
│ ├── __init__.py # 统一导出
│ ├── table.py # TableBaseMixin, UUIDTableBaseMixinCRUD + 时间戳 + 分页)
│ ├── polymorphic.py # 联表继承工具create_subclass_id_mixin 等)
│ └── info_response.py # DTO 用的 id/时间戳 Mixin
├── user.py # 用户模型
├── user_authn.py # 用户 WebAuthn 凭证
├── group.py # 用户组模型
├── policy.py # 存储策略模型
├── physical_file.py # 物理文件模型(文件去重)
├── object.py # 统一对象模型(文件/目录)+ 上传会话 + 文件元数据
├── share.py # 分享模型
├── tag.py # 标签模型
├── download.py # 离线下载任务
├── task.py # 任务模型
├── node.py # 节点模型
├── order.py # 订单模型
├── redeem.py # 兑换码模型
├── report.py # 举报模型
├── setting.py # 系统设置模型
├── source_link.py # 源链接模型
├── storage_pack.py # 容量包模型
├── webdav.py # WebDAV 账户模型
├── color.py # 主题颜色 DTO
├── model_base.py # 响应基类 DTOResponseBase, MCP 等)
├── migration.py # 数据库初始化和迁移
└── database.py # 数据库连接配置
```
---
## 基础类
### SQLModelBase
所有模型的基类,位于 `models.base.sqlmodel_base`,使用自定义元类 `__DeclarativeMeta` 实现:
- `use_attribute_docstrings=True`:使用属性后的 docstring 作为字段描述
- `validate_by_name=True`:允许按名称验证
- **自动设置 table=True**:继承 TableBaseMixin 的类自动成为数据库表
- **联表继承支持**:自动检测并处理 Joined Table Inheritance
- **多态支持**:支持 `polymorphic_on`, `polymorphic_identity` 等参数
- **Python 3.14 兼容**:包含针对 PEP 649 的兼容性修复
### TableBaseMixin
数据库表 Mixin位于 `models.mixin.table`,继承后自动设置 `table=True`
包含以下公共字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 自增主键 |
| `created_at` | `datetime` | 创建时间 |
| `updated_at` | `datetime` | 更新时间(自动更新) |
提供的 CRUD 方法:
- `add()` - 新增记录(类方法)
- `save()` - 保存实例(**必须使用返回值**
- `update()` - 更新记录(**必须使用返回值**
- `delete()` - 删除记录
- `get()` - 查询记录(类方法,支持分页、排序、时间筛选、多态加载)
- `get_with_count()` - 分页查询并返回总数(类方法,返回 `ListResponse[T]`
- `get_exist_one()` - 获取存在的记录(不存在则抛出 404
- `count()` - 统计记录数(类方法,支持时间筛选)
分页排序请求类:
- `TimeFilterRequest` - 时间筛选参数
- `PaginationRequest` - 分页排序参数
- `TableViewRequest` - 组合分页排序和时间筛选
**使用方式**
```python
from models.base import SQLModelBase
from models.mixin import TableBaseMixin
class MyModel(SQLModelBase, TableBaseMixin):
name: str
```
### UUIDTableBaseMixin
继承自 TableBaseMixin将主键改为 UUID 类型:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | UUID 主键(自动生成) |
**使用方式**
```python
from models.base import SQLModelBase
from models.mixin import UUIDTableBaseMixin
class MyUUIDModel(SQLModelBase, UUIDTableBaseMixin):
name: str
```
**注意**:当有 Base 类已继承 SQLModelBase 时,子类不需要重复继承:
```python
class UserBase(SQLModelBase):
username: str
class User(UserBase, UUIDTableBaseMixin): # 不需要再写 SQLModelBase
password: str
```
### ListResponse[T]
泛型分页响应类,用于所有 LIST 端点的标准化响应格式:
```python
class ListResponse(BaseModel, Generic[ItemT]):
count: int # 符合条件的记录总数
items: list[T] # 当前页的记录列表
```
**使用示例**
```python
@router.get("", response_model=ListResponse[UserResponse])
async def list_users(session: SessionDep, table_view: TableViewRequestDep):
return await User.get_with_count(session, table_view=table_view)
```
---
## 数据库表模型
### 1. User用户
**表名**: `user`
**基类**: `UUIDTableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 用户 UUID主键 |
| `username` | `str` | 用户名,唯一,不可更改 |
| `nickname` | `str?` | 用户昵称 |
| `password` | `str` | 密码Argon2 加密) |
| `status` | `UserStatus` | 用户状态active/admin_banned/system_banned |
| `storage` | `int` | 已用存储空间(字节) |
| `two_factor` | `str?` | 两步验证密钥TOTP |
| `avatar` | `str` | 头像类型/地址 |
| `score` | `int` | 用户积分 |
| `group_expires` | `datetime?` | 当前用户组过期时间 |
| `theme` | `ThemeType` | 主题类型light/dark/system |
| `language` | `str` | 语言偏好(默认 zh-CN |
| `timezone` | `int` | 时区 UTC 偏移(-12 ~ 12 |
| `group_id` | `UUID` | 所属用户组(外键) |
| `previous_group_id` | `UUID?` | 之前的用户组(用于过期后恢复) |
**关系**:
- `group`: 所属用户组
- `previous_group`: 之前的用户组(用于过期后恢复)
- `tags`: 用户的标签列表
- `authns`: 用户的 WebAuthn 凭证列表
---
### 2. UserAuthnWebAuthn 凭证)
**表名**: `userauthn`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `credential_id` | `str` | 凭证 IDBase64 编码) |
| `credential_public_key` | `str` | 凭证公钥Base64 编码) |
| `sign_count` | `int` | 签名计数器(防重放) |
| `credential_device_type` | `str` | 设备类型single_device/multi_device |
| `credential_backed_up` | `bool` | 凭证是否已备份 |
| `transports` | `str?` | 支持的传输方式(逗号分隔) |
| `name` | `str?` | 用户自定义凭证名称 |
| `user_id` | `UUID` | 所属用户(外键) |
---
### 3. Group用户组
**表名**: `group`
**基类**: `UUIDTableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 用户组 UUID主键 |
| `name` | `str` | 用户组名称,唯一 |
| `max_storage` | `int` | 最大存储空间(字节) |
| `share_enabled` | `bool` | 是否允许创建分享 |
| `web_dav_enabled` | `bool` | 是否允许使用 WebDAV |
| `admin` | `bool` | 是否为管理员组 |
| `speed_limit` | `int` | 速度限制KB/s0 为不限制 |
---
### 4. GroupOptions用户组选项
**表名**: `groupoptions`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `group_id` | `UUID` | 关联的用户组(外键,唯一) |
| `share_download` | `bool` | 是否允许分享下载 |
| `share_free` | `bool` | 是否免积分获取内容 |
| `relocate` | `bool` | 是否允许文件重定位 |
| `source_batch` | `int` | 批量获取源地址数量 |
| `select_node` | `bool` | 是否允许选择节点 |
| `advance_delete` | `bool` | 是否允许高级删除 |
| `archive_download` | `bool` | 是否允许打包下载 |
| `archive_task` | `bool` | 是否允许创建打包任务 |
| `webdav_proxy` | `bool` | 是否允许 WebDAV 代理 |
| `aria2` | `bool` | 是否允许使用 aria2 |
| `redirected_source` | `bool` | 是否使用重定向源 |
---
### 5. GroupPolicyLink用户组-策略关联)
**表名**: `grouppolicylink`
**基类**: `SQLModelBase`(关联表)
| 字段 | 类型 | 说明 |
|------|------|------|
| `group_id` | `UUID` | 用户组(复合主键) |
| `policy_id` | `UUID` | 存储策略(复合主键) |
---
### 6. Policy存储策略
**表名**: `policy`
**基类**: `UUIDTableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 策略 UUID主键 |
| `name` | `str` | 策略名称,唯一 |
| `type` | `PolicyType` | 策略类型local/s3 |
| `server` | `str?` | 服务器地址 |
| `bucket_name` | `str?` | 存储桶名称 |
| `is_private` | `bool` | 是否为私有空间 |
| `base_url` | `str?` | 访问文件的基础 URL |
| `access_key` | `str?` | Access Key |
| `secret_key` | `str?` | Secret Key |
| `max_size` | `int` | 允许上传的最大文件尺寸(字节) |
| `auto_rename` | `bool` | 是否自动重命名 |
| `dir_name_rule` | `str?` | 目录命名规则 |
| `file_name_rule` | `str?` | 文件命名规则 |
| `is_origin_link_enable` | `bool` | 是否开启源链接访问 |
**关系**:
- `options`: 一对一关联 PolicyOptions
---
### 7. PolicyOptions存储策略选项
**表名**: `policyoptions`
**基类**: `UUIDTableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 主键 |
| `policy_id` | `UUID` | 关联的策略(外键,唯一) |
| `token` | `str?` | 访问令牌 |
| `file_type` | `str?` | 允许的文件类型 |
| `mimetype` | `str?` | MIME 类型 |
| `od_redirect` | `str?` | OneDrive 重定向地址 |
| `chunk_size` | `int` | 分片上传大小(字节),默认 50MB |
| `s3_path_style` | `bool` | 是否使用 S3 路径风格 |
---
### 8. PhysicalFile物理文件
**表名**: `physicalfile`
**基类**: `UUIDTableBaseMixin`
表示磁盘上的实际文件。多个 Object 可以引用同一个 PhysicalFile实现文件共享而不复制物理文件。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 物理文件 UUID主键 |
| `storage_path` | `str` | 物理存储路径(相对于存储策略根目录) |
| `size` | `int` | 文件大小(字节) |
| `checksum_md5` | `str?` | MD5 校验和(用于文件去重和完整性校验) |
| `policy_id` | `UUID` | 存储策略(外键) |
| `reference_count` | `int` | 引用计数(有多少个 Object 引用此物理文件) |
**索引**:
- `ix_physical_file_policy_path`: (policy_id, storage_path)
- `ix_physical_file_checksum`: (checksum_md5)
**关系**:
- `policy`: 存储策略
- `objects`: 引用此物理文件的所有逻辑对象(一对多)
**业务方法**:
- `increment_reference()`: 增加引用计数
- `decrement_reference()`: 减少引用计数
- `can_be_deleted`: 属性,是否可物理删除(引用计数为 0
---
### 9. Object统一对象
**表名**: `object`
**基类**: `UUIDTableBaseMixin`
合并了文件和目录,通过 `type` 字段区分。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 对象 UUID主键 |
| `name` | `str` | 对象名称(文件名或目录名) |
| `type` | `ObjectType` | 对象类型file/folder |
| `password` | `str?` | 对象独立密码 |
| `size` | `int` | 文件大小(字节),目录为 0 |
| `upload_session_id` | `str?` | 分块上传会话 ID |
| `physical_file_id` | `UUID?` | 关联的物理文件(仅文件有效,目录为 NULL |
| `parent_id` | `UUID?` | 父目录外键NULL 表示根目录) |
| `owner_id` | `UUID` | 所有者用户(外键) |
| `policy_id` | `UUID` | 存储策略(外键) |
| `is_banned` | `bool` | 是否被封禁 |
| `banned_at` | `datetime?` | 封禁时间 |
| `banned_by` | `UUID?` | 封禁操作者 UUID |
| `ban_reason` | `str?` | 封禁原因 |
**约束**:
- 同一父目录下名称唯一owner_id + parent_id + name
- 名称不能包含斜杠
**索引**:
- `ix_object_owner_updated`: (owner_id, updated_at)
- `ix_object_parent_updated`: (parent_id, updated_at)
- `ix_object_owner_type`: (owner_id, type)
- `ix_object_owner_size`: (owner_id, size)
**关系**:
- `file_metadata`: 一对一关联 FileMetadata
- `physical_file`: 关联的物理文件(仅文件有效)
- `owner`: 所有者用户
- `banner`: 封禁操作者
- `parent`: 父目录(自引用)
- `children`: 子对象列表(自引用)
- `source_links`: 源链接列表
- `shares`: 分享列表
- `policy`: 存储策略
**业务属性**:
- `source_name`: 向后兼容属性,返回物理文件的存储路径
- `is_file`: 是否为文件
- `is_folder`: 是否为目录
**类方法**:
- `get_by_path()`: 根据路径获取对象
- `get_children()`: 获取子对象列表
---
### 10. FileMetadata文件元数据
**表名**: `filemetadata`
**基类**: `UUIDTableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 主键 |
| `object_id` | `UUID` | 关联的对象(外键,唯一) |
| `width` | `int?` | 图片/视频宽度(像素) |
| `height` | `int?` | 图片/视频高度(像素) |
| `duration` | `float?` | 音视频时长(秒) |
| `bitrate` | `int?` | 比特率kbps |
| `mime_type` | `str?` | MIME 类型 |
| `checksum_md5` | `str?` | MD5 校验和 |
| `checksum_sha256` | `str?` | SHA256 校验和 |
**关系**:
- `object`: 关联的 Object一对一
---
### 11. UploadSession上传会话
**表名**: `uploadsession`
**基类**: `UUIDTableBaseMixin`
用于管理分片上传的会话状态。会话有效期为 24 小时,过期后自动失效。
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 会话 UUID主键 |
| `file_name` | `str` | 原始文件名 |
| `file_size` | `int` | 文件总大小(字节) |
| `chunk_size` | `int` | 分片大小(字节) |
| `total_chunks` | `int` | 总分片数 |
| `uploaded_chunks` | `int` | 已上传分片数 |
| `uploaded_size` | `int` | 已上传大小(字节) |
| `storage_path` | `str?` | 文件存储路径 |
| `expires_at` | `datetime` | 会话过期时间 |
| `owner_id` | `UUID` | 上传者用户(外键) |
| `parent_id` | `UUID` | 目标父目录(外键) |
| `policy_id` | `UUID` | 存储策略(外键) |
**关系**:
- `owner`: 上传者用户
- `parent`: 目标父目录
- `policy`: 存储策略
**业务属性**:
- `is_expired`: 会话是否已过期
- `is_complete`: 上传是否完成
---
### 12. SourceLink源链接
**表名**: `sourcelink`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `name` | `str` | 链接名称 |
| `downloads` | `int` | 通过此链接的下载次数 |
| `object_id` | `UUID` | 关联的对象(外键,必须是文件) |
---
### 13. Share分享
**表名**: `share`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `code` | `str` | 分享码,唯一 |
| `password` | `str?` | 分享密码(加密后) |
| `object_id` | `UUID` | 关联的对象(外键) |
| `views` | `int` | 浏览次数 |
| `downloads` | `int` | 下载次数 |
| `remain_downloads` | `int?` | 剩余下载次数NULL 为不限制) |
| `expires` | `datetime?` | 过期时间NULL 为永不过期) |
| `preview_enabled` | `bool` | 是否允许预览 |
| `source_name` | `str?` | 源名称(冗余字段) |
| `score` | `int` | 兑换所需积分 |
| `user_id` | `UUID` | 创建分享的用户(外键) |
---
### 14. Report举报
**表名**: `report`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `reason` | `int` | 举报原因代码 |
| `description` | `str?` | 补充描述 |
| `share_id` | `int` | 被举报的分享(外键) |
---
### 15. Tag标签
**表名**: `tag`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `name` | `str` | 标签名称 |
| `icon` | `str?` | 标签图标 |
| `color` | `str?` | 标签颜色 |
| `type` | `TagType` | 标签类型manual/automatic |
| `expression` | `str?` | 自动标签的匹配表达式 |
| `user_id` | `UUID` | 所属用户(外键) |
**约束**: 同一用户下标签名称唯一
---
### 16. Task任务
**表名**: `task`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `status` | `TaskStatus` | 任务状态queued/running/completed/error |
| `type` | `int` | 任务类型([TODO] 待定义枚举) |
| `progress` | `int` | 任务进度0-100 |
| `error` | `str?` | 错误信息 |
| `user_id` | `UUID` | 所属用户(外键) |
**索引**: `ix_task_status`, `ix_task_user_status`
**关系**:
- `props`: 一对一关联 TaskProps
- `downloads`: 一对多关联 Download
---
### 17. TaskProps任务属性
**表名**: `taskprops`
**基类**: `TableBaseMixin`(主键为外键 task_id
| 字段 | 类型 | 说明 |
|------|------|------|
| `task_id` | `int` | 关联的任务(外键,主键) |
| `source_path` | `str?` | 源路径 |
| `dest_path` | `str?` | 目标路径 |
| `file_ids` | `str?` | 文件ID列表逗号分隔 |
---
### 18. Download离线下载
**表名**: `download`
**基类**: `UUIDTableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `UUID` | 主键 |
| `status` | `DownloadStatus` | 下载状态running/completed/error |
| `type` | `int` | 任务类型([TODO] 待定义枚举) |
| `source` | `str` | 来源 URL 或标识 |
| `total_size` | `int` | 总大小(字节) |
| `downloaded_size` | `int` | 已下载大小(字节) |
| `g_id` | `str?` | Aria2 GID |
| `speed` | `int` | 下载速度bytes/s |
| `parent` | `str?` | 父任务标识 |
| `error` | `str?` | 错误信息 |
| `dst` | `str` | 目标存储路径 |
| `user_id` | `UUID` | 所属用户(外键) |
| `task_id` | `int?` | 关联的任务(外键) |
| `node_id` | `int` | 执行下载的节点(外键) |
**约束**: 同一节点下 g_id 唯一
**索引**: `ix_download_status`, `ix_download_user_status`
**关系**:
- `aria2_info`: 一对一关联 DownloadAria2Info
- `aria2_files`: 一对多关联 DownloadAria2File
---
### 19. DownloadAria2InfoAria2下载信息
**表名**: `downloadaria2info`
**基类**: `TableBaseMixin`(主键为外键 download_id
| 字段 | 类型 | 说明 |
|------|------|------|
| `download_id` | `UUID` | 关联的下载任务(外键,主键) |
| `info_hash` | `str?` | InfoHashBT种子 |
| `piece_length` | `int` | 分片大小 |
| `num_pieces` | `int` | 分片数量 |
| `num_seeders` | `int` | 做种人数 |
| `connections` | `int` | 连接数 |
| `upload_speed` | `int` | 上传速度bytes/s |
| `upload_length` | `int` | 已上传大小(字节) |
| `error_code` | `str?` | 错误代码 |
| `error_message` | `str?` | 错误信息 |
---
### 20. DownloadAria2FileAria2下载文件
**表名**: `downloadaria2file`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `download_id` | `UUID` | 关联的下载任务(外键) |
| `file_index` | `int` | 文件索引从1开始 |
| `path` | `str` | 文件路径 |
| `length` | `int` | 文件大小(字节) |
| `completed_length` | `int` | 已完成大小(字节) |
| `is_selected` | `bool` | 是否选中下载 |
---
### 21. Node节点
**表名**: `node`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `status` | `NodeStatus` | 节点状态online/offline |
| `name` | `str` | 节点名称,唯一 |
| `type` | `int` | 节点类型([TODO] 待定义枚举) |
| `server` | `str` | 节点地址IP 或域名) |
| `slave_key` | `str?` | 从机通讯密钥 |
| `master_key` | `str?` | 主机通讯密钥 |
| `aria2_enabled` | `bool` | 是否启用 Aria2 |
| `rank` | `int` | 节点排序权重 |
**索引**: `ix_node_status`
**关系**:
- `aria2_config`: 一对一关联 Aria2Configuration
- `downloads`: 一对多关联 Download
---
### 22. Aria2ConfigurationAria2配置
**表名**: `aria2configuration`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `node_id` | `int` | 关联的节点(外键,唯一) |
| `rpc_url` | `str?` | RPC地址 |
| `rpc_secret` | `str?` | RPC密钥 |
| `temp_path` | `str?` | 临时下载路径 |
| `max_concurrent` | `int` | 最大并发数1-50默认5 |
| `timeout` | `int` | 请求超时时间默认300 |
---
### 23. Order订单
**表名**: `order`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `order_no` | `str` | 订单号,唯一 |
| `type` | `int` | 订单类型([TODO] 待定义枚举) |
| `method` | `str?` | 支付方式 |
| `product_id` | `int?` | 商品 ID |
| `num` | `int` | 购买数量 |
| `name` | `str` | 商品名称 |
| `price` | `int` | 订单价格(分) |
| `status` | `OrderStatus` | 订单状态pending/completed/cancelled |
| `user_id` | `UUID` | 所属用户(外键) |
---
### 24. Redeem兑换码
**表名**: `redeem`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `type` | `int` | 兑换码类型([TODO] 待定义枚举) |
| `product_id` | `int?` | 关联的商品/权益 ID |
| `num` | `int` | 可兑换数量/时长等 |
| `code` | `str` | 兑换码,唯一 |
| `used` | `bool` | 是否已使用 |
---
### 25. StoragePack容量包
**表名**: `storagepack`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `name` | `str` | 容量包名称 |
| `active_time` | `datetime?` | 激活时间 |
| `expired_time` | `datetime?` | 过期时间 |
| `size` | `int` | 容量包大小(字节) |
| `user_id` | `UUID` | 所属用户(外键) |
---
### 26. WebDAVWebDAV 账户)
**表名**: `webdav`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `name` | `str` | WebDAV 账户名 |
| `password` | `str` | WebDAV 密码 |
| `root` | `str` | 根目录路径(默认 / |
| `readonly` | `bool` | 是否只读 |
| `use_proxy` | `bool` | 是否使用代理下载 |
| `user_id` | `UUID` | 所属用户(外键) |
**约束**: 同一用户下账户名唯一
---
### 27. Setting系统设置
**表名**: `setting`
**基类**: `TableBaseMixin`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int` | 主键 |
| `type` | `SettingsType` | 设置类型/分组 |
| `name` | `str` | 设置项名称 |
| `value` | `str?` | 设置值 |
**约束**: type + name 唯一
**SettingsType 枚举值**:
`aria2`, `auth`, `authn`, `avatar`, `basic`, `captcha`, `cron`, `file_edit`, `login`, `mail`, `mail_template`, `mobile`, `path`, `preview`, `pwa`, `register`, `retry`, `share`, `slave`, `task`, `thumb`, `timeout`, `upload`, `version`, `view`, `wopi`
---
## 模型关系图
### 一对一关系
```
┌───────────────────────────────────────────────────────────────────┐
│ 一对一关系 │
├───────────────────────────────────────────────────────────────────┤
│ │
│ Group ◄─────────────────────────> GroupOptions │
│ group_id (unique FK) │
│ │
│ Policy ◄────────────────────────> PolicyOptions │
│ policy_id (unique FK) │
│ │
│ Object ◄────────────────────────> FileMetadata │
│ object_id (unique FK) │
│ │
│ Node ◄──────────────────────────> Aria2Configuration │
│ node_id (unique FK) │
│ │
│ Task ◄──────────────────────────> TaskProps │
│ task_id (PK/FK) │
│ │
│ Download ◄──────────────────────> DownloadAria2Info │
│ download_id (PK/FK) │
│ │
└───────────────────────────────────────────────────────────────────┘
```
**新增关系**:
```
┌───────────────────────────────────────────────────────────────────┐
│ 一对多关系(新增) │
├───────────────────────────────────────────────────────────────────┤
│ │
│ PhysicalFile ◄──────────────────> Object (多个) │
│ physical_file_id (FK) 文件去重多个Object可引用 │
│ 同一个PhysicalFile │
│ │
│ User ◄──────────────────────────> UploadSession │
│ owner_id (FK) 用户的上传会话列表 │
│ │
└───────────────────────────────────────────────────────────────────┘
```
| 主表 | 从表 | 外键 | 说明 |
|------|------|------|------|
| Group | GroupOptions | `group_id` (unique) | 每个用户组有且仅有一个选项配置 |
| Policy | PolicyOptions | `policy_id` (unique) | 每个存储策略有且仅有一个扩展选项 |
| Object | FileMetadata | `object_id` (unique) | 每个文件对象有且仅有一个元数据 |
| Node | Aria2Configuration | `node_id` (unique) | 每个节点有且仅有一个 Aria2 配置 |
| Task | TaskProps | `task_id` (PK) | 每个任务有且仅有一个属性配置 |
| Download | DownloadAria2Info | `download_id` (PK) | 每个下载任务有且仅有一个 Aria2 信息 |
---
### 一对多关系
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ 一对多关系 │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────> Download │
│ │ │
│ ├──────> Object ◄──────┬──────> SourceLink │
│ │ │ ↑ │ │
│ │ │ │ └──────> Share ──────> Report │
│ Group ──────> User ───┼─────────┘ │ │
│ │ │ │ (自引用parent-children) │
│ │ ├──────> Order │
│ │ │ │
│ │ ├──────> StoragePack │
│ │ │ │
│ │ ├──────> Tag │
│ │ │ │
│ │ ├──────> Task ──────> Download │
│ │ │ ↑ │
│ │ ├──────> WebDAV │ │
│ │ │ │ │
│ │ └──────> UserAuthn │ │
│ │ │ │
│ └──────> Policy ──────> Object │ │
│ │ │
│ Node ───────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
```
| 一端 | 多端 | 外键 | 说明 |
|------|------|------|------|
| **Group** | User | `group_id` | 用户组包含多个用户 |
| **Group** | User | `previous_group_id` | 用户组过期后恢复关系 |
| **User** | Download | `user_id` | 用户的离线下载任务 |
| **User** | Object | `owner_id` | 用户拥有的文件/目录 |
| **User** | Order | `user_id` | 用户的订单 |
| **User** | Share | `user_id` | 用户创建的分享 |
| **User** | StoragePack | `user_id` | 用户的容量包 |
| **User** | Tag | `user_id` | 用户的标签 |
| **User** | Task | `user_id` | 用户的任务 |
| **User** | WebDAV | `user_id` | 用户的 WebDAV 账户 |
| **User** | UserAuthn | `user_id` | 用户的 WebAuthn 凭证 |
| **User** | UploadSession | `owner_id` | 用户的上传会话 |
| **Policy** | Object | `policy_id` | 存储策略下的对象 |
| **Policy** | PhysicalFile | `policy_id` | 存储策略下的物理文件 |
| **PhysicalFile** | Object | `physical_file_id` | 物理文件被多个逻辑对象引用(文件去重) |
| **Object** | Object | `parent_id` | 目录的子文件/子目录(自引用) |
| **Object** | SourceLink | `object_id` | 文件的源链接 |
| **Object** | Share | `object_id` | 对象的分享 |
| **Share** | Report | `share_id` | 分享的举报 |
| **Task** | Download | `task_id` | 任务关联的下载 |
| **Node** | Download | `node_id` | 节点执行的下载任务 |
| **Download** | DownloadAria2File | `download_id` | 下载任务的文件列表 |
---
### 多对多关系
```
┌─────────────────────────────────────────────────────────┐
│ 多对多关系 │
├─────────────────────────────────────────────────────────┤
│ │
│ Group ◄────── GroupPolicyLink ──────> Policy │
│ │
│ - 一个用户组可以使用多个存储策略 │
│ - 一个存储策略可以被多个用户组使用 │
│ │
└─────────────────────────────────────────────────────────┘
```
| 表1 | 表2 | 关联表 | 说明 |
|-----|-----|--------|------|
| Group | Policy | GroupPolicyLink | 用户组可使用的存储策略 |
---
## 完整关系 ER 图
```
┌──────────────┐
│ Setting │
│ (独立表) │
└──────────────┘
┌──────────────┐
│ Redeem │
│ (独立表) │
└──────────────┘
┌──────────────┐ 1:1 ┌──────────────┐
│ Group │◄────────────>│ GroupOptions │
│ │ └──────────────┘
│ │
│ │──────┐ M:N ┌──────────────────┐
│ │ └──────>│ GroupPolicyLink │◄───┐
└──────┬───────┘ └──────────────────┘ │
│ │
│ 1:N │
▼ │
┌──────────────┐ ┌──────────────┐ │
│ User │ │ Policy │◄───────┘
│ │ │ │
│ │ │ │◄────────────>┌───────────────┐
│ │ └──────┬───────┘ 1:1 │ PolicyOptions │
│ │ │ 1:N └───────────────┘
│ │──────────────┐ │
└──────┬───────┘ │ │
│ │ ▼
│ 1:N │ ┌──────────────┐ ┌──────────────┐
│ │ │ Object │◄────>│ Object │
│ │ │ │ │ (children) │
│ │ │ │ └──────────────┘
├──────────────────────┼─┤ │
│ │ └──────┬───────┘
│ │ │
│ │ │ 1:N ┌──────────────┐
│ │ ├─────────────>│ SourceLink │
│ │ │ └──────────────┘
│ │ │
│ │ │
│ │ │ 1:N ┌──────────────┐
│ │ └─────────────>│ Share │─────> Report
│ │ └──────────────┘
│ │
│ │
├──> Download ◄────────┼───────────────────────── Task
│ ▲ │
│ │ │
│ │ │
│ └─────────────┼─────────────────────── Node
│ │
├──> Order │
│ │
├──> StoragePack │
│ │
├──> Tag │
│ │
├──> WebDAV │
│ │
└──> UserAuthn │
```
---
## 枚举类型
### ObjectType
```python
class ObjectType(StrEnum):
FILE = "file" # 文件
FOLDER = "folder" # 目录
```
### PolicyType
```python
class PolicyType(StrEnum):
LOCAL = "local" # 本地存储
S3 = "s3" # S3 兼容存储
```
### StorageType
```python
class StorageType(StrEnum):
LOCAL = "local" # 本地存储
QINIU = "qiniu" # 七牛云
TENCENT = "tencent" # 腾讯云
ALIYUN = "aliyun" # 阿里云
ONEDRIVE = "onedrive" # OneDrive
GOOGLE_DRIVE = "google_drive" # Google Drive
DROPBOX = "dropbox" # Dropbox
WEBDAV = "webdav" # WebDAV
REMOTE = "remote" # 远程存储
```
### UserStatus
```python
class UserStatus(StrEnum):
ACTIVE = "active" # 正常
ADMIN_BANNED = "admin_banned" # 管理员封禁
SYSTEM_BANNED = "system_banned" # 系统封禁
```
### CaptchaType
```python
class CaptchaType(StrEnum):
DEFAULT = "default" # 默认验证码
GCAPTCHA = "gcaptcha" # Google reCAPTCHA
CLOUD_FLARE_TURNSTILE = "cloudflare turnstile" # Cloudflare Turnstile
```
### ThemeType
```python
class ThemeType(StrEnum):
LIGHT = "light" # 浅色主题
DARK = "dark" # 深色主题
SYSTEM = "system" # 跟随系统
```
### AvatarType
```python
class AvatarType(StrEnum):
DEFAULT = "default" # 默认头像
GRAVATAR = "gravatar" # Gravatar
FILE = "file" # 自定义文件
```
### TagType
```python
class TagType(StrEnum):
MANUAL = "manual" # 手动标签
AUTOMATIC = "automatic" # 自动标签
```
### TaskStatus
```python
class TaskStatus(StrEnum):
QUEUED = "queued" # 排队中
RUNNING = "running" # 处理中
COMPLETED = "completed" # 已完成
ERROR = "error" # 错误
```
### DownloadStatus
```python
class DownloadStatus(StrEnum):
RUNNING = "running" # 进行中
COMPLETED = "completed" # 已完成
ERROR = "error" # 错误
```
### NodeStatus
```python
class NodeStatus(StrEnum):
ONLINE = "online" # 正常
OFFLINE = "offline" # 离线
```
### OrderStatus
```python
class OrderStatus(StrEnum):
PENDING = "pending" # 待支付
COMPLETED = "completed" # 已完成
CANCELLED = "cancelled" # 已取消
```
### 待定义枚举([TODO]
以下枚举已定义框架,具体值待业务需求确定:
- `TaskType` - 任务类型
- `DownloadType` - 下载类型
- `NodeType` - 节点类型
- `OrderType` - 订单类型
- `RedeemType` - 兑换码类型
- `ReportReason` - 举报原因
---
## DTO 模型
### 用户相关
| DTO | 说明 |
|-----|------|
| `LoginRequest` | 登录请求 |
| `RegisterRequest` | 注册请求 |
| `TokenResponse` | 访问令牌响应access_token, refresh_token, expires_in |
| `UserResponse` | 用户信息响应(包含 group |
| `UserPublic` | 用户公开信息 |
| `UserSettingResponse` | 用户设置响应 |
| `WebAuthnInfo` | WebAuthn 信息 |
| `AuthnResponse` | WebAuthn 响应 |
| `UserAdminUpdateRequest` | 管理员更新用户请求 |
| `UserCalibrateResponse` | 用户存储校准响应 |
| `UserAdminDetailResponse` | 管理员用户详情响应 |
### 用户组相关
| DTO | 说明 |
|-----|------|
| `GroupBase` | 用户组基础字段 |
| `GroupOptionsBase` | 用户组选项基础字段 |
| `GroupAllOptionsBase` | 用户组所有选项基础字段 |
| `GroupResponse` | 用户组响应(包含 options |
| `GroupCreateRequest` | 管理员创建用户组请求 |
| `GroupUpdateRequest` | 管理员更新用户组请求 |
| `GroupDetailResponse` | 管理员用户组详情响应 |
| `GroupListResponse` | 用户组列表响应 |
### 存储策略相关
| DTO | 说明 |
|-----|------|
| `PolicyBase` | 存储策略基础字段 |
| `PolicyOptionsBase` | 存储策略选项基础字段 |
| `PolicyResponse` | 存储策略响应id, name, type, max_size |
| `PolicySummary` | 存储策略摘要 |
### 对象相关
| DTO | 说明 |
|-----|------|
| `ObjectBase` | 对象基础字段 |
| `ObjectResponse` | 对象响应(目录列表中的单个项) |
| `DirectoryCreateRequest` | 创建目录请求parent_id, name, policy_id? |
| `DirectoryResponse` | 目录响应id, parent, objects, policy |
| `ObjectMoveRequest` | 移动对象请求src_ids, dst_id |
| `ObjectDeleteRequest` | 删除对象请求ids |
| `ObjectCopyRequest` | 复制对象请求src_ids, dst_id |
| `ObjectRenameRequest` | 重命名对象请求id, new_name |
| `ObjectPropertyResponse` | 对象基本属性响应 |
| `ObjectPropertyDetailResponse` | 对象详细属性响应(含元数据、分享统计) |
### 上传相关
| DTO | 说明 |
|-----|------|
| `CreateUploadSessionRequest` | 创建上传会话请求file_name, file_size, parent_id |
| `UploadSessionResponse` | 上传会话响应id, chunk_size, total_chunks |
| `UploadChunkResponse` | 上传分片响应uploaded_chunks, is_complete |
| `CreateFileRequest` | 创建空白文件请求 |
### 管理员文件管理
| DTO | 说明 |
|-----|------|
| `AdminFileResponse` | 管理员文件响应 |
| `FileBanRequest` | 文件封禁请求 |
| `AdminFileListResponse` | 管理员文件列表响应 |
### 管理员概况
| DTO | 说明 |
|-----|------|
| `MetricsSummary` | 统计摘要(日期列表、每日增量、总计) |
| `LicenseInfo` | 许可证信息 |
| `VersionInfo` | 版本信息 |
| `AdminSummaryData` | 管理员概况数据 |
| `AdminSummaryResponse` | 管理员概况响应 |
### 系统设置
| DTO | 说明 |
|-----|------|
| `SiteConfigResponse` | 站点配置响应 |
| `ThemeResponse` | 主题颜色响应 |
| `SettingItem` | 设置项type, name, value |
| `SettingsListResponse` | 设置列表响应 |
| `SettingsUpdateRequest` | 更新设置请求settings[] |
| `SettingsUpdateResponse` | 更新设置响应updated, created |
### 分享相关
| DTO | 说明 |
|-----|------|
| `ShareBase` | 分享基础字段 |
| `ShareCreateRequest` | 创建分享请求 |
| `ShareResponse` | 分享响应 |
| `AdminShareListItem` | 管理员分享列表项 |
### 任务相关
| DTO | 说明 |
|-----|------|
| `TaskPropsBase` | 任务属性基础字段 |
| `TaskSummary` | 任务摘要 |
### 通用响应
| DTO | 说明 |
|-----|------|
| `ResponseBase` | 通用响应基类code, msg, data |
| `ListResponse[T]` | 泛型分页响应count, items |
| `MCPBase` | MCP 基类 |
| `MCPRequestBase` | MCP 请求基类 |
| `MCPResponseBase` | MCP 响应基类 |
---
## 使用示例
### 查询用户及其关联数据
```python
from sqlalchemy.orm import selectinload
# 获取用户及其用户组
user = await User.get(
session,
User.id == user_id,
load=User.group
)
# 获取用户的所有文件
objects = await Object.get(
session,
(Object.owner_id == user_id) & (Object.type == ObjectType.FILE),
fetch_mode="all"
)
# 分页查询并返回总数
from models.mixin import TableViewRequest, ListResponse
table_view = TableViewRequest(offset=0, limit=20, desc=True, order="created_at")
result: ListResponse[User] = await User.get_with_count(session, table_view=table_view)
print(f"总数: {result.count}, 当前页: {len(result.items)}")
```
### 创建文件对象
```python
file = Object(
name="example.txt",
type=ObjectType.FILE,
size=1024,
owner_id=user.id,
parent_id=folder.id,
policy_id=policy.id,
physical_file_id=physical_file.id,
)
file = await file.save(session) # 必须使用返回值
```
### 多对多关系操作
```python
# 为用户组添加存储策略
from models import GroupPolicyLink
link = GroupPolicyLink(group_id=group.id, policy_id=policy.id)
session.add(link)
await session.commit()
```
### 文件上传流程
```python
# 1. 创建上传会话
upload_session = UploadSession(
file_name="large_file.zip",
file_size=104857600, # 100MB
chunk_size=52428800, # 50MB
total_chunks=2,
owner_id=user.id,
parent_id=folder.id,
policy_id=policy.id,
)
upload_session = await upload_session.save(session)
# 2. 上传分片后更新进度
upload_session.uploaded_chunks += 1
upload_session.uploaded_size += chunk_size
upload_session = await upload_session.save(session)
# 3. 检查是否完成
if upload_session.is_complete:
# 创建 PhysicalFile 和 Object 记录
...
```
### 文件引用计数(去重)
```python
# 复制文件时,只增加引用计数,不复制物理文件
if src.is_file and src.physical_file_id:
physical_file = await PhysicalFile.get(session, PhysicalFile.id == src.physical_file_id)
physical_file.increment_reference()
await physical_file.save(session)
# 删除文件时,减少引用计数
physical_file.decrement_reference()
if physical_file.can_be_deleted:
# 引用计数为0可以删除物理文件
await storage_service.delete_file(physical_file.storage_path)
await PhysicalFile.delete(session, physical_file)
else:
await physical_file.save(session)
```