# domains007

Base64 编码的 API 域名配置包，用于 Android App 域名容灾。

## 核心思路

> **用 npm + CDN 作为免费、高可用的域名配置分发通道，让 Android App 在域名挂了的时候能自我修复。**

```text
域名挂了时（你的操作）：
  python3 publish.py  →  自动拉接口 → 生成 domains.json → npm publish
      │
      ▼
  jsdelivr / unpkg CDN 自动更新（1-5分钟）
      │
      ▼
Android App：
  KeepAlive.init(...) + KeepAlive.failoverInterceptor()
       │
       ├─ 1. 用当前域名发起 okhttp 请求
       ├─ 2. 域名实效 → 拦截器同步切换
       ├─ 3. CDN 拉取 domains.json → base64 解码 → 获得新域名
       ├─ 4. 新域名测速通过 → 切换 → 自动重试
       └─ 5. SharedPreferences 缓存最新域名
```

## 为什么这个方案成立

npm + CDN（unpkg / jsdelivr）的基础设施可用性远超你自己某个 API 域名：

| 渠道 | 被墙/挂掉概率 | 速度 | 成本 |
| ------ | :---------: | ------ | ------ |
| 你自己的 API 域名 | 高（这就是问题） | — | 服务器成本 |
| `npmjs.com` | 极低 | 全球 CDN | **免费** |
| `unpkg.com` | 极低 | Cloudflare | **免费** |
| `jsdelivr.net` | 极低（国内可用） | 全球 + 国内节点 | **免费** |

App 同时配 3 个 CDN 地址作为 fallback，几乎不可能全挂。

## 项目结构

```text
domains/
├── package.json        # name=domains007, version
├── domains.json        # ★ 核心：base64 编码的域名配置（发布到 npm）
├── index.js            # CommonJS 入口，自动解码
├── index.mjs           # ESM 入口
├── index.d.ts          # TypeScript 类型定义
├── publish.py          # 一键发布脚本（本地工具，不入库）
├── encode.js           # 旧的手动编码脚本（已被 publish.py 取代）
├── .npmignore
└── README.md
```

## 关键设计

| 设计点 | 实现 |
| ------ | ------ |
| **域名不暴露** | `domains.json` 的 `payload` 是 base64 编码的 JSON，CDN 上浏览看不到明文 |
| **多渠道** | 按 `channels` 区分不同产品，一个 App 一个 channel |
| **多 CDN 容灾** | jsdelivr + jsdelivr@latest + unpkg，三个地址依次尝试 |
| **三级降级** | CDN → 本地 SharedPreferences 缓存 → 编译期内置兜底 |
| **版本比较** | `domains.json` 的 `version` 递增，App 比较后才更新本地缓存 |
| **自动发布** | `publish.py` 调接口拉域名，自动 +1 版本并发布 |

## domains.json 设计

```json
{
  "version": 4,
  "updatedAt": "2026-08-16T15:20:28.319Z",
  "payload": "eyJjaGFubmVscyI6eyJ4Y2hhdCI6ey..."
}
```

`payload` 是 base64 编码的 JSON，解码后是多渠道结构：

```json
{
  "channels": {
    "xchat": { "primary": "https://xchat-0816-hw-app-api.hbshxnn.cn", "fallbacks": [] },
    "bwcj":  { "primary": "https://bwcj-0816-hw-app-api.hbshxnn.cn",  "fallbacks": [] },
    "dypj":  { "primary": "https://dypj-0816-hw-app-api.hbshxnn.cn",  "fallbacks": [] },
    "xsm":   { "primary": "https://xsm-0816-hw-app-api.hbshxnn.cn",   "fallbacks": [] },
    "91mf":  { "primary": "https://91mf-0816-hw-app-api.hbshxnn.cn",  "fallbacks": [] }
  }
}
```

## Usage (Node.js)

```js
const domains = require('domains007');
console.log(domains.getChannel('xchat'));   // { primary, fallbacks }
console.log(domains.getChannelNames());     // ["xchat", "bwcj", ...]
console.log(domains.getVersion());          // 4
```

## 发布流程（一键脚本）

域名更新走接口，**无需手动编辑任何文件**。每次执行 `publish.py` 自动完成整个发布：

```bash
cd npm_plan/domains
python3 publish.py           # 交互：打印摘要 → 输入 y 发布
python3 publish.py --yes     # 跳过确认直接发布
```

脚本自动完成 6 件事：

1. 遍历 5 个产品调接口拉最新域名（**失败的产品用旧配置兜底**，其余继续）
2. **所有域名都无变动 → 打印提示并退出**，不发布
3. `domains.json` 的 `version` +1、`package.json` 的 `version` patch +1
4. 打印「每个产品新域名 + 新旧版本号」变更摘要
5. `npm publish`
6. `git commit` 到**本地**（不 push）

### 域名接口

- 地址：`http://16.163.130.1:8080/api/v1/domains?product={product}&key=DOMAINS_API`
- 支持的产品：`xchat` / `bwcj` / `dypj` / `xsm` / `91mf`
- 返回 `data[]`，**第一个 domain 作 primary，其余作 fallbacks**

### 首次使用（发布前登录一次）

当前 token 可能已失效，先登录：

```bash
npm login
```

之后每次只需 `python3 publish.py` 回车 `y` 即可。

## Android: Fetch from CDN

```text
https://cdn.jsdelivr.net/npm/domains007/domains.json
https://cdn.jsdelivr.net/npm/domains007@latest/domains.json
https://unpkg.com/domains007@latest/domains.json
```

## 进阶方向

- **签名校验**：对 payload 加 HMAC 签名，App 内置公钥验签，防止 CDN 被劫持篡改
- **灰度发布**：利用 npm dist-tag（`npm publish --tag beta`）实现按 tag 拉不同版本

## License

MIT
