# 司空 2 私有化 OpenAPI 接入部署与联调手册

> 适用项目：AItrackwalker 铁路无人机智能巡检平台
> 代码基线日期：2026-07-25
> 设计依据：[司空 2 私有化 OpenAPI 集成设计](./dji-flighthub2-private-openapi-integration-design.md)

## 1. 当前交付范围

本次实现新增独立的 `uav-access-service`，并将原有巡检平台从模拟器适配扩展为 `FLIGHTHUB2` 真实适配器。代码已经具备以下链路：

| 领域 | 已实现能力 | 当前边界 |
|---|---|---|
| 鉴权 | 从只读密钥文件读取 `x-user-token`；平台到接入服务使用独立内部令牌 | 令牌由司空 2 部署后签发 |
| 健康检查 | 校验本地配置并调用项目拓扑接口 | 必须使用真实项目验证权限范围 |
| 设备同步 | 拉取机场、飞行器和负载拓扑，写入平台设备及机场表 | 设备型号字段需结合实际返回样本复核 |
| 航线 | 平台航点编译为 WPML/KMZ，申请 STS、上传对象存储、提交导入回调、轮询导入结果 | WPML 机型和负载枚举必须按真实设备确认 |
| 任务 | 飞前检查、创建高级任务、暂停/恢复/取消、任务状态对账 | 默认关闭飞行控制，真机验收后才允许开启 |
| 实时事件 | EventAPI HTTP 接入、MQTT Bridge 订阅、原始事件去重、状态归一化、Outbox 投递 Kafka | EventAPI 签名和 MQTT 主题结构需部署后冻结 |
| 平台投影 | Kafka Inbox 去重，更新任务、命令、设备状态、遥测及实际航迹 | 依赖真实事件样本补充字段映射 |
| 可观测性 | Actuator 健康检查、Prometheus 指标入口、请求 ID、原始事件审计和对账任务 | 生产告警阈值需按现场频率配置 |
| 媒体 | 已预留厂商媒体映射表和事件入口 | 媒体清单/下载接口和对象存储策略确认后实现镜像 Worker |
| 直播/DRC | 架构和安全开关已预留 | 不在本次默认启用范围 |

数据库迁移由 Flyway 自动执行：

- 平台库：`platform/backend/.../V17__flighthub2_integration.sql`
- 接入域：`uav-access-service/.../V1__uav_access_foundation.sql`

接入域使用 PostgreSQL 的独立 `uav_access` schema，不与平台业务表混写。

## 2. 部署拓扑

```mermaid
flowchart LR
    UI["巡检平台前端"] --> API["platform/backend"]
    API --> ACCESS["uav-access-service"]
    ACCESS --> FH["司空 2 私有化 OpenAPI"]
    FH --> CALLBACK["EventAPI 回调入口"]
    CALLBACK --> ACCESS
    FH --> BRIDGE["司空 2 MQTT Bridge"]
    BRIDGE --> EMQX["EMQX"]
    EMQX --> ACCESS
    ACCESS --> KAFKA["Kafka: uav.access.events.v1"]
    KAFKA --> API
    ACCESS --> DB[("PostgreSQL / uav_access")]
    API --> DB2[("PostgreSQL / public")]
```

生产网络建议：

1. `platform/backend`、Kafka、PostgreSQL 和 `uav-access-service` 放在应用内网。
2. 仅将 `POST /api/v1/events/flighthub` 通过 HTTPS 反向代理开放给司空 2 EventAPI。
3. `8091` 端口不能直接暴露到不可信网络；内部接口通过 `X-Access-Token` 保护，但仍应配合防火墙或安全组。
4. EMQX 的 `1883` 仅用于隔离网络联调；跨主机或跨安全域应使用 `8883/TLS`。
5. 司空 2 OpenAPI、STS 对象存储地址、DNS 和证书链必须能从 `uav-access-service` 容器访问。

## 3. 部署文件

| 文件 | 用途 |
|---|---|
| `infra/docker-compose.yml` | 基础平台和默认关闭的接入服务 |
| `infra/docker-compose.flighthub2.yml` | 启用司空 2 的生产覆盖配置 |
| `infra/docker-compose.server.yml` | GPU/服务器资源与重启策略覆盖 |
| `infra/.env.flighthub2.example` | 参数模板，不包含真实密钥 |
| `uav-access-service/Dockerfile` | 接入服务镜像构建 |
| `infra/prometheus.yml` | 平台与接入服务指标采集 |

真实 `.env.flighthub2` 和用户令牌文件均已加入忽略规则或位于被忽略目录，禁止提交到 Git。

## 4. 部署后必须确认的参数

### 4.1 阻断上线的必填参数

| 环境变量 | 获取位置 | 验证方法 |
|---|---|---|
| `FLIGHTHUB_BASE_URL` | 司空 2 私有化部署输出或域名规划 | 容器内解析 DNS，证书有效，健康检查不报网络错误 |
| `FLIGHTHUB_USER_TOKEN_HOST_FILE` | 将司空 2 用户 Token 写入宿主机只读文件 | 调用项目拓扑接口返回业务码 `0` |
| `FLIGHTHUB_ORGANIZATION_UUID` | 司空 2 组织管理/API 返回 | 与 Token 所属组织一致 |
| `FLIGHTHUB_PROJECT_UUID` | 司空 2 项目详情/API 返回 | 拓扑、STS、航线和任务接口均使用同一项目 |
| `FLIGHTHUB_WPML_*_ENUM_VALUE` | 实际飞行器、机场、负载型号及 WPML 兼容表 | 导入一条最小安全航线并确认解析成功 |
| `FLIGHTHUB_EVENT_CALLBACK_TOKEN` | 双方协商生成 | 回调缺失或错误 Token 返回 401，正确 Token 入 Inbox |
| `UAV_ACCESS_INTERNAL_TOKEN` | 本方生成，至少 32 个随机字符 | 平台健康检查成功，错误 Token 返回 401 |
| `FLIGHTHUB_MQTT_USERNAME/PASSWORD` | EMQX 或现场 Broker 账号 | 连接成功且事件可进入接入服务 |

用户令牌采用 `x-user-token` 请求头发送。不要把 Token 直接写进 `.env`；当前实现优先读取 `FLIGHTHUB_USER_TOKEN_FILE`。

### 4.2 需要用真实实例冻结的参数

这些值已有配置入口，但不能仅依靠离线文档安全定版：

| 项目 | 默认值/当前实现 | 部署后动作 |
|---|---|---|
| OpenAPI 根地址和前缀 | `FLIGHTHUB_BASE_URL` + `/openapi/v2.0/...` | 确认私有化网关是否增加统一前缀 |
| API Token 生命周期 | 文件挂载 | 确认有效期、刷新方式、撤销方式和最小权限 |
| WPML 版本 | `1.0.6` | 以部署版本和设备固件兼容矩阵为准 |
| 飞行器/负载枚举 | 无安全默认值，强制必填 | 用项目真实拓扑和官方枚举表确认四个值 |
| STS 存储类型 | 支持 `minio`、`aws` S3 兼容上传 | 若实际返回 `ali`，需增加 OSS 上传实现后才能启用航线发布 |
| S3 寻址 | Path-style 默认开启 | 根据 STS endpoint、bucket 和网关证书验证 |
| 任务 `device_type` | 空值时不发送 | 根据机场/设备和高级任务接口实际要求填写 |
| 失控动作/续飞策略 | `0` / `1` | 由飞行安全负责人审核，不允许仅沿用示例 |
| 返航高度 | `100m` | 按线路净空、空域和现场 SOP 审核 |
| EventAPI 鉴权 | 当前支持共享 Header Token | 若司空 2 使用签名、时间戳或固定 Header，按真实协议替换校验器 |
| EventAPI 事件字段 | 接受原始 JSON 并做通用归一化 | 保存脱敏样本，冻结任务、设备、媒体字段映射 |
| MQTT Broker/TLS | 默认本地 EMQX、`tcp://emqx:1883` | 生产改 TLS，确认 CA、账号、ACL 和双向认证要求 |
| MQTT Topic/QoS | `flighthub2/#`、QoS 1 | 以 Bridge 配置和真实主题样本为准，收紧通配范围 |
| 媒体回传 | 仅建表和事件入口 | 确认媒体清单、下载授权、URL 有效期和 MinIO 镜像规则 |
| 遥测频率 | 不在接入端硬编码 | 用真机样本确定抽稀、乱序窗口和存储周期 |
| 直播/DRC | 默认未启用 | 单独进行网络、时延、审计和飞行安全评审 |

所有部署后确认结果都应回填到本文件第 10 节的验收记录，不应只保存在聊天或临时工单中。

## 5. 首次部署

以下命令在 `infra` 目录执行。

### 5.1 准备环境文件

```bash
cp .env.flighthub2.example .env.flighthub2
chmod 600 .env.flighthub2
```

编辑 `.env.flighthub2`，替换所有 `replace-...` 占位值。

生成服务间令牌和回调令牌时可使用：

```bash
openssl rand -hex 32
```

### 5.2 创建司空 2 用户 Token 密钥文件

```bash
install -d -m 700 secrets
umask 077
read -r -s FLIGHTHUB_TOKEN
printf '%s' "$FLIGHTHUB_TOKEN" > secrets/flighthub-user-token
unset FLIGHTHUB_TOKEN
chmod 600 secrets/flighthub-user-token
```

文件内容只能包含 Token 本身。不要附加 JSON、变量名或引号。

### 5.3 配置 MQTT Bridge

若使用本项目 EMQX：

1. 使用 `https://<部署主机>:18083` 或仅内网的 Dashboard 管理入口。
2. 开启密码认证，创建 `FLIGHTHUB_MQTT_USERNAME` 对应用户。
3. 配置最小 ACL：司空 2 Bridge 只允许发布约定主题；接入服务只允许订阅该主题。
4. 在司空 2 MQTT Bridge 中填写 EMQX 地址、端口、账号、密码、Topic 和 QoS。
5. 生产环境启用 TLS 后，将 `FLIGHTHUB_MQTT_BROKER_URL` 改为 `ssl://<broker>:8883`，并把内部 CA 导入接入服务 JVM truststore。

如果现场已有受管 Broker，可不使用本项目 EMQX，但需要从覆盖文件移除 `uav-access-service -> emqx` 的启动依赖，且保留等价的 TLS、认证和 ACL。

### 5.4 校验编排

```bash
docker compose \
  --env-file .env.flighthub2 \
  -f docker-compose.yml \
  -f docker-compose.flighthub2.yml \
  --profile flighthub2 \
  config --quiet
```

服务器 GPU 环境再追加：

```text
-f docker-compose.server.yml
```

若命令提示 `must be set` 或 `must be confirmed`，说明必填参数仍未完成，不应绕过。

### 5.5 构建和启动

通用环境：

```bash
docker compose \
  --env-file .env.flighthub2 \
  -f docker-compose.yml \
  -f docker-compose.flighthub2.yml \
  --profile flighthub2 \
  up -d --build
```

服务器 GPU 环境：

```bash
docker compose \
  --env-file .env.flighthub2 \
  -f docker-compose.yml \
  -f docker-compose.flighthub2.yml \
  -f docker-compose.server.yml \
  --profile flighthub2 \
  up -d --build
```

启动时两套 Flyway 迁移会自动执行。先确认迁移成功，再执行平台初始化。

### 5.6 健康检查

```bash
curl -fsS http://127.0.0.1:8091/actuator/health
curl -fsS http://127.0.0.1:8080/actuator/health
docker compose --env-file .env.flighthub2 \
  -f docker-compose.yml -f docker-compose.flighthub2.yml \
  --profile flighthub2 ps
```

查看日志时不得复制或输出 Token：

```bash
docker compose --env-file .env.flighthub2 \
  -f docker-compose.yml -f docker-compose.flighthub2.yml \
  --profile flighthub2 logs --tail=200 uav-access-service backend
```

## 6. 平台初始化

### 6.1 注册司空 2 连接

`workspaceId` 当前保存司空 2 项目 UUID；真实凭据不写入平台表，`secretRef` 只记录密钥引用名称。

```bash
curl -fsS -X POST http://127.0.0.1:8080/api/v1/uav/connections \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "司空2生产环境",
    "vendorCode": "FLIGHTHUB2",
    "connectionMode": "PRIVATE_OPENAPI_V2",
    "environment": "PRODUCTION",
    "endpointUrl": "https://flighthub2.example.internal",
    "workspaceId": "<FLIGHTHUB_PROJECT_UUID>",
    "secretRef": "file:/run/secrets/flighthub-user-token",
    "enabled": true
  }'
```

记录响应中的 `connection_id`。

### 6.2 检查连接和同步设备

```bash
curl -fsS -X POST \
  http://127.0.0.1:8080/api/v1/uav/connections/<connection_id>/check

curl -fsS -X POST \
  http://127.0.0.1:8080/api/v1/uav/connections/<connection_id>/sync-devices

curl -fsS http://127.0.0.1:8080/api/v1/uav/devices
```

连接只有在拓扑接口调用成功后才会从 `MATERIALS_REQUIRED` 变为 `AVAILABLE`。不要手工改数据库绕过健康检查。

### 6.3 配置 EventAPI

司空 2 回调目标：

```text
POST https://<公开接入域名>/api/v1/events/flighthub
```

当前接入端识别以下 Header：

```text
X-FlightHub-Event-Token: <FLIGHTHUB_EVENT_CALLBACK_TOKEN>
X-FlightHub-Event-Id: <唯一事件 ID，可选>
X-FlightHub-Event-Type: <事件类型，可选>
X-Connection-Id: <平台 connection_id>
```

其中 `X-Connection-Id` 应配置为第 6.1 节创建的连接 ID，确保事件投影到正确连接。若司空 2 EventAPI 控制台不能配置这些 Header，需根据实际回调契约调整 `FlightHubEventController`，不能关闭鉴权。

反向代理只放行上述 POST 路径，并配置：

- HTTPS 和可信证书；
- 请求体大小限制；
- 来源 IP 白名单（若司空 2 提供固定出口）；
- 限流；
- 保留请求 ID，日志中隐藏鉴权 Header。

## 7. 分阶段联调与飞行控制开关

### 阶段 A：只读连接

保持：

```text
FLIGHTHUB_FLIGHT_CONTROL_ENABLED=false
```

完成以下检查：

1. OpenAPI 健康检查成功。
2. 机场、飞行器、负载层级与司空 2 控制台一致。
3. EventAPI 和 MQTT 各至少收到一类可识别事件。
4. 重复投递同一事件不会重复更新业务状态。

### 阶段 B：航线导入

1. 选定空旷测试场的最小闭合或短航线。
2. 确认四个 WPML 设备枚举。
3. 在平台完成航线校验和发布后，调用独立同步接口：

   ```bash
   curl -fsS -X POST \
     http://127.0.0.1:8080/api/v1/route-versions/<version_id>/uav-connections/<connection_id>/synchronize
   ```

4. 若返回 `IMPORTING`，等待数秒后重复调用；接口会刷新司空 2 导入结果。
5. 观察 STS 上传、导入回调和导入状态轮询。
6. 在司空 2 控制台人工核对高度、速度、航点、返航参数和负载动作。

航线导入未通过时，不得进入任务联调。

### 阶段 C：任务影子模式

仍保持飞行控制关闭，通过只读接口和测试项目验证：

- 航线导入幂等和状态对账；
- 幂等键重复提交；
- 任务状态归一化；
- EventAPI 丢失时批量对账；
- 任务创建及暂停、恢复、取消命令均被安全开关拒绝。

### 阶段 D：受控真机验收

经飞行安全负责人批准后，将：

```text
FLIGHTHUB_FLIGHT_CONTROL_ENABLED=true
```

只重建或重启 `uav-access-service`：

```bash
docker compose \
  --env-file .env.flighthub2 \
  -f docker-compose.yml \
  -f docker-compose.flighthub2.yml \
  --profile flighthub2 \
  up -d --no-deps uav-access-service
```

先做单机场、单飞行器、单航线任务；验证创建、起飞、执行、暂停、恢复、取消、完成和异常终止。任务创建成功只是“司空 2 已接受”，平台不会再把接受结果伪装为执行成功，最终状态来自事件或对账。

## 8. 升级、回滚和密钥轮换

### 8.1 升级

1. 备份 PostgreSQL 和 `.env.flighthub2` 的版本记录，但不把明文密钥放入制品库。
2. 先在测试项目执行 `docker compose ... config --quiet`。
3. 构建并滚动启动 `uav-access-service`、`backend`。
4. 复核 Flyway、健康检查、Outbox 堆积和 Kafka 消费延迟。

### 8.2 紧急停飞

优先在司空 2 控制台执行现场安全 SOP，同时将：

```text
FLIGHTHUB_FLIGHT_CONTROL_ENABLED=false
```

并重启接入服务。该开关禁止新的任务创建和控制命令，不替代飞行中的现场处置。

### 8.3 Token 轮换

1. 在司空 2 创建新 Token 并赋予最小权限。
2. 原子替换宿主机密钥文件。
3. 重启接入服务并执行连接检查。
4. 验证成功后撤销旧 Token。

Token 轮换失败时回退到上一份受控密钥文件；不要在日志、命令行参数或工单正文中传递 Token。

### 8.4 应用回滚

应用镜像可回滚到上一版本，但 Flyway 数据库迁移默认只向前。`V17` 和接入域 `V1` 均为增量建表/加列，旧应用可以忽略这些字段。禁止用 `git reset --hard` 或直接删表作为部署回滚手段。

## 9. 监控和排障

Prometheus 已增加：

- `backend:8080/actuator/prometheus`
- `uav-access-service:8091/actuator/prometheus`

上线至少监控：

- 接入服务健康状态和 JVM 资源；
- OpenAPI 失败率、401/403/429/5xx；
- 航线导入长期停留在处理中；
- 任务长时间未对账；
- Kafka 消费延迟；
- Event Inbox 重复率；
- Outbox 未投递数量；
- MQTT 断连和重连次数；
- 设备最后在线时间。

常见故障判断：

| 症状 | 优先检查 |
|---|---|
| `CONFIGURATION_REQUIRED` | `FLIGHTHUB_ENABLED`、必填环境变量、HTTPS 安全门 |
| 401/鉴权失败 | Token 文件内容、有效期、用户权限、系统时间 |
| 拓扑为空 | 项目 UUID、用户是否加入项目、设备是否绑定到该项目 |
| 航线上传失败 | STS provider/endpoint/bucket、容器 DNS、Path-style、时间同步 |
| 航线导入失败 | WPML 版本、机型/负载枚举、动作参数和航点坐标 |
| 任务被拒绝 | 飞前检查结果、设备在线状态、电量、航线 UUID、控制安全开关 |
| 有事件但平台不更新 | `X-Connection-Id`、Kafka Topic、消费者组、Inbox/Outbox 状态 |
| MQTT 无消息 | Bridge 地址、账号 ACL、Topic filter、TLS CA 和 QoS |

## 10. 部署验收记录模板

每个环境复制一份填写，建议文件名：

```text
dji-flighthub2-acceptance-<environment>-<yyyyMMdd>.md
```

| 检查项 | 确认值/证据 | 负责人 | 日期 | 结果 |
|---|---|---|---|---|
| 司空 2 部署版本 |  |  |  |  |
| OpenAPI Base URL/前缀 |  |  |  |  |
| 组织 UUID / 项目 UUID |  |  |  |  |
| Token 权限与有效期 |  |  |  |  |
| 飞行器/机场/负载和固件 |  |  |  |  |
| WPML 版本及四个枚举 |  |  |  |  |
| STS provider/endpoint/寻址方式 |  |  |  |  |
| EventAPI 鉴权和事件样本 |  |  |  |  |
| MQTT Broker/TLS/Topic/QoS |  |  |  |  |
| 任务 `device_type` |  |  |  |  |
| 失控动作/续飞/返航策略 |  |  |  |  |
| 遥测字段和频率 |  |  |  |  |
| 媒体清单和下载机制 |  |  |  |  |
| 航线导入验收 |  |  |  |  |
| 单机任务全状态验收 |  |  |  |  |
| 断网、重复事件、对账验收 |  |  |  |  |
| 飞行控制开关审批 |  |  |  |  |

只有当阻断项全部通过且飞行安全审批完成后，生产环境才允许设置 `FLIGHTHUB_FLIGHT_CONTROL_ENABLED=true`。
