自托管图床与图库:映匣(中文)/ PixVault(英文)。
面向个人与团队:上传、分类、外链分享、API Key 对接;支持公开分类随机取图。图片与元数据本地存储,无需依赖第三方云存储。
仓库:github.com/clockclock1/PixVault
镜像:ghcr.io/clockclock1/pixvault
许可证:Apache-2.0
| 能力 | 说明 |
|---|---|
| 图库 Gallery | 分类管理、磁盘文件夹同步、排序/筛选、缩略图与渐进式 JPEG |
| 公开分类 | 分类可设为公开,通过 API 随机取一张图 |
| 外链直链 | /s/{id} 返回渐进式 JPEG,适合 Markdown / <img> |
| API Key | 外部程序用 pv_ 密钥调用上传与图库接口 |
| 简易图床 | 轻量 /api/upload → /i/{id} 原图外链 |
| Web 控制台 | 登录、图库编辑、Key 管理、内置 API 文档 |
| 单进程部署 | 前端可嵌入二进制,一个可执行文件即可运行 |
┌─────────────────┐ Cookie / API Key ┌──────────────────────────┐
│ React Web UI │ ◄───────────────────────► │ Axum (Rust) HTTP 服务 │
│ Vite + TS │ │ 默认 :8765 │
└────────┬────────┘ └────────────┬─────────────┘
│ 开发态代理 /api /i /g /s │
▼ ▼
uniapp-android SQLite + 本地磁盘
(可选安卓客户端) data/gallery、data/app.db
| 层级 | 技术 | 作用 |
|---|---|---|
| 后端 | Rust + Axum 0.8 | 异步 HTTP、multipart 上传、CORS、路由 |
| 存储 | SQLite(rusqlite,bundled) | 用户、会话、分类、图片元数据、API Key、场景配置 |
| 密码 | Argon2 | 管理员密码哈希 |
| 图片 | image / mozjpeg-rs / EXIF | 尺寸探测、衍生图(缩略图 / 渐进式)、拍摄时间 |
| 前端嵌入 | rust-embed(feature embed-ui) |
发布版把 Vite 产物打进单 EXE |
| 前端 | React 19 + Vite 6 + Tailwind 4 + Motion | 图库编辑、控制台、API 文档面板 |
| 安卓 | UniApp(Vue) | 可选移动端,见 uniapp-android/README.md |
运行时数据默认在可执行文件旁的 data/(可用 DATA_DIR 覆盖):
data/
app.db # SQLite 主库
logs/image-host.log # 运行日志
gallery/ # 图库原图(按分类文件夹)
gallery/_derived/ # 缩略图 / 渐进式衍生图
images/ # 简易图床原图(若使用 /api/upload)
分类与磁盘对应:data/gallery/<分类文件夹>/。把图片拷进文件夹后,后台会周期扫描入库,也可在控制台「立即同步」。
| 方式 | 用途 |
|---|---|
HttpOnly Cookie session=… |
Web 登录;API Key 管理等「仅 Web」接口 |
API Key pv_… |
外部脚本 / App。Header:Authorization: Bearer pv_… 或 X-API-Key: pv_… |
| 公开无鉴权 | /s/、/g/、/i/、/api/v1/public/*、/api/health 等 |
/api/v1/upload 仅接受 API Key,不接受 Cookie。
- 原样存、原样流:上传不强制重编码,保留 progressive JPEG;支持 HTTP Range。
- 衍生图异步生成:缩略图与渐进式 JPEG 后台队列生成,
derivativesReady标记就绪状态。 - 内容去重:图库上传按内容哈希去重,
duplicate: true表示复用已有记录。 - 外链
/s/{id}:固定公开 URL,优先返回渐进式 JPEG,未就绪时短暂回落原图。
frontend/ # Web 前端(开发端口 3000)
backend/ # Rust API(默认端口 8765)
embedded-ui/ # 发布时由前端 dist 拷入,供 rust-embed
src/ # Axum 路由与业务
uniapp-android/ # 安卓客户端(独立工程)
.github/workflows/ # Release 编译二进制 + 推送 Docker
Dockerfile # 基于预编译 linux 二进制的镜像
build-release.ps1 # Windows 本地打单文件 EXE
发布 Release 后,镜像推送到 GHCR:
docker pull ghcr.io/clockclock1/pixvault:latest
docker run -d --name pixvault \
-p 8765:8765 \
-v pixvault-data:/app/data \
-e PUBLIC_BASE_URL=https://img.example.com \
-e ADMIN_USERNAME=admin \
-e ADMIN_PASSWORD='改成强密码' \
ghcr.io/clockclock1/pixvault:latest指定版本:
docker run -d -p 8765:8765 -v pixvault-data:/app/data \
ghcr.io/clockclock1/pixvault:v0.1.0docker-compose.yml 示例:
services:
pixvault:
image: ghcr.io/clockclock1/pixvault:latest
ports:
- "8765:8765"
environment:
BIND_ADDR: "0.0.0.0:8765"
PUBLIC_BASE_URL: "https://img.example.com"
ADMIN_USERNAME: admin
ADMIN_PASSWORD: "改成强密码"
RUST_LOG: "info"
volumes:
- pixvault-data:/app/data
restart: unless-stopped
volumes:
pixvault-data:首次启动若库中无用户,会用 ADMIN_USERNAME / ADMIN_PASSWORD 创建管理员(默认 admin / admin123,生产务必修改)。
从 Releases 下载对应平台产物:
| 产物 | 平台 |
|---|---|
pixvault-windows-amd64.exe |
Windows x64 |
pixvault-windows-arm64.exe |
Windows ARM64 |
pixvault-linux-amd64 |
Linux x64 |
pixvault-linux-arm64 |
Linux ARM64 |
pixvault-macos-amd64 |
macOS Intel |
pixvault-macos-arm64 |
macOS Apple Silicon |
# Linux / macOS
chmod +x pixvault-linux-amd64
PUBLIC_BASE_URL=https://img.example.com ./pixvault-linux-amd64
# Windows(PowerShell)
$env:PUBLIC_BASE_URL = "https://img.example.com"
.\pixvault-windows-amd64.exe二进制已嵌入前端,浏览器访问 http://127.0.0.1:8765 即可。数据目录默认在 EXE 同级 data/。
创建 GitHub Release(published)会自动触发:
- Build Executables — 交叉编译上述 6 个产物并挂到 Release
- Docker Image — 推送
ghcr.io/clockclock1/pixvault:<tag>与latest
.\build-release.ps1流程:frontend Vite 构建 → 拷贝到 backend/embedded-ui → cargo build --release --features embed-ui → 输出 release/image-host.exe。
# 终端 1:后端
cd backend
cp .env.example .env # 按需修改
cargo run
# 终端 2:前端
cd frontend
npm install
npm run dev- 前端:
http://localhost:3000 - 后端:
http://0.0.0.0:8765 - Vite 已将
/api、/i、/g、/s代理到后端。
手动嵌入前端后编译后端:
cd frontend && npm ci && npm run build
rm -rf ../backend/embedded-ui && mkdir -p ../backend/embedded-ui
cp -R dist/. ../backend/embedded-ui/
cd ../backend
cargo build --release --features embed-ui外网 HTTPS 时,请把 PUBLIC_BASE_URL 设为对外域名,以便上传 API 返回完整外链。
server {
listen 443 ssl http2;
server_name img.example.com;
# ssl_certificate ...;
# ssl_certificate_key ...;
client_max_body_size 64m;
location / {
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_buffering off;
}
}| 变量 | 默认 | 说明 |
|---|---|---|
BIND_ADDR |
0.0.0.0:8765 |
监听地址 |
PUBLIC_BASE_URL |
由 BIND_ADDR 推导 |
外链完整前缀(上传 API / 随机图 JSON 中的 url) |
DATA_DIR |
EXE 旁 ./data |
数据与数据库目录 |
ADMIN_USERNAME |
admin |
首次无用户时种子管理员 |
ADMIN_PASSWORD |
admin123 |
首次种子密码 |
RUST_LOG |
(crate 默认) | 如 info / debug |
CORS_ORIGIN |
未设则开放 CORS | 设为具体前端源时可带 Cookie 跨域 |
SESSION_DAYS |
30 |
会话有效天数 |
COOKIE_SECURE |
关 | 设为 1/true 时 Cookie 加 Secure |
参考:backend/.env.example。
控制台登录后打开 「API 接口」 可查看完整文档(参数表、响应示例、cURL / fetch / Python),并支持下载 JSON。下列为速查。
# API Key(推荐外部调用)
Authorization: Bearer pv_你的密钥
# 或
X-API-Key: pv_你的密钥在 Web:图库编辑 → API 接口 → 创建 Key。Key 管理接口仅 Cookie 会话可用。
错误响应一般为:
{ "error": "说明文字" }| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /api/health |
公开 | 健康检查 { ok, service } |
| GET | /api/auth/status |
公开 | { needsBootstrap } |
| POST | /api/auth/register |
公开 | 仅无用户时可注册 |
| POST | /api/auth/login |
公开 | 登录,Set-Cookie + 可选 token |
| POST | /api/auth/logout |
登录/Key | 退出 |
| GET | /api/auth/me |
登录/Key | 当前用户 |
| POST | /api/auth/password |
登录/Key | 改密(成功后清会话) |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/keys |
列出 Key(含完整 secret) |
| POST | /api/keys |
创建,body 可选 { "name": "脚本" } |
| DELETE | /api/keys/{id} |
物理删除 |
curl -X POST "https://img.example.com/api/v1/upload" \
-H "Authorization: Bearer pv_…" \
-F "file=@/path/to/photo.jpg" \
-F "categoryId=可选分类UUID"响应示例:
{
"id": "image-uuid",
"url": "https://img.example.com/s/image-uuid",
"contentType": "image/jpeg",
"size": 102400,
"progressive": true,
"width": 1920,
"height": 1080,
"duplicate": false
}url 为完整公开外链(PUBLIC_BASE_URL + /s/{id})。
先在分类管理中开启「公开分类」。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/public/categories |
列出公开分类 |
| GET | /api/v1/public/random?categoryId= |
随机一张;redirect=1 时 302 到图片 |
# JSON
curl "https://img.example.com/api/v1/public/random?categoryId=分类UUID"
# 直接当图片用
curl -L -o random.jpg \
"https://img.example.com/api/v1/public/random?categoryId=分类UUID&redirect=1"HTML:
<img src="https://img.example.com/api/v1/public/random?categoryId=分类UUID&redirect=1" alt="随机图" />| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/upload |
Key | multipart file/image → 相对路径 /i/{id} |
| GET | /api/images/{id} |
Key | 元数据 |
| GET | /i/{id} |
公开 | 原图流(支持 Range) |
| GET | /i/{id}/{filename} |
公开 | 带文件名的原图 URL |
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET/POST | /api/gallery/categories |
Key | 列表 / 新建(可含 isPublic) |
| PUT/DELETE | /api/gallery/categories/{id} |
Key | 更新(含公开开关)/ 删除 |
| GET/PUT | /api/gallery/settings |
Key | 展示设置 |
| GET | /api/gallery/images |
Key | view=public|editor,可选 categoryId |
| POST | /api/gallery/upload |
Key | 入库图库,EXIF/mtime、去重 |
| PUT/DELETE | /api/gallery/images/{id} |
Key | 改分类排序 / 删除 |
| PUT | /api/gallery/images/reorder |
Key | { "ids": [...] } |
| POST | /api/gallery/images/batch-delete |
Key | { "ids": [...] } |
| POST | /api/gallery/sync |
Key | 磁盘 ↔ 数据库同步 |
| GET | /g/{id} |
公开 | ?v=original|progressive|thumb |
| GET | /g/{id}/{filename} |
公开 | 同上,带文件名 |
分类更新示例(开启公开):
curl -X PUT "https://img.example.com/api/gallery/categories/{id}" \
-H "Authorization: Bearer pv_…" \
-H "Content-Type: application/json" \
-d '{"isPublic":true}'| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /api/share/{id} |
公开 | 元数据 + imageUrl / thumbUrl |
| GET | /api/share/{id}/image |
公开 | 渐进式 JPEG |
| GET | /api/share/{id}/thumb |
公开 | 缩略图 |
| GET | /s/{id} |
公开 | 推荐直链 |
| GET | /s/{id}/raw |
公开 | 同 /s/{id} |
Markdown:
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /api/scene |
登录/Key | 读取场景 JSON |
| PUT | /api/scene |
登录/Key | 保存场景 JSON |
默认 multipart / 请求体上限约 64MB(见服务端 DefaultBodyLimit)。反向代理请同步调大 client_max_body_size。