# 短链系统设计方案对比与融合总结

## 一、核心目标

短链系统本质只做两件事：

1. **压缩**：把长网址映射成短字符串。
2. **跳转**：用户访问短链时，准确、快速地返回长链。

围绕这两件事，不同场景会衍生出容量、并发、成本、安全、业务规则等差异。

---

## 二、方案 A：Cloudflare Sproutlink（当前项目）

### 2.1 架构组成

| 组件 | 说明 |
|------|------|
| 边缘运行时 | Cloudflare Worker |
| 数据库 | Cloudflare D1（SQLite） |
| 静态前端 | Vite React SPA，通过 `ASSETS` 绑定由 Worker 直接服务 |
| 短码生成 | D1 单表原子自增 + 线性同余混洗 + Base62 编码 |
| 部署 | Serverless，静态资源不计入 Worker Bundle |

### 2.2 短码生成原理

1. **原子自增序列**：`link_seq` 表保存 `next_id`，每次创建时 `UPDATE ... SET next_id = next_id + 1 RETURNING next_id`。
2. **线性同余混洗**：把顺序的 `seq` 映射到 `[0, 62^4)` 空间，公式为 `index = (A × seq + B) mod 62^4`。
   - `A`：与 `62^4` 互质的乘数（`LINK_MULTIPLIER` secret，默认内置大质数）。
   - `B`：偏移量（`LINK_OFFSET` secret）。
   - 双射特性保证唯一，同时让短码看起来“不连续”。
3. **Base62 编码**：字符集 `0-9A-Za-z`，固定 **4 位**，容量 `62^4 ≈ 1477 万`。
4. **自定义短码**：管理员可指定 1–32 位 `a-zA-Z0-9_-`，但不可为恰好 4 位纯英数字，避免与自动码池冲突。

### 2.3 跳转与规则校验

- 使用 **HTTP 302 临时重定向**，确保每次点击都经过 Worker，便于统计。
- 访问流程：查 D1 → 校验过期/点击上限 → 校验 Geo/设备/浏览器/IP/密码 → 302 跳转并原子递增 `clicks`。

### 2.4 业务规则能力

| 规则 | 实现 |
|------|------|
| 有效期 | `expires_at` 字段 |
| 点击上限 | `max_clicks` 字段 |
| 密码保护 | PBKDF2 哈希，密码页表单验证 |
| Geo 限制 | `request.cf.country` |
| 设备/浏览器 | `User-Agent` + `Sec-CH-UA*` |
| IP 白/黑名单 | `CF-Connecting-IP`，支持 IPv4 CIDR |
| 软删除 | `deleted` 字段 |

### 2.5 优势特点

- **成本极低**：Serverless + D1 + 静态托管，免费/低价套餐即可运行。
- **部署极简**：单人可维护，无需维护服务器、Redis、MySQL 集群。
- **短码最短**：4 位 Base62，对二维码、短信字数敏感场景友好。
- **访问规则丰富**：密码、Geo、设备、浏览器、IP 规则开箱即用，适合私域/营销场景。
- **短码防扫号**：线性同余混洗让相邻创建的短码不是连续递增，提高被遍历的难度。

### 2.6 局限

- **容量有限**：4 位码仅 1477 万，公开大规模服务很快耗尽。
- **读并发依赖 D1**：无独立缓存层，热点短链会直接把压力打到数据库。
- **无缓存穿透防护**：缺少布隆过滤器或空值缓存，面对恶意遍历存在一定风险。
- **Cloudflare 生态绑定**：迁移或私有化部署成本较高。

---

## 三、方案 B：大规模分布式短链设计（经典大厂方案）

### 3.1 架构组成

| 组件 | 说明 |
|------|------|
| 发号器 | 分布式 ID 发号器（MySQL 号段模式 / Redis 自增） |
| 数据库 | MySQL 持久化存储 `code → target_url` |
| 缓存 | Redis 读缓存 |
| 防护 | 布隆过滤器 + 空值缓存 + 网关限流 |
| 跳转状态码 | HTTP 302 |

### 3.2 短码生成原理

1. **分布式 ID 发号器**：为每个长链接分配一个全局唯一、单调递增的数字 ID。
2. **Base62 编码**：把十进制 ID 转换成 62 进制，固定 **6 位**。
   - 字符集 `0-9a-zA-Z`。
   - 容量 `62^6 ≈ 568 亿`，足够公开服务长期运行。
3. **唯一索引兜底**：MySQL 对 `code` 加唯一索引，防止并发冲突。

### 3.3 读链路（高并发重点）

```
用户点击 /abc123
    ↓
网关限流 + WAF
    ↓
Redis 查 sl:abc123
    ├─ 命中 → 直接返回 302
    └─ 未命中 → 布隆过滤器判断
         ├─ 一定不存在 → 直接 404
         └─ 可能存在 → 查 MySQL
              ├─ 存在 → 写入 Redis → 302
              └─ 不存在 → 写短时空值到 Redis → 404
```

### 3.4 写链路

```
用户提交长链
    ↓
参数校验、黑名单域名过滤
    ↓
分布式发号器取号
    ↓
Base62 编码
    ↓
MySQL 插入（唯一索引防冲突）
    ↓
写入 Redis + 布隆过滤器
    ↓
返回短链
```

### 3.5 优势特点

- **容量巨大**：6 位码 568 亿，远超 4 位方案。
- **读性能强**：Redis 缓存扛住读多写少特性，适合热点营销短信/微博场景。
- **生产级健壮**：布隆过滤器防缓存穿透、空值缓存防击穿、网关限流防刷。
- **去 Cloudflare 依赖**：技术栈通用，易于私有化或多云部署。

### 3.6 局限

- **成本高**：需要维护 MySQL、Redis、发号器、网关等多套组件。
- **短码更长**：6 位比 4 位在短信/二维码场景略逊。
- **业务规则薄弱**：标准方案通常只讲“跳转”，缺少密码、Geo、设备等访问控制能力。

---

## 四、两套方案核心差异对比

| 维度 | Sproutlink（Cloudflare） | 大规模分布式方案 |
|------|--------------------------|------------------|
| **短码长度** | 4 位 | 6 位 |
| **容量** | 62⁴ ≈ 1477 万 | 62⁶ ≈ 568 亿 |
| **发号方式** | D1 原子自增 + 线性同余混洗 | 分布式 ID 发号器 |
| **存储** | Cloudflare D1 | MySQL + Redis |
| **读缓存** | 无 | Redis |
| **防缓存穿透** | 无 | 布隆过滤器 + 空值缓存 |
| **并发能力** | 中等，依赖 D1 | 高，依赖 Redis |
| **访问规则** | 丰富（Geo/设备/IP/密码） | 通常无或简单 |
| **部署成本** | 极低 | 较高 |
| **适用场景** | 私域、小团队、个人、轻量营销 | 公开互联网、大厂、海量用户 |

---

## 五、融合后的推荐设计

如果让我从零设计一个**可大可小、可进化的短链系统**，我会把两套方案取长补短：

### 5.1 用大规模方案做底座

- **短码长度**：默认 6 位 Base62，容量 568 亿。
- **发号器**：采用 **MySQL 号段模式**（Segment），每个实例本地批量领号，减少 DB 压力。
- **存储**：MySQL 持久化 + Redis 读缓存。
- **防护**：布隆过滤器 + 空值缓存 + 网关限流。
- **状态码**：统一使用 **302**，保证点击量可统计。

### 5.2 吸收 Sproutlink 的规则引擎

| 规则 | 实现位置 |
|------|----------|
| 有效期 | MySQL `expires_at` + 定时清理任务 |
| 点击上限 | MySQL 原子递增 `clicks` / `max_clicks` |
| 密码保护 | Worker / API 网关，密码页验证后 302 |
| Geo 限制 | CDN 边缘（Cloudflare、阿里云 CDN 等）或网关解析 `CF-IPCountry` |
| 设备/浏览器 | 解析 `User-Agent` + Client Hints |
| IP 白/黑名单 | 网关层，支持 IPv4 / IPv6 / CIDR |
| 自定义短码 | 管理员接口，与自动 6 位码池隔离 |

### 5.3 可选：保留短码混洗能力

在 6 位方案中，可以**可选地**对发号器拿到的顺序 ID 做一次可逆混洗（如线性同余），让短码看起来不连续，提升防扫号能力。注意混洗函数必须保证双射。

### 5.4 最终数据流

**写（生成短链）**：

```
提交长链
 → 校验 + 黑名单过滤
 → 号段发号器取唯一 ID
 → 可选混洗
 → Base62 编码
 → MySQL 写入
 → 写入 Redis + 布隆过滤器
 → 返回 6 位短链
```

**读（访问短链）**：

```
用户访问 /abc123
 → 网关限流/WAF
 → Redis 查缓存
    ├─ 命中 → 规则校验 → 302
    └─ 未命中 → 布隆过滤器
         ├─ 不存在 → 404
         └─ 可能存在 → MySQL 查询
              ├─ 存在 → 写 Redis → 规则校验 → 302
              └─ 不存在 → 写空值缓存 → 404
```

---

## 六、选型建议

| 场景 | 推荐方案 |
|------|----------|
| 个人博客、小团队、私域运营、预算敏感 | **Sproutlink（Cloudflare）** |
| 公开互联网、大厂、营销短信、微博 | **大规模分布式方案（6 位 + 发号器 + Redis）** |
| 希望初期低成本、后期可平滑扩展 | **融合方案**：Worker/D1 起步，用户量增长后迁移到 MySQL + Redis |

---

## 七、一句话总结

> **Sproutlink 是“小而美、功能全、低成本”的私域短链方案；大规模分布式方案是“高容量、高并发、抗攻击”的公开短链标准架构。最优实践是把后者的容量与并发能力，与前者的访问规则引擎结合起来，按需分层演进。**
