Skip to content

feat: 从服务器导入文件(引用模式)#173

Open
sharkAndshark wants to merge 16 commits intomainfrom
feature/server-file-import
Open

feat: 从服务器导入文件(引用模式)#173
sharkAndshark wants to merge 16 commits intomainfrom
feature/server-file-import

Conversation

@sharkAndshark
Copy link
Copy Markdown
Owner

@sharkAndshark sharkAndshark commented Mar 17, 2026

概述

实现 Issue #167 - 从服务器导入文件(引用模式)

功能

后端 API

接口 方法 描述
/api/server-files/directories GET 获取可导入目录列表
/api/server-files/browse GET 浏览目录内容
/api/server-files/import POST 导入文件(引用模式)

前端

  • 文件列表页新增「从服务器导入」按钮
  • 弹窗支持:目录导航、多选文件、批量导入

数据库变更

ALTER TABLE files ADD COLUMN source_type VARCHAR DEFAULT 'upload';

配置

SERVER_DATA_DIRS=/data/maps,/data/backup

🤖 GLM-5 Code Review 结果

🔴 安全问题

问题 说明
符号链接绕过 未检查子目录是否为符号链接
路径信息泄露 返回完整 canonical 路径

🟡 潜在 Bug

  • 路径拼接未处理尾部斜杠
  • .json 扩展名过于宽泛
  • 导入失败静默跳过

📋 修复建议

  • 符号链接检查
  • 返回相对路径
  • 增加失败反馈

Closes #167

sharkAndshark added a commit that referenced this pull request Mar 17, 2026
1. 符号链接绕过:添加 check_symlink_target 函数检查符号链接目标是否在白名单内,在 browse_directory 和 import_files 中拒绝指向白名单外的链接

2. 路径信息泄露:list_directories 返回 canonical 绝对路径而非原始路径

3. 前端路径拼接:添加 joinPath 辅助函数处理尾部斜杠,避免双斜杠问题

4. 导入失败反馈:ImportResponse 增加 failed 字段,返回失败文件路径及原因
实现 Issue #167

后端:
- 新增 server_files_handlers.rs 处理服务器文件 API
- GET /api/server-files/directories - 获取可导入目录列表
- GET /api/server-files/browse - 浏览目录内容
- POST /api/server-files/import - 导入文件(引用模式)
- files 表新增 source_type 字段('upload' | 'server_import')

前端:
- 新增「从服务器导入」按钮(仅管理员可见)
- 新增服务器文件导入弹窗
- 支持浏览目录、选择文件、批量导入

安全:
- 路径白名单:仅允许访问 SERVER_DATA_DIRS 配置的目录
- 路径遍历防护:拒绝 .. 和符号链接跳出白名单
- 文件类型验证:与上传一致
问题:导入的文件只插入数据库,没有触发 import_spatial_data() 等处理
修复:在导入后启动 tokio::spawn 后台任务,与上传流程一致

- 复用 upload.rs 的处理逻辑
- 支持 mbtiles/pmtiles/空间数据文件
- 导入后状态:uploaded → processing → ready/failed
移除管理员限制,普通成员也可使用
1. 符号链接绕过:添加 check_symlink_target 函数检查符号链接目标是否在白名单内,在 browse_directory 和 import_files 中拒绝指向白名单外的链接

2. 路径信息泄露:list_directories 返回 canonical 绝对路径而非原始路径

3. 前端路径拼接:添加 joinPath 辅助函数处理尾部斜杠,避免双斜杠问题

4. 导入失败反馈:ImportResponse 增加 failed 字段,返回失败文件路径及原因
1. TOCTOU 竞态条件:先 canonicalize 再检查白名单,防止攻击者在检查和解析之间替换文件

2. 链式符号链接:使用 canonicalize 完全解析所有符号链接,而非仅检查一级

3. 性能优化:缓存白名单目录的 canonical 路径,避免重复解析

移除了冗余的 check_symlink_target 函数,所有安全检查现在基于 canonical 路径
问题修复:
1. 数据库 schema 缺少 source_type 字段 - 添加 ALTER TABLE 和索引
2. 文件大小限制 - 使用与上传相同的 max_size 配置
3. 重复导入检查 - 查询已有记录避免重复
4. 并发导入数量 - 从 50 降至 20
5. 日志路径泄露 - 移除完整路径记录

代码质量:
- 添加常量 MAX_IMPORT_FILES 和 MAX_FILE_SIZE_MB
- 改进错误消息(包含具体大小信息)
- 统一日志格式(不记录敏感路径)
ensure_workspace_schema_and_backfill 中已添加该字段,init_database 中的添加是冗余的
后端修复:
- 修复并发问题: 在 spawn 异步任务前释放数据库锁,避免任务阻塞
- 改进错误处理: spawn 内部不再忽略数据库更新错误,记录详细日志
- 增强路径验证: 添加更完整的文件扩展名和元数据检查

前端修复:
- 部分文件导入失败时保持 modal 打开,让用户能看到失败信息
- 清空已成功导入的文件选择,避免重复导入
@sharkAndshark sharkAndshark force-pushed the feature/server-file-import branch from 26f9adf to 06a434d Compare March 17, 2026 08:01
将 duckdb::Error 转换为 Box<dyn std::error::Error + Send + Sync>
以解决编译错误 E0277
移除多余空行以符合 biome 格式要求
修复编译错误:
- E0308: match arms 类型不匹配
- E0282: 类型推断失败

将 pmtiles 分支的返回类型从 Box<dyn Error> 改为 String,与 import_mbtiles 和 import_spatial_data 保持一致
- 移除多余空格 (line 313)
- 使用箭头函数括号 (line 328)
- 将 .map_err(|e| e.to_string())? 改为 match 表达式
- async 块返回 () 不能使用 ? 操作符
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 从服务器导入文件(引用模式)

1 participant