文章

OneApi 部署接入与测试实战记录


一、背景与目标

老快手里有多个大模型的原始 API Key,也有多个agent,希望只维护一个入口、一个 Token,应用侧永远只用一个 Base URL。OneApi 就是干这个的——**统一网关 / 聚合代理**。

本次目标:
1. 找到服务器上的 one-api 部署
2. 加固超管账号
3. 验证 Token 能否真正调用
4. 搞清楚"某个 Key 失效会不会自动切换"
5. 把 OneApi 接进 OpenClaw 当自定义渠道

开干!

二、OneApi 部署现状(NSA服务器 122.***.***.***)

| 项目 | 值 |
|---|---|
| 部署形态 | systemd 服务(非 Docker),实际是 **one-hub** 分支 |
| 服务名 | `one-api.service`(active running,root 运行)|
| 二进制 | `/usr/local/bin/one-api` |
| 启动命令 | `/usr/local/bin/one-api --port 3000 --log-dir /data/disk/one-api/logs` |
| 数据源 | SQLite:`/data/disk/one-api/data/one-api.db` |
| 监听端口 | `*:3000`(内部)|
| 对外入口 | nginx 反代 `api.miaohaha.cn:443` → `127.0.0.1:3000` |
| 反向代理 | Cloudflare → nginx → one-api |

**密码算法是 bcrypt**(`$2a$10$...`),不是明文也不是 md5。改密码必须用 bcrypt 生成哈希写库,不能直接填明文。

---

三、超管账号安全加固

原账号 `root / 123456` 太弱,已改为:

- 用户名:`admin***`
- 密码:`******************`
- 角色:超管(role=100,不变)


验证结果:
- ✅ 新凭据登录成功(`success:true`,role=100)
- ✅ 旧 `root / 123456` 已被拒绝

---

四、连通性测试:踩了端口的坑

4.1 第一次测试失败

用 `https://***ip***:3000/v1/chat/completions` 测试 → **连接超时(HTTP 000)**。

4.2 根因定位

- `***ip***` 解析到 **Cloudflare 的 IP**
- **Cloudflare 免费版不代理 3000 端口**(只支持 80/443/8080/8443 等固定列表)
- 直连服务器 `***ip***:3000` 也超时 → **腾讯云安全组没放行 3000**

4.3 服务器内部验证 Token 有效

在服务器内部用 `127.0.0.1:3000` 测:
- `GET /v1/models` → HTTP 200,列出 3 个模型
- `chat/completions` → HTTP 200,模型正常回复

**结论:OneApi 和 Token 本身没问题,是外部网络层不可达。**

 4.4 正确入口

nginx 已经把 `***ip***:443` 反代到 one-api。用 `https://***ip***`(**不带 :3000**)实测:
- `GET /v1/models` → HTTP 200
- 真实对话 `chat/completions` → HTTP 200,正常返回

 4.5 Token 测试总结

| 测试项 | 结果 |
|---|---|
| 服务器内部 `127.0.0.1:3000/v1/models` | ✅ HTTP 200 |
| 外部 `https://***ip***`(443) | ✅ HTTP 200 |
| 外部 `https://***ip***:3000`(误用) | ❌ 连接超时 |

**可用模型(OneApi 全名):**
- `deepseek-ai/DeepSeek-V3.2`
- `deepseek-ai/DeepSeek-V4-Flash`
- `deepseek-ai/DeepSeek-V4-Pro`

---

## 五、高可用:某个 Key 失效会不会自动切换?

**会"自动重试切换",但前提是同一个模型配了多个渠道(多个 Key)。**

one-api/one-hub 默认开启 `AutoRetry`。某次请求选中的渠道返回 401/402 欠费时,会自动在**提供同一模型的其他可用渠道**里重试。

### 当前实际配置

| 项目 | 状态 |
|---|---|
| 渠道数量 | 只有 1 个(DeepSeek)|
| 冗余 | 单点,无备胎 |
| 自动重试 | 默认开启 |

**所以现在:那个 DeepSeek Key 一旦欠费,3 个模型全部不可用,不会自动切——因为没其他渠道可切。**

### 两个关键坑

1. **切换只在"同模型多渠道"之间发生。** 不同家的 Key 提供的是不同模型,那是"模型覆盖"不是"故障冗余"。请求 `gpt-4o` 只走 OpenAI 渠道,OpenAI 欠费了不会自动改走 DeepSeek(模型名不匹配)。
2. **自动禁用 ≠ 自动创造备用 Key。** 坏渠道连续失败 N 次会被标记"禁用",只是把坏的摘掉,不会凭空变出新 Key。

### 怎么做冗余

- **方案 A(同模型多渠道)**:再加一个 DeepSeek 渠道(另一个号/另一个 Key)
- **方案 B(单渠道多 Key)**:一个渠道的 `key` 字段填多个 Key(逗号/换行分隔),one-api 自动轮询

---

## 六、如何知道"单渠道多 Key"全失效了?

在单渠道多 Key 模式下,one-api 逐个尝试 Key,**只要还有 1 个有效,请求就能成功、渠道就正常**。所以:

> **渠道状态变"异常/禁用" ≈ 这个渠道的所有 Key 都失效了**(或上游不通)。

三种观测方法:
1. **后台渠道页**看状态 / 点"测试"逐 Key 验(全红=全失效)
2. **看日志** `/data/disk/one-api/logs/one-hub.log`,区分:
   - `401` / `invalid api key` → Key 失效
   - `402` / `insufficient` / `quota` → **欠费**
   - `timeout` / `connection refused` → 网络/上游不通
3. **配告警**(见下)

---

## 七、告警与自动禁用阈值

当前环境**未配置任何告警**。

### 自动禁用阈值
- 后台「设置」→ 渠道连续失败自动禁用阈值,建议设为 `3`(默认 5,调小更快发现)
- 也可写 systemd 环境变量 `CHANNEL_DISABLE_THRESHOLD=3`(不含敏感信息,可由助手代劳)

### 告警
- 后台「设置 → 告警」支持 Webhook / Bark / 邮件 / 飞书 / 钉钉 / Telegram
- **告警含敏感凭据,必须在后台 UI 自己填,不要发到聊天里**

---

## 八、OpenClaw 集成:从 1010 到打通

### 8.1 配置位置

OpenClaw 跑在韩立服务器,配置:`/root/.openclaw/openclaw.json`,gateway 监听 `36626`。
LLM 渠道在 `/models/providers` 下,每项是驼峰格式:

```json
"deepseek": {
  "baseUrl": "https://api.deepseek.com/v1",
  "apiKey": "<key>",
  "api": "openai-completions",
  "models": [{"id": "deepseek-v4-flash", "name": "DeepSeek V4 Flash"}]
}
```

### 8.2 踩坑:错误码 1010(模型连接失败)

最初按腾讯云文章(https://cloud.tencent.com/developer/article/2625144)的下划线格式填:
```json
{"provider":"miaohaha","base_url":"...","api_key":"...","model":{...}}
```
→ **OpenClaw 不认下划线格式**,读不到 `base_url`/`api_key` → 地址和 Key 为空 → 1010。

**关键差异:OpenClaw 的 `baseUrl` 必须带 `/v1`**(它不自动补路径,直接拼 `/chat/completions`)。这和 OpenAI SDK 场景相反(SDK 自动补 `/v1`,填了就变双 `/v1` 导致 404)。

### 8.3 正确配置(驼峰 + 带 /v1)

```json
{
  "baseUrl": "https://api.miaohaha.cn/v1",
  "api": "openai-completions",
  "apiKey": "sk-KZ38...(你的 one-api Token)",
  "models": [
    {"id": "deepseek-ai/DeepSeek-V4-Flash", "name": "DeepSeek V4 Flash (OneAPI)"},
    {"id": "deepseek-ai/DeepSeek-V3.2",    "name": "DeepSeek V3.2 (OneAPI)"},
    {"id": "deepseek-ai/DeepSeek-V4-Pro",  "name": "DeepSeek V4 Pro (OneAPI)"}
  ]
}
```

### 8.4 验证

- ✅ 用 OpenClaw 同款 Node 运行时从服务器直连 `api.miaohaha.cn/v1` → HTTP 200(排除 IPv6/Node 连不上的嫌疑)
- ✅ 配置写入后重启 gateway(`systemctl --user restart openclaw-gateway`),36626 正常监听
- ✅ 备份:`/root/.openclaw/openclaw.json.bak.*`

### 8.5 改名

渠道名 `miaohaha` → **`MiaoAI`**(只改渠道名,模型 id 全名不动)。

---

## 九、Base URL 填法速查表(最容易混)

| 客户端 | Base URL 填法 |
|---|---|
| OpenAI 官方 SDK / 多数代码框架 | `https://api.miaohaha.cn`(不带 /v1)|
| **OpenClaw 自定义渠道** | `https://api.miaohaha.cn/v1`(**必须带** /v1)|
| ❌ 不要 | `https://api.miaohaha.cn:3000/...`(:3000 不通)|
| ❌ 不要 | `https://api.miaohaha.cn/v1/chat/completions`(这是路径不是 Base URL)|

---

## 十、关键经验清单(避坑)

1. **外部访问走 443,别用 :3000** —— Cloudflare 不代理 3000,安全组也没开
2. **OpenAI SDK 的 Base URL 不带 /v1,OpenClaw 的必须带 /v1** —— 这是两个相反的场景
3. **腾讯云那篇文章的 JSON 是 Lighthouse 面板格式(下划线),OpenClaw 要驼峰** —— 直接粘会 1010
4. **模型 id 必须是 one-api 全名**(`deepseek-ai/DeepSeek-V4-Flash`),简写路由不到
5. **单渠道多 Key 才能故障切换,跨模型不切**
6. **渠道被禁用 ≈ 所有 Key 失效**,配告警 + 自动禁用阈值最省心
7. **各家原始 Key 是高度敏感凭据,建议在后台自己填,别发聊天里**
8. **改 OpenClaw 配置后必须重启 gateway 才生效**

---

## 附:最小可跑调用示例(Python)

```python
import requests

resp = requests.post(
    "https://api.miaohaha.cn/v1/chat/completions",
    headers={
        "Authorization": "Bearer sk-KZ38...(你的 one-api Token)",
        "Content-Type": "application/json",
    },
    json={
        "model": "deepseek-ai/DeepSeek-V4-Flash",
        "messages": [{"role": "user", "content": "你好"}],
        "max_tokens": 1024,
    },
    timeout=60,
)
print(resp.json()["choices"][0]["message"]["content"])
```

> ⚠️ 本文中 Token 已做脱敏(`sk-KZ38...`)。完整 Token 仅存于服务器 `openclaw.json` 与 one-api 数据库,属敏感凭据,请妥善保管。

许可协议:  CC BY 4.0