830 lines
22 KiB
Markdown
830 lines
22 KiB
Markdown
# API 接口文档(v1)- ImageForge
|
||
|
||
面向两类使用者:
|
||
- **网站(Web)**:上传/批量/历史/账单等(可能包含匿名试用)。
|
||
- **对外 API(Developer API)**:API Key 调用、可计量可计费、适配 CI/CD 与服务端集成。
|
||
|
||
产品范围与计费口径见:
|
||
- `docs/prd.md`
|
||
- `docs/billing.md`
|
||
|
||
---
|
||
|
||
## 1. 基础信息
|
||
|
||
- **Base URL**: `https://your-domain.com/api/v1`
|
||
- **数据格式**: JSON(除明确标注“返回二进制”接口)
|
||
- **时间格式**: ISO 8601 / UTC(如:`2025-01-15T10:30:00Z`)
|
||
- **ID 格式**: UUID 字符串
|
||
|
||
---
|
||
|
||
## 2. 认证
|
||
|
||
支持三种身份:
|
||
|
||
### 2.1 JWT(网站/管理后台)
|
||
```http
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 2.2 API Key(对外 API)
|
||
```http
|
||
X-API-Key: <your-api-key>
|
||
```
|
||
|
||
> **注意**:仅 **Pro** 和 **Business** 套餐用户可创建 API Key。Free 用户尝试创建时返回 `FORBIDDEN`(HTTP `403`)。
|
||
|
||
### 2.3 匿名试用(仅网站场景)
|
||
- 不提供 API Key;
|
||
- 通过 Cookie 维持匿名会话(服务端签发),仅允许较小文件与较低频率。
|
||
- 每日 10 次;进入处理即预留次数,处理失败会自动归还,不可压缩或无体积收益的有效图片仍计次。超出返回 `QUOTA_EXCEEDED`(HTTP `402`)。
|
||
- 日界:自然日(UTC+8),次日 00:00 重置。
|
||
- **匿名试用硬限制:Cookie + IP 双限制**(两者任一超出都拒绝),降低刷会话绕过风险。
|
||
|
||
---
|
||
|
||
## 3. 通用约定
|
||
|
||
### 3.1 幂等(强烈建议)
|
||
对会产生计费/创建任务的接口,建议客户端传:
|
||
```http
|
||
Idempotency-Key: <uuid-or-random-string>
|
||
```
|
||
|
||
规则(建议口径):
|
||
- 同一个 `Idempotency-Key` 在 TTL 内重复请求,若请求参数一致则返回首次结果(不重复扣费/不重复创建任务)。
|
||
- 若参数不一致,返回 `409 IDEMPOTENCY_CONFLICT`。
|
||
|
||
### 3.2 限流(Rate Limit)
|
||
超出限制返回:
|
||
- HTTP `429`
|
||
- 错误码:`RATE_LIMITED`
|
||
|
||
API Key 按其 `rate_limit` 字段执行每分钟限制;登录、注册、找回密码和 Token 验证按 IP/账号执行独立限制。
|
||
|
||
### 3.3 配额(Quota / Billing)
|
||
配额不足(当期额度耗尽)返回:
|
||
- HTTP `402`
|
||
- 错误码:`QUOTA_EXCEEDED`
|
||
|
||
配额周期:
|
||
- Pro/Business(付费):按订阅周期重置(`period_start` ~ `period_end`),不是自然月。
|
||
- Free(未订阅):按自然月(UTC+8)重置。
|
||
- 匿名试用:按自然日(UTC+8)重置。
|
||
|
||
建议头(可选):
|
||
- `X-Quota-Limit`
|
||
- `X-Quota-Remaining`
|
||
- `X-Quota-Reset-At`
|
||
|
||
### 3.4 通用响应格式(JSON)
|
||
成功:
|
||
```json
|
||
{ "success": true, "data": {} }
|
||
```
|
||
|
||
错误:
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": {
|
||
"code": "ERROR_CODE",
|
||
"message": "错误描述",
|
||
"request_id": "req_..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.5 错误码(建议集合)
|
||
| 错误码 | HTTP | 说明 |
|
||
|---|---:|---|
|
||
| `INVALID_REQUEST` | 400 | 参数不合法 |
|
||
| `INVALID_IMAGE` | 400 | 图片解码失败/文件损坏 |
|
||
| `UNSUPPORTED_FORMAT` | 400 | 不支持的格式 |
|
||
| `TOO_MANY_PIXELS` | 400 | 像素超限(防图片炸弹) |
|
||
| `UNAUTHORIZED` | 401 | 未认证 |
|
||
| `FORBIDDEN` | 403 | 权限不足 |
|
||
| `NOT_FOUND` | 404 | 资源不存在 |
|
||
| `IDEMPOTENCY_CONFLICT` | 409 | 幂等 key 冲突 |
|
||
| `QUOTA_EXCEEDED` | 402 | 配额不足 |
|
||
| `FILE_TOO_LARGE` | 413 | 文件过大 |
|
||
| `RATE_LIMITED` | 429 | 请求过于频繁 |
|
||
| `EMAIL_NOT_VERIFIED` | 403 | 邮箱未验证 |
|
||
| `INVALID_TOKEN` | 400 | Token 无效或已过期 |
|
||
| `COMPRESSION_FAILED` | 500 | 压缩失败 |
|
||
| `STORAGE_UNAVAILABLE` | 503 | 存储不可用 |
|
||
| `MAIL_SEND_FAILED` | 500 | 邮件发送失败 |
|
||
|
||
---
|
||
|
||
## 4. 认证接口
|
||
|
||
### 4.1 用户注册
|
||
```http
|
||
POST /auth/register
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "email": "user@example.com", "password": "securepassword123", "username": "myusername" }
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"user": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"email": "user@example.com",
|
||
"username": "myusername",
|
||
"email_verified": false,
|
||
"created_at": "2025-01-15T10:30:00Z"
|
||
},
|
||
"token": "eyJhbGciOi...",
|
||
"message": "注册成功,验证邮件已发送至您的邮箱"
|
||
}
|
||
}
|
||
```
|
||
|
||
> **注意**:注册后自动发送验证邮件。用户需验证邮箱后才能使用压缩功能(未验证时调用压缩接口返回 `EMAIL_NOT_VERIFIED`)。
|
||
|
||
### 4.2 用户登录
|
||
```http
|
||
POST /auth/login
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "email": "user@example.com", "password": "securepassword123" }
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"token": "eyJhbGciOi...",
|
||
"expires_at": "2025-01-22T10:30:00Z",
|
||
"user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "username": "myusername", "role": "user" }
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 刷新 Token
|
||
```http
|
||
POST /auth/refresh
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 4.4 登出
|
||
```http
|
||
POST /auth/logout
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 4.5 发送验证邮件
|
||
用户注册后自动发送一次;此接口用于重新发送。
|
||
|
||
```http
|
||
POST /auth/send-verification
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
**限流**:同一用户 1 分钟内最多 1 次
|
||
|
||
响应:
|
||
```json
|
||
{ "success": true, "data": { "message": "验证邮件已发送,请查收" } }
|
||
```
|
||
|
||
### 4.6 验证邮箱
|
||
```http
|
||
POST /auth/verify-email
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "token": "verification-token-from-email" }
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{ "success": true, "data": { "message": "邮箱验证成功", "session_invalidated": false } }
|
||
```
|
||
|
||
同一路径也用于确认邮箱变更。邮箱变更成功时 `session_invalidated=true`,服务端会提升 `token_version`,客户端必须清除旧 JWT 并重新登录。
|
||
|
||
### 4.7 请求密码重置
|
||
```http
|
||
POST /auth/forgot-password
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "email": "user@example.com" }
|
||
```
|
||
|
||
**限流**:同一 IP 1 分钟内最多 3 次
|
||
|
||
响应(无论邮箱是否存在都返回成功,防止枚举):
|
||
```json
|
||
{ "success": true, "data": { "message": "如果该邮箱已注册,您将收到重置邮件" } }
|
||
```
|
||
|
||
### 4.8 重置密码
|
||
```http
|
||
POST /auth/reset-password
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "token": "reset-token-from-email", "new_password": "new-secure-password" }
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{ "success": true, "data": { "message": "密码重置成功,请重新登录" } }
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 图片压缩接口
|
||
|
||
### 5.1 单图压缩(同步,返回 JSON + 下载链接)
|
||
适用于网站与轻量同步调用(服务端可选择是否落盘/落对象存储)。
|
||
|
||
```http
|
||
POST /compress
|
||
Content-Type: multipart/form-data
|
||
Authorization: Bearer <token> # 或 X-API-Key;网站匿名试用可不带
|
||
Idempotency-Key: <key> # 建议
|
||
```
|
||
|
||
表单字段:
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---:|---|
|
||
| `file` | File | 是 | 图片文件 |
|
||
| `compression_rate` | Integer | 否 | 压缩率 1-100;JPEG/WebP/AVIF 以该比例为体积上限,无损格式为尽力优化;同格式且不缩放时,100 保留原始编码;优先级高于 `level` |
|
||
| `level` | String | 否 | `high` / `medium` / `low`(兼容参数,默认 `medium`) |
|
||
| `output_format` | String | 否 | 输出格式:`png/jpeg/webp/avif/gif/bmp/tiff/ico`(默认保持原格式;ICO 自动等比缩至 256x256 边界) |
|
||
| `max_width` | Integer | 否 | 大于 0 的最大宽度(等比缩放) |
|
||
| `max_height` | Integer | 否 | 大于 0 的最大高度(等比缩放) |
|
||
| `target_size_bytes` | Integer | 否 | 不小于 1024 的目标体积(字节),仅 `jpeg/webp/avif` 输出支持;不能与 `compression_rate` 同时指定 |
|
||
| `preserve_metadata` | Boolean | 否 | 是否保留 EXIF/ICC(默认 `false`);元数据输出仅支持 `jpeg/png/webp` |
|
||
|
||
处理约束:
|
||
- 动画 GIF/APNG/WebP/AVIF 不会静默截取首帧,而是返回 `400 UNSUPPORTED_FORMAT`。
|
||
- EXIF 方向会先应用到像素,再移除或归一化方向标记;透明图片转 JPEG 时以白色合成背景。
|
||
- 目标体积搜索先尝试原尺寸最高质量;无法满足时固定感知质量下限并搜索可用的最大分辨率,避免以极低质量强行保留像素尺寸。显式传入 `max_width` 或 `max_height` 时不会再次自动缩放,只在指定尺寸内调节质量。
|
||
- WebP 优先使用 libwebp 原生目标码率控制并预留安全余量,仍超过硬上限时自动回退到通用搜索;无法在清晰度保护范围内达到目标时返回 `400 INVALID_REQUEST`。
|
||
- `compression_rate=100` 只有在同格式且未指定缩放时属于免计量原样请求;格式转换或缩放后若体积变小,正常计 1 次。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"task_id": "550e8400-e29b-41d4-a716-446655440100",
|
||
"file_id": "550e8400-e29b-41d4-a716-446655440101",
|
||
"format_in": "png",
|
||
"format_out": "png",
|
||
"original_size": 1024000,
|
||
"compressed_size": 256000,
|
||
"saved_bytes": 768000,
|
||
"saved_percent": 75.0,
|
||
"download_url": "/downloads/550e8400-e29b-41d4-a716-446655440101",
|
||
"expires_at": "2025-01-15T11:30:00Z",
|
||
"billing": { "units_charged": 1 }
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 单图压缩(同步,直接返回二进制)
|
||
更贴近开发者体验,适用于 SDK/CI。
|
||
|
||
```http
|
||
POST /compress/direct
|
||
Content-Type: multipart/form-data
|
||
X-API-Key: <your-api-key> # 或 Bearer token(不建议匿名)
|
||
Idempotency-Key: <key> # 建议
|
||
```
|
||
|
||
表单字段与 `/compress` 一致(包括 `output_format`、`target_size_bytes`)。
|
||
|
||
成功响应:
|
||
- HTTP `200`
|
||
- Body:压缩后的图片二进制
|
||
- `Content-Type`: `image/png` / `image/jpeg` / `image/webp` / `image/avif` / `image/gif` / `image/bmp` / `image/tiff` / `image/x-icon`
|
||
|
||
建议响应头(示例):
|
||
```http
|
||
ImageForge-Original-Size: 1024000
|
||
ImageForge-Compressed-Size: 256000
|
||
ImageForge-Saved-Bytes: 768000
|
||
ImageForge-Saved-Percent: 75.0
|
||
ImageForge-Units-Charged: 1
|
||
```
|
||
|
||
### 5.3 批量压缩(异步任务)
|
||
适用于多文件或大文件;由 Worker 处理并持续更新进度。
|
||
|
||
```http
|
||
POST /compress/batch
|
||
Content-Type: multipart/form-data
|
||
Authorization: Bearer <token> # 或 X-API-Key
|
||
Idempotency-Key: <key> # 建议
|
||
```
|
||
|
||
表单字段:
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---:|---|
|
||
| `files[]` | File[] | 是 | 图片文件数组(上限由套餐决定) |
|
||
| `compression_rate` | Integer | 否 | 压缩率 1-100;JPEG/WebP/AVIF 以该比例为体积上限,无损格式为尽力优化;同格式且不缩放时,100 保留原始编码;优先级高于 `level` |
|
||
| `level` | String | 否 | `high` / `medium` / `low`(兼容参数) |
|
||
| `output_format` | String | 否 | 输出格式:`png/jpeg/webp/avif/gif/bmp/tiff/ico`(默认保持原格式) |
|
||
| `preserve_metadata` | Boolean | 否 | 是否保留元数据(默认 `false`) |
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"task_id": "550e8400-e29b-41d4-a716-446655440200",
|
||
"total_files": 10,
|
||
"status": "pending",
|
||
"status_url": "/compress/tasks/550e8400-e29b-41d4-a716-446655440200"
|
||
}
|
||
}
|
||
```
|
||
|
||
配额规则补充:
|
||
- 若本周期剩余单位不足以覆盖本次上传的文件数,服务端应直接返回 `402 QUOTA_EXCEEDED`(不创建任务)。
|
||
|
||
### 5.4 查询任务状态
|
||
```http
|
||
GET /compress/tasks/{task_id}
|
||
Authorization: Bearer <token> # 或 X-API-Key;匿名试用需携带 Cookie 会话
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"task_id": "550e8400-e29b-41d4-a716-446655440200",
|
||
"status": "completed",
|
||
"progress": 100,
|
||
"total_files": 10,
|
||
"completed_files": 10,
|
||
"failed_files": 0,
|
||
"files": [
|
||
{
|
||
"file_id": "550e8400-e29b-41d4-a716-446655440201",
|
||
"original_name": "photo1.png",
|
||
"original_size": 1024000,
|
||
"compressed_size": 256000,
|
||
"saved_percent": 75.0,
|
||
"status": "completed",
|
||
"download_url": "/downloads/550e8400-e29b-41d4-a716-446655440201"
|
||
}
|
||
],
|
||
"download_all_url": "/downloads/tasks/550e8400-e29b-41d4-a716-446655440200",
|
||
"created_at": "2025-01-15T10:30:00Z",
|
||
"completed_at": "2025-01-15T10:31:00Z",
|
||
"expires_at": "2025-01-22T10:30:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.5 取消任务(可选)
|
||
```http
|
||
POST /compress/tasks/{task_id}/cancel
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 5.6 删除任务与文件(隐私/合规)
|
||
```http
|
||
DELETE /compress/tasks/{task_id}
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 下载接口
|
||
|
||
### 6.1 下载单个文件
|
||
```http
|
||
GET /downloads/{file_id}
|
||
Authorization: Bearer <token> # 或 X-API-Key;匿名试用需 Cookie 会话
|
||
```
|
||
|
||
### 6.2 下载批量 ZIP
|
||
```http
|
||
GET /downloads/tasks/{task_id}
|
||
Authorization: Bearer <token> # 或 X-API-Key;匿名试用需 Cookie 会话
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 用户接口
|
||
|
||
### 7.1 获取当前用户信息
|
||
```http
|
||
GET /user/profile
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
响应(示例):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"email": "user@example.com",
|
||
"username": "myusername",
|
||
"role": "user",
|
||
"email_verified": true,
|
||
"pending_email": null
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 更新用户信息
|
||
```http
|
||
PUT /user/profile
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
### 7.3 修改密码
|
||
```http
|
||
PUT /user/password
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
### 7.4 获取压缩历史
|
||
```http
|
||
GET /user/history?page=1&limit=20
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
---
|
||
|
||
## 8. API Key 管理
|
||
|
||
### 8.1 获取 API Key 列表
|
||
```http
|
||
GET /user/api-keys
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 8.2 创建 API Key
|
||
```http
|
||
POST /user/api-keys
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "name": "Production Server", "permissions": ["compress", "batch_compress"] }
|
||
```
|
||
|
||
省略 `permissions` 时默认授予 `compress`;该权限覆盖同步压缩,并兼容批量任务、任务查询和结果下载。仅授予 `batch_compress` 时不能调用同步压缩接口。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "550e8400-e29b-41d4-a716-446655440300",
|
||
"name": "Production Server",
|
||
"key": "if_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||
"message": "请保存此 Key,它只会显示一次"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.3 轮换 API Key(可选)
|
||
```http
|
||
POST /user/api-keys/{key_id}/rotate
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 8.4 删除/禁用 API Key
|
||
```http
|
||
DELETE /user/api-keys/{key_id}
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 计费与用量(Billing)
|
||
|
||
### 9.1 获取套餐列表(公开)
|
||
```http
|
||
GET /billing/plans
|
||
```
|
||
|
||
响应(示例):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"plans": [
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440900",
|
||
"code": "pro_monthly",
|
||
"name": "Pro(月付)",
|
||
"currency": "CNY",
|
||
"amount_cents": 1999,
|
||
"interval": "monthly",
|
||
"included_units_per_period": 10000,
|
||
"max_file_size_mb": 20,
|
||
"max_files_per_batch": 50,
|
||
"retention_days": 7,
|
||
"features": { "webhook": true }
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.2 获取当前订阅
|
||
```http
|
||
GET /billing/subscription
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 9.3 获取当期用量
|
||
```http
|
||
GET /billing/usage
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"period_start": "2025-01-01T00:00:00Z",
|
||
"period_end": "2025-02-01T00:00:00Z",
|
||
"used_units": 120,
|
||
"included_units": 10000,
|
||
"bonus_units": 500,
|
||
"redeemed_units": 200,
|
||
"total_units": 10700,
|
||
"remaining_units": 10580
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.4 创建 Checkout(订阅/升级)
|
||
```http
|
||
POST /billing/checkout
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
请求体:
|
||
```json
|
||
{ "plan_id": "550e8400-e29b-41d4-a716-446655440900" }
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{ "success": true, "data": { "checkout_url": "https://pay.example.com/..." } }
|
||
```
|
||
|
||
Checkout 的 Customer 与 Session 幂等键由服务端按用户和待支付记录生成,客户端无需也不能决定该幂等边界。同一用户同一时间只允许一个未过期的 Checkout;已有未取消 Stripe 订阅时返回 `409 IDEMPOTENCY_CONFLICT`,套餐调整必须使用 Portal。
|
||
|
||
### 9.5 打开客户 Portal(管理支付方式/取消订阅)
|
||
```http
|
||
POST /billing/portal
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 9.6 发票列表
|
||
```http
|
||
GET /billing/invoices?page=1&limit=20
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
### 9.7 兑换套餐卡或次数卡
|
||
```http
|
||
POST /redemptions/redeem
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
|
||
{ "code": "IMG-XXXX-XXXX-XXXX-XXXX" }
|
||
```
|
||
|
||
### 9.8 获取自己的兑换记录
|
||
```http
|
||
GET /redemptions
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
次数卡额度拥有独立有效期,扣减时优先使用更早到期的可用额度。套餐卡不会覆盖仍然有效的 Stripe 订阅。
|
||
|
||
---
|
||
|
||
## 10. Webhooks(支付回调)
|
||
|
||
> 无需登录;必须验签与幂等处理,详见 `docs/billing.md` 与 `docs/security.md`。
|
||
|
||
### 10.1 Stripe 回调(示例)
|
||
```http
|
||
POST /webhooks/stripe
|
||
Content-Type: application/json
|
||
Stripe-Signature: t=...,v1=...
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 管理员接口
|
||
|
||
> 需要管理员权限(`role: admin`)
|
||
|
||
### 11.1 获取系统统计
|
||
```http
|
||
GET /admin/stats
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
### 11.2 用户管理(示例)
|
||
```http
|
||
GET /admin/users?page=1&limit=20&search=keyword
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
### 11.3 系统配置
|
||
```http
|
||
GET /admin/config
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
```http
|
||
PUT /admin/config
|
||
Authorization: Bearer <admin_token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
### 11.4 任务管理
|
||
```http
|
||
GET /admin/tasks?status=processing&page=1
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
```http
|
||
POST /admin/tasks/{task_id}/cancel
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
### 11.5 计费管理(建议)
|
||
```http
|
||
GET /admin/billing/subscriptions?page=1&limit=20
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
```http
|
||
POST /admin/billing/credits
|
||
Authorization: Bearer <admin_token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
修改用户名只需提交 `username`。修改邮箱必须同时提交当前密码:
|
||
|
||
```json
|
||
{
|
||
"email": "new@example.com",
|
||
"current_password": "current-password"
|
||
}
|
||
```
|
||
|
||
邮箱验证开启时,新邮箱只写入 `pending_email`,确认前主邮箱、登录邮箱和密码恢复地址均保持不变;确认后服务端原子切换主邮箱并撤销全部旧 JWT 与未使用的密码重置链接。邮箱验证关闭时,当前密码校验通过后立即切换邮箱,响应会返回替换当前会话使用的新 `token`。
|
||
|
||
### 11.6 S3 存储端点
|
||
|
||
```http
|
||
GET /admin/storage/endpoints
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
```http
|
||
POST /admin/storage/endpoints
|
||
Authorization: Bearer <admin_token>
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"name": "119 高带宽 S3",
|
||
"internal_endpoint": "http://10.70.0.2:3900",
|
||
"public_endpoint": "https://files.example.com",
|
||
"bucket": "imageforge-results",
|
||
"region": "garage",
|
||
"access_key": "<access-key>",
|
||
"secret_key": "<secret-key>",
|
||
"force_path_style": true,
|
||
"presign_ttl_seconds": 300
|
||
}
|
||
```
|
||
|
||
错误响应同时返回 `X-Request-Id`,其值与响应体 `request_id` 一致,可用于日志定位。
|
||
|
||
```http
|
||
PUT /admin/storage/endpoints/{endpoint_id}
|
||
POST /admin/storage/endpoints/{endpoint_id}/test
|
||
POST /admin/storage/endpoints/{endpoint_id}/activate
|
||
DELETE /admin/storage/endpoints/{endpoint_id}
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
凭据加密保存且不通过 API 回传。测试接口执行 Bucket 检查、内部临时对象读写删和公网预签名下载;激活接口会再次测试并原子切换活动端点。编辑端点时,后端先测试候选配置并抽查一个历史对象,全部通过后才以并发版本校验方式保存;活动端点编辑成功后保持活动,测试失败则原配置不变。
|
||
|
||
删除接口对活动或仍有关联对象的端点执行软删除:端点立即停止接收新对象并从管理列表隐藏;若删除的是活动端点,新写入自动回退本地。历史对象继续使用保留的加密配置下载和清理,关联对象清空且软删除超过 30 天后,后台任务才彻底移除端点凭据。
|
||
|
||
列表响应中的 `local_object_count` 与 `local_stored_bytes` 统计当前实际位于应用服务器的成品。存在活动 S3 时,新对象仍优先写入 S3;若本次 S3 写入失败,则自动回退本地并按实际后端完成下载和到期清理。
|
||
|
||
### 11.7 兑换码管理
|
||
|
||
```http
|
||
GET /admin/redemption-codes?page=1&limit=50&status=available&benefit_kind=plan&plan_id=<uuid>&keyword=活动
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
筛选参数均可省略:`status` 支持 `available/redeemed/disabled/expired`,`benefit_kind` 支持 `plan/units`,`keyword` 可搜索完整兑换码(精确匹配)、脱敏标识、备注和兑换人。列表响应使用 `Cache-Control: no-store`。
|
||
|
||
```http
|
||
POST /admin/redemption-codes
|
||
Authorization: Bearer <admin_token>
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"benefit_kind": "units",
|
||
"units": 100,
|
||
"duration_days": 30,
|
||
"quantity": 10,
|
||
"redeem_before": "2026-12-31T15:59:59Z",
|
||
"note": "活动批次"
|
||
}
|
||
```
|
||
|
||
套餐卡使用 `benefit_kind: "plan"` 并传入 `plan_id`。新生成的完整兑换码使用 AES-256-GCM 加密保存,管理员之后仍可在列表响应的 `code` 字段查看和复制;HMAC 哈希继续单独用于兑换校验。升级前生成的历史码无法从哈希逆向恢复,因此其 `code` 为 `null`,仍提供 `code_hint`。
|
||
|
||
```http
|
||
PUT /admin/redemption-codes/{code_id}
|
||
Authorization: Bearer <admin_token>
|
||
Content-Type: application/json
|
||
|
||
{ "is_active": false }
|
||
```
|
||
|
||
已兑换的兑换码保留为权益和审计凭据,不能再修改状态或删除。未兑换兑换码支持单独删除:
|
||
|
||
```http
|
||
DELETE /admin/redemption-codes/{code_id}
|
||
Authorization: Bearer <admin_token>
|
||
```
|
||
|
||
批量启用、停用或删除最多支持 200 个 ID;已兑换记录自动跳过,响应会返回处理、跳过和未找到数量:
|
||
|
||
```http
|
||
POST /admin/redemption-codes/batch
|
||
Authorization: Bearer <admin_token>
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"ids": ["<code_id_1>", "<code_id_2>"],
|
||
"action": "disable"
|
||
}
|
||
```
|
||
|
||
`action` 支持 `enable/disable/delete`,所有单条和批量操作都会写入管理员审计日志。
|
||
|
||
### 11.8 邮箱验证开关
|
||
|
||
`GET /admin/auth` 和 `PUT /admin/auth` 的配置体包含 `email_verification_required`。该配置独立于 SMTP,修改后立即生效,不需要重启服务。
|
||
|
||
---
|
||
|
||
## 12. WebSocket(网站任务进度)
|
||
|
||
网站侧可用 WebSocket 或 SSE(SSE 更易穿透代理)。当前先保留 WebSocket 方案:
|
||
|
||
```
|
||
ws://your-domain.com/ws/tasks/{task_id}?token=<jwt_token>
|
||
```
|
||
|
||
消息(示例):
|
||
```json
|
||
{ "type": "progress", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440200", "progress": 50, "completed_files": 5 } }
|
||
```
|