forked from boos/BoosAPI
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6839d22851 | |||
| 2ce11aa13a | |||
| b78b622f0c | |||
| 56b57f7ace |
@@ -0,0 +1,788 @@
|
||||
# BoosAPI Rust Backend Rewrite — 优化方案
|
||||
|
||||
> 基于原始计划的深度分析与重构,核心动因:**Node.js 单线程事件循环在高并发 AI API 代理场景下已触达性能天花板**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 为什么要从 Node.js 迁移到 Rust
|
||||
|
||||
### 0.1 Node.js 的致命瓶颈
|
||||
|
||||
```
|
||||
Node.js 单线程事件循环
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ │ req1 │ │ req2 │ │ req3 │ │ req4 │ ... │
|
||||
│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ ┌────────────────────────────────────┐ │
|
||||
│ │ Event Loop (1 thread) │ │
|
||||
│ │ • JSON parse/serialize blocks │ │
|
||||
│ │ • SSE framing blocks │ │
|
||||
│ │ • Protocol transform blocks │ │
|
||||
│ │ • Token routing compute blocks │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────┘
|
||||
|
||||
问题:1 个请求的 JSON 解析会阻塞所有其他 999 个并发请求
|
||||
```
|
||||
|
||||
**具体场景下 Node.js 的单线程劣化**:
|
||||
|
||||
| 场景 | Node.js 行为 | 影响 |
|
||||
|------|-------------|------|
|
||||
| 1000 并发 SSE 连接 | 事件循环被打满,每帧需遍历所有连接 | P99 延迟飙升到秒级 |
|
||||
| 大请求体 JSON 解析 | `JSON.parse()` 阻塞事件循环 | 所有连接暂停处理 |
|
||||
| 协议转换 (Anthropic ↔ OpenAI) | CPU 密集计算占用主线程 | 代理延迟增加 50-200ms |
|
||||
| Token 加权路由计算 | 计算期间无法处理新连接 | 连接建立超时 |
|
||||
| 上游响应流式转发 | Buffer 拼接 + 重序列化阻塞 | SSE 流出现卡顿 |
|
||||
|
||||
### 0.2 Rust 的对应优势
|
||||
|
||||
```
|
||||
Rust + tokio 多线程运行时
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ tokio runtime (num_cpus threads) │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ worker 0 │ │ worker 1 │ │ worker N │ │
|
||||
│ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │
|
||||
│ │ │req 1 │ │ │ │req M │ │ │ │req Z │ │ │
|
||||
│ │ │req 2 │ │ │ │req.. │ │ │ │req.. │ │ │
|
||||
│ │ │ ... │ │ │ │ ... │ │ │ │ ... │ │ │
|
||||
│ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ + spawn_blocking for heavy CPU work │
|
||||
│ + zero-cost serde (no GC pauses) │
|
||||
│ + stack-allocated futures (no heap boxing) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
优势:JSON 解析在 worker 0 阻塞时,worker 1..N 继续处理其他请求
|
||||
```
|
||||
|
||||
| 指标 | Node.js (Fastify) | Rust (Axum) | 提升 |
|
||||
|------|-------------------|-------------|------|
|
||||
| 并发连接上限 | ~5k (受事件循环限制) | ~50k+ (受内存限制) | **10x** |
|
||||
| JSON 反序列化 | ~80 MB/s (单线程) | ~400 MB/s (serde, 多线程) | **5x** |
|
||||
| SSE 帧处理延迟 | 1-10ms (排队等待) | <100μs (独立 task) | **10-100x** |
|
||||
| 内存占用/连接 | ~50KB (V8 开销) | ~5KB (栈分配) | **10x** |
|
||||
| CPU 利用率 | ~1.2 核 (cluster 也不完美) | 全部 CPU 核 (tokio work stealing) | **N 核全用** |
|
||||
|
||||
### 0.3 结论
|
||||
|
||||
**Node.js cluster/worker_threads 不是答案** —— 它们在多进程间复制状态,破坏了连接亲和性,且每个 worker 仍然是单线程瓶颈。Rust + tokio 是 AI API 网关这种 **高并发 + CPU 密集混合** 场景的正确选择。
|
||||
|
||||
---
|
||||
|
||||
## 1. 技术栈(优化后)
|
||||
|
||||
| 组件 | 选择 | 变更说明 |
|
||||
|------|------|---------|
|
||||
| HTTP 框架 | **Axum 0.8+** | 不变 |
|
||||
| 数据库 | **SQLx 0.8+** | 不变。补充:启用 `migrate` feature,使用 `journal_mode=WAL` |
|
||||
| HTTP 客户端 | **reqwest** | 不变。补充:统一配置 `pool_max_idle_per_host(32)` + `timeout(Duration::from_secs(120))` |
|
||||
| 认证 | **jsonwebtoken + argon2** | 不变。API Key 哈希从 Blake3 统一为 argon2id(避免维护两套哈希) |
|
||||
| 序列化 | **serde + serde_json** | 不变 |
|
||||
| 流式 | **axum Sse + WebSocket** | 不变 |
|
||||
| 日志/追踪 | **tracing + tracing-subscriber** | **新增:opentelemetry + opentelemetry-prometheus** 导出 metrics |
|
||||
| 配置 | **figment + dotenvy** | 不变 |
|
||||
| CLI | **clap** | 不变 |
|
||||
| 测试 | **rstest + wiremock + sqlx::test** | 不变 |
|
||||
| 速率限制 | **tower::limit + governor** | ⭐ **新增** |
|
||||
| 熔断器 | **tower::limit + custom circuit_breaker** | ⭐ **新增** |
|
||||
| 运行时 | **tokio (multi-threaded)** | 不变 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构(优化后)
|
||||
|
||||
### 2.1 整体拓扑
|
||||
|
||||
```
|
||||
React SPA (不变)
|
||||
│
|
||||
├── HTTP REST ──────┐
|
||||
├── SSE ────────────┤
|
||||
└── WebSocket ──────┤
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Axum Router (tower 中间件栈) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Middleware Stack (per-route) │ │
|
||||
│ │ RateLimit → Auth → Timeout → Metrics → Handler │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
||||
│ │ Routes │ │ Proxy Core │ │ Background Tasks │ │
|
||||
│ │ /healthz │ │ ┌──────────┐ │ │ ┌────────────────┐ │ │
|
||||
│ │ /v1/* │──│ │Conductor │ │ │ │ Cron Scheduler │ │ │
|
||||
│ │ /api/* │ │ │(orchestr)│ │ │ │ 12 tokio tasks │ │ │
|
||||
│ │ /v1/user │ │ └────┬─────┘ │ │ └────────────────┘ │ │
|
||||
│ └──────────┘ │ │ │ └────────────────────┘ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────┐ │ │
|
||||
│ │ │Channel │ │ │
|
||||
│ │ │Selector │ │ │
|
||||
│ │ │+Cooldown │ │ │
|
||||
│ │ └────┬─────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────┐ │ │
|
||||
│ │ │ Retry │ │ │
|
||||
│ │ │ Policy │ │ │
|
||||
│ │ │+Circuit │ │ │
|
||||
│ │ │ Breaker │ │ ← ⭐ 新增熔断器 │
|
||||
│ │ └────┬─────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────┐ │ │
|
||||
│ │ │Transform │ │ │
|
||||
│ │ │Pipeline │ │ │
|
||||
│ │ └────┬─────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────┐ │ │
|
||||
│ │ │Provider │ │ │
|
||||
│ │ │Executor │ │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ Transformers │ │ Services │ │ DB Layer │ │
|
||||
│ │ (lib crate) │ │ (biz logic) │ │ (sqlx models) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Metrics / Health / Observability │ │
|
||||
│ │ /metrics (Prometheus) /healthz (DB+upstream check) │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 中间件部署策略
|
||||
|
||||
```rust
|
||||
// 不同路由组的中间件栈不同
|
||||
|
||||
// /v1/* (Proxy 路由) - 最完整
|
||||
// ProxyTokenAuth → RateLimit(per-token) → Timeout(120s) → Metrics → Handler
|
||||
|
||||
// /api/* (Admin 路由) - 管理后台
|
||||
// AuthTokenAuth → RateLimit(per-user) → Timeout(30s) → Metrics → Handler
|
||||
|
||||
// /healthz (健康检查) - 无认证
|
||||
// Timeout(5s) → Handler
|
||||
```
|
||||
|
||||
### 2.3 熔断器设计(⭐ 新增)
|
||||
|
||||
```rust
|
||||
/// 每个上游 provider 独立的熔断状态
|
||||
struct CircuitBreaker {
|
||||
failure_count: AtomicU32,
|
||||
last_failure: AtomicI64, // Unix timestamp
|
||||
state: AtomicU8, // 0=Closed, 1=HalfOpen, 2=Open
|
||||
threshold: u32, // 连续失败 N 次后打开
|
||||
recovery_timeout: Duration, // 打开后多久进入 HalfOpen
|
||||
half_open_max_requests: u32, // HalfOpen 状态允许的探测请求数
|
||||
}
|
||||
|
||||
// 熔断状态在 proxy/conductor 中每个 provider 独立维护
|
||||
// 存储于 AppState 的 Arc<RwLock<HashMap<ProviderId, CircuitBreaker>>>
|
||||
```
|
||||
|
||||
**策略**:
|
||||
- 连续 **5 次** 失败 → Open(快速失败,不发送上游请求)
|
||||
- Open 持续 **30s** → HalfOpen(允许 3 个探测请求通过)
|
||||
- 探测请求全部成功 → Closed;任一失败 → 重新 Open
|
||||
|
||||
---
|
||||
|
||||
## 3. 目录结构(优化后)
|
||||
|
||||
```
|
||||
boosapi/
|
||||
├── Cargo.toml
|
||||
├── .env.example
|
||||
├── docker/
|
||||
│ ├── Dockerfile # 多阶段构建
|
||||
│ └── docker-compose.yml # ⭐ 新增:本地开发环境
|
||||
├── migrations/
|
||||
│ ├── 0001_initial_schema.sql # 核心表(~15 张)
|
||||
│ ├── 0002_proxy_tables.sql # 代理相关表(~10 张)
|
||||
│ ├── 0003_admin_tables.sql # 管理后台表(~7 张)
|
||||
│ └── 0004_oauth_tables.sql # OAuth 表
|
||||
│ # ↑ 拆分为 4 次增量迁移,每次与对应模块一起交付和测试
|
||||
│ # ↓ 回滚脚本
|
||||
│ └── rollback/ # ⭐ 新增:手动回滚脚本
|
||||
│ ├── 0001_rollback.sql
|
||||
│ ├── 0002_rollback.sql
|
||||
│ ├── 0003_rollback.sql
|
||||
│ └── 0004_rollback.sql
|
||||
├── tests/ # 集成测试
|
||||
│ ├── common/ # 测试基础设施
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── fixtures.rs # 录制回放 fixture
|
||||
│ │ └── wiremock.rs # wiremock 辅助
|
||||
│ ├── compatibility/ # ⭐ 新增:API 兼容性对比测试
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── snapshots/ # Node.js 录制快照
|
||||
│ │ └── compare.rs # 对比 runner
|
||||
│ ├── health/
|
||||
│ ├── proxy/
|
||||
│ ├── admin/
|
||||
│ ├── transformers/
|
||||
│ └── oauth/
|
||||
└── src/
|
||||
├── main.rs
|
||||
├── lib.rs
|
||||
├── config.rs
|
||||
├── error.rs
|
||||
├── metrics.rs # ⭐ 新增:Prometheus metrics 注册
|
||||
├── routes/
|
||||
│ ├── mod.rs
|
||||
│ ├── health.rs
|
||||
│ ├── metrics.rs # ⭐ 新增:GET /metrics
|
||||
│ ├── proxy/ # /v1/*
|
||||
│ ├── admin/ # /api/*
|
||||
│ └── user/ # /v1/user/*
|
||||
├── proxy/
|
||||
│ ├── mod.rs
|
||||
│ ├── conductor.rs # 请求编排
|
||||
│ ├── channel_selector.rs # 加权随机 + cooldown
|
||||
│ ├── retry_policy.rs
|
||||
│ ├── circuit_breaker.rs # ⭐ 新增
|
||||
│ ├── rate_limiter.rs # ⭐ 新增:per-token 限流
|
||||
│ └── upstream.rs # 上游请求构建
|
||||
├── transformers/
|
||||
│ ├── mod.rs
|
||||
│ ├── canonical.rs # CanonicalRequest / CanonicalResponse
|
||||
│ ├── openai.rs
|
||||
│ ├── anthropic.rs
|
||||
│ ├── gemini.rs
|
||||
│ └── shared/
|
||||
│ ├── mod.rs
|
||||
│ ├── think.rs # think tag 解析
|
||||
│ ├── tool_names.rs
|
||||
│ └── reasoning.rs
|
||||
├── providers/
|
||||
│ ├── mod.rs
|
||||
│ ├── traits.rs # Provider trait 定义
|
||||
│ ├── openai.rs
|
||||
│ ├── anthropic.rs
|
||||
│ ├── gemini.rs
|
||||
│ ├── codex.rs
|
||||
│ ├── claude.rs
|
||||
│ ├── gemini_cli.rs
|
||||
│ ├── antigravity.rs
|
||||
│ ├── newapi.rs
|
||||
│ ├── oneapi.rs
|
||||
│ ├── onehub.rs
|
||||
│ ├── donehub.rs
|
||||
│ ├── sub2api.rs
|
||||
│ └── veloera.rs
|
||||
├── services/
|
||||
│ ├── mod.rs
|
||||
│ ├── auth.rs
|
||||
│ ├── accounts.rs
|
||||
│ ├── sites.rs
|
||||
│ ├── token_routing.rs
|
||||
│ ├── models.rs
|
||||
│ ├── billing.rs
|
||||
│ ├── proxy_logging.rs
|
||||
│ ├── checkin.rs
|
||||
│ ├── oauth.rs
|
||||
│ ├── update_center.rs
|
||||
│ ├── background.rs # 后台任务调度
|
||||
│ ├── cron/ # ⭐ 独立 cron 子模块
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── model_probe.rs
|
||||
│ │ ├── channel_recovery.rs
|
||||
│ │ ├── usage_aggregation.rs
|
||||
│ │ ├── admin_snapshot.rs
|
||||
│ │ ├── oauth_refresh.rs
|
||||
│ │ ├── sub2api_refresh.rs
|
||||
│ │ ├── announcement_poll.rs
|
||||
│ │ ├── update_poll.rs
|
||||
│ │ ├── log_retention.rs
|
||||
│ │ ├── file_retention.rs
|
||||
│ │ └── webdav_backup.rs
|
||||
│ └── notifications/ # ⭐ 独立通知子模块
|
||||
│ ├── mod.rs
|
||||
│ ├── telegram.rs
|
||||
│ ├── wechat_work.rs
|
||||
│ ├── feishu.rs
|
||||
│ ├── webhook.rs
|
||||
│ ├── bark.rs
|
||||
│ ├── serverchan.rs
|
||||
│ └── email.rs
|
||||
├── db/
|
||||
│ ├── mod.rs
|
||||
│ ├── pool.rs
|
||||
│ ├── migrate.rs
|
||||
│ ├── models/
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── site.rs
|
||||
│ │ ├── account.rs
|
||||
│ │ ├── token.rs
|
||||
│ │ ├── user.rs
|
||||
│ │ ├── organization.rs
|
||||
│ │ ├── proxy_log.rs
|
||||
│ │ └── ...
|
||||
│ └── queries/
|
||||
│ ├── mod.rs
|
||||
│ ├── site_queries.rs
|
||||
│ ├── token_queries.rs
|
||||
│ └── ...
|
||||
├── auth/
|
||||
│ ├── mod.rs
|
||||
│ ├── jwt.rs
|
||||
│ ├── api_key.rs
|
||||
│ ├── password.rs
|
||||
│ └── middleware.rs # ProxyTokenAuth, AuthTokenAuth, DownstreamApiKeyAuth
|
||||
├── desktop/
|
||||
│ └── mod.rs
|
||||
└── shared/
|
||||
├── mod.rs
|
||||
├── sse.rs
|
||||
├── streaming.rs
|
||||
├── pagination.rs
|
||||
├── time.rs
|
||||
├── json.rs
|
||||
└── response.rs # 统一 API 响应信封
|
||||
```
|
||||
|
||||
**变更要点**:
|
||||
- 迁移拆分为 **4 次增量**(原方案 1 次 32 张表 → 每次 4-15 张表)
|
||||
- 新增 `rollback/` 回滚脚本目录
|
||||
- 新增 `circuit_breaker.rs`、`rate_limiter.rs`、`metrics.rs`
|
||||
- services 下的 cron 和 notifications 拆为独立子模块(原有 12+7 个文件混在一起)
|
||||
- 新增 `tests/compatibility/` — API 兼容性对比测试基础设施
|
||||
|
||||
---
|
||||
|
||||
## 4. 黄金规则(优化后)
|
||||
|
||||
1. **API 兼容性必须精确** — 端点路径、HTTP 方法、请求/响应 JSON、SSE event 格式、错误 shape 与现有 Node.js 实现完全一致。
|
||||
2. **每 handler 必有测试** — `cargo test && cargo clippy -- -D warnings` 通过才能提交。
|
||||
3. **一阶段一交付** — 每阶段产出 **可编译、可测试、可部署** 的产物。
|
||||
4. **Phase 1 是基石** — 所有后续阶段依赖 Phase 1 的接口契约。
|
||||
5. **Conventional Commits** — `feat:` / `fix:` / `refactor:` / `test:` / `docs:`。
|
||||
6. **Rust 规范** — `cargo fmt` + `cargo clippy -- -D warnings`。使用 `thiserror` 和 `anyhow`。
|
||||
7. **依赖克制** — `Arc<AppState>` 传递共享状态,禁止 `lazy_static`。
|
||||
8. **⭐ API 兼容性验证** — 每个 proxy 路由必须有对应的录制回放快照测试,CI 中运行 `cargo test --test compatibility` 对比新旧实现行为。
|
||||
9. **⭐ 性能不可退化** — P50 延迟不能超过 Node.js 实现的 80%,P99 不能超过 50%。Phase 13 中 `cargo bench` 验证。
|
||||
|
||||
---
|
||||
|
||||
## 5. API 兼容性验证框架(⭐ 新增)
|
||||
|
||||
### 5.1 录制回放策略
|
||||
|
||||
```
|
||||
Phase 0 (预备): 在 Node.js 后端前挂代理录制
|
||||
┌──────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Test │────▶│ Recorder │────▶│ Node.js │
|
||||
│ Client │ │ (mitmproxy) │ │ Backend │
|
||||
└──────────┘ └──────┬───────┘ └──────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Snapshots/ │
|
||||
│ *.json │
|
||||
└──────────────┘
|
||||
|
||||
Phase 1-13 (实现): 每次 CI 运行对比测试
|
||||
┌──────────┐ ┌──────────────┐
|
||||
│ Cargo │────▶│ Compare │
|
||||
│ Test │ │ Runner │
|
||||
└──────────┘ └──────┬───────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Snapshots│ │ Response │ │ Diff │
|
||||
│ (record) │ │ (actual) │ │ Report │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
### 5.2 快照格式
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "chat_completions_basic",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"headers": { "authorization": "Bearer sk-***", "content-type": "application/json" },
|
||||
"body": { "model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}] }
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": { "content-type": "application/json" },
|
||||
"body": { "id": "*", "object": "chat.completion", "choices": [...] },
|
||||
"ignore_fields": ["id", "created"] // 这些字段可以不同
|
||||
},
|
||||
"sse_events": null // 非流式请求
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 CI 中的兼容性门禁
|
||||
|
||||
```bash
|
||||
# .github/workflows/compat.yml (或对应的 CI 配置)
|
||||
cargo test --test compatibility -- --snapshot-dir tests/compatibility/snapshots
|
||||
# 任何字段不匹配 → CI 失败
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 分阶段计划(重构后)
|
||||
|
||||
### Phase 0: 兼容性快照录制 (1-2 天) ⭐ 新增
|
||||
|
||||
**目标**:在重构开始前录制 Node.js 实现的完整请求/响应对。
|
||||
|
||||
- 搭建 mitmproxy 录制环境指向现有 Node.js 后端
|
||||
- 对每个 proxy 端点录制 3-5 个典型场景(正常、异常、边界)
|
||||
- 快照存入 `tests/compatibility/snapshots/`
|
||||
- **无此快照,后续所有 "API 兼容" 承诺都不可验证**
|
||||
|
||||
### Phase 1: 项目脚手架 + 基础设施 (3-5 天)
|
||||
|
||||
与原始计划基本一致,增补:
|
||||
|
||||
- [x] `cargo init` + 完整 `Cargo.toml`
|
||||
- [x] Figment 配置加载(`.env` + 环境变量 + TOML 文件三层覆盖)
|
||||
- [x] `AppError` 统一错误类型(`thiserror`)
|
||||
- [x] `AppState` 定义(含 `Arc<CircuitBreakerRegistry>`、`Arc<RateLimiterRegistry>`)
|
||||
- [x] 数据库连接池 + 迁移运行
|
||||
- [x] `main.rs` graceful shutdown(`tokio::signal`)
|
||||
- [x] `GET /healthz` — 返回 DB 连通性 + 上游 provider 状态
|
||||
- [x] `GET /metrics` — Prometheus 格式 metrics 端点
|
||||
- [x] **迁移 0001**:核心表(sites, accounts, users, tokens, organizations)
|
||||
- [x] 多阶段 Dockerfile + docker-compose.yml
|
||||
- [x] CI 配置文件(GitHub Actions / Gitea Actions)
|
||||
- [x] `cargo build && cargo test && cargo clippy -- -D warnings` 全部通过
|
||||
|
||||
### Phase 2: 共享类型 & 工具 (2-3 天)
|
||||
|
||||
- SSE event 辅助类型
|
||||
- 分页参数 + 响应信封
|
||||
- 统一 API 响应格式 `ApiResponse<T>`
|
||||
- **兼容性测试框架**(对比 runner + snapshot loader)
|
||||
|
||||
### Phase 3: 认证 (3-4 天)
|
||||
|
||||
- JWT 生成/校验
|
||||
- API Key 哈希/校验(**统一使用 argon2id**,不再用 Blake3)
|
||||
- 密码哈希(argon2id)
|
||||
- 三个中间件:`ProxyTokenAuth`、`AuthTokenAuth`、`DownstreamApiKeyAuth`
|
||||
- 中间件测试覆盖率 ≥90%
|
||||
|
||||
### Phase 4-7: 核心业务(可部分并行,~4-6 周)
|
||||
|
||||
详见第 7 节的并行策略。
|
||||
|
||||
真实依赖关系(修正原方案中过于乐观的并行假设):
|
||||
|
||||
```
|
||||
Phase 4 (Admin API) ──────── 依赖 Phase 1,2,3 ──── 独立
|
||||
Phase 5 (Proxy Core) ─────── 依赖 Phase 1,2,3 ──── 独立
|
||||
Phase 6 (Transformers) ───── 依赖 Phase 5 trait ─ 需要 Phase 5 的 trait 定义
|
||||
Phase 7 (Providers) ──────── 依赖 Phase 6 ──────── 需要 CanonicalFormat
|
||||
```
|
||||
|
||||
**修正**:Phase 5 先产出 trait 定义 → Phase 6 和 Phase 7 同时基于 trait 开发。
|
||||
|
||||
### Phase 8: 流式 (SSE + WebSocket) (3-5 天)
|
||||
|
||||
- SSE 流式中继(带背压处理)
|
||||
- WebSocket handler
|
||||
- 流式 transformer pipeline
|
||||
- **关键**:背压策略 —— 当上游快于下游时,使用 `tokio::sync::mpsc::channel` 有界通道,channel 满时暂停上游读取
|
||||
|
||||
### Phase 9: 后台服务 (3-5 天)
|
||||
|
||||
- 每个 cron 任务独立的 `tokio::task` + `tokio::time::interval`
|
||||
- 优雅关闭:所有后台任务监听 `CancellationToken`
|
||||
- 熔断器状态持久化(可选:重启后从 DB 恢复 provider 熔断状态)
|
||||
|
||||
### Phase 10-12: 边缘特性 (~2-3 周)
|
||||
|
||||
- OAuth flow + 通知 + ComfyUI 反向代理 + Video Agent
|
||||
- 这些不阻塞主流程,可以后置
|
||||
|
||||
### Phase 13: 集成测试 & 性能基准 (1-2 周)
|
||||
|
||||
除了原计划内容,新增:
|
||||
- 所有兼容性快照测试通过
|
||||
- `cargo bench` 性能基准(criterion)
|
||||
- 压测对比报告(Node.js vs Rust,同机器同配置)
|
||||
|
||||
---
|
||||
|
||||
## 7. 并行工作流(修正后)
|
||||
|
||||
### 7.1 修正的依赖图
|
||||
|
||||
```
|
||||
Phase 1 (Scaffold)
|
||||
│
|
||||
├──▶ Phase 2 (Shared Types)
|
||||
│ │
|
||||
│ ▼
|
||||
│ Phase 3 (Auth)
|
||||
│ │
|
||||
│ ┌────┴────────────────────┐
|
||||
│ │ │
|
||||
│ ▼ ▼
|
||||
│ Phase 5 (Proxy) Phase 4 (Admin)
|
||||
│ │ trait 定义 │ ← 真正独立
|
||||
│ │ │
|
||||
│ ├──▶ Phase 6 (Transformer) ← 基于 trait
|
||||
│ │ │
|
||||
│ │ ▼
|
||||
│ │ Phase 7 (Providers) ← 依赖 CanonicalFormat
|
||||
│ │ │
|
||||
│ └────────┤
|
||||
│ ▼
|
||||
│ Phase 8 (SSE/WS) ← 依赖 5+6+7
|
||||
│
|
||||
├──▶ Phase 9 (Background) ← 依赖 Phase 4 的 services
|
||||
│
|
||||
├──▶ Phase 10 (OAuth) ← 依赖 Phase 4
|
||||
│
|
||||
├──▶ Phase 11 (Notifications) ← 真正独立
|
||||
│
|
||||
├──▶ Phase 12 (Edge Features) ← 真正独立
|
||||
│
|
||||
└──▶ Phase 13 (Final QA) ← 需要所有前置
|
||||
```
|
||||
|
||||
### 7.2 修正后的工作流分配
|
||||
|
||||
| 工作流 | 负责 | 真实并行度 | 备注 |
|
||||
|--------|------|-----------|------|
|
||||
| A: Core | Phase 1,2,3,5 | 阻塞 C,D | 必须先产出 Proxy trait |
|
||||
| B: Admin | Phase 4 | **与 A 并行** | 真正的独立流 |
|
||||
| C: Transformer | Phase 6 | **等待 A 的 trait** | 不能和 A 同时启动 |
|
||||
| D: Provider | Phase 7 | **等待 C** | 不能和 C 同时启动 |
|
||||
| E: Platform | Phase 9,10,11 | **等待 B 部分完成** | 假并行 |
|
||||
| F: Edge | Phase 12 | **与所有并行** | 真正的独立流 |
|
||||
| G: QA | Phase 13 + 持续测试 | **尾随** | 每 Phase 合入时跑测试 |
|
||||
|
||||
### 7.3 基于实际约束的执行时间线
|
||||
|
||||
```
|
||||
Week 1-2: Phase 0 (快照) + Phase 1 (脚手架)
|
||||
Week 3: Phase 2 (共享类型) + Phase 3 (Auth)
|
||||
Week 4-5: Phase 4 (Admin) || Phase 5 (Proxy trait + 基础实现)
|
||||
Week 6-7: Phase 6 (Transformers) || Phase 4 收尾
|
||||
Week 7-8: Phase 7 (Providers)
|
||||
Week 9: Phase 8 (SSE/WS)
|
||||
Week 10: Phase 9 (Background) || Phase 10 (OAuth)
|
||||
Week 11: Phase 11 (Notifications) || Phase 12 (Edge)
|
||||
Week 12-13: Phase 13 (QA, compat tests, bench)
|
||||
Week 14: Buffer / 灰度发布
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Provider 实现优先级(不变)
|
||||
|
||||
| 优先级 | Provider |
|
||||
|--------|----------|
|
||||
| P0 | OpenAI (Chat/Models/Images/Embeddings/Audio/Files), Anthropic (Messages) |
|
||||
| P1 | Gemini, NewAPI/OneAPI, Codex/Claude |
|
||||
| P2 | Sub2API, Veloera/DoneHub/OneHub, Antigravity |
|
||||
|
||||
---
|
||||
|
||||
## 9. 关键代码契约(供并行开发使用)
|
||||
|
||||
### 9.1 Provider trait(Phase 5 产出,Phase 7 实现)
|
||||
|
||||
```rust
|
||||
/// src/providers/traits.rs
|
||||
/// Phase 5 必须产出此 trait 并冻结签名
|
||||
#[async_trait]
|
||||
pub trait Provider: Send + Sync + 'static {
|
||||
fn provider_id(&self) -> ProviderId;
|
||||
|
||||
async fn chat_completion(
|
||||
&self,
|
||||
request: CanonicalRequest,
|
||||
channel: &Channel,
|
||||
) -> Result<CanonicalResponse, ProviderError>;
|
||||
|
||||
async fn chat_completion_stream(
|
||||
&self,
|
||||
request: CanonicalRequest,
|
||||
channel: &Channel,
|
||||
) -> Result<StreamRelay, ProviderError>;
|
||||
|
||||
fn supported_models(&self) -> &'static [&'static str];
|
||||
|
||||
/// 模型能力声明
|
||||
fn capabilities(&self) -> ModelCapabilities {
|
||||
ModelCapabilities::default() // 各 provider 按需覆盖
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Error 扩展模式
|
||||
|
||||
```rust
|
||||
/// src/error.rs
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AppError {
|
||||
#[error("Auth: {0}")]
|
||||
Auth(#[from] AuthError),
|
||||
|
||||
#[error("Proxy: {0}")]
|
||||
Proxy(#[from] ProxyError),
|
||||
|
||||
#[error("Upstream provider: {0}")]
|
||||
Provider(#[from] ProviderError),
|
||||
|
||||
#[error("Transform: {0}")]
|
||||
Transform(#[from] TransformError),
|
||||
|
||||
#[error("Circuit breaker open for provider {0}")]
|
||||
CircuitBreakerOpen(String),
|
||||
|
||||
#[error("Rate limit exceeded for {0}")]
|
||||
RateLimited(String),
|
||||
|
||||
#[error("Database: {0}")]
|
||||
Database(#[from] sqlx::Error),
|
||||
|
||||
#[error("Internal: {0}")]
|
||||
Internal(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl axum::response::IntoResponse for AppError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
// 统一错误 shape,与 Node.js 实现一致:
|
||||
// { "error": { "message": "...", "type": "...", "code": "..." } }
|
||||
let (status, code, message) = match &self {
|
||||
AppError::Auth(_) => (StatusCode::UNAUTHORIZED, "auth_error", self.to_string()),
|
||||
AppError::CircuitBreakerOpen(_) => (StatusCode::SERVICE_UNAVAILABLE, "circuit_breaker_open", self.to_string()),
|
||||
AppError::RateLimited(_) => (StatusCode::TOO_MANY_REQUESTS, "rate_limited", self.to_string()),
|
||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error", self.to_string()),
|
||||
};
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 可观测性(⭐ 新增)
|
||||
|
||||
### 10.1 Prometheus Metrics
|
||||
|
||||
```rust
|
||||
// src/metrics.rs
|
||||
use prometheus::{HistogramVec, CounterVec, IntGaugeVec};
|
||||
|
||||
pub struct Metrics {
|
||||
/// HTTP 请求延迟直方图 (method, path, status)
|
||||
pub http_request_duration: HistogramVec,
|
||||
|
||||
/// 上游 provider 调用延迟 (provider_id, model, operation)
|
||||
pub upstream_duration: HistogramVec,
|
||||
|
||||
/// 上游 provider 错误计数 (provider_id, error_type)
|
||||
pub upstream_errors: CounterVec,
|
||||
|
||||
/// 当前活跃 SSE 连接数
|
||||
pub active_sse_connections: IntGauge,
|
||||
|
||||
/// 当前活跃 WebSocket 连接数
|
||||
pub active_ws_connections: IntGauge,
|
||||
|
||||
/// 熔断器状态 (provider_id) — 0=Closed, 1=HalfOpen, 2=Open
|
||||
pub circuit_breaker_state: IntGaugeVec,
|
||||
|
||||
/// 速率限制触发计数 (route, token_id)
|
||||
pub rate_limit_hits: CounterVec,
|
||||
|
||||
/// 连接池使用率
|
||||
pub db_pool_connections: IntGauge,
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 健康检查
|
||||
|
||||
```rust
|
||||
// GET /healthz 返回
|
||||
{
|
||||
"status": "ok", // "ok" | "degraded" | "down"
|
||||
"db": "connected", // "connected" | "error"
|
||||
"upstreams": {
|
||||
"openai": "healthy",
|
||||
"anthropic": "healthy",
|
||||
"gemini": "degraded" // 延迟过高但可用
|
||||
},
|
||||
"uptime_seconds": 123456
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 风险矩阵
|
||||
|
||||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||
|------|------|------|---------|
|
||||
| API 兼容性遗漏 | 高 | 严重 | Phase 0 录制快照 + Phase 13 全量对比 |
|
||||
| 迁移一次性 32 表失败 | 中 | 高 | 拆分为 4 次增量迁移 |
|
||||
| Provider 协议变化 | 高 | 中 | wiremock 隔离上游,ci 快速发现 |
|
||||
| 人员并行冲突 | 中 | 中 | 文件所有权矩阵 + 串行合入 |
|
||||
| 性能劣化(P99 > Node.js) | 低 | 严重 | Phase 13 压测对比,提前 gate |
|
||||
| SSE 背压内存泄漏 | 中 | 高 | 有界 channel + 连接数上限 + metrics 告警 |
|
||||
| SQLx 编译慢(32 表) | 中 | 低 | 增量迁移 + CI 缓存 target/ |
|
||||
|
||||
---
|
||||
|
||||
## 12. 灰度发布策略(⭐ 新增)
|
||||
|
||||
```
|
||||
Phase 13 完成后:
|
||||
|
||||
Step 1: 10% 流量 → Rust,90% → Node.js(持续 3 天)
|
||||
监控: P50/P99 延迟、错误率、内存使用
|
||||
|
||||
Step 2: 50% 流量 → Rust,50% → Node.js(持续 3 天)
|
||||
对比: 两个版本的同指标差异
|
||||
|
||||
Step 3: 100% 流量 → Rust,Node.js 作为热备(持续 1 周)
|
||||
确认稳定后下线 Node.js 实例
|
||||
|
||||
Step 4: Node.js 实例保留 1 个月(冷备),之后销毁
|
||||
|
||||
回滚条件:
|
||||
- 错误率 > Node.js 版本的 2x
|
||||
- P99 延迟 > Node.js 版本的 120%
|
||||
- 内存持续增长(疑似泄漏)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录: 原始方案主要变更总结
|
||||
|
||||
| 变更项 | 原始方案 | 优化方案 |
|
||||
|--------|---------|---------|
|
||||
| 迁移策略 | 1 次 32 张表 | 4 次增量迁移 + 回滚脚本 |
|
||||
| API 兼容验证 | "exact compatibility" 无保障机制 | Phase 0 录制 + CI 对比测试 |
|
||||
| 熔断器 | 无 | per-provider 独立熔断 |
|
||||
| 速率限制 | 无 | tower::limit + governor |
|
||||
| 可观测性 | tracing 日志 | + Prometheus metrics + /healthz 深度检查 |
|
||||
| 并行策略 | 7 流同时启动 | 按真实依赖分阶段启动 |
|
||||
| Provider trait | 未定义 | 锁定 trait 签名作为契约 |
|
||||
| 灰度发布 | 无 | 4 步渐进切流 |
|
||||
| API Key 哈希 | Blake3 + Argon2 两套 | 统一为 argon2id |
|
||||
| 时间估算 | 未给出 | ~14 周到生产 |
|
||||
+6
-6
@@ -182,11 +182,11 @@ export function Login({ onLogin, t }: { onLogin: (token: string) => void; t: (te
|
||||
<div className="login-brand-header">
|
||||
<div className="brand-mark-frame brand-mark-frame-hero">
|
||||
<div className="brand-mark-canvas">
|
||||
<img src="/logo.png" alt="BoosAPI" className="login-brand-logo" />
|
||||
<img src="/logo.png" alt="天宥数据云" className="login-brand-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-brand-summary">
|
||||
<div className="login-brand-name">BoosAPI</div>
|
||||
<div className="login-brand-name">天宥数据云</div>
|
||||
<div className="login-brand-kicker">{t('中转站的中转站')}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -631,8 +631,8 @@ function AppShell() {
|
||||
</button>
|
||||
)}
|
||||
<div className="topbar-logo">
|
||||
<img src="/logo.png" alt="BoosAPI" style={{ width: 28, height: 28, borderRadius: 6 }} />
|
||||
<span className="topbar-logo-text">BoosAPI</span>
|
||||
<img src="/logo.png" alt="天宥数据云" style={{ width: 28, height: 28, borderRadius: 6 }} />
|
||||
<span className="topbar-logo-text">天宥数据云</span>
|
||||
</div>
|
||||
<nav className="topbar-nav">
|
||||
{topNavItems.map((item) => (
|
||||
@@ -762,8 +762,8 @@ function AppShell() {
|
||||
closeLabel={t('关闭导航')}
|
||||
>
|
||||
<div className="mobile-drawer-header">
|
||||
<img src="/logo.png" alt="BoosAPI" />
|
||||
<span>BoosAPI</span>
|
||||
<img src="/logo.png" alt="天宥数据云" />
|
||||
<span>天宥数据云</span>
|
||||
</div>
|
||||
<nav className="mobile-nav">
|
||||
{sidebarGroups.map((group) => (
|
||||
|
||||
@@ -191,7 +191,7 @@ export const zhToEnSupplemental: Record<string, string> = {
|
||||
'共 ${models.length} 个模型': 'Total ${models.length} models',
|
||||
'关闭': 'closure',
|
||||
'关于': 'about',
|
||||
'关于 BoosAPI': 'About BoosAPI',
|
||||
'关于 天宥数据云': 'About 天宥数据云',
|
||||
'管理端 IP 白名单': 'Management IP whitelist',
|
||||
'管理员': 'administrator',
|
||||
'管理员登录 Token 已被修改,请使用新 Token 登录。': 'The administrator login token has been modified, please use the new token to log in.',
|
||||
@@ -259,7 +259,7 @@ export const zhToEnSupplemental: Record<string, string> = {
|
||||
'拉取分组失败': 'Failed to pull group',
|
||||
'拉取分组失败,已回退 default': 'Failed to pull the group and has fallen back to default',
|
||||
'冷却中': 'Cooling down',
|
||||
'例如 BoosAPI': 'For example BoosAPI',
|
||||
'例如 天宥数据云': 'For example 天宥数据云',
|
||||
'例如: admin@example.com': 'For example: admin@example.com',
|
||||
'例如: smtp.qq.com': 'For example: smtp.qq.com',
|
||||
'例如: target@example.com': 'For example: target@example.com',
|
||||
@@ -770,7 +770,7 @@ export const zhToEnSupplemental: Record<string, string> = {
|
||||
'LDOH Cookie 已更新': 'LDOH Cookie updated',
|
||||
'LDOH Cookie 已清空': 'LDOH Cookie Cleared',
|
||||
'LinuxDo 可用性监控': 'LinuxDo availability monitoring',
|
||||
'BoosAPI 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。': 'boosapi is completely self-hosted, all data (accounts, tokens, routes, logs) is stored in a local SQLite database and no data is sent to any third party. Proxy requests only travel directly between your server and the upstream site.',
|
||||
'天宥数据云 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。': 'boosapi is completely self-hosted, all data (accounts, tokens, routes, logs) is stored in a local SQLite database and no data is sent to any third party. Proxy requests only travel directly between your server and the upstream site.',
|
||||
'model 不能为空': 'model cannot be empty',
|
||||
'modelLimitsEnabled 参数无效': 'modelLimitsEnabled parameter is invalid',
|
||||
'models 必须是非空数组': 'models must be a non-empty array',
|
||||
|
||||
+3
-3
@@ -71,7 +71,7 @@ const zhToEn: Record<string, string> = {
|
||||
'模型操练场': 'Model Playground',
|
||||
'模型测试': 'Model Testing',
|
||||
'关于': 'About',
|
||||
'关于 BoosAPI': 'About BoosAPI',
|
||||
'关于 天宥数据云': 'About 天宥数据云',
|
||||
'站点文档': 'Site Docs',
|
||||
'任务状态已更新': 'Task status updated',
|
||||
'会话已过期,请重新登录': 'Session expired, please sign in again',
|
||||
@@ -208,7 +208,7 @@ const zhToEn: Record<string, string> = {
|
||||
'代理端点': 'Proxy Endpoints',
|
||||
'路由行为': 'Routing Behavior',
|
||||
'指标口径': 'Metric Notes',
|
||||
'BoosAPI 将多个上游兼容供应商聚合为统一的 OpenAI / Claude 下游兼容入口。': 'BoosAPI aggregates multiple upstream compatible providers into a unified OpenAI / Claude compatible downstream endpoint.',
|
||||
'天宥数据云 将多个上游兼容供应商聚合为统一的 OpenAI / Claude 下游兼容入口。': '天宥数据云 aggregates multiple upstream compatible providers into a unified OpenAI / Claude compatible downstream endpoint.',
|
||||
'核心目标:自动签到、自动模型发现、自动路由重建、统一代理可观测性。': 'Core goals: auto check-in, auto model discovery, auto route rebuild, and unified proxy observability.',
|
||||
'1. 路由根据模型可用性自动生成。': '1. Routes are auto-generated based on model availability.',
|
||||
'2. 当模型或账号发生变更时,路由通道会自动重建。': '2. Route channels are auto-rebuilt when models or accounts change.',
|
||||
@@ -309,7 +309,7 @@ const zhToEn: Record<string, string> = {
|
||||
'零配置嵌入式数据库': 'Zero-config embedded database',
|
||||
'项目链接': 'Project Links',
|
||||
'数据与隐私': 'Data & Privacy',
|
||||
'BoosAPI 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。': 'BoosAPI is fully self-hosted. All data (accounts, tokens, routes, logs) is stored in a local SQLite database and never sent to any third party. Proxy requests travel directly between your server and upstream sites.',
|
||||
'天宥数据云 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。': '天宥数据云 is fully self-hosted. All data (accounts, tokens, routes, logs) is stored in a local SQLite database and never sent to any third party. Proxy requests travel directly between your server and upstream sites.',
|
||||
|
||||
// User SaaS translations
|
||||
'用户登录': 'User Login',
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="BoosAPI - 中转站的中转站,聚合多个 AI API 中转站为统一网关" />
|
||||
<title>BoosAPI</title>
|
||||
<meta name="description" content="天宥数据云 - AI API 聚合网关,统一管理多个中转站" />
|
||||
<title>天宥数据云</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function About() {
|
||||
<div className="animate-fade-in" style={{ maxWidth: 860 }}>
|
||||
{/* Header */}
|
||||
<div className="page-header" style={{ marginBottom: 14 }}>
|
||||
<h2 className="page-title">{tr('关于 BoosAPI')}</h2>
|
||||
<h2 className="page-title">{tr('关于 天宥数据云')}</h2>
|
||||
</div>
|
||||
|
||||
{/* Hero card */}
|
||||
@@ -90,11 +90,11 @@ export default function About() {
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="BoosAPI"
|
||||
alt="天宥数据云"
|
||||
style={{ width: 48, height: 48, borderRadius: 12, flexShrink: 0 }}
|
||||
/>
|
||||
<div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700 }}>BoosAPI</div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700 }}>天宥数据云</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-tertiary)', marginTop: 2 }}>{currentVersion}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,7 +206,7 @@ export default function About() {
|
||||
<div className="card animate-slide-up stagger-5" style={{ padding: 22 }}>
|
||||
<h3 style={{ fontSize: 15, fontWeight: 600, marginBottom: 10 }}>{tr('数据与隐私')}</h3>
|
||||
<div style={{ fontSize: 13, color: 'var(--color-text-secondary)', lineHeight: 1.8 }}>
|
||||
{tr('BoosAPI 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。')}
|
||||
{tr('天宥数据云 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1821,7 +1821,7 @@ export default function Accounts() {
|
||||
color: "var(--color-text-muted)",
|
||||
}}
|
||||
>
|
||||
配置 refresh_token 后,BoosAPI 会在 JWT 临近过期或
|
||||
配置 refresh_token 后,天宥数据云 会在 JWT 临近过期或
|
||||
401 时自动续期并回写新 token。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function Monitors() {
|
||||
<div>
|
||||
<h2 className="page-title">{tr('监控内嵌')}</h2>
|
||||
<div style={{ marginTop: 6, fontSize: 13, color: 'var(--color-text-muted)' }}>
|
||||
在 BoosAPI 内查看外部站点监控页面。
|
||||
在 天宥数据云 内查看外部站点监控页面。
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
|
||||
@@ -2313,16 +2313,16 @@ export default function OAuthManagement() {
|
||||
|
||||
{renderGuideCard(
|
||||
'本地部署',
|
||||
'BoosAPI 和浏览器在同一台机器时,不需要 SSH 隧道。直接点击“连接”,在弹窗里完成授权即可。',
|
||||
'天宥数据云 和浏览器在同一台机器时,不需要 SSH 隧道。直接点击“连接”,在弹窗里完成授权即可。',
|
||||
<div className="oauth-guide-copy">
|
||||
如果浏览器能直接访问上面的 localhost 回调地址,授权完成后会自动回到 BoosAPI。
|
||||
如果浏览器能直接访问上面的 localhost 回调地址,授权完成后会自动回到 天宥数据云。
|
||||
</div>,
|
||||
)}
|
||||
|
||||
{activeSession.instructions.sshTunnelCommand
|
||||
? renderGuideCard(
|
||||
'云端部署',
|
||||
'BoosAPI 部署在 VPS、容器或远程主机时,浏览器访问到的是你自己电脑的 localhost。先在本地开 SSH 隧道,再继续登录。',
|
||||
'天宥数据云 部署在 VPS、容器或远程主机时,浏览器访问到的是你自己电脑的 localhost。先在本地开 SSH 隧道,再继续登录。',
|
||||
<div className="oauth-guide-block-list">
|
||||
<div className="oauth-guide-block-label">常规 SSH 隧道</div>
|
||||
{renderCodeBlock(activeSession.instructions.sshTunnelCommand)}
|
||||
|
||||
@@ -1781,7 +1781,7 @@ export default function Settings() {
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={settingsModernTitleStyle}>Codex 上游传输与会话并发</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
默认采用 HTTP 优先。只有这里开启后,BoosAPI 才会在 Codex 请求上尝试把上游升级为 WebSocket。下游 Codex 客户端也必须同时启用 `/v1/responses` websocket,单开这里不会生效。
|
||||
默认采用 HTTP 优先。只有这里开启后,天宥数据云 才会在 Codex 请求上尝试把上游升级为 WebSocket。下游 Codex 客户端也必须同时启用 `/v1/responses` websocket,单开这里不会生效。
|
||||
</div>
|
||||
</div>
|
||||
<div style={settingsModernPillRowStyle}>
|
||||
@@ -1795,7 +1795,7 @@ export default function Settings() {
|
||||
</div>
|
||||
<label style={settingsModernToggleStyle}>
|
||||
<div style={settingsModernToggleCopyStyle}>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text-secondary)' }}>允许 BoosAPI 到 Codex 上游使用 WebSocket</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text-secondary)' }}>允许 天宥数据云 到 Codex 上游使用 WebSocket</span>
|
||||
<span style={{ fontSize: 12, lineHeight: 1.7, color: 'var(--color-text-muted)' }}>
|
||||
仅在下游 Codex 客户端已同步开启 `/v1/responses` websocket 时启用;否则仍按 HTTP 优先执行。
|
||||
</span>
|
||||
@@ -1878,7 +1878,7 @@ export default function Settings() {
|
||||
<div style={settingsModernTitleBlockStyle}>
|
||||
<div style={{ ...settingsModernTitleStyle, color: 'var(--color-danger)' }}>批量测活</div>
|
||||
<div style={settingsModernDescriptionStyle}>
|
||||
默认关闭。开启后,BoosAPI 会在后台定时对活跃账号模型发送最小化探测请求,用来校正“/models 能看到但实际不可用”的假阳性。
|
||||
默认关闭。开启后,天宥数据云 会在后台定时对活跃账号模型发送最小化探测请求,用来校正“/models 能看到但实际不可用”的假阳性。
|
||||
</div>
|
||||
</div>
|
||||
<div style={settingsModernPillRowStyle}>
|
||||
@@ -1904,7 +1904,7 @@ export default function Settings() {
|
||||
</div>
|
||||
<label style={settingsModernToggleStyle}>
|
||||
<div style={settingsModernToggleCopyStyle}>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text-secondary)' }}>允许 BoosAPI 后台主动批量测活</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text-secondary)' }}>允许 天宥数据云 后台主动批量测活</span>
|
||||
<span style={{ fontSize: 12, lineHeight: 1.7, color: 'var(--color-text-muted)' }}>
|
||||
首次从关闭切换到开启时,需要手动输入确认语句;关闭时可直接保存。
|
||||
</span>
|
||||
@@ -2581,7 +2581,7 @@ export default function Settings() {
|
||||
<div className="card animate-slide-up stagger-7" style={{ padding: 20, border: '1px solid color-mix(in srgb, var(--color-danger) 30%, var(--color-border))' }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 10, color: 'var(--color-danger)' }}>危险操作</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.8, marginBottom: 12 }}>
|
||||
重新初始化系统会清空当前 BoosAPI 使用中的全部数据库内容;若当前运行在外部 MySQL/Postgres,也会先清空该外部库中的 BoosAPI 数据,然后切回默认 SQLite。
|
||||
重新初始化系统会清空当前 天宥数据云 使用中的全部数据库内容;若当前运行在外部 MySQL/Postgres,也会先清空该外部库中的 天宥数据云 数据,然后切回默认 SQLite。
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--color-text-muted)', lineHeight: 1.8, marginBottom: 14 }}>
|
||||
完成后管理员 Token 会重置为 <code style={{ fontFamily: 'var(--font-mono)' }}>{FACTORY_RESET_ADMIN_TOKEN}</code>,当前会话会立即退出并刷新页面。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @Author: 橘子
|
||||
* @Project_description: BoosAPI 站点管理页
|
||||
* @Project_description: 天宥数据云 站点管理页
|
||||
* @Description: 代码是我抄的,不会也是真的
|
||||
*/
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
@@ -1099,7 +1099,7 @@ export function TokensPanel({ embedded = false, onEmbeddedActionsChange }: Token
|
||||
<input
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
||||
placeholder="例如 BoosAPI"
|
||||
placeholder="例如 天宥数据云"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -182,8 +182,8 @@ export default function UserDashboard() {
|
||||
background: 'var(--color-surface, #1a1a2e)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<img src="/logo.png" alt="BoosAPI" style={{ width: 28, height: 28, borderRadius: 6 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 600 }}>BoosAPI</span>
|
||||
<img src="/logo.png" alt="天宥数据云" style={{ width: 28, height: 28, borderRadius: 6 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 600 }}>天宥数据云</span>
|
||||
<span style={{ fontSize: 12, color: 'var(--color-text-muted)' }}>用户中心</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
|
||||
@@ -41,11 +41,11 @@ export default function UserLogin() {
|
||||
<div className="login-brand-header" style={{ justifyContent: 'center' }}>
|
||||
<div className="brand-mark-frame brand-mark-frame-hero">
|
||||
<div className="brand-mark-canvas">
|
||||
<img src="/logo.png" alt="BoosAPI" className="login-brand-logo" />
|
||||
<img src="/logo.png" alt="天宥数据云" className="login-brand-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-brand-summary" style={{ textAlign: 'center' }}>
|
||||
<div className="login-brand-name">BoosAPI</div>
|
||||
<div className="login-brand-name">天宥数据云</div>
|
||||
<div className="login-brand-kicker">用户登录</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,11 +51,11 @@ export default function UserRegister() {
|
||||
<div className="login-brand-header" style={{ justifyContent: 'center' }}>
|
||||
<div className="brand-mark-frame brand-mark-frame-hero">
|
||||
<div className="brand-mark-canvas">
|
||||
<img src="/logo.png" alt="BoosAPI" className="login-brand-logo" />
|
||||
<img src="/logo.png" alt="天宥数据云" className="login-brand-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-brand-summary" style={{ textAlign: 'center' }}>
|
||||
<div className="login-brand-name">BoosAPI</div>
|
||||
<div className="login-brand-name">天宥数据云</div>
|
||||
<div className="login-brand-kicker">用户注册</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ export function normalizeVerifyFailureMessage(message: unknown): string {
|
||||
if (!text) return '验证失败';
|
||||
const lowered = text.toLowerCase();
|
||||
if (isNetworkFailureMessage(text)) {
|
||||
return '无法连接到 BoosAPI 服务端,请检查服务状态或网络连接';
|
||||
return '无法连接到 天宥数据云 服务端,请检查服务状态或网络连接';
|
||||
}
|
||||
if (lowered.includes('user id mismatch') || lowered.includes('does not match this token')) {
|
||||
return '填写的用户 ID 与当前 Token / Cookie 不匹配';
|
||||
@@ -43,7 +43,7 @@ export function buildVerifyFailureHint(result: VerifyResultLike): string | null
|
||||
return '这不是 Token 错误判断。请检查填写的用户 ID 是否与当前 Token / Cookie 属于同一账号。';
|
||||
}
|
||||
if (isNetworkFailureMessage(result.message)) {
|
||||
return '这不是 Token 错误判断。请检查 BoosAPI 服务是否在线,以及目标站点或代理是否可达。';
|
||||
return '这不是 Token 错误判断。请检查 天宥数据云 服务是否在线,以及目标站点或代理是否可达。';
|
||||
}
|
||||
if (isTimeoutFailureMessage(result.message)) {
|
||||
return '这不是 Token 错误判断。目标站点响应超时,请稍后重试或检查代理/网络。';
|
||||
@@ -63,7 +63,7 @@ export function buildAddAccountPrereqHint(result: VerifyResultLike): string {
|
||||
return '请先修正用户 ID 并重新验证,验证成功后才能添加账号。';
|
||||
}
|
||||
if (isNetworkFailureMessage(result.message)) {
|
||||
return '验证请求未成功完成,请先检查 BoosAPI 服务、站点网络或代理配置。';
|
||||
return '验证请求未成功完成,请先检查 天宥数据云 服务、站点网络或代理配置。';
|
||||
}
|
||||
if (isTimeoutFailureMessage(result.message)) {
|
||||
return '验证请求超时,请先检查站点或代理连通性后再添加账号。';
|
||||
|
||||
@@ -43,10 +43,10 @@ export default function FactoryResetModal({
|
||||
<div className="modal-header" style={{ color: 'var(--color-danger)' }}>确认重新初始化系统</div>
|
||||
<div className="modal-body" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ padding: 12, borderRadius: 'var(--radius-sm)', background: 'var(--color-danger-bg)', color: 'var(--color-danger)', fontSize: 12, lineHeight: 1.8 }}>
|
||||
这是不可逆操作。系统会清空当前 BoosAPI 使用中的全部数据库内容,并在成功后立即退出当前登录状态。
|
||||
这是不可逆操作。系统会清空当前 天宥数据云 使用中的全部数据库内容,并在成功后立即退出当前登录状态。
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--color-text-secondary)', lineHeight: 1.9 }}>
|
||||
<div>• 当前若使用外部 MySQL/Postgres,也会先清空该外部库中的 BoosAPI 数据。</div>
|
||||
<div>• 当前若使用外部 MySQL/Postgres,也会先清空该外部库中的 天宥数据云 数据。</div>
|
||||
<div>• 系统随后会强制切回默认 SQLite。</div>
|
||||
<div>• 管理员 Token 将重置为 <code style={{ fontFamily: 'var(--font-mono)' }}>{adminToken}</code>。</div>
|
||||
<div>• 完成后会立即退出登录并刷新页面,回到当前首装初始状态。</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function ModelAvailabilityProbeConfirmModal({
|
||||
<div id={titleId} className="modal-header" style={{ color: 'var(--color-danger)' }}>确认开启批量测活</div>
|
||||
<div id={descriptionId} className="modal-body" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ padding: 12, borderRadius: 'var(--radius-sm)', background: 'var(--color-danger-bg)', color: 'var(--color-danger)', fontSize: 12, lineHeight: 1.8 }}>
|
||||
开启后,BoosAPI 会在后台对活跃账号模型做最小化探测请求。这可能被部分中转站视为批量测活或异常行为,请务必先确认你的中转站明确允许此类探测。
|
||||
开启后,天宥数据云 会在后台对活跃账号模型做最小化探测请求。这可能被部分中转站视为批量测活或异常行为,请务必先确认你的中转站明确允许此类探测。
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--color-text-secondary)', lineHeight: 1.9 }}>
|
||||
请手动输入以下整句后再开启:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 87 KiB |
Reference in New Issue
Block a user