Label Studio 本地文件存储(Local Files Storage)完整指南:离线环境下的数据导入、文件服务与标注导出
【免费下载链接】label-studioLabel Studio is a multi-type data labeling and annotation tool with standardized output format项目地址: https://gitcode.com/GitHub_Trending/la/label-studio
导读
Label Studio 的 Local Files Storage(本地文件存储)让自托管部署可以在不依赖任何对象存储服务的前提下,直接从服务器文件系统读取图片、音频、视频、文档等媒体数据,并将标注结果写回磁盘。它专为离线(air-gapped)环境或"数据不允许离开宿主机"的工作流设计,在社区版中提供了开箱即用的"我的数据目录"自动探测机制。读完本文,你将掌握如何在 Label Studio 中配置本地文件存储的三个核心操作——导入/同步(Import/Sync)、文件服务(Serve)与标注导出(Export),理解其底层路径规范化、权限校验与缓存机制,并能够独立排查 403/404 等常见故障。
概述:三个核心操作
本地文件存储的核心价值体现在三个相互独立又彼此衔接的操作上:
- 导入/同步(Import/Sync)——扫描一个目录,为每个文件创建指向本地文件的标注任务;
- 文件服务(Serve)——通过
/data/local-files/?d=...端点把文件字节流式传输给标注界面; - 导出(Export)——把完成的标注以 JSON 文件形式写入目标目录。
架构:配置、导入、文件服务与导出的完整链路
整个本地文件存储的数据流可以用下面这张流程图概括(该图源自 localfiles/README.md 中的架构图,语义与代码实现一致):
Configuration: - 环境变量: LOCAL_FILES_SERVING_ENABLED, LOCAL_FILES_DOCUMENT_ROOT - 社区版自动探测: mydata / label-studio-data Import Flow: UI "Add Source Storage" → Serializer(normalize path, validate_connection) → LocalFilesImportStorage → Sync → iter_objects(扫描目录) → use_blob_urls=true: 为每个文件创建任务,URL 为 /data/local-files/?d=path → use_blob_urls=false: 读取 JSON 文件作为任务定义 → 任务写入数据库 → 建立 LocalFilesImportStorageLink File Serving Flow: 标注界面请求 /data/local-files/?d=relative/path → localfiles_data 视图 → 校验认证 → safe_join(DOCUMENT_ROOT, path) 规范化路径 → 查找所有 storage.path 是文件目录前缀的存储 → 校验 project.has_permission → 允许: 使用 RangedFileResponse 流式返回文件并带 ETag → 拒绝: 403 Forbidden;路径不存在: 404 Not Found Export Flow: 标注保存 → post_save 信号 → LocalFilesExportStorage.save_annotation → 写入 JSON 到 storage.path/annotation_id.json → 建立 LocalFilesExportStorageLink 标注删除 → pre_delete 信号 → delete_annotation 删除 JSON 文件各环节对应的源码位置
| 环节 | 源码文件 | 关键实现 |
|---|---|---|
| 路径规范化 | functions.py | normalize_storage_path |
| 目录自动探测 | functions.py | autodetect_local_files_root |
| 存储模型与校验 | models.py | LocalFilesMixin、validate_connection |
| 文件服务端点 | views.py | localfiles_data视图 |
| REST API | api.py | Import/Export 系列 API 视图 |
| 表单字段定义 | form_layout.yml | 前端表单布局 |
| 路由注册 | io_storages/urls.py | /api/storages/localfiles/与/data/local-files/ |
关键概念
存储模型(Storage Models)
本地文件存储在数据库中对应四个模型类,职责划分非常清晰:
| 模型 | 用途 |
|---|---|
LocalFilesMixin | 共享字段(path、regex_filter、use_blob_urls)与校验逻辑 |
LocalFilesImportStorage | 源存储(Source Storage):扫描目录、创建任务 |
LocalFilesExportStorage | 目标存储(Target Storage):把标注写成 JSON 文件 |
LocalFilesImportStorageLink | 把任务关联到导入存储(追踪"哪个文件创建了哪个任务") |
LocalFilesExportStorageLink | 把标注关联到导出存储(追踪已导出的文件) |
从源码看,LocalFilesMixin的三个核心字段定义在 models.py:
path:本地绝对路径(TextField),在clean()与save()两个时机都会执行normalize_storage_path规范化;regex_filter:过滤对象的正则表达式,命中才导入;use_blob_urls:布尔值,决定文件是被当作 BLOB 生成 URL,还是被当作任务定义 JSON 解析(默认False)。
导入模式(Import Modes)
同步导入存储时,use_blob_urls决定文件如何变成任务:
use_blob_urls=True(默认"Files"模式):每个文件变成一个任务,任务中唯一的 data 字段指向/data/local-files/?d=<相对路径>。最适合标注图片、音频、视频这类单媒体文件。use_blob_urls=False("Tasks"模式):每个.json/.jsonl文件被解析为任务定义,适用于任务结构复杂或有多个 data 字段的场景。
这一分支逻辑实现在LocalFilesImportStorageBase.get_data()(models.py)中:use_blob_urls=True时构造{settings.DATA_UNDEFINED_NAME: f'{settings.HOSTNAME}/data/local-files/?d={quote(relative_path)}'}形式的任务;否则调用load_tasks_json读取文件内容。表单中对应的选择项定义在 form_layout.yml,UI 文案为 "Files - Automatically creates a task for each storage object" 与 "Tasks - Treat each JSON or JSONL file as a task definition"。
目录扫描由iter_objects()(models.py)完成:它使用path.glob('*')或path.rglob('*')(当recursive_scan开启时)遍历目录,按文件名升序排序(保证任务 ID 与文件名顺序一致),跳过目录项,并用regex_filter正则匹配文件名(regex.match,注意是 match 而非 search,即从文件名开头匹配)。
路径处理(Path Handling)
所有存储路径在保存前都会被规范化(normalize_storage_path,见 functions.py):
- 去除尾部斜杠(
/data/images/→/data/images); - 把反斜杠转换为当前操作系统的路径分隔符(Linux 上
C:\data→C:/data); - 折叠冗余分隔符(
/data//images→/data/images)。
这一步是为了防止"存储路径与请求路径不一致"导致的 404 错误——因为权限检查(见下文)会对请求文件所在目录与storage.path做字符串前缀匹配,任何格式不一致都会导致匹配失败。
权限模型(Permission Model)
/data/local-files/?d=...端点强制执行四重校验(对应 views.py 的实现顺序):
- 用户必须已认证(视图装饰器
@permission_classes([IsAuthenticated])); LOCAL_FILES_SERVING_ENABLED必须为true(否则直接返回 403);- 请求文件的所在目录必须位于至少一个
LocalFilesImportStorage.path之内——实现方式是LocalFilesImportStorage.objects.annotate(_full_path=Value(full_path_dir)).filter(_full_path__startswith=F('path')),即对请求文件目录做数据库前缀匹配; - 用户必须对该存储所属项目有访问权限(
storage.project.has_permission(request.user))。
配置指南
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
LOCAL_FILES_SERVING_ENABLED | false | 必须设为true才能通过/data/local-files/提供文件服务 |
LOCAL_FILES_DOCUMENT_ROOT | /(根目录) | 基础目录;所有存储路径必须是它的子目录 |
ENABLE_LOCAL_FILES_STORAGE | true | 是否把 Local Files 作为存储选项展示 |
这些默认值与解析逻辑定义在 core/settings/base.py:
ENABLE_LOCAL_FILES_STORAGE = get_bool_env('ENABLE_LOCAL_FILES_STORAGE', default=True) LOCAL_FILES_SERVING_ENABLED = get_bool_env('LOCAL_FILES_SERVING_ENABLED', default=False) LOCAL_FILES_DOCUMENT_ROOT = get_env('LOCAL_FILES_DOCUMENT_ROOT', default=os.path.abspath(os.sep))变量名可以加LABEL_STUDIO_或HEARTEX_前缀(按此顺序检测),例如LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED=true。
ENABLE_LOCAL_FILES_STORAGE=false时,Local Files 存储选项会在 API 注册阶段被隐藏——io_storages/all_api.py 中依据该开关决定是否注册相关视图。
社区版自动探测(Community Edition Auto-Detection)
当LOCAL_FILES_DOCUMENT_ROOT与LOCAL_FILES_SERVING_ENABLED都未设置时,社区版会自动在当前工作目录下查找mydata或label-studio-data目录(候选名定义在 functions.py 的AUTO_ROOT_CANDIDATES元组中)。若找到,则把该目录设为文档根并开启本地文件服务。
对应逻辑在 core/settings/base.py:
if ( VERSION_EDITION == 'Community' and not has_env('LOCAL_FILES_DOCUMENT_ROOT') and not has_env('LOCAL_FILES_SERVING_ENABLED') ): from label_studio.io_storages.localfiles.functions import autodetect_local_files_root _autodetected_root = autodetect_local_files_root() if _autodetected_root: LOCAL_FILES_DOCUMENT_ROOT = _autodetected_root LOCAL_FILES_SERVING_ENABLED = TrueDocker 快捷方式:把宿主机目录挂载到容器内的/label-studio/mydata,即可在不设置任何环境变量的情况下启用本地文件存储。
生产环境配置
export LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED=true export LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT=/data/labelstudio # 目录结构: # /data/labelstudio/ ← DOCUMENT_ROOT # /data/labelstudio/project1/ ← 项目 1 的存储路径 # /data/labelstudio/project2/ ← 项目 2 的存储路径注意:每个存储路径都必须是LOCAL_FILES_DOCUMENT_ROOT的子目录,不能等于文档根本身。这一限制在validate_connection()(models.py)中有三重校验:
- 路径必须存在(
Path.exists()),否则报 "does not exist"; - 路径不能与
LOCAL_FILES_DOCUMENT_ROOT相同("cannot be the same ... by security reasons"); - 路径必须是文档根的子目录(
document_root not in path.parents时报错),并提示如{DOCUMENT_ROOT}/dataset1的示例。
此外,若LOCAL_FILES_SERVING_ENABLED为False,创建存储时会直接报校验错误,提示先设置环境变量并重启;社区版还会附上community_auto_hint()的提示(创建mydata或label-studio-data目录可自动启用)。
使用指南
用本地文件创建任务
- 在项目Settings → Cloud Storage → Add Source Storage → Local Files中配置导入存储;
- 将Absolute local path(绝对本地路径)设置为
LOCAL_FILES_DOCUMENT_ROOT的子目录; - 选择导入方式:
- Files:每个媒体文件自动创建一个任务;
- Tasks:把 JSON/JSONL 文件作为任务定义读取;
- 点击Sync扫描目录并创建任务。
表单中还可选填File Filter Regex(如.*csv、.*(jpe?g|png|tiff)、.*\w+-\d+.text),只导入文件名匹配正则的文件(对应LocalFilesMixin.regex_filter字段与iter_objects中的过滤逻辑);Recursive scan开启后可递归扫描子目录。
手动导入任务(引用本地文件)
通过 JSON 手动导入任务时,用以下格式引用本地文件:
{ "data": { "image": "/data/local-files/?d=project1/images/photo.jpg", "audio": "/data/local-files/?d=project1/audio/recording.wav" } }?d=之后的路径是相对于LOCAL_FILES_DOCUMENT_ROOT的。服务端收到请求后,会先posixpath.normpath(path).lstrip('/')规范化相对路径,再通过 Django 的safe_join(local_serving_document_root, path)拼接出安全绝对路径(views.py),从而把路径逃逸(path traversal)风险限制在文档根之内。
导出标注
- 在项目Settings → Cloud Storage → Add Target Storage → Local Files中配置目标存储;
- 标注保存后会自动写成 JSON 文件;
- 文件命名规则为
<annotation_id>.json,位于存储路径下。
导出由 Django 信号驱动(models.py):
post_save信号(export_annotation_to_local_files):标注保存后,遍历项目下所有io_storages_localfilesexportstorages关联的导出存储,调用save_annotation();save_annotation()(models.py)把序列化后的标注用json.dump(..., indent=2)写入{storage_path}/{annotation_id}.json,并创建LocalFilesExportStorageLink;pre_delete信号(delete_annotation_from_local_files):标注删除时,若对应存储的can_delete_objects为True,则删除磁盘上的 JSON 文件(文件已缺失时仅记录 warning),并清理关联记录。
测试用例 test_localfiles_export.py 验证了这一行为:can_delete_objects=True时删除标注会同步删除导出文件与链接;can_delete_objects=False时导出文件保留。同步工作流测试见 fsm/tests/test_storage_sync_workflows.py。
API 参考
REST 端点
| 端点 | 方法 | 说明 |
|---|---|---|
/api/storages/localfiles/ | GET, POST | 列出/创建导入存储 |
/api/storages/localfiles/{id}/ | GET, PATCH, DELETE | 管理指定导入存储 |
/api/storages/localfiles/{id}/sync | POST | 触发同步 |
/api/storages/export/localfiles/ | GET, POST | 列出/创建导出存储 |
/data/local-files/?d={path} | GET | 提供文件内容服务(非 REST 端点) |
上述路由注册在 io_storages/urls.py,除此之外还有localfiles/validate(连接校验)、localfiles/form(表单布局)、localfiles/files(文件列表)以及对应的 export 系列端点。对应的 API 视图类定义在 api.py,序列化器在 serializers.py——其中validate()会先规范化path,再实例化存储模型调用validate_connection(),把 Django/DRF 校验错误统一转为字符串格式返回给前端。
文件服务细节
/data/local-files/视图(views.py)的行为:
- 服务被禁用或用户无权限 → 返回403;
- 文件不存在或没有匹配的存储 → 返回404;
- 客户端
If-None-Match与当前 ETag 匹配 → 返回304 Not Modified; - 支持HTTP Range 请求,用于视频/音频的拖动播放(通过
RangedFileResponse实现,views.py)。
缓存实现细节:build_localfile_response()(views.py)基于文件修改时间纳秒与文件大小生成弱 ETag(格式W/"{mtime_ns:x}-{size:x}"),使浏览器可以在文件未变化时复用缓存;MIME 类型通过mimetypes.guess_type探测,未知类型回退为application/octet-stream。
文件参考
| 文件 | 用途 |
|---|---|
| models.py | Django 模型、normalize_storage_path应用、连接校验、信号处理器 |
| views.py | /data/local-files/端点,含 ETag 与 Range 支持 |
| serializers.py | DRF 序列化器、路径规范化、错误格式化 |
| api.py | REST API 视图类 |
| functions.py | normalize_storage_path、autodetect_local_files_root |
| form_layout.yml | UI 表单字段定义 |
故障排查
常见问题
| 症状 | 原因 | 解决方案 |
|---|---|---|
/data/local-files/返回 403 | 文件服务被禁用 | 设置LOCAL_FILES_SERVING_ENABLED=true并重启 |
/data/local-files/返回 404 | 没有匹配的存储或文件不存在 | 检查存储路径是否为文件路径的前缀;确认文件存在 |
| 创建存储时校验报错 | 路径不在文档根之下 | 确保路径以LOCAL_FILES_DOCUMENT_ROOT开头且为子目录 |
| 图片显示为裂图 | 路径不匹配(如尾部斜杠) | 路径现在会自动规范化;重新同步存储即可 |
调试步骤
检查环境变量:
echo $LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED echo $LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT直接测试文件访问(注意需携带授权 Token 的
-H请求头):curl -I -H "Authorization: Token <your_token>" "http://localhost:8080/data/local-files/?d=project1/test.jpg"在 Django shell 中核查存储配置:
from io_storages.localfiles.models import LocalFilesImportStorage for s in LocalFilesImportStorage.objects.all(): print(f"{s.project.title}: {s.path}")
安全注意事项
- 默认禁用:
LOCAL_FILES_SERVING_ENABLED=false防止意外暴露文件系统; - 路径包含:所有请求都基于
LOCAL_FILES_DOCUMENT_ROOT校验(safe_join+ 前缀匹配),路径逃逸被限制在文档根内; - 项目权限:用户只能访问其有权限的、且与该文件存在前缀关联的项目存储中的文件;
- 无目录列表:仅提供明确的文件路径服务,不提供目录浏览。
警告:不要在公开的多租户部署中启用本地文件服务。该特性专为单租户的本地(on-premise)部署设计。若你确实需要多租户场景,应改用对象存储(S3/GCS/Azure Blob)等具备独立凭据隔离能力的存储后端。
【免费下载链接】label-studioLabel Studio is a multi-type data labeling and annotation tool with standardized output format项目地址: https://gitcode.com/GitHub_Trending/la/label-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考