- 认证鉴权: PSK 签名步骤 + Bearer Token 说明 - 全部公开 API 接口参考 (系统/认证/站点/配置/清单) - 数据结构: 统一响应格式/Site/SiteConfig - 工作流程: 首次接入/系统重建/配置更新 - Python + Node.js 完整代码示例
436 lines
10 KiB
Markdown
436 lines
10 KiB
Markdown
# 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();
|
||
}
|
||
```
|