Dify v1.15.0 Chatflow附件上传存储机制调研报告
一、文件上传完整流程
用户在 chatflow 中上传附件(图片、文档、视频、音频)时,流程如下:
bash
用户上传文件 (multipart/form-data)
│
▼
API Controller ── POST /files/upload
├── Web 应用: api/controllers/web/files.py
├── Service API: api/controllers/service_api/app/file.py
└── Console: api/controllers/console/files.py
│
▼
FileService.upload_file() ── api/services/file_service.py:43
├── 1. 校验扩展名黑名单 (UPLOAD_FILE_EXTENSION_BLACKLIST)
├── 2. 校验文件大小(按类型区分限制)
├── 3. 生成存储 key: upload_files/{tenant_id}/{uuid}.{ext}
├── 4. storage.save(key, content) → 写入存储后端
├── 5. 创建 UploadFile 数据库记录
└── 6. 生成签名访问 URL (source_url)
二、文件存储在哪里?
存储后端类型
由环境变量 STORAGE_TYPE 控制,支持 13 种存储后端(storage_type.py):
| 类型 | 说明 |
|---|---|
opendal (默认) |
OpenDAL 统一存储,支持 fs/s3/azblob/gcs 等多种 scheme |
s3 |
AWS S3 |
azure-blob |
Azure Blob Storage |
aliyun-oss |
阿里云 OSS |
tencent-cos |
腾讯云 COS |
huawei-obs |
华为云 OBS |
google-storage |
Google Cloud Storage |
volcengine-tos |
火山引擎 TOS |
oci-storage |
Oracle Cloud |
supabase |
Supabase Storage |
local (已废弃) |
底层仍使用 OpenDAL fs scheme |
存储路径
- 逻辑路径 :
upload_files/{tenant_id}/{uuid}.{extension} - Docker 本地存储物理路径 :
./volumes/app/storage→ 映射到容器内/app/api/storage/- 完整路径示例:
/app/api/storage/upload_files/{tenant_id}/{uuid}.jpg
- 完整路径示例:
数据库记录
文件信息记录在 upload_files 表中(api/models/model.py:2255),关键字段:
| 字段 | 说明 |
|---|---|
id |
UUID |
tenant_id |
租户 ID |
storage_type |
存储类型 |
key |
存储路径 |
name |
原始文件名 |
size |
文件大小(字节) |
extension / mime_type |
文件类型 |
hash |
SHA3-256 哈希 |
source_url |
签名访问 URL |
created_at |
创建时间 |
文件与消息的关联通过 message_files 表(api/models/model.py:1876)中的 upload_file_id 字段关联。
三、文件存储多久?是否会自动删除?
⚠️ 核心结论:上传的附件文件不会自动删除,会永久保留
Dify 没有 针对 upload_files 的 TTL/过期自动清理机制。具体分析:
1. clean_messages 定时任务 --- 只删数据库记录,不删文件
clean_messages.py 中的 clean_messages 任务(默认关闭 ,需 ENABLE_CLEAN_MESSAGES=true):
- ✅ 删除
messages表记录 - ✅ 删除关联表记录:
MessageFile、MessageFeedback、MessageChain、MessageAgentThought等 - ❌ 不删除
upload_files表记录 - ❌ 不删除 存储后端中的实际文件
关键代码(messages_clean_service.py:570-580):
python
# _batch_delete_message_relations 方法中:
session.execute(delete(MessageFile).where(MessageFile.message_id.in_(message_ids)))
# ↑ 只删除 message_files 关联记录,不触碰 upload_files 表和存储
2. 其他定时任务也不涉及上传文件清理
| 任务 | 说明 | 删除存储文件? |
|---|---|---|
clean_embedding_cache_task |
清理 embedding 缓存 | ❌ |
clean_unused_datasets_task |
清理未使用的数据集 | ❌ |
clean_workflow_runlogs_precise |
清理 workflow 日志记录 | ❌ |
mail_clean_document_notify_task |
发送邮件通知 | ❌ |
3. 唯一会自动删除文件的场景:删除 App
当删除整个 App 时,remove_app_and_related_data_task(remove_app_and_related_data_task.py)会级联删除关联的存储文件。
四、如何手动删除?
方法 1:通过 API 删除单个文件
FileService.delete_file()(api/services/file_service.py:260):
python
def delete_file(self, file_id: str):
with self._session_maker() as session, session.begin():
upload_file = session.scalar(select(UploadFile).where(UploadFile.id == file_id))
if not upload_file:
return
storage.delete(upload_file.key) # 删除存储中的实际文件
session.delete(upload_file) # 删除数据库记录
方法 2:删除整个 App(级联删除)
删除 App 时会自动清理该 App 关联的所有上传文件。
方法 3:直接操作存储和数据库
对于本地存储,可直接删除文件系统中的文件:
bash
# Docker 环境
rm /app/api/storage/upload_files/{tenant_id}/{file_uuid}.{ext}
同时清理数据库记录:
sql
DELETE FROM upload_files WHERE key = 'upload_files/{tenant_id}/{file_uuid}.{ext}';
五、相关环境变量/全局配置
存储配置
| 环境变量 | 默认值 | 说明 |
|---|---|---|
STORAGE_TYPE |
opendal |
存储后端类型 |
OPENDAL_SCHEME |
fs |
OpenDAL scheme |
OPENDAL_FS_ROOT |
storage |
本地存储根目录 |
S3_ENDPOINT / S3_BUCKET_NAME / ... |
- | S3 配置 |
文件上传限制
| 环境变量 | 默认值 | 说明 |
|---|---|---|
UPLOAD_FILE_SIZE_LIMIT |
15 | 通用文件大小上限 (MB) |
UPLOAD_IMAGE_FILE_SIZE_LIMIT |
10 | 图片上限 (MB) |
UPLOAD_VIDEO_FILE_SIZE_LIMIT |
100 | 视频上限 (MB) |
UPLOAD_AUDIO_FILE_SIZE_LIMIT |
50 | 音频上限 (MB) |
UPLOAD_FILE_BATCH_LIMIT |
5 | 单次上传批量上限 |
WORKFLOW_FILE_UPLOAD_LIMIT |
10 | Workflow 上传文件数上限 |
UPLOAD_FILE_EXTENSION_BLACKLIST |
(空) | 禁止上传的扩展名 |
FILES_ACCESS_TIMEOUT |
300 | 签名 URL 过期时间(秒) |
清理任务配置
| 环境变量 | 默认值 | 说明 |
|---|---|---|
ENABLE_CLEAN_MESSAGES |
false |
启用消息清理任务(不删文件) |
SANDBOX_EXPIRED_RECORDS_RETENTION_DAYS |
- | 消息保留天数 |
ENABLE_WORKFLOW_RUN_CLEANUP_TASK |
false |
启用 workflow run 清理 |
六、总结
flowchart TD
A[用户上传附件] --> B[FileService.upload_file]
B --> C[存储到 STORAGE_TYPE 指定的后端]
B --> D[记录到 upload_files 表]
D --> E[关联到 message_files 表]
C --> F["⚠️ 永久保留\n无自动清理"]
D --> F
G[clean_messages 定时任务] -->|仅删除| H[message_files 关联记录]
G -.->|❌ 不删除| D
G -.->|❌ 不删除| C
I[手动删除方式] --> J[FileService.delete_file]
I --> K[删除 App 级联删除]
I --> L[直接操作存储+DB]
| 问题 | 答案 |
|---|---|
| 文件存储在哪里? | 由 STORAGE_TYPE 决定,默认 OpenDAL 本地文件系统 (/app/api/storage/upload_files/{tenant_id}/) |
| 存储多久? | 永久,没有 TTL 或过期机制 |
| 是否自动删除? | 否 。clean_messages 任务只删消息数据库记录,不删上传文件 |
| 如何手动删除? | FileService.delete_file(file_id) / 删除 App 级联删除 / 直接操作存储 |
| 全局变量控制? | STORAGE_TYPE 控制存储位置;ENABLE_CLEAN_MESSAGES 控制消息清理(但不清理文件);各类 UPLOAD_* 控制上传限制 |
⚠️ 重要提示 :如果你需要自动清理上传的附件文件,目前 Dify 没有内置此功能 ,需要自行实现定时任务,扫描
upload_files表中未被引用的记录并调用storage.delete()清理。