# 授权分发管理系统 — 开发文档

本文档面向**客户端集成开发者**与**二次部署维护人员**，说明对外 API、在线更新、安全策略、Webhook 及集成示例。

---

## 目录

1. [快速开始](#1-快速开始)
2. [后台配置清单](#2-后台配置清单)
3. [接口总览](#3-接口总览)
4. [API 签名机制](#4-api-签名机制)
5. [授权密钥验证](#5-授权密钥验证)
6. [客户端在线检测](#6-客户端在线检测)
7. [在线更新（整包 + 热更新）](#7-在线更新整包--热更新)
8. [域名授权验证](#8-域名授权验证)
9. [绑定模式](#9-绑定模式)
10. [安全策略](#10-安全策略)
11. [Webhook 回调](#11-webhook-回调)
12. [错误码汇总](#12-错误码汇总)
13. [Web 服务器配置](#13-web-服务器配置)
14. [数据库升级](#14-数据库升级)
15. [集成示例](#15-集成示例)

---

## 1. 快速开始

### 1.1 部署

1. 上传项目到服务器，确保 `runtime/` 可写。
2. 浏览器访问 `/install.php` 完成全新安装。
3. **已有旧库**：按 [14. 数据库升级](#14-数据库升级) 依次执行增量 SQL。
4. 后台 **系统 → 站点配置** 填写站点 URL、SMTP、易支付等。
5. 配置 crontab 每分钟执行 `cron/cron.php`（到期提醒、Webhook 重试等）。
6. **Nginx** 站点导入 `deploy/nginx-api.conf` 重写规则，或客户端 API 使用 `.php` 完整路径。
7. 生产环境删除 `install.php`、`test_mail.php`、`examples/`。

### 1.2 客户端集成最小流程

```
1. 用户在应用商店购买 → 获得 auth_key（授权密钥）
2. 用户在「我的授权」绑定 IP / 域名（或 IP+域名成对）
3. 客户端启动时：
   a. POST /api/client/check.php   → 授权心跳
   b. POST /api/client/update.php  → 检查整包更新（独立模式）
   c. POST /api/client/hotfix.php  → 拉取热更新（独立模式）
4. 按 next_check 间隔重复心跳
```

> **推荐**：开启「在线更新独立接口」，授权与更新分流，降低单次请求体积、便于 CDN 与缓存策略。

---

## 2. 后台配置清单

| 配置项 | 路径 | 说明 |
|--------|------|------|
| 站点 URL | 系统 → 站点配置 | 邮件链接、更新 API 地址拼接 |
| API 请求签名 | 站点配置 | 开启后 `check_auth` / `auth_info` 须签名 |
| IP + 域名双绑定 | 站点配置 | 成对绑定、同时校验 |
| 失败封禁次数/时长 | 站点配置 | 连续验证失败临时锁定 |
| 删除指令 | 站点配置 | 超次失败下发客户端删除指令 |
| 客户端 API 强制签名 | 站点配置 | `client/*` 接口强制 HMAC |
| 开启热更新下发 | 站点配置 | 总开关，关闭后不返回 update/hotfix |
| **在线更新独立接口** | 站点配置 | 是：`check` 仅授权；否：聚合返回 |
| 响应数据签名 | 站点配置 | 客户端可校验 `data` 完整性 |
| Nonce 防重放 | 站点配置 | 同一 nonce 600s 内不可复用 |
| app_id / app_secret | 应用管理 → 编辑应用 | 客户端签名密钥 |
| 发行版本 | 应用管理 → 在线更新 → 发行版本 | 整包更新 |
| 热更新配置 | 应用管理 → 在线更新 → 热更新配置 | JSON/URL 增量下发 |

---

## 3. 接口总览

| 接口 | 方法 | 用途 |
|------|------|------|
| `/api/check_auth.php` | POST | 轻量授权校验（密钥 + IP/域名绑定） |
| `/api/auth_info.php` | POST | 查询授权信息（到期、绑定数等） |
| `/api/auth/verify.php` | POST | 域名授权（Web 插件，Header + domainId） |
| `/api/client/check.php` | POST | 客户端心跳（授权 + 可选聚合更新） |
| `/api/client/update.php` | POST | **独立**整包更新检测 |
| `/api/client/hotfix.php` | POST | **独立**热更新增量拉取 |
| `/api/client/download.php` | GET | 发行包下载（须授权 + 签名） |

**短路径**（Apache `api/.htaccess` 或 Nginx 重写后可用）：

- `/api/auth/verify`
- `/api/client/check`、`/update`、`/hotfix`、`/download`

**未配置重写时**，必须使用带 `.php` 的完整 URL，否则返回 404。

---

## 4. API 签名机制

### 4.1 请求签名（HMAC-SHA256）

1. 收集参与签名的参数（**不含** `sign` 本身）。
2. 去掉值为空字符串或 `null` 的键。
3. 按参数名 **ksort** 升序。
4. 拼接为 `key1=value1&key2=value2`。
5. `sign = HMAC-SHA256(拼接串, app_secret)`，十六进制小写。

**时间戳**：`timestamp` 为 Unix 秒，与服务端偏差不超过 **±300 秒**。

### 4.2 响应签名（client/* 可选）

开启「响应数据签名」后，响应含：

```json
{
  "code": 1,
  "msg": "ok",
  "timestamp": 1718700000,
  "data": { ... },
  "sign": "hmac_sha256_of_json_data"
}
```

验签：`sign = HMAC-SHA256(json_encode(data), app_secret)`（`JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES`）。

### 4.3 PHP 签名示例

```php
function makeSign(array $params, string $secret): string
{
    unset($params['sign']);
    ksort($params);
    $parts = [];
    foreach ($params as $k => $v) {
        if ($v === '' || $v === null) continue;
        $parts[] = $k . '=' . $v;
    }
    return hash_hmac('sha256', implode('&', $parts), $secret);
}

$params = [
    'app_id'    => '1',
    'auth_key'  => 'YOUR_AUTH_KEY',
    'timestamp' => (string)time(),
];
$params['sign'] = makeSign($params, $appSecret);
```

---

## 5. 授权密钥验证

### 5.1 check_auth — 轻量校验

**URL**：`POST /api/check_auth.php`

**Content-Type**：`application/json` 或 `application/x-www-form-urlencoded`

| 参数 | 必填 | 说明 |
|------|------|------|
| auth_key | 是 | 授权密钥 |
| app_id | 签名时必填 | 应用 ID |
| client_ip | 建议 | 客户端 IP，用于绑定校验 |
| client_domain | 建议 | 客户端域名 |
| timestamp | 签名时必填 | Unix 时间戳 |
| sign | 签名时必填 | HMAC 签名 |

**成功响应**（`code = 1`）：

```json
{
  "code": 1,
  "msg": "验证通过",
  "data": {
    "expire_time": 0,
    "version": "标准版",
    "edition": "standard",
    "bind_limit": 3,
    "server_time": 1718700000,
    "next_check": 3600
  }
}
```

- `expire_time = 0` 表示永久授权。
- `next_check`：建议下次心跳间隔（秒），来自站点配置。

**失败时**可能附带 `data.command` 删除指令，见 [10. 安全策略](#10-安全策略)。

### 5.2 auth_info — 授权信息查询

**URL**：`POST /api/auth_info.php`

参数与签名规则同 `check_auth`（参与签名字段：`app_id`、`auth_key`、`timestamp`）。

**成功 data 字段**：

| 字段 | 说明 |
|------|------|
| app_name | 应用名称 |
| version_name | 授权版本名 |
| status | 1 正常 / 0 封禁 |
| expire_time | 到期 Unix 时间，0=永久 |
| remain_days | 剩余天数，-1=永久，0=已过期 |
| bind_count | 当前绑定数 |

---

## 6. 客户端在线检测

### 6.1 check — 授权心跳

**URL**：`POST /api/client/check.php`

| 参数 | 必填 | 说明 |
|------|------|------|
| app_id | 是 | 应用 ID |
| auth_key | 是 | 授权密钥 |
| client_version | 否 | 客户端 semver，如 `1.0.0` |
| client_ip | 否 | 绑定校验用 |
| client_domain | 否 | 绑定校验用 |
| hotfix_since | 否 | 聚合模式下热更新游标 |
| session_token | 否 | 续期会话，首次可空 |
| nonce | 建议 | 8–64 字符，防重放 |
| timestamp | 是 | Unix 时间戳 |
| sign | 是 | HMAC 签名 |

**参与签名的字段**（独立/聚合模式相同）：

`app_id`, `auth_key`, `client_version`, `client_ip`, `client_domain`, `hotfix_since`, `session_token`, `nonce`, `timestamp`

**成功 data.auth 示例**：

```json
{
  "valid": true,
  "auth_key": "ABCD12***",
  "app_name": "示例应用",
  "edition": "standard",
  "version_name": "标准版",
  "expire_time": 1735689600,
  "remain_days": 30,
  "bind_limit": 3,
  "is_trial": 0,
  "server_time": 1718700000,
  "next_check": 3600,
  "session_token": "64位hex..."
}
```

### 6.2 聚合模式 vs 独立模式

由后台 **在线更新独立接口** 控制：

| 模式 | check 返回 | 更新获取方式 |
|------|------------|--------------|
| **独立（默认）** | 仅 `auth`，另含 `update_api`、`hotfix_api` URL | 分别调 `update`、`hotfix` |
| **聚合** | `auth` + `update` + `hotfix` | 一次 check 拿全部 |

**独立模式成功示例**：

```json
{
  "code": 1,
  "data": {
    "auth": { ... },
    "update_api": "https://example.com/api/client/update.php",
    "hotfix_api": "https://example.com/api/client/hotfix.php"
  }
}
```

**聚合模式**额外包含：

```json
{
  "update": { "has_update": false },
  "hotfix": { "manifest_id": 0, "count": 0, "items": [] }
}
```

---

## 7. 在线更新（整包 + 热更新）

### 7.1 后台发布

1. **发行版本**（整包）：上传安装包或填写外链 `package_url`；设置 `version_code`（须大于客户端当前值）、`force_update`、`min_edition`。
2. **热更新配置**：`hotfix_key` 唯一；`payload_type` 支持 `json` / `url` / `text`；可限定 `min_version_code` / `max_version_code` / `min_edition`。
3. 站点配置开启 **开启热更新下发**。

### 7.2 update — 整包更新（独立 API）

**URL**：`POST /api/client/update.php`

| 参数 | 必填 | 说明 |
|------|------|------|
| app_id | 是 | |
| auth_key | 是 | |
| client_version | 否 | 当前客户端版本 |
| client_ip / client_domain | 否 | 绑定校验 |
| timestamp / nonce / sign | 是 | |

**签名参与字段**（不含 `hotfix_since`、`session_token`）：

`app_id`, `auth_key`, `client_version`, `client_ip`, `client_domain`, `nonce`, `timestamp`

**有更新时 data.update**：

```json
{
  "has_update": true,
  "latest_version": "1.2.0",
  "version_code": 1002000,
  "force": false,
  "title": "版本 1.2.0",
  "changelog": "修复若干问题",
  "package_url": "https://example.com/api/client/download.php?release_id=3",
  "package_hash": "sha256...",
  "package_size": 10485760,
  "channel": "stable"
}
```

### 7.3 hotfix — 热更新（独立 API）

**URL**：`POST /api/client/hotfix.php`

额外参数：

| 参数 | 说明 |
|------|------|
| hotfix_since | 上次成功应用的 `manifest_id`（首次传 0） |

**签名参与字段**：

`app_id`, `auth_key`, `client_version`, `client_ip`, `client_domain`, `hotfix_since`, `nonce`, `timestamp`

**data.hotfix 示例**：

```json
{
  "manifest_id": 15,
  "count": 2,
  "items": [
    {
      "id": 14,
      "key": "feature_x",
      "title": "开启功能 X",
      "type": "json",
      "data": { "enabled": true, "limit": 100 },
      "hash": "abc...",
      "priority": 10,
      "publish_time": 1718600000
    }
  ]
}
```

客户端应持久化 `manifest_id`，下次请求携带以实现增量拉取。

### 7.4 download — 安装包下载

**URL**：`GET /api/client/download.php`

| 参数 | 说明 |
|------|------|
| release_id | 发行版本 ID |
| app_id | 应用 ID |
| auth_key | 授权密钥 |
| timestamp / sign | 签名必填（开启签名时） |

响应为二进制流，Header `X-Package-Hash` 含 SHA256。

### 7.5 推荐客户端调用顺序（独立模式）

```
1. POST check.php     → 校验授权，保存 session_token
2. POST update.php    → 若有整包更新则下载安装
3. POST hotfix.php    → 应用热更新条目，更新 manifest_id 本地存储
4. sleep(next_check)  → 回到步骤 1
```

---

## 8. 域名授权验证

面向 **Web 插件 / 域名前缀授权** 场景，与密钥绑定体系独立。

**URL**：`POST /api/auth/verify.php`

**Headers**：

| Header | 值 |
|--------|-----|
| X-App-ID | 应用 `app_code`（非数字 app_id） |
| X-App-Edition | `standard` 或 `pro` |
| Content-Type | application/json |

**Body**：

```json
{
  "domainId": "D12345",
  "domain": "example.com",
  "fullName": "sub.example.com"
}
```

- `domainId`：用户在「我的授权」绑定域名后获得的 ID（格式由系统生成）。
- `domain`：主域名，须与绑定记录一致。
- `fullName`：完整访问域名，用于计算授权前缀。

**成功**（HTTP 200，`code: 200`）：

```json
{
  "code": 200,
  "msg": "授权验证通过",
  "data": { "prefix": "sub" }
}
```

---

## 9. 绑定模式

### 9.1 单绑模式（默认）

用户每次添加一条 **IP** 或 **域名** 绑定；客户端验证时上报 `client_ip` 或 `client_domain`，匹配任一已绑定项即可。

### 9.2 双绑模式

站点配置开启 **IP + 域名双绑定** 后：

- 用户须 **成对** 添加 IP + 域名（同一 `bind_group`）。
- 客户端须 **同时** 上报 `client_ip` 与 `client_domain`，且须匹配同一绑定对。

---

## 10. 安全策略

### 10.1 验证失败临时封禁

- 连续验证失败达到阈值（默认 10 次）→ 授权临时锁定（默认 1 小时）。
- 锁定期间返回 `code = -15`。
- 用户中心「我的授权」可查看锁定状态与剩余时间。

### 10.2 远程删除指令

超次失败（默认 ≥20 次，可配置）且开启删除指令时，失败响应 `data` 含：

```json
{
  "command": {
    "action": "delete",
    "target": "program",
    "reason": "verify_fail_exceeded",
    "fail_count": 20,
    "fail_limit": 20,
    "message": "授权验证失败次数过多，请按指令删除或卸载客户端",
    "issued_at": 1718700000
  }
}
```

`target`：`program`（主程序）/ `data`（本地数据）/ `full`（全部），由客户端 SDK 解释执行。

### 10.3 频率限制

| 接口 | 限制 |
|------|------|
| check_auth | 60 次 / IP / 分钟 |
| client/* | 120 次 / IP+app / 分钟 |

---

## 11. Webhook 回调

### 11.1 配置

后台 **系统 → Webhook**：URL、密钥、订阅事件、应用范围。

### 11.2 事件类型

| 事件 | 触发时机 |
|------|----------|
| order.paid | 订单支付成功 |
| order.refunded | 订单退款 |
| auth.trial | 试用领取 |
| auth.expired | 授权到期（cron） |
| auth.banned | 授权封禁 |
| `*` | 全部事件 |

### 11.3 请求格式

```
POST {your_webhook_url}
Content-Type: application/json
X-Webhook-Sign: HMAC-SHA256(body, secret)
User-Agent: AuthSystem-Webhook/1.0
```

```json
{
  "event": "order.paid",
  "timestamp": 1718700000,
  "data": { ... }
}
```

失败自动重试最多 3 次（指数退避），由 `cron/cron.php` 驱动。

---

## 12. 错误码汇总

### 12.1 授权类（check_auth / client/* / auth_info）

| code | 说明 |
|------|------|
| 1 | 成功 |
| -1 | 缺少授权密钥 |
| -2 | 授权密钥无效 |
| -3 | 授权已被封禁 |
| -4 | 授权已过期 |
| -5 | IP 未绑定 / 绑定不匹配 |
| -6 | 域名未绑定 / 绑定不匹配 |
| -7 | 双绑模式参数不全或不匹配 |
| -10 | 系统繁忙 |
| -11 | 请求过于频繁 |
| -12 | 签名无效 / 缺少 app_id / 授权与应用不匹配 |
| -13 | 应用已下架 |
| -14 | nonce 重复无效 / 请求重复 |
| -15 | 授权临时锁定（连续失败） |
| -16 | 在线更新未开启 |

### 12.2 域名验证 auth/verify

| code | 说明 |
|------|------|
| 200 | 验证通过 |
| 400 | 参数缺失 |
| 403 | 域名/版本/应用不匹配或授权无效 |
| 405 | 非 POST |
| 500 | 服务器异常 |

---

## 13. Web 服务器配置

### 13.1 Nginx

在 `server { }` 内 include `deploy/nginx-api.conf`：

```nginx
location /api/ {
    rewrite ^/api/auth/verify$ /api/auth/verify.php last;
    rewrite ^/api/client/check$ /api/client/check.php last;
    rewrite ^/api/client/update$ /api/client/update.php last;
    rewrite ^/api/client/hotfix$ /api/client/hotfix.php last;
    rewrite ^/api/client/download$ /api/client/download.php last;
}
```

### 13.2 Apache

`api/.htaccess` 已内置等效 RewriteRule，确保 `AllowOverride All`。

### 13.3 生产环境 API 基址示例

```
https://api.example.com/api/client/check.php
https://api.example.com/api/client/update.php
https://api.example.com/api/client/hotfix.php
https://api.example.com/api/check_auth.php
```

---

## 14. 数据库升级

已有旧库按顺序导入（全新安装用 `sql/auth_system.sql` 即可）：

| 脚本 | 内容 |
|------|------|
| upgrade_v2.sql | 续费、找回密码、到期提醒 |
| upgrade_v3.sql | 优惠券、版本升级、退款、API 签名 |
| upgrade_v4.sql | 试用、Webhook、多管理员 |
| upgrade_v5.sql | （按项目版本） |
| upgrade_v6.sql | 在线更新表、client_session、热更新 |
| upgrade_v7_dual_bind.sql | IP+域名双绑定 |
| upgrade_v8_auth_fail_lock.sql | 失败临时封禁 |
| upgrade_v9_auth_verify_log.sql | 验证日志 |
| upgrade_v10_client_delete_cmd.sql | 删除指令 |
| upgrade_v11_client_update_split_api.sql | 在线更新独立 API 开关 |

```bash
mysql -u用户名 -p 数据库名 < sql/upgrade_v11_client_update_split_api.sql
```

---

## 15. 集成示例

### 15.1 热更新 + 在线检测 Demo

路径：`examples/client_hotfix_demo.php`

```bash
php examples/client_hotfix_demo.php demo \
  https://api.example.com \
  1 \
  YOUR_AUTH_KEY \
  YOUR_APP_SECRET \
  1.0.0 \
  1.2.3.4 \
  app.example.com
```

类 `AuthClientHotfix` 方法：

| 方法 | 说明 |
|------|------|
| `check()` | 自动识别聚合/独立模式，完成授权 + 更新 + 热更新 |
| `fetchUpdate()` | 仅调独立整包 API |
| `fetchHotfix()` | 仅调独立热更新 API |

### 15.2 版本号比较规则

服务端将 semver 转为整数比较：

- `1.2.3` → `1002003`（主×1000000 + 次×1000 + 修订）
- 发行版 `version_code` 须 **大于** 客户端转换值才视为有更新

### 15.3 接口选型建议

| 场景 | 推荐接口 |
|------|----------|
| 桌面/移动客户端心跳 + 更新 | `client/check` + `update` + `hotfix` |
| 简单 SDK，仅验证密钥 | `check_auth` |
| 查询到期时间、绑定数 | `auth_info` |
| WordPress / Web 插件域名授权 | `auth/verify` |

---

## 附录：响应通用结构

**client/* 与 check_auth**（JSON）：

```json
{
  "code": 1,
  "msg": "ok",
  "timestamp": 1718700000,
  "data": { },
  "sign": "可选，响应签名"
}
```

- `code = 1` 为成功；负数或 HTTP 非 2xx 为失败。
- 所有客户端 API 建议校验响应 `sign`（若后台已开启）。

---

*文档版本：2026-06 · 与仓库 `upgrade_v11` 同步*
