一个 C/S 架构的跨设备数据接入层。任何消费方只认一个 Server 地址,Server 自动路由到对应设备拿数据回来。
适合「多台机器(Mac / Windows PC / NAS / Linux 小主机)各有本地数据,互相够不着」的场景——让知识库里的 agent、脚本、人,都能像查本地数据一样查所有设备的数据。
整理人:小虾子 · 日期:2026-06-27
消费方(AI agent、脚本、人)
│
统一入口
│
┌────────▼────────┐
│ Bridge Server │ 唯一入口:注册中心 + 心跳 + 路由转发
│ Ubuntu .33:9100 │
└──┬──────┬─────┬──┘
│ │ │
┌────▼┐ ┌──▼──┐ ┌▼────┐
│Mac C│ │PC C │ │NAS C│ 每台设备一个 Client
└─────┘ └─────┘ └─────┘
| 角色 | 职责 |
|---|---|
| Server | 唯一入口:注册中心 + 心跳管理 + 配置管理 + 路由转发 |
| Client | 扫描本地数据源 → 注册到 Server → 执行 Server 下发的查询指令 |
| 消费方 | 只认 Server 地址,不关心数据在哪台机器 |
请求链路:消费方 → Server(REST)→ 路由定位 source/db 所在 Client → 转发到 Client /exec → Client 调用对应模块 → 结果原路返回。
local-data-bridge/
├── server/ # Server 端
│ ├── server.py # 启动入口
│ ├── app.py # flask 应用(消费方 REST API + 注册/心跳端点)
│ ├── registry.py # Client 注册表 + 心跳管理 + 数据源索引
│ ├── router.py # source/db → Client 路由 + 转发
│ └── config.py # server YAML 解析
├── client/ # Client 端
│ ├── client.py # 启动入口
│ ├── app.py # flask 应用(POST /exec)
│ ├── executor.py # 分发 (module, action) 到数据模块
│ ├── scanner.py # 扫描配置构建模块 + 汇总上报源
│ ├── registration.py # 注册 + 心跳后台线程
│ └── config.py # client YAML 解析
├── modules/ # 数据模块(可扩展)
│ ├── files.py # 文件:读 / 列 / 全文搜索
│ ├── sqlite.py # SQLite:自动发现表结构 / 只读查询
│ └── _template.py # 新模块模板(Phase 2 参考)
├── shared/ # 公共层
│ ├── protocol.py # 协议常量 + 统一响应 + 异常 + SQL 只读校验
│ ├── config.py # YAML 加载工具
│ ├── auth.py # token 认证
│ └── httpclient.py # urllib 封装(转发 / 注册)
├── config/ # 配置示例
│ ├── bridge-server.yaml
│ ├── bridge-client-mac.yaml
│ ├── bridge-client-pc.yaml
│ └── bridge-client-nas.yaml
├── tests/ # 测试套件(266 用例,100% 覆盖)
├── requirements.txt
└── pyproject.toml # pytest + coverage 配置
需要 Python 3.8+(仅依赖标准库 + Flask + PyYAML)。
pip install -r requirements.txt开发/测试再装:
pip install pytest pytest-covpython server/server.py --config config/bridge-server.yaml
# 可覆盖:--host 0.0.0.0 --port 9100python client/client.py --config config/bridge-client-mac.yaml
# 可覆盖:--host 0.0.0.0 --port 9101 --no-register| 平台 | 方式 |
|---|---|
| macOS / Linux | nohup python client/client.py --config ... > bridge.log 2>&1 &,或写 systemd unit(Type=simple + Restart=always) |
| Windows | 任务计划程序(登录时启动,pythonw.exe 后台运行),或 start /b pythonw client\client.py --config ... |
| Synology NAS | 任务计划(触发「开机」),或套件容器;注意 Python 版本需 3.8+ |
server:
host: 0.0.0.0 # 内网监听;仅本机访问改 127.0.0.1
port: 9100
token: "bridge-secret-token-change-me" # 消费方/Client 访问 Server 的凭证
auth:
clients: # 登记 Client 及其 client_token(Server 转发时携带)
mac: "mac-client-token"
pc: "pc-client-token"
nas: "nas-client-token"server:
url: http://192.168.5.33:9100
token: "bridge-secret-token-change-me" # 与 Server 的 server.token 一致
client:
identity: mac # mac / pc / nas
listen_host: 0.0.0.0
listen_port: 9101 # 接收 Server 转发查询的端口
client_token: "mac-client-token" # 与 Server 的 auth.clients.<identity> 一致
modules:
files:
enabled: true
mounts:
- name: hermes
path: /Users/jovi/.hermes
access: read
sqlite:
enabled: true
databases:
- name: kanban
path: /Users/jovi/.hermes/kanban/boards/backlog/kanban.db
readonly: trueWindows 路径用正斜杠
/或双反斜杠\\;中文路径全链路 UTF-8。
所有请求发往 Server,携带 Authorization: Bearer <server.token>(/health 除外)。
| Method | Path | 说明 |
|---|---|---|
| GET | /health |
Server 状态 + 所有 Client 在线状态(无需认证) |
| GET | /sources |
所有数据源(哪些在线哪些离线) |
| Method | Path | 说明 |
|---|---|---|
| GET | /files/{source}?path=xxx&offset=0&limit=100 |
读文件(按行分页) |
| GET | /files/{source}/list?path=xxx |
列目录(名称/类型/大小/修改时间) |
| GET | /files/{source}/search?q=xxx&path=xxx |
全文搜索(返回文件+行号+匹配行) |
| Method | Path | 说明 |
|---|---|---|
| GET | /sqlite |
所有数据库列表 |
| GET | /sqlite/{db}/tables |
列出表(自动从 sqlite_master 发现) |
| GET | /sqlite/{db}/schema/{table} |
建表语句 + 字段(PRAGMA table_info) |
| POST | /sqlite/{db} |
执行只读查询,body:{"sql": "SELECT ..."} |
# 读文件
curl -H "Authorization: Bearer bridge-secret-token-change-me" \
"http://192.168.5.33:9100/files/hermes?path=notes.md"
# 列目录
curl -H "Authorization: Bearer ..." \
"http://192.168.5.33:9100/files/hermes/list?path=sub"
# 全文搜索
curl -H "Authorization: Bearer ..." \
"http://192.168.5.33:9100/files/hermes/search?q=关键词&path="
# 查 SQLite
curl -X POST -H "Authorization: Bearer ..." -H "Content-Type: application/json" \
-d '{"sql":"SELECT id,title,status FROM tasks LIMIT 10"}' \
"http://192.168.5.33:9100/sqlite/kanban"
# → {"status":"ok","data":{"columns":["id","title","status"],"rows":[[1,"t1","pending"],...]}}| 场景 | HTTP | 返回 |
|---|---|---|
| Client 离线 | 503 | {"error":"source offline","client":"pc"} |
| 数据源不存在 | 404 | {"error":"source not found"} |
| SQL 非 SELECT | 400 | {"error":"only SELECT allowed"} |
| 认证失败 | 401 | {"error":"unauthorized"} |
| 文件不存在 | 404 | {"error":"file not found"} |
| 路径穿越 | 400 | {"error":"path traversal not allowed"} |
- 配置驱动:挂载什么、暴露什么,改 YAML 不改代码。
- 模块化:文件 / SQLite 各自独立,按
enabled加载。Phase 2 加新模块不需改核心——参照modules/_template.py实现模块协议(name+execute(action, params)+ 可选list_sources()),在client/scanner.py的build_modules注册一行即可。 - 自动发现:SQLite 挂上即扫
sqlite_master,无需手写 schema;Client 启动即注册所有源。 - 只读优先:Phase 1 全部只读。SQLite 双重防护——语义层
is_readonly_select(只允许 SELECT/WITH 单语句、关键字黑名单)+ 运行时mode=ro只读连接兜底。文件严格防路径穿越(..拒绝 +resolve()后校验仍在挂载根内)。 - 热插拔:Client 断线 → Server 连接失败即返 503;超 90s 无心跳标记 offline;Client 重连自动重新注册恢复服务。
- 内网安全:Server 只绑内网;统一 token 认证(
hmac.compare_digest常量时间比较)。 - 跨平台:
pathlib.Path统一路径、全链路 UTF-8、不依赖 grep/find(搜索用pathlib+re)、HTTP 转发用标准库urllib。
pytest # 全部测试
pytest --cov # 带覆盖率(目标 98%+)- 266 个用例,行+分支覆盖率 100%。
- 单元测试:
shared/*、modules/*、server/registry、server/router(注入 fake HTTP)、client/*。 - 集成测试(
tests/test_integration.py):真实 Server + Client(werkzeugmake_server)端到端验证——注册、文件读/列/搜、SQLite 查询、断开 503、重连恢复、SQL 注入拦截、路径穿越拦截、认证。
| 标准 | 状态 | 说明 |
|---|---|---|
Server python server.py 启动监听 9100 |
✅ | python server/server.py --config config/bridge-server.yaml |
| Client 自动注册,Server 显示 online | ✅ | /health 返回 client 在线状态 |
| 经 Server 查 SQLite 返回数据 | ✅ | POST /sqlite/{db} |
| 经 Server 读文件返回内容 | ✅ | GET /files/{source}?path= |
| Client 断开 → 503,重连恢复 | ✅ | 集成测试 test_offline_returns_503_* / test_reconnect_* |
| Windows Client 注册与被查 | ✅ | 纯 Python 标准库 + Flask,跨平台一致 |
| SQL 注入防护:非 SELECT 拒绝 | ✅ | is_readonly_select + mode=ro 双重 |
| 跨平台同代码可跑 | ✅ | pathlib + UTF-8 + 标准库 |
下列接口已在设计中预留,Phase 1 不实现:本地 CLI 转发(lark-cli / xurl)、本地 Python 服务(GA4 报告)、HTTP API 代理。新增时按 modules/_template.py 的模块协议实现,并在 client/scanner.py:build_modules 注册即可,核心代码无需改动。