docs: 添加 PAR Server 集成开发文档 (API.md)
- 认证鉴权: PSK 签名步骤 + Bearer Token 说明 - 全部公开 API 接口参考 (系统/认证/站点/配置/清单) - 数据结构: 统一响应格式/Site/SiteConfig - 工作流程: 首次接入/系统重建/配置更新 - Python + Node.js 完整代码示例
This commit is contained in:
435
docs/API.md
Normal file
435
docs/API.md
Normal file
@@ -0,0 +1,435 @@
|
||||
# PAR Server 集成开发文档
|
||||
|
||||
> 版本: v1.0 | Base URL: `http://<host>:9012`
|
||||
|
||||
## 目录
|
||||
- [1. 认证鉴权](#1-认证鉴权)
|
||||
- [2. 接口参考](#2-接口参考)
|
||||
- [3. 数据结构](#3-数据结构)
|
||||
- [4. 工作流程](#4-工作流程)
|
||||
- [5. 代码示例](#5-代码示例)
|
||||
|
||||
---
|
||||
|
||||
## 1. 认证鉴权
|
||||
|
||||
### PSK 签名方式
|
||||
|
||||
```
|
||||
X-Timestamp: 1782800000
|
||||
X-Signature: Base64( HMAC-SHA256(PSK, "METHOD:path:timestamp") )
|
||||
X-Email: your@email.com (身份标识,可传多个)
|
||||
```
|
||||
|
||||
#### 签名步骤
|
||||
|
||||
```
|
||||
① 获取服务端时间
|
||||
GET /api/v1/time → {"timestamp": 1782800000}
|
||||
|
||||
② 拼接签名内容
|
||||
signContent = "POST:/api/v1/sites/pthome/submit:1782800000"
|
||||
|
||||
③ 计算签名
|
||||
signature = Base64( HMAC-SHA256(PSK, signContent) )
|
||||
|
||||
④ 发送请求
|
||||
curl -H "X-Timestamp: 1782800000" \
|
||||
-H "X-Signature: oNTk7GIA..." \
|
||||
-H "X-Email: mediabot@example.com" \
|
||||
...
|
||||
```
|
||||
|
||||
#### 注意事项
|
||||
- 时间窗口默认 60 秒(可配置)
|
||||
- 建议先调 `/api/v1/time` 获取服务端时间
|
||||
- PSK 由管理员在 PAR 管理后台生成和分发
|
||||
- `X-Email` 不参与签名,仅用于审计和身份关联
|
||||
|
||||
### Bearer Token 方式(管理后台用)
|
||||
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
Token 通过 `POST /api/v1/auth/login` 获取,24 小时有效。
|
||||
|
||||
---
|
||||
|
||||
## 2. 接口参考
|
||||
|
||||
### 2.1 系统
|
||||
|
||||
| 方法 | 路径 | 认证 | 说明 |
|
||||
|------|------|------|------|
|
||||
| GET | `/health` | 无 | 健康检查 |
|
||||
| GET | `/api/v1/time` | 无 | 服务端时间戳(秒) |
|
||||
|
||||
### 2.2 认证
|
||||
|
||||
| 方法 | 路径 | 认证 | 说明 |
|
||||
|------|------|------|------|
|
||||
| POST | `/api/v1/auth/register` | 无 | 注册账户(密码可选) |
|
||||
| POST | `/api/v1/auth/login` | 无 | 登录,返回 Bearer token |
|
||||
| POST | `/api/v1/auth/recover` | PSK | PSK 签名恢复账户(自动创建+提权) |
|
||||
|
||||
#### POST /api/v1/auth/register
|
||||
|
||||
```json
|
||||
// Request
|
||||
{ "email": "mediabot@example.com", "password": "optional" }
|
||||
|
||||
// Response 200
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"account": {
|
||||
"id": 5,
|
||||
"email": "mediabot@example.com",
|
||||
"trustLevel": "MEMBER",
|
||||
"isActive": true,
|
||||
"createdAt": "2026-06-29T14:00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 携带 `X-Medibot-Timestamp` + `X-Medibot-Signature` PSK 签名头可自动提权为 TRUSTED
|
||||
|
||||
#### POST /api/v1/auth/recover
|
||||
|
||||
```json
|
||||
// Request (带 PSK 签名头)
|
||||
{ "email": "mediabot@example.com" }
|
||||
|
||||
// Response 200
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"account": { "id": 5, "email": "...", "trustLevel": "TRUSTED", ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 全局清单
|
||||
|
||||
#### GET /api/v1/manifest
|
||||
|
||||
```json
|
||||
// Response 200
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"version": "1.0",
|
||||
"sites": [
|
||||
{
|
||||
"siteId": "pthome",
|
||||
"name": "PThome",
|
||||
"siteType": "private",
|
||||
"schemaVersion": "1.0.0",
|
||||
"status": "active",
|
||||
"latestVersion": "1.2.0",
|
||||
"configHash": "a1b2c3d4...",
|
||||
"updatedAt": "2026-06-29T14:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 站点
|
||||
|
||||
| 方法 | 路径 | 认证 | 说明 |
|
||||
|------|------|------|------|
|
||||
| GET | `/api/v1/sites` | 无 | 所有活跃站点 |
|
||||
| GET | `/api/v1/sites/{siteId}` | 无 | 单个站点详情 |
|
||||
| POST | `/api/v1/sites/{siteId}/submit` | PSK | 提交站点收录 |
|
||||
|
||||
#### POST /api/v1/sites/{siteId}/submit
|
||||
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"name": "PThome", // 必填
|
||||
"baseUrl": "https://pthome.net", // 必填
|
||||
"description": "PT 资源站",
|
||||
"iconUrl": "https://...",
|
||||
"siteType": "private" // private/public
|
||||
}
|
||||
|
||||
// Response 200
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"siteId": "pthome",
|
||||
"name": "PThome",
|
||||
"baseUrl": "https://pthome.net",
|
||||
"siteType": "private",
|
||||
"schemaVersion": "1.0.0",
|
||||
"status": "active",
|
||||
"createdAt": "2026-06-29T14:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 配置/适配脚本
|
||||
|
||||
| 方法 | 路径 | 认证 | 说明 |
|
||||
|------|------|------|------|
|
||||
| GET | `/api/v1/configs/{siteId}` | 无 | 已批准配置版本列表 |
|
||||
| GET | `/api/v1/configs/{siteId}/{version}` | 无 | 下载指定版本配置 |
|
||||
| POST | `/api/v1/configs/{siteId}/submit` | PSK | 提交新配置版本 |
|
||||
|
||||
#### GET /api/v1/configs/{siteId}
|
||||
|
||||
```json
|
||||
// Response 200
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": 10,
|
||||
"siteId": "pthome",
|
||||
"version": "1.2.0",
|
||||
"configHash": "a1b2c3...",
|
||||
"fileSize": 8192,
|
||||
"schemaVersion": "1.0.0",
|
||||
"status": "approved",
|
||||
"isLatest": true,
|
||||
"createdAt": "2026-06-29T14:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /api/v1/configs/{siteId}/{version}
|
||||
|
||||
支持 `If-None-Match` 条件请求(ETag = configHash),返回 304 表示内容未变化。
|
||||
|
||||
```json
|
||||
// Response 200 (Content-Type: application/json)
|
||||
"{\"rules\": [...]}" // 原始配置 JSON 内容
|
||||
```
|
||||
|
||||
#### POST /api/v1/configs/{siteId}/submit
|
||||
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"version": "1.3.0", // 必填
|
||||
"config": "{...json...}", // 必填:适配脚本 JSON
|
||||
"schemaVersion": "1.0.0"
|
||||
}
|
||||
|
||||
// Response 200
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"id": 11,
|
||||
"siteId": "pthome",
|
||||
"version": "1.3.0",
|
||||
"configHash": "e5f6a7...",
|
||||
"status": "pending",
|
||||
"createdAt": "2026-06-29T14:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据结构
|
||||
|
||||
### 统一响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": { ... },
|
||||
"timestamp": 1782800000
|
||||
}
|
||||
```
|
||||
|
||||
| code | 说明 |
|
||||
|------|------|
|
||||
| 200 | 成功 |
|
||||
| 400 | 参数错误 |
|
||||
| 401 | 认证失败 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器错误 |
|
||||
|
||||
### 站点 (Site)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 内部 ID |
|
||||
| siteId | String | 站点唯一标识(如 `pthome`) |
|
||||
| name | String | 显示名称 |
|
||||
| description | String | 描述 |
|
||||
| baseUrl | String | 站点 URL |
|
||||
| iconUrl | String | 图标 URL |
|
||||
| siteType | String | `private` / `public` |
|
||||
| schemaVersion | String | 当前 Schema 版本 |
|
||||
| status | String | `active` / `pending` / `deprecated` |
|
||||
| createdAt | DateTime | 创建时间 |
|
||||
|
||||
### 配置版本 (SiteConfig)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 内部 ID |
|
||||
| siteId | String | 关联站点 |
|
||||
| version | String | 版本号(如 `1.2.3`) |
|
||||
| configHash | String | 内容 SHA-256(用于 ETag) |
|
||||
| fileSize | Long | 文件大小(字节) |
|
||||
| schemaVersion | String | Schema 版本 |
|
||||
| status | String | `pending` / `approved` / `rejected` |
|
||||
| isLatest | Boolean | 是否最新版本 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 工作流程
|
||||
|
||||
### 4.1 首次接入
|
||||
|
||||
```
|
||||
① 管理员在 PAR 生成 PSK → 提供给 mediabot
|
||||
|
||||
② medibot 用 PSK 注册(自动提权)
|
||||
POST /api/v1/auth/register
|
||||
Headers: X-Medibot-Timestamp, X-Medibot-Signature
|
||||
Body: {"email": "mediabot@example.com"}
|
||||
|
||||
③ 提交站点
|
||||
POST /api/v1/sites/pthome/submit (带 PSK 签名)
|
||||
|
||||
④ 提交适配脚本
|
||||
POST /api/v1/configs/pthome/submit (带 PSK 签名)
|
||||
|
||||
⑤ 配置经管理员审核后变为 approved
|
||||
```
|
||||
|
||||
### 4.2 系统重建恢复
|
||||
|
||||
```
|
||||
① medibot 检测 401 → 调 POST /api/v1/auth/recover
|
||||
(PSK 签名 → 自动重建账户 + TRUSTED)
|
||||
|
||||
② 所有旧凭据无需修改,签名方式不变
|
||||
```
|
||||
|
||||
### 4.3 配置更新
|
||||
|
||||
```
|
||||
① GET /api/v1/configs/{siteId} 查看当前版本
|
||||
② 准备新配置 JSON → POST /api/v1/configs/{siteId}/submit
|
||||
③ 等待管理员审核通过
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 代码示例
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import hashlib, hmac, base64, time, requests
|
||||
|
||||
PSK = "your-psk-from-admin"
|
||||
BASE = "http://par-server:9012/api/v1"
|
||||
EMAIL = "mediabot@example.com"
|
||||
|
||||
def get_server_time():
|
||||
return requests.get(f"{BASE}/time").json()["timestamp"]
|
||||
|
||||
def hmac_headers(method, path):
|
||||
ts = get_server_time()
|
||||
sign_content = f"{method}:{path}:{ts}"
|
||||
sig = base64.b64encode(
|
||||
hmac.new(PSK.encode(), sign_content.encode(), hashlib.sha256).digest()
|
||||
).decode()
|
||||
return {
|
||||
"X-Timestamp": str(ts),
|
||||
"X-Signature": sig,
|
||||
"X-Email": EMAIL
|
||||
}
|
||||
|
||||
def submit_site(site_id, name, base_url, site_type="private"):
|
||||
path = f"/api/v1/sites/{site_id}/submit"
|
||||
return requests.post(f"{BASE}{path}",
|
||||
json={"name": name, "baseUrl": base_url, "siteType": site_type},
|
||||
headers=hmac_headers("POST", path)
|
||||
).json()
|
||||
|
||||
def submit_config(site_id, version, config_json, schema_version="1.0.0"):
|
||||
path = f"/api/v1/configs/{site_id}/submit"
|
||||
return requests.post(f"{BASE}{path}",
|
||||
json={"version": version, "config": config_json, "schemaVersion": schema_version},
|
||||
headers=hmac_headers("POST", path)
|
||||
).json()
|
||||
|
||||
def list_configs(site_id):
|
||||
return requests.get(f"{BASE}/configs/{site_id}").json()
|
||||
|
||||
def download_config(site_id, version):
|
||||
resp = requests.get(f"{BASE}/configs/{site_id}/{version}")
|
||||
if resp.status_code == 304:
|
||||
return None # 未变化
|
||||
return resp.json()
|
||||
|
||||
# ---- 使用 ----
|
||||
submit_site("pthome", "PThome", "https://pthome.net")
|
||||
submit_config("pthome", "1.0.0", '{"rules":[...]}')
|
||||
print(list_configs("pthome"))
|
||||
```
|
||||
|
||||
### JavaScript (Node.js)
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
const PSK = 'your-psk-from-admin';
|
||||
const BASE = 'http://par-server:9012/api/v1';
|
||||
const EMAIL = 'mediabot@example.com';
|
||||
|
||||
async function getServerTime() {
|
||||
const res = await fetch(`${BASE}/time`);
|
||||
return (await res.json()).timestamp;
|
||||
}
|
||||
|
||||
function sign(method, path, timestamp) {
|
||||
const content = `${method}:${path}:${timestamp}`;
|
||||
const sig = crypto.createHmac('sha256', PSK).update(content).digest('base64');
|
||||
return sig;
|
||||
}
|
||||
|
||||
async function hmacHeaders(method, path) {
|
||||
const ts = await getServerTime();
|
||||
return {
|
||||
'X-Timestamp': String(ts),
|
||||
'X-Signature': sign(method, path, ts),
|
||||
'X-Email': EMAIL
|
||||
};
|
||||
}
|
||||
|
||||
async function submitSite(siteId, name, baseUrl, siteType = 'private') {
|
||||
const path = `/api/v1/sites/${siteId}/submit`;
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...await hmacHeaders('POST', path) },
|
||||
body: JSON.stringify({ name, baseUrl, siteType })
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function submitConfig(siteId, version, config, schemaVersion = '1.0.0') {
|
||||
const path = `/api/v1/configs/${siteId}/submit`;
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...await hmacHeaders('POST', path) },
|
||||
body: JSON.stringify({ version, config, schemaVersion })
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user