1
0
forked from boos/BoosAPI

4 Commits

Author SHA1 Message Date
Aibo117 6839d22851 feat: rebrand BoosAPI to 天宥数据云 with new logo
- Replace logo images (favicon, logo) with custom branding
- Update all frontend visible text from "BoosAPI" to "天宥数据云"
- Update HTML title, meta description, and i18n strings
- Cover login/register/user-dashboard/about/settings/OAuth pages
2026-05-18 11:09:37 +08:00
Aibo117 2ce11aa13a docs: restructure Rust backend rewrite plan with optimized architecture
- Add Node.js single-threaded bottleneck analysis as core motivation
- Add circuit breaker, rate limiting, and Prometheus observability
- Split 32-table migration into 4 incremental migrations with rollbacks
- Add API compatibility verification framework (record/replay)
- Fix parallel workstream dependency graph (7 streams → staged)
- Add 4-step canary release strategy
- Unify API key hashing to argon2id
- Add 14-week execution timeline
2026-05-16 12:27:55 +08:00
boos b78b622f0c docs: add subagent team collaboration strategy to Rust plan
Adds workstream map, parallel phase timing, shared contracts (AppState
extension, router composition, error type patterns), file ownership matrix,
subagent task template, git branch strategy, and coordinator workflow for
multi-agent parallel development.
2026-05-16 11:27:42 +08:00
boos 56b57f7ace docs: add Rust backend rewrite plan
Phase-by-phase migration plan for replacing the Node.js/Fastify backend
with Axum + SQLx + tokio in Rust. 13 phases covering scaffold through
integration testing.
2026-05-16 11:26:12 +08:00
21 changed files with 830 additions and 42 deletions
+788
View File
@@ -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
View File
@@ -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) => (
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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 />
+4 -4
View File
@@ -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>
+1 -1
View File
@@ -1821,7 +1821,7 @@ export default function Accounts() {
color: "var(--color-text-muted)",
}}
>
配置 refresh_token 后,BoosAPI 会在 JWT 临近过期或
配置 refresh_token 后,天宥数据云 会在 JWT 临近过期或
401 时自动续期并回写新 token。
</div>
</div>
+1 -1
View File
@@ -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 }}>
+3 -3
View File
@@ -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)}
+5 -5
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
/**
* @Author: 橘子
* @Project_description: BoosAPI 站点管理页
* @Project_description: 天宥数据云 站点管理页
* @Description: 代码是我抄的,不会也是真的
*/
import { useEffect, useMemo, useRef, useState } from 'react';
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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 }}>
+2 -2
View File
@@ -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>
+2 -2
View File
@@ -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 '验证请求超时,请先检查站点或代理连通性后再添加账号。';
+2 -2
View File
@@ -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