# OIP-GoInOne **Repository Path**: nanayin/OIP-GoInOne ## Basic Information - **Project Name**: OIP-GoInOne - **Description**: OIP-GoInOne是奥集能开放架构的 代理服务端(网关节点),是整个系统的网关枢纽——所有客户端的入口。OIP-GoInOne树形去中心化部署,每个组织可部署自己的 OIP-GoInOne节点,节点间以树形结构互通。 - **Primary Language**: Rust - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2026-10-12 - **Last Updated**: 2026-10-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # OIP-GoInOne · 统一接入底座 > **文档版本**:v1.1 | **更新**:2026-09-26 | **状态**:正式发布 > **关联**:[OIP-GoInOne-Design.md](../OIP/docs/OIP-GoInOne-Design.md) · [OIP-Protocol.md](../OIP/docs/OIP-Protocol.md) · [OIP.md](../OIP/docs/OIP.md) `OIP-GoInOne` 是开放互联协议(Open Interconnect Protocol, OIP)的 **统一接入底座 / 代理服务端(网关)**,是整个系统的入口枢纽。`OIP-GoInOne` 树形去中心化部署:每个组织部署自己的节点,节点间以树形结构互通;所有客户端经 `OIP-GoInOne` 完成认证、路由与上行请求转发。 > Rust 实现(edition 2024;axum + tokio + jsonwebtoken + x25519-dalek + aes-gcm + redis + reqwest + axum-server),对齐 OIP 规范仓库中的设计文档 [OIP-GoInOne-Design.md](../OIP/docs/OIP-GoInOne-Design.md)。 ## 核心能力 | 能力 | 说明 | | --- | --- | | SignalR 实时通道 | `/oip/v1/hub` WebSocket JSON / TXT 线协议 + negotiate 协商;不支持 WebSocket 时降级为 SSE | | DID 认证代理 | JWT access/refresh 轮换(HS256,密钥 ≥ 32 字节,`kid` 随头部下发,支持旧密钥宽限期) | | 安全加密通信 | X25519 密钥协商 + AES-256-GCM 会话加密;`crypto/des.rs` 提供兼容加密 | | 节点握手四步 | hello → ack → negotiate → sync → 建立树形拓扑 | | **跨组织树形路由** | 父/子节点寻址、上行转发与跨节点转发落点(`/oip/v1/node/forward`),握手 ack 阶段建立拓扑 | | 服务注册表 | 子节点/服务心跳注册与健康探测 | | 断路器 | 连续失败熔断(Open/HalfOpen/Closed 状态机) | | 限流 | 单实例令牌桶 / 跨实例 Redis 滑动窗口:握手 10/min、登录与刷新 5/min、业务 API 100/min/DID、bootstrap 60/min、全局 5000/min(两种后端实现均参与判定) | | **业务插件接入** | Plugin Registry(§3.7):清单上报/依赖门控/挂载状态机 + bootstrap 挂载树投影 + 启动期挂载一致性自检 | | **运维急停** | `/admin/kill-switch`:HSM(Ed25519)监管签名校验后激活/释放全局断路器,状态经共享键跨实例收敛 | | **可观测** | `/metrics` Prometheus 文本暴露 + 请求/5xx 计数中间件(R9) | | 遗留前端兼容 | `/api/v1/*`、`/graph/*` 前门转译;`/orginone/kernel/*`、`/orginone/anydata/*` 命名空间别名双挂载 | ## 无状态设计 `OIP-GoInOne` 是**无状态网关**(P6 网关无状态、能力降级):进程内 MUST NOT 驻留跨请求的业务状态,因此实例可任意增删、天然支持集群化,无需会话迁移。一切状态按下表外置到权威源或共享缓存层: | 状态类别 | 归属位置 | 说明 | | --- | --- | --- | | 会话映射 / 判定凭据 / 路由表 | 共享缓存层(Redis) | 进程内不驻留,VET 状态变更事件到达即失效(P4 缓存从属于权威源) | | 业务事实与状态 | `OIP-Book`(全局权威源) | 网关只做代理转发与裁定执行,不持有(P1) | | 待投递消息 / 请求日志 | `OIP-Message` | 网关不持久化日志,请求日志异步上报 `OIP-Message` 归集(G8) | | 节点间数据 / 消息同步 | `OIP-Book`(数据)/ `OIP-Message`(消息) | 网关不做节点级同步(P5 树形去中心) | - **网关不落盘**:`OIP-GoInOne` 自身不持久化任何业务数据与访问日志;日志异步上报 `OIP-Message`,上报失败 MUST NOT 阻断业务请求。 - **缓存可失效**:网关缓存 MUST 可被 `OIP-Book` 的权威事件失效,权威状态只在 `OIP-Book`,缓存不得反写。 - **降级不降级为放行**:对端不可达时按 P6 执行能力降级或 fail-closed,MUST NOT 降级为放行。 - **启动期 fail-fast**:`env=production` 而未配置 `redis_uri`、`redis_uri` 已配置但不可达、`auth.jwt_secret` 短于 32 字节、插件挂载一致性自检失败,任一成立即拒绝启动,不进入降级形态。 ## 快速开始 ```bash # 1. 准备配置(模板见 config.example.yaml) cp config.example.yaml config.yaml # 编辑 config.yaml:填写 node.node_did、auth.jwt_secret(≥32 字节)、redis_uri 等 # 2. 构建 / 测试 / Lint(与 CI 门禁一致) cargo build --release --locked # 发布构建 cargo test --all-targets # 单元测试 + tests/cross_service_contract.rs 跨服务契约集成测试 cargo clippy --all-targets -- -D warnings # Lint(零警告) cargo fmt --all --check # 格式检查 # 3. Redis 门控用例(需 REDIS_URI 指向可用实例;CI 以 redis:7 服务提供) REDIS_URI=redis://127.0.0.1:6379 cargo test --lib -- --ignored # 4. 运行(可用 OIP_GOINONE_CONFIG 覆盖配置路径) OIP_GOINONE_CONFIG=config.yaml cargo run ``` ## 配置 配置只经 `config.yaml` 注入:复制模板 `config.example.yaml` 为 `config.yaml` 后填写真实值,`config.yaml` 不入库(`.gitignore` 已排除);配置文件路径可用环境变量 `OIP_GOINONE_CONFIG` 覆盖。凭据一律由部署平台的密钥管理注入,不写入代码。`src/config.rs` 以 `#[serde(deny_unknown_fields)]` 严格解析,未列出的键会被拒绝。 | 键 | 默认值 | 说明 | | --- | --- | --- | | `listen` | (无默认,必填) | HTTP/WS 监听地址;模板为 `"0.0.0.0:8088"` | | `log_level` | `info` | 日志级别(trace/debug/info/warn/error) | | `env` | `development` | 运行环境;`production` 下强制要求 `redis_uri` 非空(P2 状态外置硬门禁) | | `node.node_name` | `本地 goinone 节点` | 节点名称(`node` 段缺省时取该默认值) | | `node.node_did` | `did:oip:local_goinone` | 节点 DID(`node` 段缺省时取该默认值) | | `node.protocol_version` | `v5.18` | 协议版本声明(与 `OIP-Protocol.md` 文档版本一致) | | `node.compliance_level` | `OIP-Core` | 合规级别声明 | | `node.parent_url` | `""` | 父节点地址;留空表示本节点为树根 | | `node.capabilities` | `vet` / `transfer` / `market` / `audit` / `auth` | 本节点能力清单 | | `book.base_url` | `""` | 上游 `OIP-Book` 对接地址 | | `book.auth_token` | `""` | `OIP-Book` 内部调用令牌(由密钥管理注入) | | `book.perm_cache_ttl_secs` | `300` | 权限缓存 TTL(秒) | | `data.base_url` | `""` | 数据核地址;留空表示未接入,`DataProxy` 回落节点间转发 | | `data.jwt_secret` | `""` | 与数据核共享的 HS256 密钥(≥32 字节),`OIP-GoInOne` 据此铸造数据核形状令牌 | | `data.token_ttl_secs` | `3600` | 铸造令牌有效期(秒) | | `data.timeout_secs` | `30` | 单次数据核调用超时(秒) | | `auth.jwt_secret` | `""`(必填且 ≥32 字节) | HS256 签名密钥;缺失或过短即拒绝启动 | | `auth.jwt_kid` | `k1` | 当前密钥 ID,随 JWT 头部下发(轮换时递增) | | `auth.jwt_previous_secret` | `""` | 轮换宽限期内作为备用接受验签的旧密钥 | | `auth.access_ttl_minutes` | `15` | Access Token 有效期(分钟) | | `auth.refresh_ttl_days` | `7` | Refresh Token 有效期(天) | | `tls.enabled` | `false` | 启用后以 HTTPS/WSS 对外提供服务(R5) | | `tls.cert_path` / `tls.key_path` | `""`(模板填写 `certs/server.crt` / `certs/server.key`;TLS 关闭时该目录不创建) | TLS 证书与私钥路径(`certs/*.key` 不在库内) | | `proxy.allowed_hosts` | `[]` | 出站代理上游域名白名单;留空不限制域名,仍拦截内网地址 | | `proxy.allow_private` | `false` | 是否允许代理访问内网/回环地址(仅本地调试置 true) | | `cluster.allowed_node_hosts` | `[]` | 跨节点转发目标主机白名单;留空不限制主机,仍拒绝回环/链路本地/组播/保留段 | | `connections.max_connections` | `10000` | 单实例最大连接数 | | `connections.heartbeat_secs` | `30` | 客户端心跳间隔(秒) | | `connections.timeout_secs` | `90` | 服务端超时断开阈值(秒) | | `redis_uri` | `""` | Redis 连接串(会话映射/限流/服务注册/refresh 状态,`env=production` 时必填) | | `hsm.public_key` | `""` | base64(STANDARD)Ed25519 监管公钥;留空则 `/admin/kill-switch` 报 ProofInvalid,配置了但非法则在启动期拒绝启动 | > 模板 `config.example.yaml` 未含 `data.*` 与 `cluster.*` 两项:`data.*` 在形态Ⅱ 由内嵌编排层注入;`cluster.allowed_node_hosts` 是节点间对等体白名单加固(留空=不限制主机名,仍拦截回环/链路本地/组播/保留段),需要时按上表在 `config.yaml` 中补充。 ## 持续集成 `.github/workflows/ci.yml` 在 push(`master`/`main`)与 `pull_request` 时执行,运行于 `ubuntu-latest`,工具链 Rust `1.98.1`(含 `rustfmt`、`clippy`),并以服务容器提供 `redis:7`(注入 `REDIS_URI`): ```bash cargo fmt --all --check cargo clippy --all-targets -- -D warnings cargo test --all-targets cargo test --lib -- --ignored # Redis 集成用例 cargo build --release --locked ``` `.github/workflows/contract-check.yml` 执行协议面漂移校验,且为 **fail-closed**:若同级目录不存在 `../OIP/tools/gen_protocol.py`,先尝试 `git clone --depth 1 https://gitee.com/orginone/OIP.git ../OIP`;克隆后仍不可用则报错退出(非跳过)。可用时执行 `python3 ../OIP/tools/gen_protocol.py --check`,任何漂移即失败。`src/protocol_gen.rs`、`src/topics_gen.rs` 由该脚本生成。 ## API 端点 `src/router.rs` 的 `build()` 为全部端点的唯一装配点;`DECLARED_ROUTES` 是由 `build()` 派生的声明表(57 项),供插件挂载一致性门禁与前门转译表使用。 ### 认证与节点 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/oip/v1/auth/login` | DID 登录,签发 access/refresh token | | POST | `/oip/v1/auth/refresh` | 刷新 access token | | POST | `/oip/v1/auth/token-auth` | 校验既有 accessToken,返回持有者 DID(验证面 D2) | | GET | `/oip/v1/bootstrap` | 匿名节点能力发现 | | POST | `/oip/v1/bootstrap` | 节点能力声明握手 | | POST | `/oip/v1/node/handshake/hello` | 握手第一步:问候 | | POST | `/oip/v1/node/handshake/ack` | 握手第二步:确认身份 | | POST | `/oip/v1/node/handshake/negotiate` | 握手第三步:协商 | | POST | `/oip/v1/node/handshake/sync` | 握手第四步:同步 | | POST | `/oip/v1/node/forward` | 跨节点转发落点(树形路由) | ### 插件与消息接入 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/oip/v1/plugins/register` | 业务服务插件上报(清单入库) | | POST | `/oip/v1/plugins/status` | 插件心跳续报 / 状态查询 | | POST | `/oip/v1/message/register` | `OIP-Message` 服务接入注册 | | POST | `/oip/v1/message/notify` | 消息投递通知接入 | ### 实时通道 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/oip/v1/hub/negotiate` | SignalR 连接协商 | | GET / POST | `/oip/v1/hub` | SignalR 实时通道(WebSocket;浏览器不支持 WebSocket 时降级为 SSE) | | POST | `/orginone/kernel/hub/negotiate`、`/orginone/anydata/hub/negotiate` | 命名空间别名协商 | | GET / POST | `/orginone/kernel/hub`、`/orginone/anydata/hub` | 命名空间别名通道 | ### REST 降级通道(§3.11.7) | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/oip/v1/rest/request`、`/oip/v1/rest/requests` | 通用业务请求 | | POST | `/oip/v1/rest/auth` | 认证 | | POST | `/oip/v1/rest/dataproxy`、`/oip/v1/rest/datanotify`、`/oip/v1/rest/virtualnotify` | 数据代理与通知 | | POST | `/oip/v1/rest/qrauth` | 扫码认证 | | POST | `/oip/v1/rest/httpforward` | HTTP 转发 | | GET | `/oip/v1/rest/online` | 在线列表 | | GET | `/oip/v1/rest/createtemporarytoken` | 铸造临时令牌 | ### System API(§3.11.2a) | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | `/oip/v1/system/online` | 在线列表 | | GET | `/oip/v1/system/connection-id` | 当前连接 ID | | POST | `/oip/v1/system/temporary-token` | 铸造临时令牌 | | POST | `/oip/v1/system/http-forward` | HTTP 转发 | | POST | `/oip/v1/system/data-share` | 数据分享 | | POST | `/oip/v1/system/load-share` | 加载分享 | ### 命名空间别名与遗留路径 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST / GET | `/orginone/kernel/rest/*`、`/orginone/anydata/*` | 与 `/oip/v1/*` 双挂载的同源别名(`auth`、`tokenAuth`、`createTemporaryToken`、`temporaryTokenAuth`、`qrauth`、`dataShare`、`loadShare`、`dataProxy`、`dataNotify`、`virtualNotify`、`request`、`requests`、`httpForward`、`online`,并含 `tokenauth` 等小写形式) | | ANY | `/api/v1/*`、`/graph/*` | 遗留前端路径别名,由前门转译为内核操作(形态Ⅱ 桌面终端) | ### 运维与探针 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST / GET | `/admin/kill-switch` | 运维急停:POST 需 HSM 监管签名以激活/释放,GET 免签查询断路器状态 | | GET | `/metrics` | Prometheus 指标文本暴露(R9) | | GET | `/healthz` / `/readyz` | 健康检查 | **SignalR Hub 方法**(`HUB_METHODS`,§3.9 G4): | 方法 | 说明 | | --- | --- | | `Auth.Login` | 握手后 DID 认证,返回 connection_id | | `Auth` | 认证 action 分发(`AUTH_METHODS`:`Login` / `Refresh` / `TokenAuth`) | | `TokenAuth` | 校验既有 accessToken,返回持有者 DID(验证面 D2) | | `Request` / `Requests` | 通用业务请求,按插件注册表就近路由(未挂载返回能力降级 G5) | | `DataProxy` / `DataNotify` | 数据代理与数据变更通知 | | `Online` / `Presence.GetOnlineDids` | 在线 DID 查询 | | `GetConnectionId` | 当前连接 ID | | `Ping` | 心跳 | | `Node.RoutingInfo` | 节点拓扑信息 | ## 目录结构 ```text OIP-GoInOne/ ├── Cargo.toml / Cargo.lock # 工程清单与锁定依赖(构建走 --locked) ├── config.example.yaml # 配置模板(config.yaml 不入库) ├── Dockerfile # 生产镜像(多阶段构建,rust:1.98-bookworm → debian:bookworm-slim) ├── docker-compose.yml # 本地编排:oip-goinone + Redis ├── deny.toml # cargo-deny 依赖策略 ├── README.md / README.en.md / AGENTS.md ├── .github/workflows/ # ci.yml(构建/测试/Redis 门控)、contract-check.yml(协议面漂移门禁) ├── tests/ │ └── cross_service_contract.rs # 跨服务契约集成测试 └── src/ ├── main.rs # 二进制入口(薄壳,含 JWT 密钥长度校验、插件挂载自检、优雅停机) ├── lib.rs # 库入口(模块声明) ├── auth/ # DID 认证代理(JWT 签发/验证/轮换,refresh 状态存储) ├── breaker.rs # 断路器状态机 ├── clock.rs # 时间抽象(SystemClock / ManualClock) ├── config.rs # 配置加载与校验 ├── connections/ # 连接注册与管理(顶替/心跳/超时) ├── crypto/ # X25519 + AES-256-GCM(cipher)/ JWT(jwt)/ 兼容 DES(des) ├── error.rs # OipError / OipErrorCode 与 HTTP 状态映射 ├── handlers/ # HTTP + SignalR Hub 处理器(admin 急停、hub、message、plugin、rest、front_door) ├── hsm.rs # HSM 监管签名校验(急停端点,只持公钥) ├── ids.rs # DID / VET / 连接 ID 生成 ├── metrics.rs # Prometheus 指标(/metrics 文本暴露 + 请求/5xx 中间件) ├── node/ # 节点身份与握手(hello/ack/negotiate/sync) ├── plugin/ # 业务服务插件注册表(清单/依赖门控/挂载状态机/自检) ├── protocol_gen.rs # 协议面生成物(由 OIP 规范仓库经 gen_protocol.py 派生) ├── proxy/ # 上行代理(data / front_door / routes / guard 出站策略) ├── ratelimit.rs # 令牌桶 + Redis 滑动窗口限流 ├── redis_mirror.rs # Redis 状态镜像(内存表为权威,写路径 fire-and-forget 镜像) ├── registry/ # 服务注册表(心跳/健康) ├── response.rs # 统一 OperateResult 响应 ├── router.rs # 路由装配 + DECLARED_ROUTES 声明表 ├── routing/ # 跨组织树形路由(寻址/上行转发/node/forward 落点) ├── signalr/ # SignalR JSON / TXT 线协议编解码 ├── sse.rs # SignalR 会话注册表(不支持 WebSocket 时降级为 SSE) ├── state.rs # 全局状态与后端选型 ├── topics.rs # 事件主题注册表与主题模式匹配 └── topics_gen.rs # 主题生成物(由 OIP 规范仓库经 gen_protocol.py 派生) ``` ## 测试 | 层级 | 位置 | 说明 | | --- | --- | --- | | 单元测试 | 各模块 `#[cfg(test)]` | JWT 签发/轮换、X25519 协商与 AES-GCM 加解密、断路器、限流、出站策略、SignalR 编解码 | | 路由集成测试 | `src/router.rs` 的 `#[cfg(test)]` | 声明表 ↔ 挂载一致性、命名空间别名可达性(`tower::ServiceExt::oneshot`) | | Hub 端到端 | `src/handlers/hub.rs` 的 `#[cfg(test)]` | JSON 与 TXT 两种线协议的完整会话流程 | | 跨服务契约集成测试 | `tests/cross_service_contract.rs` | 与 `OIP-Book` / `OIP-Message` 的契约面(10 个用例) | | Redis 门控用例 | `#[ignore]` + `REDIS_URI` | 需真实 Redis;未设置时打印跳过信息并返回 | ## 安全基线 - `auth.jwt_secret` 必须 ≥ 32 字节,入口与 AuthService 双重校验,缺失或过短即拒绝启动 - 配置仅经 `config.yaml` 注入(该文件不入库),凭据不得硬编码 - 500 类错误对外返回通用信息,异常详情只进进程内日志;网关不持久化业务数据与访问日志,请求日志异步上报 `OIP-Message` - 认证、bootstrap、节点握手、节点转发、插件与消息接入、REST 与前门业务入口均受限流(单实例令牌桶 / 跨实例 Redis 滑动窗口),并共享全局 5000/min 桶;`/oip/v1/hub` 实时通道、`/admin/kill-switch`、`/metrics` 与健康探针不设端点配额 - 出站代理与跨节点转发各受独立白名单约束,回环 / 私有 / 链路本地(含 `169.254.169.254` 云元数据)/ 组播 / `0.0.0.0/8` / CGNAT / `240.0.0.0/4` 保留段一律拒绝;DNS 解析结果同样复核,防 DNS 重绑定 - `/admin/kill-switch` 的激活与释放必须经 HSM 公钥验签,未配置 `hsm.public_key` 时该端点不可用(返回 ProofInvalid) ## 文档 - 设计文档:[OIP-GoInOne-Design.md](../OIP/docs/OIP-GoInOne-Design.md) - 协议规范:[OIP-Protocol.md](../OIP/docs/OIP-Protocol.md) - 测试策略:[OIP-Test-Strategy.md](../OIP/docs/OIP-Test-Strategy.md) - 协议面生成脚本:[gen_protocol.py](../OIP/tools/gen_protocol.py) - 开发约定:[AGENTS.md](AGENTS.md) - License: MIT