# img-proxy
**Repository Path**: taony/img-proxy
## Basic Information
- **Project Name**: img-proxy
- **Description**: img-proxy is an image processing gateway written in Go.
- **Primary Language**: Go
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-10-01
- **Last Updated**: 2026-10-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# img-proxy
轻量级图片处理网关 · 安全 · 高性能 · 纯 Go
`img-proxy` 是一个用 Go 编写的图片处理网关。它将原图加密后落盘,对客户端按需实时生成不同尺寸、格式的图片,并通过签名 URL 控制访问。服务端内置磁盘缓存、限流、Prometheus 指标与优雅关闭,适合作为中小规模业务的图片中台入口,也便于后续扩展为分布式架构。
---
## 目录
- [核心特性](#核心特性)
- [快速开始](#快速开始)
- [配置说明](#配置说明)
- [API 参考](#api-参考)
- [图片处理参数](#图片处理参数)
- [安全设计](#安全设计)
- [架构与目录](#架构与目录)
- [部署建议](#部署建议)
- [开发与测试](#开发与测试)
- [常见问题](#常见问题)
- [路线图](#路线图)
- [参与贡献](#参与贡献)
- [许可证](#许可证)
---
## 核心特性
- **加密存储**:支持信封加密(AES-256-GCM,每图独立 DEK)、格式伪装(单字节 XOR)或明文存储。
- **实时处理**:基于 `disintegration/imaging` 实现缩放、填充裁剪、区域裁剪、补边等模式。
- **签名鉴权**:HMAC-SHA256 签名 + 时间戳,防止盗链与重放攻击。
- **多级缓存**:L1 浏览器缓存(immutable)、L3 服务端磁盘缓存,附带 singleflight 合并、负缓存、LRU 淘汰、TTL 抖动。
- **限流防护**:按客户端 IP 限流,默认 100 QPS。
- **可观测性**:结构化 JSON 日志、Prometheus 指标、`/healthz` 健康检查。
- **零外部依赖**:P0 版本仅依赖本地文件系统,无需 Redis / MinIO / MySQL / KMS。
- **优雅关闭**:监听 SIGINT/SIGTERM,10 秒内完成 HTTP Shutdown。
> **P0 已知限制**:WebP / AVIF 仅支持输入解码,输出编码会返回 `415 UNSUPPORTED_FORMAT`;项目当前为单机 MVP,分布式组件将在后续版本引入。
---
## 快速开始
### 环境要求
- Go 1.23 或更高版本
- 无需其他外部服务
### 1. 克隆仓库
```bash
git clone https://github.com/YOUR_USERNAME/img-proxy.git
cd img-proxy
```
### 2. 生成密钥
```bash
# 32 字节主密钥(hex 编码)
openssl rand -hex 32
# 示例输出:e9260773354374ef985e22820c957bf540cfcea0fb75f20d3cd01f2bc7e43e2f
# 签名密钥与管理员 Token 可随机生成
openssl rand -hex 16
```
### 3. 配置
```bash
cp config.example.json config.json
# 编辑 config.json,填入 master_key、sign_secret、admin_token
```
`config.json` 已加入 `.gitignore`,**请勿提交到仓库**。
### 4. 启动
```bash
go run ./cmd/img-proxy
```
服务默认监听 `:8080`,数据目录为 `./data`。首次启动会自动创建 `data/originals`、`data/cache` 与 `data/meta.json`。
### 5. 上传并访问图片
```bash
export ADMIN_TOKEN="your-admin-token"
# 上传原图(信封加密)
FILE_ID=$(curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-F "file=@photo.jpg" \
"http://localhost:8080/admin/images?encrypt=envelope" | jq -r '.file_id')
# 生成签名 URL
SIGN_RESP=$(curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://localhost:8080/admin/sign?file_id=$FILE_ID&width=800&height=600&mode=fill&format=jpg&quality=80")
SIGN=$(echo "$SIGN_RESP" | jq -r '.sign')
TS=$(echo "$SIGN_RESP" | jq -r '.t')
# 访问处理后的图片
curl -o thumb.jpg "http://localhost:8080/image/$FILE_ID?width=800&height=600&mode=fill&format=jpg&quality=80&t=$TS&sign=$SIGN"
```
---
## 配置说明
默认读取 `./config.json`,可通过 `-config` 指定其他路径:
```bash
go run ./cmd/img-proxy -config /path/to/config.json
```
环境变量优先级高于配置文件,便于容器化与临时覆盖。
| 配置项(`config.json`) | 环境变量 | 必填 | 默认值 | 说明 |
|---|---|:---:|---|---|
| `master_key` | `IMGPROXY_MASTER_KEY` | 是 | - | 32 字节主密钥,hex 或 base64 编码 |
| `sign_secret` | `IMGPROXY_SIGN_SECRET` | 是 | - | URL 签名 HMAC 密钥 |
| `admin_token` | `IMGPROXY_ADMIN_TOKEN` | 是 | - | 管理接口 Bearer Token |
| `addr` | `IMGPROXY_ADDR` | 否 | `:8080` | 监听地址 |
| `data_dir` | `IMGPROXY_DATA_DIR` | 否 | `./data` | 数据目录 |
| `rate_qps` | `IMGPROXY_RATE_QPS` | 否 | `100` | 每 IP 限流 QPS |
| `cache_ttl` | `IMGPROXY_CACHE_TTL` | 否 | `168h` | 正缓存 TTL |
| `cache_max_mb` | `IMGPROXY_CACHE_MAX_MB` | 否 | `500` | 磁盘缓存上限(MB) |
---
## API 参考
### 公开接口
| 方法 | 路径 | 说明 |
|---|---|---|
| `GET` | `/image/{fileID}` | 访问处理后的图片(需签名) |
| `GET` | `/healthz` | 健康检查 |
| `GET` | `/metrics` | Prometheus 指标 |
### 管理接口(Header: `Authorization: Bearer {admin_token}`)
| 方法 | 路径 | 说明 |
|---|---|---|
| `POST` | `/admin/images?encrypt=envelope\|disguise\|none` | 上传原图,`multipart` 字段为 `file` |
| `GET` | `/admin/sign` | 生成签名(联调辅助) |
| `POST` | `/admin/cache/invalidate` | 按 `file_id` 失效该图全部缓存 |
#### 上传原图
```bash
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-F "file=@photo.jpg" \
"http://localhost:8080/admin/images?encrypt=envelope"
```
响应:
```json
{
"file_id": "abc123...",
"width": 1920,
"height": 1080,
"format": "jpg"
}
```
#### 生成签名
```bash
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://localhost:8080/admin/sign?file_id=abc123...&width=300&height=200&mode=fill&format=jpg&quality=80"
```
响应:
```json
{
"sign": "...",
"t": 1726473600
}
```
#### 缓存失效
```bash
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_id":"abc123..."}' \
http://localhost:8080/admin/cache/invalidate
```
---
## 图片处理参数
`GET /image/{fileID}` 支持以下查询参数:
| 参数 | 必填 | 说明 |
|---|---|---|
| `width` / `height` | 至少一个 | 目标尺寸,范围 `1~4096`;只给一个时按原图比例换算 |
| `mode` | 否 | `fit`(等比缩放,默认)/ `fill`(填充裁剪)/ `crop`(区域裁剪)/ `pad`(补边) |
| `format` | 否 | `jpg` / `png`;缺省保持原格式;`webp` / `avif` 仅输入解码,输出暂不支持 |
| `quality` | 否 | 质量 `1~100`,默认 `80`;PNG 无损,忽略该参数 |
| `x`, `y`, `w`, `h` | `mode=crop` 时必填 | 裁剪区域坐标与尺寸 |
| `bg` | 否 | `mode=pad` 填充色,6 位十六进制,默认 `FFFFFF` |
| `t` | 是 | Unix 时间戳,签名有效期 ±10 分钟 |
| `sign` | 是 | HMAC-SHA256 签名 |
### 签名算法
```
payload = "{file_id}\n{width}\n{height}\n{mode}\n{format}\n{quality}\n{t}"
sign = hex(HMAC-SHA256(sign_secret, payload))
```
缺失字段使用规范化默认值参与签名(如 `format` 缺省取原图格式)。
---
## 安全设计
### 信封加密
原图采用两级信封加密:
1. 每张图片生成独立的 32 字节 **DEK**(Data Encryption Key)。
2. 使用 DEK 通过 AES-256-GCM 加密图片本体,输出 `nonce ‖ ciphertext` 存入 `data/originals/`。
3. DEK 再由 **CMK**(`master_key`)加密后,以 base64 存入 `data/meta.json` 的 `dek_cipher` 字段。
4. 明文 DEK 与明文图片仅在请求生命周期内存在于内存,**全程不落盘**。
即使攻击者拿到整个 `data/` 目录,没有 `master_key` 也无法还原任何图片。
### 数据分布
| 位置 | 内容 | 是否敏感 |
|---|---|---|
| `data/originals/{file_id}` | 加密后的图片密文 | 否 |
| `data/meta.json` | 加密后的 DEK | 否 |
| `master_key` | 32 字节主密钥 | **是** |
| 运行时内存 | 明文 DEK、明文图片 | 是(请求结束即释放) |
### 其他安全机制
- **签名鉴权**:HMAC-SHA256 + 时间窗,防止盗链与重放。
- **限流**:按 IP 限流,默认 100 QPS。
- **输入校验**:`file_id` 白名单字符集,禁止路径遍历;目标尺寸、格式、颜色均受限。
- **审计日志**:每次 `/image` 访问记录 `trace_id`、`file_id`、来源 IP、处理参数、缓存命中、耗时。
### KMS 抽象
`internal/crypto/decrypt.go` 中定义了 `KMS` 接口(仅 `UnwrapDEK`),当前为本地实现 `LocalKMS`。生产环境可替换为阿里云 KMS、AWS KMS 等真实服务,业务代码无需改动。
---
## 架构与目录
```
客户端 / CDN
│
▼
┌─────────────────────────────────────┐
│ img-proxy HTTP 服务 │
│ 参数解析 → 签名校验 → 限流 → 缓存 → │
│ 读原图 → 解密 → 图片处理 → 回写缓存 │
└─────────────────────────────────────┘
│
├── data/originals/ 加密原图
├── data/cache/ 处理结果缓存
└── data/meta.json 图片元数据
```
### 目录结构
```
cmd/img-proxy/ 程序入口
internal/
api/ HTTP 路由、处理器、中间件、签名、限流
cache/ 磁盘缓存(正/负缓存、singleflight、LRU 淘汰)
config/ 配置加载(配置文件 + 环境变量覆盖)
crypto/ 信封加密 / 伪装加密 / KMS 接口
imageproc/ 图片解码、缩放、裁剪、编码
meta/ 文件元数据存储
observe/ 日志与 Prometheus 指标
params/ 查询参数解析、校验、缓存键
storage/ 本地原图存储
doc/ 详细规格说明书
```
---
## 部署建议
### 本地开发
```bash
go run ./cmd/img-proxy
```
### 编译二进制
```bash
CGO_ENABLED=0 go build -ldflags="-s -w" -o img-proxy ./cmd/img-proxy
./img-proxy -config config.json
```
### 容器化(示例)
```dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o img-proxy ./cmd/img-proxy
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/img-proxy /app/img-proxy
EXPOSE 8080
ENTRYPOINT ["/app/img-proxy"]
```
> 仓库暂未提供官方镜像,建议自行构建并挂载数据卷持久化 `data/`。
### 生产注意事项
1. **密钥管理**:`master_key`、`sign_secret`、`admin_token` 务必通过环境变量或密钥管理系统注入,不要写入配置文件后提交。
2. **HTTPS**:公开访问时应在网关前放置 Nginx / CDN / API Gateway 终止 TLS。
3. **数据持久化**:`data_dir` 应挂载到独立卷,避免容器重建丢失原图与缓存。
4. **监控**:采集 `/metrics` 的 Prometheus 指标,关注 `imgproxy_requests_total`、`imgproxy_cache_total`、`imgproxy_request_duration_seconds`。
5. **备份**:`data/meta.json` 与 `data/originals/` 需定期备份;丢失 `master_key` 将导致所有加密图片无法解密。
---
## 开发与测试
```bash
# 运行全部测试
go test ./...
# 查看测试覆盖率
go test -cover ./...
# 格式化与静态检查
go fmt ./...
go vet ./...
```
---
## 常见问题
**Q: 为什么 WebP / AVIF 输出返回 415?**
A: P0 版本使用纯 Go 的 `disintegration/imaging`,它没有 WebP / AVIF 编码器。输入端已支持 WebP 解码,输出将在接入 libvips 后开放。
**Q: 忘记 `master_key` 怎么办?**
A: `master_key` 是解密所有信封加密图片的唯一凭证,丢失后无法恢复。请务必妥善保管并备份。
**Q: 如何轮换 `master_key`?**
A: 当前版本未提供自动轮换工具。轮换需要遍历所有 `dek_cipher`,用旧主密钥解包 DEK 后再用新主密钥包装。
**Q: 可以水平扩展吗?**
A: P0 为单机 MVP,元数据与缓存均存储在本地文件。多节点部署需要共享存储(MinIO/S3)与共享缓存(Redis),将在 P1 版本支持。
---
## 路线图
- [x] P0:单机 MVP(签名鉴权、信封加密、图片处理、本地磁盘缓存、限流、Prometheus)
- [ ] P1:分布式支持(Redis 缓存、MinIO/S3 对象存储、MySQL 元数据、真实 KMS)
- [ ] P2:AI 智能裁剪、水印、WebP/AVIF 输出、异步批处理
---
## 参与贡献
欢迎 Issue 与 PR!
1. Fork 本仓库
2. 创建功能分支:`git checkout -b feature/your-feature`
3. 提交改动:`git commit -am 'Add some feature'`
4. 推送分支:`git push origin feature/your-feature`
5. 创建 Pull Request
请确保 `go test ./...` 通过,并保持代码风格一致。
---
## 许可证
[MIT](LICENSE)