# ODMS Connect 功能说明书

> 版本：v1.0 · 日期：2026-05-24  
> 面向读者：产品经理、技术对接方、IT 负责人

---

## 1. 产品定位

ODMS Connect 将企业内部关系数据库**标准化暴露为 OData v4 接口**，是 ODMS 数据接入层。

**解决的核心问题：**

| 痛点 | Connect 的解法 |
|------|----------------|
| 数据分散在多个数据库，上层应用接入方式各异 | 统一 OData v4 协议，一种 URL 格式访问所有数据 |
| 每接一个系统都要改代码 | 配置化 Entity 定义，无需改代码 |
| 不知道"谁在查我的数据" | Client Credential + 审计日志，访问可追溯 |
| 数据搬走再分析太重 | 连接而非复制，原库不动 |

**Connect 的边界：**
- 只做关系数据库（PostgreSQL / MySQL / SQL Server / Oracle）
- OData 查询路径：`GET` → Runtime → 数据库（实时查询）
- 写操作通过 Insight Action 走审批流，不经过 Connect 直写
- 字段/行级权限由 Studio 承担，Connect 只做 Entity 级别的访问控制

---

## 2. 系统架构

```
┌─────────────────────────────────────────┐
│            Connect Backend              │
│  (FastAPI, :8000)                       │
│                                         │
│  • DataSource / Service / Entity 配置   │
│  • Runtime 注册 & 心跳管理              │
│  • Client Credential 管理              │
│  • OAuth /oauth/token 签发 JWT         │
│  • 审计日志存储                         │
└────────────┬────────────────────────────┘
             │ 10s 心跳 + 配置同步
             ▼
┌─────────────────────────────────────────┐
│            Connect Runtime              │
│  (FastAPI, :8100)                       │
│                                         │
│  • OData v4 协议实现                    │
│  • JWT 验证中间件                       │
│  • 直连数据库执行查询（asyncpg/aioodbc）│
│  • 访问日志上报（fire-and-forget）      │
└────────────┬────────────────────────────┘
             │ asyncpg / aioodbc
             ▼
         企业数据库（PostgreSQL / SQL Server 等）

┌─────────────────────────────────────────┐
│         Connect Frontend                │
│  (Next.js 15, :3000)                   │
│                                         │
│  • 数据源 / 服务 / Entity 管理界面     │
│  • Runtime 状态监控                     │
│  • Client Credential 管理界面          │
│  • 审计日志查看                         │
└─────────────────────────────────────────┘
```

**架构要点：**
- Backend 负责配置存储（PostgreSQL），不直接执行 OData 查询
- Runtime 无状态，重启后自动重新注册并拉取配置，无数据丢失
- Runtime 与 Backend 之间只有配置同步，OData 请求不经过 Backend
- 多个 Runtime 可并行注册（不同 Runtime ID），用于多部署场景

---

## 3. 核心概念

| 概念 | 说明 | 类比 |
|------|------|------|
| **DataSource** | 数据库连接配置（host/port/db/账号密码/默认 schema）| 数据库驱动连接串 |
| **Entity** | 映射到一张表或视图的业务对象，定义暴露哪些字段 | 一个表的 API 视图 |
| **Service** | 将一组 Entity 打包，绑定到指定 Runtime 发布的 OData 端点 | REST 服务路由组 |
| **Runtime** | 无状态 OData 服务进程，向 Backend 注册后自动拉取配置 | API Gateway 节点 |
| **Client** | 访问 Runtime 的调用方身份，持有 client_id + client_secret | Service Account |

---

## 4. 功能清单

### 4.1 数据源管理

| 功能 | 说明 |
|------|------|
| 新建 / 编辑 / 删除数据源 | 完整 CRUD，支持 POST / PATCH / DELETE |
| 支持数据库类型 | PostgreSQL、SQL Server、MySQL（Oracle 部分支持）|
| 测试连接 | 新建时可验证连接可达性 |
| Schema 自省 | 自动读取 DB 中的 schema → 表 / 视图 → 字段列表（含类型、主键、nullable）|
| EDM 类型自动推断 | `VARCHAR` → `Edm.String`，`NUMERIC` → `Edm.Decimal`，`BOOLEAN` → `Edm.Boolean` 等，支持手动覆盖 |
| Default Schema 配置 | 设置默认 schema，简化 Entity 定义时的 source_schema 填写 |

### 4.2 Entity 定义

| 功能 | 说明 |
|------|------|
| 从表 / 视图创建 Entity | source_schema + source_table 指向数据库对象 |
| 字段选择 | 只暴露指定字段，不暴露的字段不出现在 OData 响应中 |
| 主键配置 | 指定 key_field，支持单主键 |
| 字段只读标记 | `is_readonly: true` 的字段在 PATCH/POST 时不可被调用方修改 |
| EDM 类型映射 | 每个字段可单独调整 EDM 类型 |
| 视图支持 | 视图与表配置方式完全相同，source_table 填视图名即可 |

### 4.3 服务管理

| 功能 | 说明 |
|------|------|
| 新建 / 编辑 / 删除服务 | 完整 CRUD |
| 绑定 DataSource + Runtime | 一个服务绑定一个数据源、一个 Runtime |
| Entity 列表配置 | 一个服务可包含多个 Entity |
| 发布 / 下线 | 发布后 Runtime 在下次心跳（10s 内）自动加载；下线后立即对调用方不可见 |
| 服务详情页 | 展示 Entity 列表 + OData URL + 已授权 Client + $metadata 快速复制 |

### 4.4 OData v4 协议

Runtime 实现的 OData v4 端点：

| 路径 | 说明 |
|------|------|
| `GET /{service}/` | Service Document（EntitySet 列表）|
| `GET /{service}/$metadata` | CSDL 元数据（XML 格式，无需认证）|
| `GET /{service}/$metadata?format=json` | CSDL 元数据（JSON 格式）|
| `GET /{service}/{Entity}` | 集合查询 |
| `GET /{service}/{Entity}({key})` | 单条查询 |
| `POST /{service}/{Entity}` | 新增记录（需 Client 有 POST 权限）|
| `PATCH /{service}/{Entity}({key})` | 更新记录（需 Client 有 PATCH 权限）|
| `DELETE /{service}/{Entity}({key})` | 删除记录（需 Client 有 DELETE 权限）|

**支持的查询选项：**

| 参数 | 说明 | 示例 |
|------|------|------|
| `$top` | 返回前 N 条（分页大小）| `$top=20` |
| `$skip` | 跳过前 N 条 | `$skip=40` |
| `$count=true` | 响应中包含 `@odata.count` 总数 | `?$count=true` |
| `$select` | 只返回指定字段 | `$select=id,name,balance` |
| `$filter` | 条件过滤（OData 语法）| `$filter=status eq 'active'` |
| `$orderby` | 排序 | `$orderby=balance desc` |

**`$filter` 支持的运算符：**
- 比较：`eq / ne / gt / ge / lt / le`
- 逻辑：`and / or / not`
- 字符串函数：`contains(field,'val') / startswith / endswith`
- 空值：`field eq null`

**不在当前范围（Roadmap）：**
- `$expand`：需要 NavigationProperty 关系定义
- `$search`：全文检索

### 4.5 Runtime 管理

| 功能 | 说明 |
|------|------|
| 注册 | Runtime 启动时自动向 Backend 注册，记录 runtime_id / name / base_url |
| 10s 心跳 | 每 10 秒向 Backend 报活，并拉取最新配置（`RuntimeConfigBundle`）|
| 在线 / 离线状态 | Backend 根据最后心跳时间判断 Runtime 状态 |
| 配置热加载 | 服务发布 / 下线后，Runtime 在下次心跳内自动生效，无需重启 |
| 多 Runtime | 不同 runtime_id 的实例可并行注册，互不干扰 |
| 前端管理页 | 显示所有已注册 Runtime、状态、最后心跳时间 |

### 4.6 访问控制（Phase 1）

**架构：Connect Backend 内置轻量 Auth Server，调用方通过 `client_credentials` grant 获取 JWT。**

```
调用方 → POST /oauth/token (client_id + client_secret)
       → Connect Backend 签发 JWT（含 allowed_entities + allowed_methods）
       → 调用方携带 Bearer JWT 请求 Runtime OData 端点
       → Runtime 本地验证 JWT（无需回调 Backend）
```

| 功能 | 说明 |
|------|------|
| Client 创建 | 生成 client_id（`cid_xxx`）+ client_secret（`secrets.token_urlsafe(32)`）；secret 仅创建时展示一次 |
| Client 列表 / 吊销 | 吊销设置 `is_active=false`，立即生效（下次 token 请求失败）|
| OAuth Token 签发 | `POST /oauth/token`，grant_type=client_credentials，返回 Bearer JWT，有效期 1 小时 |
| JWT 内容 | 含 `client_id / service_id / allowed_entities / allowed_methods / exp` |
| Runtime JWT 验证 | Runtime 本地验证签名 + Entity 白名单 + HTTP 方法权限 |
| $metadata 豁免 | `/{service}/$metadata` 无需 JWT，可公开访问 |
| 零权限默认 | 新建 Client 默认无任何 Entity 权限，需显式配置 |
| Token TTL | 默认 1 小时（3600 秒）|

**Phase 2（Roadmap，按需启动）：**
- 对接外部 OAuth（Azure AD / Keycloak / Okta）
- Runtime 验证策略可配置切换（JWKS 端点），不改 Runtime 代码

### 4.7 审计日志

Connect 维护两类独立审计日志，面向 IT 负责人：

**类型 1：配置变更日志（`config_change`）**

| 触发动作 | 记录内容 |
|---------|---------|
| DataSource 新增 / 编辑 / 删除 | actor + action + resource_type=datasource |
| Service 新增 / 发布 / 下线 / 删除 | actor + action + resource_type=service |
| Client 创建 / 吊销 | actor + action + resource_type=client |
| Permission 变更 | actor + action + resource_type=permission |

**类型 2：Runtime 访问日志（`runtime_access`）**

| 记录内容 | 示例 |
|---------|------|
| client_id + service + entity + method + status_code | `[runtime-local] GET /AuroraTradingService/Customers → 200` |

- Runtime 每次请求处理完成后 fire-and-forget 上报，不影响响应性能
- 仅记录非 5xx 请求（服务端错误不记录以避免日志洪水）

**审计查看：** Connect Dashboard → `/dashboard/audit`，支持按 `log_type` 过滤。

---

## 5. API 端点汇总

### Backend（:8000）

| 类别 | 方法 | 路径 | 说明 |
|------|------|------|------|
| Auth | POST | `/auth/login` | 管理员登录，返回 Bearer JWT |
| Auth | GET | `/auth/me` | 当前登录用户信息 |
| OAuth | POST | `/oauth/token` | Client Credential 签发 JWT |
| DataSource | GET/POST | `/api/datasources` | 列表 / 新建 |
| DataSource | GET/PATCH/DELETE | `/api/datasources/{id}` | 详情 / 编辑 / 删除 |
| DataSource | POST | `/api/datasources/{id}/introspect` | 自省 DB 结构 |
| Service | GET/POST | `/api/services` | 列表 / 新建 |
| Service | GET/PATCH/DELETE | `/api/services/{id}` | 详情 / 编辑 / 删除 |
| Service | POST | `/api/services/{id}/publish` | 发布 |
| Service | POST | `/api/services/{id}/unpublish` | 下线 |
| Runtime | GET | `/api/runtimes` | 已注册 Runtime 列表 |
| Runtime | POST | `/api/runtimes/register` | Runtime 注册（Runtime 自动调用）|
| Runtime | POST | `/api/runtimes/{id}/heartbeat` | 心跳（Runtime 自动调用）|
| Runtime | GET | `/api/runtime-configs/{id}` | 拉取配置包（Runtime 自动调用）|
| Client | GET/POST | `/api/clients` | 列表 / 新建 |
| Client | DELETE | `/api/clients/{id}` | 吊销 |
| Audit | GET | `/api/audit/logs` | 查询审计日志（支持 log_type 过滤）|
| Audit | POST | `/api/audit/access-log` | 接收 Runtime 访问日志上报（内部端点）|
| Permission | GET | `/api/permissions/features` | Feature 权限列表 |
| Permission | POST/DELETE | `/api/permissions/features` | 权限配置 |
| Health | GET | `/health` | 健康检查 |

### Runtime（:8100）

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/health` | 健康检查 |
| GET | `/{service}/` | Service Document |
| GET | `/{service}/$metadata` | CSDL 元数据（无需 JWT）|
| GET | `/{service}/$metadata?format=json` | CSDL 元数据 JSON |
| GET | `/{service}/{Entity}` | 集合查询 |
| GET | `/{service}/{Entity}({key})` | 单条查询 |
| POST | `/{service}/{Entity}` | 新增（需 POST 权限）|
| PATCH | `/{service}/{Entity}({key})` | 更新（需 PATCH 权限）|
| DELETE | `/{service}/{Entity}({key})` | 删除（需 DELETE 权限）|

---

## 6. 数据流

### 查询流（Read Path）

```
调用方
  │ GET /AuroraTradingService/Customers?$filter=risk_level eq 'high'
  │ Authorization: Bearer <client_jwt>
  ▼
Runtime 中间件
  ├─ 验证 JWT 签名（本地，无网络调用）
  ├─ 检查 allowed_entities：Customers ∈ 白名单 ✓
  ├─ 检查 allowed_methods：GET ∈ 允许方法 ✓
  └─ 通过 → 继续处理
  ▼
OData 路由
  ├─ 解析 $filter → SQL WHERE risk_level = 'high'
  ├─ 解析 $top/$skip → SQL LIMIT/OFFSET
  └─ 从 RuntimeState.bundle 获取 datasource + adapter
  ▼
PostgreSQL Adapter
  └─ 执行 SELECT ... FROM aurora_trading.customers WHERE risk_level = 'high'
  ▼
OData 响应构建
  └─ { "@odata.context": "...", "value": [...] }
  ▼
调用方收到 OData JSON 响应
  │
  └─ (异步) Runtime fire-and-forget → POST /api/audit/access-log → Backend 存审计
```

### 配置同步流（Config Sync）

```
管理员在 Connect Frontend 操作
  → 调用 Backend API 修改 Service/Entity 配置
  → Backend 更新 PostgreSQL

Runtime 心跳（每 10 秒）
  → POST /api/runtimes/{id}/heartbeat
  → GET /api/runtime-configs/{id}
  → 原子替换 RuntimeState.bundle（线程安全）
  → 新配置立即对后续请求生效
```

---

## 7. 部署说明

### 7.1 环境要求

| 组件 | 要求 |
|------|------|
| Backend | Python 3.12+，PostgreSQL 14+（存 Connect 自身配置）|
| Runtime | Python 3.12+，可访问目标数据库 |
| Frontend | Node.js 18+，`npm run dev`（开发）/ `npm run build` + `npm start`（生产）|

### 7.2 启动命令

```bash
# Backend（需从 odms-connect/ 根目录运行）
cd odms-connect
uv run uvicorn backend.main:app --port 8000 --reload

# Runtime（设置必要环境变量）
cd odms-connect/runtime
CONNECT_BACKEND_URL=http://127.0.0.1:8000 \
CONNECT_RUNTIME_ID=runtime-local \
CONNECT_RUNTIME_NAME="Local Runtime" \
CONNECT_RUNTIME_BASE_URL=http://127.0.0.1:8100 \
uv run uvicorn main:app --port 8100 --reload

# Frontend
cd odms-connect/frontend
npm install && npm run dev
```

### 7.3 Runtime 环境变量

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `CONNECT_BACKEND_URL` | `http://127.0.0.1:8000` | Backend 地址 |
| `CONNECT_RUNTIME_ID` | `runtime-local` | 唯一标识，多 Runtime 时需不同 ID |
| `CONNECT_RUNTIME_NAME` | `Local Runtime` | 展示名称 |
| `CONNECT_RUNTIME_BASE_URL` | `http://127.0.0.1:8100` | 自身对外 URL（供 Backend 记录）|
| `CONNECT_HEARTBEAT_SECONDS` | `10` | 心跳间隔（秒）|

### 7.4 Backend 配置（config.py）

| 配置 | 默认值 | 说明 |
|------|--------|------|
| `DATABASE_URL` | `postgresql+asyncpg://postgres:postgres@localhost:5432/odms_connect` | Connect 自身 DB |
| `secret_key` | `change-me-in-production` | JWT 签名密钥（**生产必须改**）|
| `algorithm` | `HS256` | JWT 算法 |
| `cors_origins` | `["http://localhost:3000"]` | 前端允许跨域来源 |

> **生产注意：** `secret_key` 必须修改为足够随机的字符串。该密钥同时用于管理员 JWT 和 Client JWT，泄露后所有 token 均失效。

---

## 8. 不在范围内（明确边界）

| 能力 | 说明 | 由谁处理 |
|------|------|---------|
| SaaS / REST API 数据接入 | Connect 只做关系数据库 | CData OData Adapter 等工具转换后接入 Studio |
| 字段级 / 行级权限 | Connect 只做 Entity 级访问控制 | Studio 承担 |
| 写操作审批流 | Connect 不拦截写操作做审批 | Insight Action + 审批流 |
| 数据存储 / 缓存 | 不存业务数据，实时查询 | 无 |
| 多租户隔离 | 当前单租户 | Roadmap |
| $expand / $search | 依赖 NavigationProperty 关系定义 | Roadmap |
| 外部 OAuth 对接 | Phase 2，按需启动 | Roadmap |
