Gallery Alice API

Gallery Alice 是一个 50% Vibe Coding 出的摄影作品存储、备份、共享、管理一体化前后端分离项目。本项目使用现代技术栈,S3 多储存桶储存,ImageMagick 负责图片转换,后端管理使用 Rust 构建,有完善的储存/流量负载均衡管理系统...

图片使用 JPEG XL 无损压缩储存,缩略图使用 AVIF 压缩减少图片体积,原图支持多种格式转换(均在服务器端异步处理)。

本文档提供面向普通注册/验证用户与匿名访问的接口。

对应后端版本 v0.5.5

基础信息

  • Base URL: /api
  • 认证方式: 默认需要 Authorization: Bearer <token>(少数接口支持 ?token= 查询参数)。
  • 时间格式: ISO8601 (RFC3339) UTC
  • CORS: 所有 API 开放全站 CORS;公开图像访问会根据用户的 allowed_origins/配置的 ALWAYS_ALLOWED_ORIGINS 在服务器端判断。
  • 缓存策略: 缩略图/原图接口返回 Cache-Control: public, max-age=15552000, s-maxage=15552000, stale-while-revalidate=86400, stale-if-error=604800,带 ETagVary: Origin 与对应的 Access-Control-Allow-Origin
  • 图片统计: access_count 记录回源请求次数(304 也计数),traffic_stats.bytes 只在原图 200 返回时累加实际输出大小,两者通过内存队列异步写库,可能有轻微延迟。
  • 上传格式: 仅支持 jpg/png/avif/webp/jxl。非 JXL 先用 ImageMagick + cjxl 转为无损 JXL 后上传;上传后会异步生成 AVIF 缩略图与 JPG 原图缓存。jxl 上传可以跳过转换,但仍会生成缓存。
  • 存储桶: storage_buckets 表存储主/备份桶;启动时若为空会从环境变量 S3_* 初始化。上传优先主桶并同步备份,status=quota_full|suspend 的桶禁止写入,相关请求返回 507 Insufficient Storage
  • 接口前缀: 所有 API 前缀为 /api/:uuid 支持 me(需登录)。

数据模型

User

{
  "uuid": "string",
  "email": "string",
  "display_name": "string | null",
  "thumb_quality": 60,
  "original_quality": 90,
  "role": 1,
  "expose_leaderboard": false,
  "allowed_origins": ["string | null"],
  "billing_mode_uuid": "uuid | null",
  "created_at": "2024-01-01T00:00:00Z"
}
  • 角色: 0=未验证、1=已验证用户、2=管理员
  • allowed_origins 允许空字符串(跳过校验)或 null(全部来源)
  • billing_mode_uuid 必须为有效 display_status=normal 的计费方案;管理员可设置 hidden。

Photo

{
  "uuid": "string",
  "user_uuid": "string",
  "title": "string | null",
  "iso": "string | null",
  "shutter_speed": "string | null",
  "aperture": "string | null",
  "equipment": "string | null",
  "taken_at": "2024-01-01T00:00:00Z | null",
  "location": "string | null",
  "note": "string | null",
  "size_bytes": 1024,
  "access_count": 100,
  "primary_storage_bucket_uuid": "uuid | null",
  "backup_storage_bucket_uuids": ["uuid"],
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:00Z"
}

Album

{
  "uuid": "string",
  "user_uuid": "string",
  "name": "string",
  "description": "string | null",
  "is_public": false,
  "photo_count": 0,
  "created_at": "2024-01-01T00:00:00Z"
}

StorageBucketBasicInfo

{
  "name": "string",
  "region": "string | null",
  "capacity_limit_bytes": 0,
  "used_bytes": 0,
  "used_updated_at": "2024-01-01T00:00:00Z | null",
  "status": "null | quota_warning | quota_full | suspend"
}

用户统计

{
  "photo_count": 0,
  "total_size_bytes": 0,
  "total_backup_size_bytes": 0,
  "total_storage_bytes": 0,
  "total_access_count": 0,
  "total_traffic_bytes": 0
}

流量历史记录项

{
  "bucket_start": "YYYY-MM-DD",
  "request_count": 0,
  "bytes": 0
}

身份认证与健康检查

POST /api/auth/signup

创建未验证用户并发送验证邮件。

POST /api/auth/signup
Content-Type: application/json

Body:

{
  "email": "user@example.com",
  "password": "secure_password",
  "display_name": "optional"
}

Response 202 Accepted:

{
  "message": "verification email sent",
  "user": { /* User */ },
  "verification_sent": true
}
  • display_name 会 trim,空字符串视为未设置;重复时 409 Conflict
  • 返回 user (role=0),不含 JWT token

GET /api/auth/verify?token=…

邮箱验证并激活账号,返回登录态(role 变 1,管理员保持 2)。

GET /api/auth/verify?token=<token>

Response 200 OK:

{
  "token": "jwt_token",
  "expires_at": "2024-01-02T00:00:00Z",
  "user": { /* User */ }
}

POST /api/auth/login

POST /api/auth/login
Content-Type: application/json

Body:

{
  "email": "user@example.com",
  "password": "secure_password"
}

Response 200 OK: 同 verify 返回 {token, expires_at, user}

  • 仅 role>=1 可登录,未验证或缺少权限返回 403 Forbidden
  • 支持管理员登录(role=2)

GET /api/health

健康检查。

GET /api/health

Response 200 OK: OK

用户 API

GET /api/users/:uuid

返回用户资料(支持 :uuid=me)。

Response 200 OK: User

权限: 登录必需,仅本人或管理员可查询。

PUT /api/users/:uuid

更新多种用户字段(支持批量)。

{
  "display_name?": "string | null",
  "origins?": ["https://example.com", ""],
  "thumb_quality?": 60,
  "original_quality?": 90,
  "expose_leaderboard?": true,
  "billing_mode_uuid?": "uuid"
}
  • display_name 需唯一;空值或 null 可清空
  • origins 中空字符串会被忽略成 nullnull 表示全部来源
  • thumb_quality/original_quality 取值 1-100;修改后会清理用户缓存并在下一次访问按新质量重建
  • billing_mode_uuid 仅可设置为 display_status=normal 的方案,且不可设为 null(管理员例外)
  • 登录必需,仅本人可修改;:uuid=me

DELETE /api/users/:uuid

删除用户并清理资源(主/备桶对象、缓存、DB 级联)。

Response 204 No Content

权限: 本人或管理员;me 代表当前用户。

GET /api/users/:uuid/stats

获取资源使用情况。

{
  "photo_count": 0,
  "total_size_bytes": 0,
  "total_backup_size_bytes": 0,
  "total_storage_bytes": 0,
  "total_access_count": 0,
  "total_traffic_bytes": 0
}
  • total_access_count: 回源次数(304 也计数)
  • total_traffic_bytes: 原图 200 的实际输出大小
  • 登录必需,仅本人或管理员可访问

GET /api/users/:uuid/traffic/history

获取流量历史记录。

GET /api/users/:uuid/traffic/history?granularity=day&source=origin&format=all&from=2024-01-01&to=2024-01-31

Response:

{
  "granularity": "day|month",
  "items": [ /* TrafficHistoryBucket */ ]
}
  • granularity: day(默认)或 month
  • source: origin|cdn|all(default origin);cdn 需自定义数据源
  • format: jpg|avif|png|jxl|webp|all(default all
  • bucket_start 月粒度为月初(YYYY-MM-01
  • 登录必需,仅本人或管理员可查看

GET /api/users/:uuid/leaderboard

获取用户访问量最多的 10 张照片。

GET /api/users/:uuid/leaderboard?metric=total

Query:

  • metric: total(默认)|daily(访问量 / 存活天数)

Response:

{
  "items": [
    {
      "uuid": "photo-uuid",
      "title": "string | null",
      "access_count": 1000,
      "daily_avg": 42.5,
      "created_at": "2024-01-01T00:00:00Z",
      "thumbnail_url": "/api/thumbnails/<uuid>"
    }
  ]
}
  • 目标用户 expose_leaderboard=true 时可匿名访问;否则仅本人或管理员可查
  • :uuid=me 需要登录

GET /api/users/uuid_by_display_name

通过用户 display name 获取 uuid,进而方便获取 photos 和 album。

GET /api/users/uuid_by_display_name?display_name=John

Response 200 OK:

{
  "uuid": "user-uuid"
}
  • 匿名可访问;无匹配返回 404 Not Found,多匹配返回 409 Conflict

GET /api/users/:uuid/storage

获取用户的储存桶节点信息。只返回 uuid,需要通过 GET /api/storage/buckets/:uuid/basic 进一步查询。

{
  "primary": "bucket-uuid | null",
  "backups": ["bucket-uuid"]
}
  • 返回用户实际生效的主桶与备份桶(无系统主桶时 primary=null
  • 备份桶去重并排除主桶;:uuid 支持 me
  • 登录必需,仅本人或管理员可查

GET /api/storage/buckets/:uuid/basic

获取储存桶信息。

返回 StorageBucketBasicInfo;不返回 bucket/endpoint/密钥。

权限: 登录必需

照片接口

GET /api/photos

获取照片列表。

GET /api/photos?page=1&per_page=20

Response:

{
  "items": [
    {
      "uuid": "photo-uuid",
      "user_uuid": "user-uuid",
      "title": "string | null",
      "thumbnail_url": "/api/thumbnails/<uuid>"
    }
  ],
  "page": 1,
  "per_page": 20,
  "total": 123
}
  • 普通用户只返回自己的照片;管理员可查看所有
  • per_page 最大 100
  • 登录必需

POST /api/photos

上传照片。

POST /api/photos
Content-Type: multipart/form-data

表单字段:

  • file (required): jpg/png/avif/webp/jxl
  • title, iso, shutter_speed, aperture, equipment, taken_at (RFC3339), location, note (optional)
  • album_ids: 逗号分隔的 UUID 列表(optional)

Response 200 OK:

{
  "photo": { /* Photo */ },
  "original_url": "/api/originals/<uuid>",
  "thumbnail_url": "/api/thumbnails/<uuid>"
}
  • 用户需 role>=1、upload_disabled=false、并设置有效 billing_mode_uuid
  • jxl 上传先转无损 JXL;上传后异步生成原图 JPG 与缩略图 AVIF 缓存(jxl 上传同样会生成)
  • 主桶 status=quota_full|suspend 时返回 507 Insufficient Storage
  • 图片转换队列会去重,相同 photo/format 不重复执行

GET /api/photos/:uuid

获取照片元数据。

返回 {photo, original_url, thumbnail_url}

PUT /api/photos/:uuid

修改照片元数据。

更新任意元数据字段:{title?, iso?, shutter_speed?, aperture?, equipment?, taken_at?, location?, note?}

  • 仅 photo 所有者或管理员可操作
  • 返回更新后的 photo

DELETE /api/photos/:uuid

删除照片记录、主/备桶对象与本地缓存。

  • 仅所有者或管理员可操作
  • 返回 204 No Content

GET /api/thumbnails/:uuid

返回 AVIF 缩略图;缺失时回源主/备桶并重建缓存。

  • 请求需要 Origin 在用户白名单或 ALWAYS_ALLOWED_ORIGINS 中,或提供 ?token=;管理员授权可忽略
  • 响应含 Cache-Control, ETag, Vary: Origin, Access-Control-Allow-Origin
  • 支持 If-None-Match → 304;304 不计流量但计 access_count
  • Origin 验证失败返回 403 Forbidden

GET /api/originals/:uuid

返回分辨率未压缩的照片,默认为 jpg 格式。

GET /api/originals/:uuid?format=jpg|avif|png|jxl|webp
  • 默认 format=jpg,所有输出格式使用用户 original_quality
  • 访问会写入统计队列:access_count(含 304)、traffic_stats.bytes(仅 200,按实际输出大小)
  • 同样需 Origin 允许或携带 ?token=;管理员授权绕过
  • 支持 CDN 缓存,回源请求才增加计数
  • 304 不计流量

相册接口

GET /api/albums

返回包含 photo_count/is_public/user_uuid 的相册列表。

  • 登录必需;普通用户只看自己,管理员返回全部

POST /api/albums

创建相册。

{
  "name": "My Album",
  "description": "optional"
}
  • 返回 {album},相册包含 photo_count

PUT /api/albums/:uuid

修改相册信息。

{
  "name?": "string",
  "description?": "string",
  "is_public?": true,
  "add_photos?": ["uuid"],
  "remove_photos?": ["uuid"]
}
  • 仅处理相册所有者的照片;管理员可跨用户操作
  • 返回更新后的 album

DELETE /api/albums/:uuid

删除相册。

  • 仅相册所有者或管理员可操作,204 No Content
  • 删除相册不删除照片,仅解除关联

GET /api/users/:uuid/public_albums

获取用户的公开相册列表。

  • 允许匿名访问;返回该用户所有 is_public=true 相册;:uuid=me 需登录

GET /api/albums/:uuid/photos

分页返回相册中的照片(仅含 uuid/title/thumbnail_url)。

  • page 默认 1,per_page 默认 20(最大 100)
  • 公共相册可匿名访问;私密相册需归属或管理员

POST /api/albums/:uuid/photos

将照片加入相册。

{ "photo_ids": ["uuid1","uuid2"] }
  • 只处理相册所有者的照片;已存在的关联忽略
  • 返回 {album}

DELETE /api/albums/:uuid/photos

从相册移除照片;不存在的关联忽略。

错误响应

  • 400 Bad Request: 参数/格式错误
  • 401 Unauthorized: 未认证或 token 失效
  • 403 Forbidden: 无权限或 Origin 不在白名单
  • 404 Not Found: 资源不存在
  • 409 Conflict: display_name 等冲突
  • 507 Insufficient Storage: 主桶已满/暂停写入

错误体通常包含:

{
  "error": "Description"
}

最佳实践

  1. 在用户设置中提示添加自有域名到 allowed_origins,避免 403 错误。
  2. 上传推荐使用 JXL,减少服务器转换负担。
  3. 通过 CDN 缓存缩略图/原图可显著降低 access_counttraffic_stats.bytes
  4. 调整 thumb_quality/original_quality 后会清缓存,首次访问可能略慢。