Files
ystp/docs/api.md
237899745 49189d346b
Some checks failed
CI / verify (push) Has been cancelled
feat: expand redemption code management
2026-07-25 22:32:31 +08:00

813 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API 接口文档v1- ImageForge
面向两类使用者:
- **网站Web**:上传/批量/历史/账单等(可能包含匿名试用)。
- **对外 APIDeveloper 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": "邮箱验证成功" } }
```
### 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-100JPEG/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 时以白色合成背景。
- 目标体积搜索优先保持原分辨率,必要时在清晰度保护范围内缩放;仍无法达到时返回 `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-100JPEG/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"
}
}
```
### 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
Idempotency-Key: <key>
```
请求体:
```json
{ "plan_id": "550e8400-e29b-41d4-a716-446655440900" }
```
响应:
```json
{ "success": true, "data": { "checkout_url": "https://pay.example.com/..." } }
```
### 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
```
### 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 或 SSESSE 更易穿透代理)。当前先保留 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 } }
```