- 替换 Field(max_length=X) 为 StrX/TextX 类型别名(21 个 sqlmodels 文件) - 替换 get + 404 检查为 get_exist_one()(17 个路由文件,约 50 处) - 替换 save + session.refresh 为 save(load=...) - 替换 session.add + commit 为 save()(dav/provider.py) - 更新所有依赖至最新版本 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Models 数据库模型文档
本目录包含 DiskNext Server 的所有数据库模型定义,基于 SQLModel 框架实现。
目录结构
models/
├── base/ # 基础模型类
│ ├── __init__.py # 导出 SQLModelBase
│ └── sqlmodel_base.py # SQLModelBase 基类(自定义元类,支持联表继承)
├── mixin/ # Mixin 模块
│ ├── __init__.py # 统一导出
│ ├── table.py # TableBaseMixin, UUIDTableBaseMixin(CRUD + 时间戳 + 分页)
│ ├── 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 # 响应基类 DTO(ResponseBase, 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- 组合分页排序和时间筛选
使用方式:
from models.base import SQLModelBase
from models.mixin import TableBaseMixin
class MyModel(SQLModelBase, TableBaseMixin):
name: str
UUIDTableBaseMixin
继承自 TableBaseMixin,将主键改为 UUID 类型:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
UUID |
UUID 主键(自动生成) |
使用方式:
from models.base import SQLModelBase
from models.mixin import UUIDTableBaseMixin
class MyUUIDModel(SQLModelBase, UUIDTableBaseMixin):
name: str
注意:当有 Base 类已继承 SQLModelBase 时,子类不需要重复继承:
class UserBase(SQLModelBase):
username: str
class User(UserBase, UUIDTableBaseMixin): # 不需要再写 SQLModelBase
password: str
ListResponse[T]
泛型分页响应类,用于所有 LIST 端点的标准化响应格式:
class ListResponse(BaseModel, Generic[ItemT]):
count: int # 符合条件的记录总数
items: list[T] # 当前页的记录列表
使用示例:
@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. UserAuthn(WebAuthn 凭证)
表名: userauthn
基类: TableBaseMixin
| 字段 | 类型 | 说明 |
|---|---|---|
id |
int |
主键 |
credential_id |
str |
凭证 ID(Base64 编码) |
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/s),0 为不限制 |
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: 一对一关联 FileMetadataphysical_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: 一对一关联 TaskPropsdownloads: 一对多关联 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: 一对一关联 DownloadAria2Infoaria2_files: 一对多关联 DownloadAria2File
19. DownloadAria2Info(Aria2下载信息)
表名: downloadaria2info
基类: TableBaseMixin(主键为外键 download_id)
| 字段 | 类型 | 说明 |
|---|---|---|
download_id |
UUID |
关联的下载任务(外键,主键) |
info_hash |
str? |
InfoHash(BT种子) |
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. DownloadAria2File(Aria2下载文件)
表名: 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: 一对一关联 Aria2Configurationdownloads: 一对多关联 Download
22. Aria2Configuration(Aria2配置)
表名: 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. WebDAV(WebDAV 账户)
表名: 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
class ObjectType(StrEnum):
FILE = "file" # 文件
FOLDER = "folder" # 目录
PolicyType
class PolicyType(StrEnum):
LOCAL = "local" # 本地存储
S3 = "s3" # S3 兼容存储
PolicyType
class PolicyType(StrEnum):
LOCAL = "local" # 本地存储
S3 = "s3" # S3 兼容存储
UserStatus
class UserStatus(StrEnum):
ACTIVE = "active" # 正常
ADMIN_BANNED = "admin_banned" # 管理员封禁
SYSTEM_BANNED = "system_banned" # 系统封禁
CaptchaType
class CaptchaType(StrEnum):
DEFAULT = "default" # 默认验证码
GCAPTCHA = "gcaptcha" # Google reCAPTCHA
CLOUD_FLARE_TURNSTILE = "cloudflare turnstile" # Cloudflare Turnstile
ThemeType
class ThemeType(StrEnum):
LIGHT = "light" # 浅色主题
DARK = "dark" # 深色主题
SYSTEM = "system" # 跟随系统
AvatarType
class AvatarType(StrEnum):
DEFAULT = "default" # 默认头像
GRAVATAR = "gravatar" # Gravatar
FILE = "file" # 自定义文件
TagType
class TagType(StrEnum):
MANUAL = "manual" # 手动标签
AUTOMATIC = "automatic" # 自动标签
TaskStatus
class TaskStatus(StrEnum):
QUEUED = "queued" # 排队中
RUNNING = "running" # 处理中
COMPLETED = "completed" # 已完成
ERROR = "error" # 错误
DownloadStatus
class DownloadStatus(StrEnum):
RUNNING = "running" # 进行中
COMPLETED = "completed" # 已完成
ERROR = "error" # 错误
NodeStatus
class NodeStatus(StrEnum):
ONLINE = "online" # 正常
OFFLINE = "offline" # 离线
OrderStatus
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 响应基类 |
使用示例
查询用户及其关联数据
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)}")
创建文件对象
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) # 必须使用返回值
多对多关系操作
# 为用户组添加存储策略
from models import GroupPolicyLink
link = GroupPolicyLink(group_id=group.id, policy_id=policy.id)
session.add(link)
await session.commit()
文件上传流程
# 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 记录
...
文件引用计数(去重)
# 复制文件时,只增加引用计数,不复制物理文件
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)