Commit 617108d4 authored by 谢宇轩's avatar 谢宇轩

fix: update docker network setting

parent d0ec3142
# Host-side Compose interpolation only. This value is not injected into the
# query-api container. It must name the Docker network already joined by the
# target Neo4j container.
NEO4J_DOCKER_NETWORK=neo4jagent_default
......@@ -2,6 +2,12 @@
面向组内共享的轻量查询服务。适用于 Neo4j Community:调用方只持有 Workspace API Key,通过预定义模板查询图数据。
## 文档入口
完整的部署、调用、协作与运维流程见 [使用手册](docs/USAGE_GUIDE.md)。它覆盖首次部署、源码运行、配置覆盖、Key 生命周期、模板与快捷查询、结果解码、备份恢复、故障排查和测试验收。
本文保留产品边界、配置参考和快速命令,适合作为项目首页;需要按角色执行操作时,请从使用手册开始。
## 能力与边界
- 一个 Key 对应一个 Workspace;一个 Workspace 对应一个 Neo4j URI 与数据库。
......@@ -16,10 +22,11 @@
## Docker 启动
镜像:`neo4j-query-service:0.1.0`。示例 Compose 接入已有的 `neo4jagent_default` 网络,不启动或修改已有 Neo4j。
镜像:`neo4j-query-service:0.1.0`。示例 Compose 接入已有的 Neo4j Docker 网络,不启动或修改已有 Neo4j。默认网络名为 `neo4jagent_default`;若部署环境不同,请在项目目录的 `.env` 中设置 `NEO4J_DOCKER_NETWORK=<实际网络名>`(可从 `.env.example` 复制),或在启动命令前设置同名环境变量。
```bash
cp examples/workspaces.yaml config.local.yaml
cp .env.example .env
# 编辑 URI、数据库、账号和密码。已有本地容器可使用 bolt://neo4j-agent:7687。
docker compose up -d --build
docker compose exec query-api query-service healthcheck
......
......@@ -21,6 +21,8 @@ services:
networks:
neo4j:
external: true
name: neo4jagent_default
# Set NEO4J_DOCKER_NETWORK on the deployment host when the existing
# Neo4j Compose project uses a different network name.
name: ${NEO4J_DOCKER_NETWORK:-neo4jagent_default}
volumes:
query-data:
# Neo4j Workspace Query Service 使用手册
本手册对应当前 `0.1.0` 实现。它面向三类人:部署服务的运维人员、维护可信查询模板的维护者,以及通过 API 读取图数据的普通调用方。
服务的基本模式是:**一个 API Key 只属于一个 Workspace;调用方只能执行该 Workspace 中已经保存的模板或快捷查询,不能提交任意 Cypher。**Neo4j 账号、密码和原始配置留在服务端,元数据(Key 摘要、模板、快捷查询、审计日志)保存在服务自己的 SQLite 数据库中。
## 目录
- [1. 先判断是否适用](#1-先判断是否适用)
- [2. 角色、数据与生命周期](#2-角色数据与生命周期)
- [3. 快速启动:Docker](#3-快速启动docker)
- [4. 源码开发与本地运行](#4-源码开发与本地运行)
- [5. 配置完整参考](#5-配置完整参考)
- [6. API Key 管理](#6-api-key-管理)
- [7. 模板管理:维护者场景](#7-模板管理维护者场景)
- [8. 数据读取:调用方场景](#8-数据读取调用方场景)
- [9. 快捷查询协作场景](#9-快捷查询协作场景)
- [10. 返回结果与错误处理](#10-返回结果与错误处理)
- [11. 运行限制与安全边界](#11-运行限制与安全边界)
- [12. 日常运维:健康、备份、恢复与升级](#12-日常运维健康备份恢复与升级)
- [13. 故障排查](#13-故障排查)
- [14. 测试与验收](#14-测试与验收)
## 1. 先判断是否适用
适用场景:
- 组内应用需要读取 Neo4j,但不应持有数据库账号和密码。
- 能由可信维护者预先定义查询,并由使用者只传业务参数。
- 希望按 Workspace 隔离 API Key、模板、快捷查询和审计记录。
- 使用 Neo4j Community,或不依赖数据库端的细粒度权限模型。
不适用场景:
- 需要创建、更新、删除节点/关系、管理索引或执行任意 Cypher。
- 要求调用方临时编写查询、调用过程、APOC 或自定义函数。
- 要求高可用多副本、跨实例共享 SQLite 或把 SQLite WAL 放到网络文件系统。
服务不会把同一个 Neo4j 数据库自动切分为多个数据集。两个 Workspace 指向同一物理数据库时,看到的图数据仍相同;隔离的仅是 Key、模板和快捷查询。
## 2. 角色、数据与生命周期
| 对象 | 谁能操作 | 生命周期与含义 |
|---|---|---|
| Workspace | 部署人员通过配置 | 启动时加载;修改源配置或环境变量后必须重建/重启服务 |
| `reader` Key | 部署人员创建 | 可查看模板、执行模板、管理本 Workspace 的快捷查询 |
| `maintainer` Key | 部署人员创建 | 包含 `reader` 权限,另可创建、更新和禁用模板 |
| 模板 | `maintainer` | 更新会新增不可变版本;禁用后不可重新启用 |
| 快捷查询 | 同 Workspace 的任意有效 Key | 固定模板版本与补齐后的参数;模板升级不会改写它 |
| SQLite 元数据 | 服务自身 | 保存 Key 摘要、模板、快捷查询、审计;不保存图数据 |
删除某个 Workspace 的配置不会删除 SQLite 中历史 Key、模板或快捷查询,但这些对象立刻不可访问。以后以同一 Workspace ID 重新启用,会恢复对原有对象的访问;若切换到不同业务数据集,应使用新的 ID。
## 3. 快速启动:Docker
前提:已安装 Docker,且目标 Neo4j 容器已经运行并位于某个 Docker 网络中。此项目不会启动、修改或重建 Neo4j。默认网络名是 `neo4jagent_default`,但生产环境常因 Neo4j 项目名不同而使用其他名称;启动前必须按下面步骤确认实际网络名。
### 3.1 确认已有 Neo4j 网络
在**部署目标机器**执行:
```bash
docker network ls
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Networks}}'
```
从输出中找到目标 Neo4j 容器所在的网络。例如容器在 `prod-neo4j_default`,就在项目目录创建 `.env`:
```bash
cp .env.example .env
# 编辑 .env:
NEO4J_DOCKER_NETWORK=prod-neo4j_default
```
也可以不创建 `.env`,只对本次启动指定:
```bash
NEO4J_DOCKER_NETWORK=prod-neo4j_default docker compose up -d --build
```
这个变量只在部署主机上由 Docker Compose 用来选择外部网络,**不会传入 `query-api` 容器**,也不会覆盖 `NQ_` 服务配置。若 `docker network ls` 中完全找不到承载目标 Neo4j 的网络,应先部署/启动 Neo4j;不要只创建一个同名空网络,因为服务仍无法解析或连接数据库。
### 3.2 准备 Workspace 配置
```bash
cp examples/workspaces.yaml config.local.yaml
```
编辑 `config.local.yaml` 中的 Workspace。以下是最小可用示例;`uri`、数据库、用户和密码必须替换成实际值。
```yaml
config_version: 1
db_path: /data/service.db
workspaces:
- id: team
name: Team graph
neo4j:
uri: bolt://neo4j-agent:7687
database: neo4j
username: neo4j
password: replace-with-a-real-password
```
`id` 必须以小写字母开头,只能使用小写字母、数字、`_` 和 `-`,长度不超过 64。不要将真实密码提交到 Git。
### 3.3 启动并验证
```bash
docker compose up -d --build
docker compose exec query-api query-service healthcheck
curl http://127.0.0.1:8080/health/live
curl http://127.0.0.1:8080/health/ready
```
默认 HTTP 地址是 `http://127.0.0.1:8080`,交互式 API 页面是 `http://127.0.0.1:8080/docs`,OpenAPI 文档为 `/openapi.json`。示例仅绑定本机回环地址;若要给组成员访问,应先通过反向代理提供 TLS、身份边界与网络访问控制,再有意修改 Compose 的端口映射。
### 3.4 创建首把 Key
`seed` 生成的完整 Key **只会在这一次输出**,应立即保存到受保护的密钥管理系统。
```bash
docker compose exec query-api query-service keys seed \
--config /run/query-service/config.yaml \
--workspace team --name owner --role maintainer
```
把返回 JSON 里的 `key` 赋给本地环境变量(不要提交该变量值):
```bash
export MAINTAINER_KEY='nq_<key-id>.<secret>'
```
至此可以进入 [模板管理](#7-模板管理维护者场景)。
## 4. 源码开发与本地运行
要求 Python 3.12 或 3.13 与 `uv`。开发模式也必须显式指定配置文件;服务进程本身不会读取环境变量。
```bash
uv sync --frozen --python 3.12
uv run query-service db migrate --config /absolute/path/config.local.yaml
uv run query-service serve --config /absolute/path/config.local.yaml
```
用于开发的 `db_path` 必须是绝对路径。停止服务后,使用相同配置文件再次运行 `serve` 即可。服务对同一个 SQLite 元数据文件使用进程锁;第二个服务进程会启动失败,不能通过增加 Uvicorn worker 或启动多个容器来扩容。
若要模拟 Docker 的“源配置 + 环境覆盖 + 私有运行配置”流程,可先运行:
```bash
NQ_CONFIG_SOURCE=/absolute/path/config.local.yaml \
NQ_CONFIG_OUTPUT=/tmp/neoquery-runtime.yaml \
query-service-bootstrap
```
该命令会接管进程并启动服务,适合验证启动流程;日常源码调试通常直接使用 `query-service serve --config` 更直观。
## 5. 配置完整参考
### 5.1 YAML 字段
```yaml
config_version: 1
listen_host: 0.0.0.0
port: 8080
db_path: /data/service.db
limits:
query_timeout_seconds: 10
request_timeout_seconds: 12
max_concurrency: 16
workspace_concurrency: 4
key_concurrency: 2
max_rows: 1000
max_response_bytes: 5242880
max_request_bytes: 262144
log_retention_days: 14
log_max_records: 100000
workspaces:
- id: team
name: Team graph
neo4j:
uri: bolt://neo4j-agent:7687
database: neo4j
username: neo4j
password: replace-at-deployment
limits:
max_concurrency: 3
max_rows: 500
```
全局限制的可配置范围如下。所有超时单位为秒,所有字节限制单位为字节。
| 字段 | 默认值 | 有效范围 / 作用 |
|---|---:|---|
| `query_timeout_seconds` | 10 | `>0` 且最多 300;每个 Neo4j 事务时限 |
| `request_timeout_seconds` | 12 | `>0` 且最多 360;整个 HTTP 请求时限 |
| `max_concurrency` | 16 | 1–1024;所有 Workspace 合计的执行查询数 |
| `workspace_concurrency` | 4 | 1–1024;单 Workspace 执行查询数 |
| `key_concurrency` | 2 | 1–1024;单 Key 执行查询数 |
| `max_rows` | 1000 | 1–100000;单次返回行数上限 |
| `max_response_bytes` | 5 MiB | 1024–100 MiB;完整响应大小上限 |
| `max_request_bytes` | 256 KiB | 1024–10 MiB;业务路由请求体上限 |
| `log_retention_days` | 14 | 1–3650;审计记录最长保留天数 |
| `log_max_records` | 100000 | 至少 100;审计记录数量上限 |
Workspace 可覆盖 `query_timeout_seconds`、`request_timeout_seconds`、`max_concurrency`、`key_concurrency`、`max_rows` 与 `max_response_bytes`,但只能收紧:最终取全局与 Workspace 值的较小者。Workspace 的 `max_concurrency` 对应该 Workspace 的并发上限;`max_request_bytes` 不能按 Workspace 覆盖。
Neo4j URI 只允许 `bolt://`、`neo4j://` 及其 `+s`/`+ssc` 变体,且只能包含协议、主机和端口。不能使用 `system` 数据库,也不能把用户名、密码、路径、查询串或片段放进 URI。
### 5.2 Docker 环境覆盖
优先级为:内置默认值 < YAML < 明确设置的 `NQ_` 环境变量。未知的 `NQ_` 环境变量会让启动失败,不会静默忽略;错误配置也不会回退到旧的运行配置。
| 环境变量 | 覆盖内容 |
|---|---|
| `NQ_CONFIG_SOURCE` | 源配置,默认 `/etc/query-service/workspaces.yaml` |
| `NQ_CONFIG_OUTPUT` | 启动生成的私有配置,默认 `/run/query-service/config.yaml` |
| `NQ_LISTEN_HOST`、`NQ_PORT`、`NQ_DB_PATH` | 监听地址、端口、SQLite 路径 |
| `NQ_QUERY_TIMEOUT_SECONDS`、`NQ_REQUEST_TIMEOUT_SECONDS` | 两类超时 |
| `NQ_MAX_CONCURRENCY`、`NQ_WORKSPACE_CONCURRENCY`、`NQ_KEY_CONCURRENCY` | 三层查询并发上限 |
| `NQ_MAX_ROWS`、`NQ_MAX_RESPONSE_BYTES`、`NQ_MAX_REQUEST_BYTES` | 请求与返回限制 |
| `NQ_LOG_RETENTION_DAYS`、`NQ_LOG_MAX_RECORDS` | 审计保留策略 |
| `NQ_WORKSPACE_OVERRIDES_JSON` | 对已有 Workspace 的 `neo4j`、`limits`、`name` 进行覆盖 |
`NQ_WORKSPACE_OVERRIDES_JSON` 不能创建新的 Workspace ID。典型用途是在部署系统中替换密码或收紧某一 Workspace 的配额:
```json
{
"team": {
"neo4j": {"password": "provided-by-the-deployment-platform"},
"limits": {"max_rows": 100}
}
}
```
### 5.3 配置文件和持久化目录权限
容器以 UID/GID `10001` 运行。配置源必须允许该 UID 读取;生成的 `/run/query-service/config.yaml` 始终为 `0600`,不写入日志。Compose 使用 tmpfs 保存 `/run/query-service`,并将整个 `/data` 持久化,因为 SQLite WAL/SHM 文件必须与主数据库一起保留。
不要把 SQLite 数据库或 WAL 放在 NFS 等网络文件系统上,也不要让两个服务进程共享同一份元数据数据库。
## 6. API Key 管理
### 6.1 创建、查看与撤销
```bash
# 创建无期限维护者 Key
docker compose exec query-api query-service keys seed \
--config /run/query-service/config.yaml --workspace team --name owner --role maintainer
# 创建 90 天有效的调用方 Key
docker compose exec query-api query-service keys seed \
--config /run/query-service/config.yaml --workspace team --name member-a \
--role reader --expires-in-days 90
# 仅列出元数据,不会显示完整 Key
docker compose exec query-api query-service keys list \
--config /run/query-service/config.yaml --workspace team
# 按 Key ID 撤销,重复撤销安全
docker compose exec query-api query-service keys revoke \
--config /run/query-service/config.yaml --key-id <key-id>
```
在源码模式中,保留相同子命令,把配置路径换成实际绝对路径即可。创建、过期和撤销均在下一个请求即时生效,无需重启服务;已经开始的查询会在其原有超时范围内结束。
### 6.2 轮换 Key
1. 创建新 Key。
2. 将调用方配置切换到新 Key,并确认健康调用成功。
3. 撤销旧 Key。
快捷查询属于 Workspace,不属于创建它的 Key,因此轮换 Key 后仍然存在。丢失完整 Key 时不能找回,只能新建并替换。
### 6.3 HTTP 认证格式
所有业务路由都要求:
```http
Authorization: Bearer nq_<key-id>.<secret>
```
下面命令约定:
```bash
export BASE_URL='http://127.0.0.1:8080'
export WORKSPACE='team'
export API_KEY='nq_<key-id>.<secret>'
alias nqcurl='curl -sS -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json"'
```
业务 API 的公共前缀为:`/api/v1/workspaces/{workspace_id}`。Key 与路径中的 Workspace 不一致时返回 403。
## 7. 模板管理:维护者场景
模板是受信任的只读查询定义。只有 `maintainer` 能管理模板;服务会先进行保守的词法检查、参数约束和 `EXPLAIN` 校验,随后才保存定义。
### 7.1 创建模板
先根据实际图模型复制并修改 `examples/template.json`,或创建以下文件 `template.json`:
```json
{
"name": "Entity neighbors",
"description": "Return one-hop paths for a business entity",
"cypher": "MATCH p=(n {entity_id: $entity_id})-[r]-(m) RETURN p LIMIT $limit",
"parameter_schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"entity_id": {"type": "string", "minLength": 1, "maxLength": 128},
"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}
},
"required": ["entity_id"]
},
"example_params": {"entity_id": "company_123"}
}
```
提交:
```bash
nqcurl -X POST "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates" \
--data-binary @template.json
```
成功返回 201,并包含模板 ID、`version: 1`、定义、创建者和时间。请保存返回的 `id`:后续执行、更新与禁用均使用它。
### 7.2 参数 Schema 规则
`parameter_schema` 使用受限 JSON Schema:
- 每一层都必须明确单一 `type`;只允许 `string`、`integer`、`number`、`boolean`、`null`、`array`、`object`。
- 对象必须设置 `additionalProperties: false`;数组必须提供 `items`。
- 支持 `required`、`enum`、`default`、`description`、数值范围、字符串长度、数组数量、对象属性数量。
- 不支持 `pattern`、`$ref`、组合条件、自定义验证或类型自动转换。
- 每一个 Cypher `$parameter` 必须在顶层 `properties` 声明,且名称完全相同;每个参数必须是 required,或具备 default。
- JSON 整数必须是真正的整数而非字符串/布尔值,且在 Neo4j 64 位整数范围内;浮点数不能为 NaN 或无穷大。
- 嵌套 Schema 最多 12 层,实际参数最多 32 层。
`example_params` 会补齐默认值并传给 `EXPLAIN`,仅用于创建/更新时验证,不会执行查询。
### 7.3 更新模板(生成新版本)
先读取当前模板,拿到 `version`:
```bash
nqcurl "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates/<template-id>"
```
在完整定义中添加 `expected_version`,再用 `PUT` 提交。不能只提交发生变化的字段。
```json
{
"name": "Entity neighbors",
"description": "Updated description",
"cypher": "MATCH p=(n {entity_id: $entity_id})-[r]-(m) RETURN p LIMIT $limit",
"parameter_schema": {"type": "object", "additionalProperties": false, "properties": {"entity_id": {"type": "string"}, "limit": {"type": "integer", "default": 50}}, "required": ["entity_id"]},
"example_params": {"entity_id": "company_123"},
"expected_version": 1
}
```
```bash
nqcurl -X PUT "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates/<template-id>" \
--data-binary @template-v2.json
```
成功后返回版本 2。若其他维护者已先更新,或模板已被禁用,返回 409;应重新读取当前版本、合并变更并再次提交。历史版本仍可读取和执行,直到模板被禁用。
### 7.4 禁用模板
```bash
nqcurl -X POST "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates/<template-id>/disable" \
--data '{}'
```
禁用是永久操作:所有历史版本、引用该模板的快捷查询都会停止执行并返回 409;当前版本不再出现在模板列表中。本版本没有重新启用或删除模板的接口,执行前请确认这是预期行为。
### 7.5 可信模板的查询边界
模板只能是一个只读查询。服务拒绝写入、管理语句、过程调用(包括子查询)、APOC、命名空间/自定义函数、`LOAD CSV`、`USE`、`EXPLAIN`/`PROFILE` 前缀、分号与多语句。内置函数采用白名单,Cypher 还会以只读模式 `EXPLAIN` 校验,并在回滚事务中执行。
这是一层 API 防护,而不是 Neo4j 数据库 RBAC 的替代品。维护者 Key 应视为可信管理权限;仍应为服务使用的 Neo4j 账号配置最小权限并控制网络访问。
## 8. 数据读取:调用方场景
### 8.1 查看可用模板与详情
```bash
# 分页:默认 20,limit 最大 100
nqcurl "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates?offset=0&limit=20"
# 当前版本
nqcurl "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates/<template-id>"
# 指定历史版本
nqcurl "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates/<template-id>?version=1"
```
列表仅返回已启用模板的当前版本;按 `created_at`、ID 排序。详情可读取指定历史版本,即使模板已经禁用,但不能执行禁用模板。
### 8.2 执行模板:必填参数、默认值与历史版本
```bash
nqcurl -X POST "$BASE_URL/api/v1/workspaces/$WORKSPACE/templates/<template-id>/execute" \
--data '{
"params": {"entity_id": "company_123", "limit": 10},
"version": 1,
"max_rows": 10
}'
```
`params` 可省略为 `{}`,但没有默认值的参数必须提供。`version` 省略时执行当前版本;指定版本可回放历史模板。`max_rows` 省略时使用该 Workspace 的行数上限,指定值只能进一步收紧,不能超过配置上限。
当行数达到上限时,服务额外读取一行判断是否存在更多结果,并以 `truncated: true` 返回;不会读取整个结果集。若响应体会超过字节上限,服务返回错误而不会截断 JSON。
### 8.3 常见调用方处理逻辑
1. 通过 `/templates` 展示可选能力,或由业务配置持久化模板 ID。
2. 按模板 Schema 构造严格 JSON 参数,不传未知字段,也不做字符串到数字的隐式转换。
3. 执行请求后检查 HTTP 状态和 `truncated`;若截断,使用业务支持的筛选、分页参数或更小查询范围重新请求。
4. 依赖 `X-Request-ID`/响应中的 `request_id` 关联故障与审计,不向日志写入 API Key 或业务参数。
5. 对 429 按指数退避后重试;对 503/504 仅在业务允许时有限重试;对 401/403/409/422 不应盲目重试。
## 9. 快捷查询协作场景
快捷查询适合把“模板 + 某组固定业务参数 + 固定模板版本”分享给同一 Workspace 的成员。`reader` 和 `maintainer` 都可以管理它们。
### 9.1 创建固定查询
```bash
nqcurl -X POST "$BASE_URL/api/v1/workspaces/$WORKSPACE/shortcuts" \
--data '{
"name": "Company 123 neighbors",
"template_id": "<template-id>",
"template_version": 1,
"params": {"entity_id": "company_123", "limit": 10}
}'
```
可省略 `template_version`,服务会解析为创建时的当前具体版本。服务会验证参数并补齐默认值,返回的快捷查询包含 `id` 与初始 `revision: 1`。模板后续升级不会改变该快捷查询。
### 9.2 查看与执行
```bash
nqcurl "$BASE_URL/api/v1/workspaces/$WORKSPACE/shortcuts?offset=0&limit=20"
nqcurl "$BASE_URL/api/v1/workspaces/$WORKSPACE/shortcuts/<shortcut-id>"
nqcurl -X POST "$BASE_URL/api/v1/workspaces/$WORKSPACE/shortcuts/<shortcut-id>/execute" \
--data '{}'
```
快捷查询执行只接受空对象 `{}` 或无请求体;传入 `params`、`max_rows` 或其他覆盖字段会返回 422。这样可确保共享快捷查询可复现,且不被调用者改写参数或版本。
### 9.3 修改与删除
修改需要提交完整的新定义和当前 `expected_revision`,以避免覆盖他人的变更:
```bash
nqcurl -X PUT "$BASE_URL/api/v1/workspaces/$WORKSPACE/shortcuts/<shortcut-id>" \
--data '{
"name": "Company 123 neighbors (top 20)",
"template_id": "<template-id>",
"template_version": 1,
"params": {"entity_id": "company_123", "limit": 20},
"expected_revision": 1
}'
nqcurl -X DELETE "$BASE_URL/api/v1/workspaces/$WORKSPACE/shortcuts/<shortcut-id>"
```
并发修改返回 409;先重新读取详情、基于新 revision 合并后再提交。删除后不可恢复,其他调用方读取或执行会得到 404。
## 10. 返回结果与错误处理
### 10.1 成功结果
执行模板或快捷查询成功时,结构如下:
```json
{
"request_id": "a-request-id",
"template_id": "template-id",
"template_version": 1,
"columns": ["n"],
"rows": [[{
"type": "node",
"value": {
"element_id": "4:...",
"labels": ["Company"],
"properties": {
"name": {"type": "string", "value": "Example"}
}
}
}]],
"row_count": 1,
"truncated": false,
"duration_ms": 7
}
```
每一个单元格都采用 `{ "type": "…", "value": … }`,因此 Neo4j 属性名不会与协议字段冲突。
| `type` | `value` 表示 |
|---|---|
| `null`、`boolean`、`string` | 对应 JSON 值 |
| `integer` | 十进制字符串,避免 JavaScript 精度丢失 |
| `float` | 普通数值;非有限值为字符串 |
| `map`、`list` | 递归编码的对象或数组 |
| `bytes` | Base64 字符串 |
| `date`、`time`、`datetime` | ISO 格式字符串 |
| `duration` | `months`、`days`、`seconds`、`nanoseconds` |
| `point` | `srid` 与 `coordinates` |
| `node` | `element_id`、有序 `labels`、递归编码 `properties` |
| `relationship` | `element_id`、类型、起止节点 `element_id`、属性 |
| `path` | 有序 `nodes` 与 `relationships` |
`element_id` 只用于同一响应内的节点/关系关联,不应保存为长期业务 ID。
### 10.2 错误格式和状态码
所有业务路由错误使用同一格式,并在响应头提供 `X-Request-ID`:
```json
{
"error": {"code": "invalid_params", "message": "Parameters do not match the template schema"},
"request_id": "a-request-id"
}
```
| HTTP 状态 | 常见 code / 含义 | 调用方处理 |
|---:|---|---|
| 401 | `invalid_key` | 检查认证头、Key 是否过期或已撤销;不要重试同一 Key |
| 403 | `workspace_forbidden`、`workspace_disabled`、`role_forbidden` | 修正 Workspace/角色或联系部署人员 |
| 404 | `not_found`、`key_not_found` | 检查资源 ID 与所在 Workspace |
| 409 | `version_conflict`、`template_disabled` | 重新读取状态;禁用模板不可恢复 |
| 413 | `request_too_large` | 缩小请求体 |
| 422 | `invalid_request`、`invalid_params`、`invalid_template`、`invalid_query`、`response_too_large` | 修正请求、模板或查询范围,勿盲目重试 |
| 429 | `concurrency_limit` | 退避后重试;服务不排队 |
| 500 | `internal_error` | 保存 request ID 并联系服务维护者 |
| 503 | `neo4j_unavailable`、`storage_unavailable`、`audit_unavailable` | 短暂故障可按业务策略有限重试 |
| 504 | `request_timeout`、`query_timeout` | 缩小/优化可信模板查询;必要时联系维护者 |
客户端断开请求时服务会取消数据库会话、回收并发名额,并在审计中记录 499;这不是可交付给客户端的正常 HTTP 响应。
## 11. 运行限制与安全边界
- 服务以单实例、单 worker 运行。全局、Workspace 与 Key 三层并发已满时立即返回 429,不建立队列。
- 请求超时或客户端断开会取消 Neo4j 会话;服务不会自动重试查询。
- 原始 Key、参数值、Cypher 正文和查询结果不进入访问审计。审计只记录请求 ID、Key ID、Workspace、路由模式、资源/版本、状态、耗时、行数和错误码。
- 审计日志每分钟清理一次,因此记录数可能在两次清理之间短暂超过阈值。
- 健康检查不写业务审计;业务请求若无法写审计,会返回 503,避免“执行了但无记录”。
- 若一个 Workspace 的 Neo4j 不可达,其他 Workspace 仍可使用;驱动按需连接。
- 结果行数和字节数限制不能替代成本控制:单条巨大记录、无界聚合或低选择性匹配仍可能昂贵。维护者应写入足够的筛选条件、范围和 `LIMIT`。
## 12. 日常运维:健康、备份、恢复与升级
### 12.1 健康检查
```bash
# 进程存活;不检查 SQLite
curl -f http://127.0.0.1:8080/health/live
# SQLite 可用;Docker healthcheck 使用此接口
curl -f http://127.0.0.1:8080/health/ready
# 容器内 CLI 检查当前配置的端口
docker compose exec query-api query-service healthcheck \
--config /run/query-service/config.yaml
```
`/health/ready` 只验证本地元数据存储,不逐一探测每个 Neo4j Workspace;某一图库不可达时具体业务调用会返回 503。
### 12.2 在线备份
使用 CLI 生成一致性的单个 SQLite 备份文件,目标路径必须不存在:
```bash
docker compose exec query-api query-service db backup \
--config /run/query-service/config.yaml \
--output /data/backup-20260910.db
```
在源码模式使用:
```bash
uv run query-service db backup \
--config /absolute/path/config.local.yaml \
--output /absolute/path/backup-20260910.db
```
不要在服务运行时仅复制主 `.db` 文件;WAL 中可能仍有数据。应使用上述在线备份命令,并将备份复制到独立、安全的存储位置。
### 12.3 恢复
恢复会替换整个服务元数据,因此应在维护窗口内进行:
1. 停止服务。
2. 备份当前整个 SQLite 目录,保留主库、`-wal` 与 `-shm` 文件。
3. 将已验证的备份文件放到配置中的 `db_path`。
4. 移走与旧数据库对应的 `-wal`、`-shm` 文件,避免使用旧日志恢复新库。
5. 启动服务,检查 `/health/ready`,并用一个有效 Key 执行只读调用。
恢复不修改 Neo4j 图数据。因为完整 Key 从不存库,恢复后只能继续使用已在外部保存的 Key;若原 Key 已遗失,创建新 Key。
### 12.4 升级、配置变更与停用 Workspace
1. 先在线备份 SQLite 元数据。
2. 更新镜像或源码依赖;数据库迁移会在 `serve` 启动时自动运行,也可先手工执行 `query-service db migrate`。
3. 对配置/环境变量变更,重建或重启容器;运行中不会热加载。
4. 使用 `healthcheck`、Key 鉴权和一个低风险模板执行验证。
首版不支持破坏性 downgrade。若升级失败,停止服务并从备份恢复元数据,随后回退镜像/代码;不要手工修改服务 SQLite 表。
## 13. 故障排查
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 容器立即退出并提示启动配置无效 | YAML 语法、未知 `NQ_` 变量、Workspace 重复、URI、密码、绝对 `db_path` | 修正源配置/环境变量后重新启动;错误配置不会使用旧运行配置 |
| `health/live` 成功但 `health/ready` 为 503 | `/data` 挂载、SQLite 文件权限或损坏 | 检查目录可写、数据卷和运行日志;必要时按恢复流程处理 |
| Key 返回 401 | `Authorization` 格式、Key 是否过期/撤销 | 使用新创建的完整 Key;无法恢复丢失的 Key |
| Key 返回 403 | URL Workspace 与 Key 所属 Workspace、角色、配置是否包含该 Workspace | 使用正确路径/Key,或让部署人员创建对应角色 Key |
| 模板/快捷查询 404 | ID、Workspace、是否已删除 | 先列表或详情确认资源所在 Workspace |
| 更新返回 409 | `expected_version`/`expected_revision` 是否过期,模板是否禁用 | 重新读取并合并;禁用模板没有重新启用路径 |
| 模板创建返回 422 | Cypher 是否只读、参数名与 Schema 是否严格一致、`example_params` 是否有效 | 从错误场景收窄模板,先用最小只读查询验证 |
| 执行返回 429 | 全局/Workspace/Key 并发是否达到配置限制 | 调用方退避;运维人员按容量评估后调整配置并重启 |
| 执行返回 503 | 目标 Neo4j 连通性、凭据、SQLite 审计存储 | 检查服务网络和 Neo4j;对暂时故障有限重试 |
| 执行返回 504 | 查询范围、索引、参数选择性、配置超时 | 优化可信模板或缩小范围,不要简单无限增大超时 |
| 执行返回 `response_too_large` | 行数、单行体积、图路径/属性大小 | 传更低 `max_rows`、收窄返回字段或拆分模板 |
| 第二个服务启动失败 | 是否共享了相同 `db_path` | 保持一份元数据数据库只由一个服务进程提供 HTTP |
排障时记录 `X-Request-ID`,并检查服务 stdout 日志。不要为排障记录或粘贴 API Key、真实密码、业务参数和完整查询结果。
## 14. 测试与验收
常规静态检查与单元测试:
```bash
uv run ruff check src migrations tests
uv run pytest -q
```
真实 Neo4j 集成测试只连接专用的 `127.0.0.1:17687`,会创建并删除带 `NQAcceptance` 标签的测试数据。切勿将该端口映射到现有业务库。
```bash
docker run -d --name neoquery-acceptance-db -p 127.0.0.1:17687:7687 \
-e NEO4J_AUTH=neo4j/acceptance-only-2026 \
-e NEO4J_server_memory_heap_initial__size=256m \
-e NEO4J_server_memory_heap_max__size=256m \
-e NEO4J_server_memory_pagecache_size=128m neo4j:5.26.0
uv run pytest -q --integration
docker rm -f neoquery-acceptance-db
```
`tests/docker_acceptance.py` 是容器端到端验收脚本。它需要 alpha、beta、local 三个 Workspace:alpha/beta 必须连接隔离库;local 才能指向实际图数据库,且脚本只执行限量读取,不输出业务数据。详细验证结果见 [验收记录](../ACCEPTANCE.md)。
## 参考入口
- 项目首页的 [README](../README.md):能力边界、快速命令和配置摘要。
- OpenAPI 页面:服务启动后访问 `http://127.0.0.1:8080/docs`;若部署到了其他地址,请替换主机和端口。
- [Neo4j Python Driver 事务文档](https://neo4j.com/docs/python-manual/current/transactions/):了解读取事务和驱动行为。
- [SQLite WAL 文档](https://www.sqlite.org/wal.html):了解持久化与备份注意事项。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment