# 外部网站 API 接入

适用于 PHP / Java / Python / Node 等其他网站的后端、注册回调、订单任务和人员名单自动化。每个人使用独立的 `external_id`，生成独立的四字母短链接及二维码；同一个人的重试会复用原链接。

网页版阅读地址：`https://hm.ce.asodijqjoi.com/api-docs`。可在后台将此地址作为目标生成文档活码。

## 1. 部署升级及创建密钥

部署本版源码后，在短链项目根目录执行：

```bash
php think app:init
php think api:key create "网站A"
```

宝塔 PHP 8.3 可将上面的 `php` 换成 `/www/server/php/83/bin/php`。初始化会保留旧链接，并创建 API 接入、链接归属和请求重试记录表。

创建命令返回 `client_id`、接入名称和 `api_key`。**完整 API Key 只在创建或轮换时显示，数据库只保存其 SHA-256 摘要。** 每个接入网站创建一个 Key；接入网站之间的链接、业务编号、统计及请求编号独立。

```bash
php think api:key list
php think api:key rotate 接入ID
php think api:key disable 接入ID
php think api:key enable 接入ID
```

轮换会立即使旧 Key 失效，并保留原接入的链接及业务编号。轮换不改变启停状态，已停用的接入需要再执行 `enable`。停用 Key 不影响已经生成的公开短链接。

将 Key 存在**调用方网站的后端环境变量**中，再由后端把短链和二维码交给前端展示。浏览器页面及二维码中不应携带 API Key。

## 2. 认证与请求格式

接口前缀：`https://hm.ce.asodijqjoi.com/api/v1`。

```http
Authorization: Bearer 你的API_Key
Content-Type: application/json
```

API 使用独立 Key，无需管理后台登录、Cookie 或 CSRF token。API Key 不接受 URL 查询参数传递，也不能替代管理后台登录。接口错误沿用 `{"detail":"错误说明"}` 格式。

## 3. 为一个人生成链接和二维码

`POST /api/v1/links`

```bash
curl --request POST 'https://hm.ce.asodijqjoi.com/api/v1/links' \
  --header "Authorization: Bearer $SHORTLINK_API_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: signup-event-1001' \
  --data '{
    "external_id": "user:1001",
    "target_url": "https://example.com/customer?user=1001",
    "title": "用户1001的客服",
    "note": "网站注册时自动生成",
    "include_qr": true
  }'
```

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `target_url` | 是 | 完整 HTTP / HTTPS 目标，最多 4096 字符；同域仅允许 `/api-docs` 只读文档页，其他同域目标禁止 |
| `external_id` | 推荐 | 你的网站业务编号，如 `user:1001`；1–128 字符，区分大小写，无首尾空白或控制字符 |
| `title` | 否 | 自定义网页标题，最多 100 字符；默认空，使用系统品牌标题 |
| `note` | 否 | 管理备注，最多 500 字符；默认空 |
| `include_qr` | 否 | 布尔值，默认 `false`；`true` 直接附带 SVG 二维码图片数据 |

首次创建返回 HTTP 201，示例（二维码数据已省略）：

```json
{
  "link": {
    "code": "abcd",
    "short_url": "https://hm.ce.asodijqjoi.com/abcd",
    "target_url": "https://example.com/customer?user=1001",
    "title": "用户1001的客服",
    "note": "网站注册时自动生成",
    "external_id": "user:1001",
    "is_active": true,
    "visits": 0,
    "created_at": "2026-11-13T01:02:03.123456Z",
    "updated_at": "2026-11-13T01:02:03.123456Z",
    "qr_url": "https://hm.ce.asodijqjoi.com/api/v1/links/abcd/qr.svg",
    "mini_code_enabled": true,
    "mini_code_url": "https://hm.ce.asodijqjoi.com/api/v1/links/abcd/mini-code.png",
    "mini_code_status_url": "https://hm.ce.asodijqjoi.com/api/v1/links/abcd/mini-code",
    "qr_data_uri": "data:image/svg+xml;base64,..."
  },
  "reused": false
}
```

`qr_data_uri` 可直接作为网页 `<img>` 的 `src`，也可将逗号后的 Base64 解码保存为 `.svg`。`qr_url` 是受保护的接口，下载时仍需 Bearer Key，不能直接作为普通访客的无认证图片地址。

普通用户打开 `short_url` 或扫描二维码无需 Key。二维码编码的是固定短链接，修改目标或标题不会改变二维码。短链为公开访问地址，业务编号用于关联人员记录。

## 4. 为多人批量生成

`POST /api/v1/links/batch`，每次 1–50 条、请求正文最多 256KB：

```json
{
  "include_qr": true,
  "links": [
    {"external_id":"user:1001","target_url":"https://example.com/customer?user=1001","title":"客服入口"},
    {"external_id":"user:1002","target_url":"https://example.com/customer?user=1002","title":"客服入口"}
  ]
}
```

返回 `{"links":[...],"created":2,"reused":0}`，每条结果增加 `reused` 布尔值。结果顺序与输入顺序一致；存在新增返回 201，全部复用返回 200。同一批次不能重复业务编号。

批次中的参数错误、已有编号内容冲突、额度不足或数据库写入失败都会使整批失败，不保留本批新增记录。一次提交超过 50 人时，调用方拆批处理；跨多个批次的任务可使用相同业务编号安全重跑。

## 5. 自动化重试规则

- **人员去重：** 同一接入、同一 `external_id`、相同创建内容，再调用返回 HTTP 200、`reused:true` 及现有链接。不同人员即使目标相同，也会有不同短码；不同接入网站可使用相同业务编号。
- **内容冲突：** 同一业务编号传入不同的创建网址、标题或备注，返回 409。修改已有链接使用 PATCH，避免任务重试意外覆盖人工修改。
- **业务编号不可修改：** 如果同一个人在不同活动中应有不同链接，可用 `campaign:42:user:1001` 作为编号。
- **请求重试：** POST 单条及批量接口可附带 `Idempotency-Key`，1–128 位字母、数字、下划线、点、冒号或连字符。相同编号和相同请求内容返回首次响应及首次状态码，并增加 `Idempotency-Replayed: true`，不会重复创建。
- **请求冲突：** 同一接入的请求编号用于不同内容、不同接口或不同 `include_qr` 时返回 409；JSON 对象的字段顺序不会影响去重。
- **有效期：** 人员映射和已成功请求记录持久保存，不随进程重启或密钥轮换失效。请求重放返回首次响应快照；获取当前状态使用 GET。失败的请求不保存成功快照，修正后可以重试。
- **不提供业务编号时：** 每次普通 POST 都会新建一条；网络重试应使用 `Idempotency-Key`。

## 6. 查询、修改和二维码下载

所有接口只访问该 API Key 所属接入创建的链接；访问其他接入或后台单独创建的链接返回 404。管理员后台仍可统一管理所有链接。

| 方法及路径 | 用途 |
| --- | --- |
| `GET /api/v1/links` | 本接入的列表及统计 |
| `GET /api/v1/links?external_id=user%3A1001` | 按业务编号精确查找 |
| `GET /api/v1/links/{code}` | 查询当前目标、状态、计数及二维码地址 |
| `PATCH /api/v1/links/{code}` | 修改目标、标题、备注、启停状态 |
| `GET /api/v1/links/{code}/qr.svg` | 下载 SVG 二维码，需携带 Bearer Key |
| `GET /api/v1/links/{code}/mini-code` | 查询太阳码的当前生成状态，需 Bearer Key |
| `POST /api/v1/links/{code}/mini-code` | 补生成或重试失败的太阳码，成功缓存不重复生成 |
| `GET /api/v1/links/{code}/mini-code.png` | 下载本地缓存的 PNG 太阳码，需 Bearer Key |

列表支持 `q`（短码 / 网址 / 标题 / 备注）、`status=all|active|disabled`、`page`、`page_size`（1–100）。`counts` 为本接入的全部统计，`total` 为筛选后的记录数。

PATCH 只支持 `target_url`、`title`、`note`、`is_active`，示例：

```json
{"target_url":"https://example.com/new-customer","title":"售后客服","is_active":true}
```

```bash
curl 'https://hm.ce.asodijqjoi.com/api/v1/links/abcd/qr.svg' \
  --header "Authorization: Bearer $SHORTLINK_API_KEY" \
  --output abcd.svg
```

## 7. 调用示例

### PHP 网站后端

复用 `examples/php-client.php` 的函数，调用方 PHP 需启用 curl：

```php
require '/你的集成目录/php-client.php';

$link = createPersonShortLink(
    getenv('SHORTLINK_API_BASE_URL'),
    getenv('SHORTLINK_API_KEY'),
    'user:' . $userId,
    'https://example.com/customer?user=' . rawurlencode((string) $userId),
    '专属客服'
);

// 保存到你的网站用户记录，交给对应用户展示。
$shortUrl = $link['short_url'];
$qrImage = $link['qr_data_uri'];
```

也可命令行验证：

```bash
export SHORTLINK_API_BASE_URL='https://hm.ce.asodijqjoi.com'
export SHORTLINK_API_KEY='替换为实际API_Key'
php examples/php-client.php 'user:1001' 'https://example.com/customer?user=1001' '专属客服'
```

### Python 自动化人员名单

`examples/batch_generate.py` 仅使用 Python 标准库，供其他系统调用，不是 PHP 服务端的运行依赖。

```bash
export SHORTLINK_API_BASE_URL='https://hm.ce.asodijqjoi.com'
export SHORTLINK_API_KEY='替换为实际API_Key'
python examples/batch_generate.py examples/people.example.json \
  --output generated-links.json --qr-dir generated-qr
```

人员格式见 `examples/people.example.json`。脚本自动分成每批最多 50 人，遇到临时网络错误、429 或 5xx 会使用同一个请求编号重试；将结果写入 JSON，可选将每个人的二维码保存为 SVG。输出文件已存在时应指定新路径。

## 8. 额度、跨域与错误处理

默认按接入 ID 独立计算固定一分钟窗口：

```dotenv
API_RATE_LIMIT_PER_MINUTE=120
API_CREATE_LIMIT_PER_MINUTE=300
API_ALLOWED_ORIGINS=
```

所有已认证 API 请求计入请求频率，批量请求按一次请求计；生成额度按实际新增链接数量扣减，复用或成功请求重放不重复扣减。批量超额会整批回滚。429 返回 `Retry-After: 60`，调用方等待后使用原编号重试。

服务器之间调用无需设置跨域变量。确有受控浏览器调用需求时，可填写允许的完整 HTTPS origin，用英文逗号分隔，例如 `API_ALLOWED_ORIGINS=https://client.example.com`。仅 API v1 开放指定来源的 GET / POST / PATCH 及预检，管理后台不开放跨域 Cookie。

| 状态码 | 含义 / 处理 |
| --- | --- |
| 200 / 201 | 成功；依据 `reused`、`created` 和重放响应头判断结果 |
| 401 | Key 缺失、错误、已轮换或已停用 |
| 403 | 跨域来源或预检未获准 |
| 404 | 链接不存在或不属于当前接入 |
| 409 | 业务编号或请求编号内容冲突；修改链接使用 PATCH |
| 413 | 请求过大；单条 / PATCH 32KB，批量 256KB |
| 422 | JSON、网址、字段、长度或批次数量错误 |
| 429 | 请求频率或生成额度超限，等待后重试 |
| 503 | 数据库暂不可用或短码生成繁忙，使用原编号重试 |

Nginx 示例已添加 `HTTP_AUTHORIZATION` 传递并允许 256KB 请求体；使用自定义代理时需保留 Authorization 请求头。HTTPS、iframe 目标限制及固定公开域名仍遵循项目 README。

## 9. 自动生成小程序太阳码

3.4 起默认自动调用 [葵码平台](https://tym.kf001.site/#api) 生成 PNG 太阳码。码内内容是 `short_url`，修改目标和标题不会重新生成。该平台为太阳码图片接口，实际微信扫码打开效果需手机实测。

单条或批量创建成功后才调用平台；批量将尚未缓存的图片合并请求，每次至多 50 条。短链及人员映射仍按原规则整批提交或回滚。**平台失败不会撤销已成功创建的短链**，所以 HTTP 201 只表示短链创建成功，不保证太阳码已生成。

每条链接增加以下字段：

| 字段 | 说明 |
| --- | --- |
| `mini_code_enabled` | 当前是否开启自动生成 |
| `mini_code_url` | 受保护的 PNG 下载地址；关闭功能时为 `null` |
| `mini_code_status_url` | 受保护的状态查询 / POST 重试地址；关闭功能时为 `null` |

生成结果独立缓存，幂等创建的响应仍为首次快照。查询当前图片状态：

```bash
curl 'https://hm.ce.asodijqjoi.com/api/v1/links/abcd/mini-code' \
  --header "Authorization: Bearer $SHORTLINK_API_KEY"
```

```json
{"code":"abcd","mini_code":{"status":"ready","error":null,"retry_after":0}}
```

`status` 为 `ready`、`pending`、`failed`、`not_generated` 或 `disabled`。`pending` 可稍后查询；失败冷却期间自动重放不会重复请求平台，`retry_after` 给出剩余秒数。已有短链尚未生成或生成失败时，调用方可主动重试：

```bash
curl --request POST 'https://hm.ce.asodijqjoi.com/api/v1/links/abcd/mini-code' \
  --header "Authorization: Bearer $SHORTLINK_API_KEY"
```

重试返回 HTTP 200 和同样的状态结构，按 `mini_code.status` 判断是否成功。已有成功缓存直接复用。下载接口不会触发平台调用；未准备好返回 HTTP 503 和 `Retry-After`。

```bash
curl 'https://hm.ce.asodijqjoi.com/api/v1/links/abcd/mini-code.png' \
  --header "Authorization: Bearer $SHORTLINK_API_KEY" --output abcd-mini-code.png
```

以上接口仍只允许操作当前接入所属的链接。`mini_code_url` 不能直接交给无认证的访客作为图片地址；调用网站应由后端携带 Key 下载，再保存到自己的网站或转成 `data:image/png;base64,...` 交给前端显示。不要把任一平台的 Key 放进图片 URL 或网页。

短链服务器默认设置：

```dotenv
MINI_CODE_ENABLED=true
MINI_CODE_API_BASE=https://tym.kf001.site
MINI_CODE_API_KEY=
MINI_CODE_SIZE=600
MINI_CODE_TIMEOUT=12
MINI_CODE_CACHE_PATH=data/mini-codes
```

平台当前允许匿名调用；如其开启鉴权，`MINI_CODE_API_KEY` 填葵码平台提供的 Key。它与本系统的 `Authorization: Bearer` 接入密钥独立，不会返回给调用网站。宝塔需启用 PHP `curl` 扩展，允许 PHP 写入缓存目录。短链域名过长、平台额度不足或出站连接失败时，依据状态接口中的 `error` 修正后重试。
