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,带 ETag、Vary: 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
创建未验证用户并发送验证邮件。
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
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
健康检查。
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 中空字符串会被忽略成 null;null 表示全部来源
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:
- 匿名可访问;无匹配返回
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
上传照片。
表单字段:
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"
}
最佳实践
- 在用户设置中提示添加自有域名到
allowed_origins,避免 403 错误。
- 上传推荐使用 JXL,减少服务器转换负担。
- 通过 CDN 缓存缩略图/原图可显著降低
access_count 和 traffic_stats.bytes。
- 调整
thumb_quality/original_quality 后会清缓存,首次访问可能略慢。